Visual Object sample and deployment templates that show scaleout and upgrade.
This commit is contained in:
Родитель
ec8453416d
Коммит
5d2dea281e
|
@ -0,0 +1,8 @@
|
|||
.dockerignore
|
||||
.env
|
||||
.git
|
||||
.gitignore
|
||||
.vs
|
||||
.vscode
|
||||
*/bin
|
||||
*/obj
|
|
@ -0,0 +1,84 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Common
|
||||
{
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
|
||||
[DataContract]
|
||||
public sealed class Color
|
||||
{
|
||||
public static double[][] CurrentColorsPalette =
|
||||
{
|
||||
new[] {0.0, 0.0, 1.0, 0.0},
|
||||
new[] {0.0, 1.0, 0.0, 0.0},
|
||||
new[] {1.0, 0.0, 0.0, 0.0}
|
||||
};
|
||||
|
||||
public static double[][] HistoryColorsPalette =
|
||||
{
|
||||
new[] {1.0, 0.0, 0.0, 0.0},
|
||||
new[] {1.0, 1.0, 0.0, 0.0},
|
||||
new[] {1.0, 1.0, 1.0, 0.0}
|
||||
};
|
||||
|
||||
public Color(double r, double g, double b, double a)
|
||||
{
|
||||
this.R = r;
|
||||
this.G = g;
|
||||
this.B = b;
|
||||
this.A = a;
|
||||
}
|
||||
|
||||
public Color(Color other)
|
||||
{
|
||||
this.R = other.R;
|
||||
this.G = other.G;
|
||||
this.B = other.B;
|
||||
this.A = other.A;
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public double R { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public double G { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public double B { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public double A { get; private set; }
|
||||
|
||||
public static Color CreateRandom(double[][] colorPalette, Random rand = null)
|
||||
{
|
||||
if (rand == null)
|
||||
{
|
||||
rand = new Random((int) DateTime.Now.Ticks);
|
||||
}
|
||||
|
||||
int colorIndex = rand.Next(colorPalette.GetLength(0));
|
||||
|
||||
return new Color(
|
||||
r: colorPalette[colorIndex][0] + rand.NextDouble(),
|
||||
g: colorPalette[colorIndex][1] + rand.NextDouble(),
|
||||
b: colorPalette[colorIndex][2] + rand.NextDouble(),
|
||||
a: colorPalette[colorIndex][3] + rand.NextDouble());
|
||||
}
|
||||
|
||||
public void ToJson(StringBuilder builder)
|
||||
{
|
||||
builder.AppendFormat(
|
||||
"{{ \"r\":{0}, \"g\":{1}, \"b\":{2}, \"a\":{3} }}",
|
||||
this.R.ToString(NumberFormatInfo.InvariantInfo),
|
||||
this.G.ToString(NumberFormatInfo.InvariantInfo),
|
||||
this.B.ToString(NumberFormatInfo.InvariantInfo),
|
||||
this.A.ToString(NumberFormatInfo.InvariantInfo));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Common
|
||||
{
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
|
||||
[DataContract]
|
||||
public sealed class Coordinate
|
||||
{
|
||||
public Coordinate(double x, double y, double z)
|
||||
{
|
||||
this.X = x;
|
||||
this.Y = y;
|
||||
this.Z = z;
|
||||
}
|
||||
|
||||
public Coordinate(Coordinate other)
|
||||
{
|
||||
this.X = other.X;
|
||||
this.Y = other.Y;
|
||||
this.Z = other.Z;
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public double X { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public double Y { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public double Z { get; private set; }
|
||||
|
||||
public static Coordinate CreateRandom(Random rand = null)
|
||||
{
|
||||
if (rand == null)
|
||||
{
|
||||
rand = new Random((int) DateTime.Now.Ticks);
|
||||
}
|
||||
|
||||
return new Coordinate(rand.NextDouble(), rand.NextDouble(), rand.NextDouble());
|
||||
}
|
||||
|
||||
public string ToJson()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
this.ToJson(sb);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void ToJson(StringBuilder builder)
|
||||
{
|
||||
builder.AppendFormat(
|
||||
"{{ \"x\":{0}, \"y\":{1}, \"z\":{2} }}",
|
||||
this.X.ToString(NumberFormatInfo.InvariantInfo),
|
||||
this.Y.ToString(NumberFormatInfo.InvariantInfo),
|
||||
this.Z.ToString(NumberFormatInfo.InvariantInfo));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Common
|
||||
{
|
||||
public interface IVisualObjectsBox
|
||||
{
|
||||
string GetJson();
|
||||
|
||||
void SetObject(string objectId, string objectJson);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Common
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
[DataContract]
|
||||
public class Speed
|
||||
{
|
||||
public Speed(Speed other)
|
||||
{
|
||||
this.XSpeed = other.XSpeed;
|
||||
this.YSpeed = other.YSpeed;
|
||||
this.ZSpeed = other.ZSpeed;
|
||||
}
|
||||
|
||||
public Speed(double x, double y, double z)
|
||||
{
|
||||
this.XSpeed = x;
|
||||
this.YSpeed = y;
|
||||
this.ZSpeed = z;
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public double XSpeed { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public double YSpeed { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public double ZSpeed { get; private set; }
|
||||
|
||||
public static Speed CreateRandom(Random rand = null)
|
||||
{
|
||||
if (rand == null)
|
||||
{
|
||||
rand = new Random((int) DateTime.Now.Ticks);
|
||||
}
|
||||
|
||||
return new Speed(rand.NextDouble()*0.05, rand.NextDouble()*0.05, rand.NextDouble()*0.05);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Common
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
|
||||
[DataContract]
|
||||
public class VisualObject
|
||||
{
|
||||
private const int HistoryLength = 7;
|
||||
|
||||
public VisualObject(string name, Speed speed, Coordinate location, Color color, Color historyColor, double rotation = 0)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Speed = speed;
|
||||
this.CurrentLocation = location;
|
||||
this.CurrentColor = color;
|
||||
this.HistoryColor = historyColor;
|
||||
this.Rotation = rotation;
|
||||
this.LocationHistory = new List<Coordinate>();
|
||||
this.HistoryStartIndex = -1;
|
||||
}
|
||||
|
||||
public VisualObject(VisualObject other)
|
||||
{
|
||||
this.Name = other.Name;
|
||||
this.Speed = new Speed(other.Speed);
|
||||
|
||||
this.CurrentLocation = new Coordinate(other.CurrentLocation);
|
||||
this.LocationHistory = new List<Coordinate>(other.LocationHistory.Count);
|
||||
foreach (Coordinate c in other.LocationHistory)
|
||||
{
|
||||
this.LocationHistory.Add(new Coordinate(c));
|
||||
}
|
||||
|
||||
this.CurrentColor = new Color(other.CurrentColor);
|
||||
this.HistoryColor = new Color(other.HistoryColor);
|
||||
|
||||
this.Rotation = other.Rotation;
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public string Name { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public Speed Speed { get; private set; }
|
||||
|
||||
[DataMember(Name = "current")]
|
||||
public Coordinate CurrentLocation { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public Color CurrentColor { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public Color HistoryColor { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public int HistoryStartIndex { get; set; }
|
||||
|
||||
[DataMember(Name = "history")]
|
||||
public List<Coordinate> LocationHistory { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public double Rotation { get; set; }
|
||||
|
||||
public static VisualObject CreateRandom(string name, Random rand = null)
|
||||
{
|
||||
if (rand == null)
|
||||
{
|
||||
rand = new Random(name.GetHashCode());
|
||||
}
|
||||
|
||||
return new VisualObject(
|
||||
name,
|
||||
Speed.CreateRandom(rand),
|
||||
Coordinate.CreateRandom(rand),
|
||||
Color.CreateRandom(Color.CurrentColorsPalette, rand),
|
||||
Color.CreateRandom(Color.HistoryColorsPalette, rand));
|
||||
}
|
||||
|
||||
public void Move(bool rotate)
|
||||
{
|
||||
if (this.LocationHistory.Count < HistoryLength)
|
||||
{
|
||||
this.HistoryStartIndex = (this.HistoryStartIndex + 1);
|
||||
this.LocationHistory.Add(new Coordinate(this.CurrentLocation));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.HistoryStartIndex = (this.HistoryStartIndex + 1)%HistoryLength;
|
||||
this.LocationHistory[this.HistoryStartIndex] = new Coordinate(this.CurrentLocation);
|
||||
}
|
||||
|
||||
double xSpeed = this.Speed.XSpeed;
|
||||
double ySpeed = this.Speed.YSpeed;
|
||||
double zSpeed = this.Speed.ZSpeed;
|
||||
|
||||
double x = this.CurrentLocation.X + xSpeed;
|
||||
double y = this.CurrentLocation.Y + ySpeed;
|
||||
double z = this.CurrentLocation.Z + zSpeed;
|
||||
|
||||
this.CurrentLocation = new Coordinate(this.CurrentLocation.X + xSpeed, this.CurrentLocation.Y + ySpeed, this.CurrentLocation.Z + zSpeed);
|
||||
|
||||
// trim to edges
|
||||
this.Speed = new Speed(
|
||||
CheckForEdge(x, xSpeed),
|
||||
CheckForEdge(y, ySpeed),
|
||||
CheckForEdge(z, zSpeed));
|
||||
|
||||
if (rotate)
|
||||
{
|
||||
this.Rotation = 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Rotation = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public string ToJson()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
this.ToJson(sb);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void ToJson(StringBuilder builder)
|
||||
{
|
||||
builder.Append("{");
|
||||
|
||||
{
|
||||
builder.Append("\"current\":");
|
||||
this.CurrentLocation.ToJson(builder);
|
||||
}
|
||||
|
||||
{
|
||||
builder.Append(", \"history\":");
|
||||
builder.Append("[");
|
||||
int currentIndex = this.HistoryStartIndex;
|
||||
if (currentIndex != -1)
|
||||
{
|
||||
bool first = true;
|
||||
do
|
||||
{
|
||||
currentIndex++;
|
||||
if (currentIndex == this.LocationHistory.Count)
|
||||
{
|
||||
currentIndex = 0;
|
||||
}
|
||||
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
this.LocationHistory[currentIndex].ToJson(builder);
|
||||
} while (currentIndex != this.HistoryStartIndex);
|
||||
}
|
||||
builder.Append("]");
|
||||
}
|
||||
|
||||
{
|
||||
builder.Append(", \"currentColor\":");
|
||||
this.CurrentColor.ToJson(builder);
|
||||
}
|
||||
|
||||
{
|
||||
builder.Append(", \"historyColor\":");
|
||||
this.HistoryColor.ToJson(builder);
|
||||
}
|
||||
|
||||
{
|
||||
builder.Append(", \"rotation\":");
|
||||
builder.Append(this.Rotation);
|
||||
}
|
||||
|
||||
builder.Append("}");
|
||||
}
|
||||
|
||||
private static double CheckForEdge(double point, double speed)
|
||||
{
|
||||
if (point < -1.0 || point > 1.0)
|
||||
{
|
||||
return speed*-1.0;
|
||||
}
|
||||
|
||||
return speed;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,113 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Common
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
public class VisualObjectsBox : IVisualObjectsBox
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, string> objectData;
|
||||
private readonly ConcurrentDictionary<string, Timer> expiryTimers;
|
||||
private readonly TimeSpan expiryInterval;
|
||||
private readonly Timer refreshTimer;
|
||||
private string currentJson;
|
||||
|
||||
public VisualObjectsBox()
|
||||
: this(Timeout.InfiniteTimeSpan, TimeSpan.FromMilliseconds(10))
|
||||
{
|
||||
}
|
||||
|
||||
public VisualObjectsBox(TimeSpan expiryInterval, TimeSpan refreshInterval)
|
||||
{
|
||||
this.expiryInterval = expiryInterval;
|
||||
if (expiryInterval != Timeout.InfiniteTimeSpan)
|
||||
{
|
||||
this.expiryTimers = new ConcurrentDictionary<string, Timer>();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.expiryTimers = null;
|
||||
}
|
||||
|
||||
this.objectData = new ConcurrentDictionary<string, string>();
|
||||
this.currentJson = "[]";
|
||||
this.refreshTimer = new Timer(
|
||||
new TimerCallback(this.RefreshJson),
|
||||
null,
|
||||
refreshInterval,
|
||||
refreshInterval);
|
||||
}
|
||||
|
||||
string IVisualObjectsBox.GetJson()
|
||||
{
|
||||
return this.currentJson;
|
||||
}
|
||||
|
||||
void IVisualObjectsBox.SetObject(string objectId, string objectJson)
|
||||
{
|
||||
if (this.expiryTimers != null)
|
||||
{
|
||||
if (this.expiryTimers.TryGetValue(objectId, out Timer expiryTimer))
|
||||
{
|
||||
ExtendExpiryTimer(expiryTimer);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.expiryTimers.AddOrUpdate(objectId,
|
||||
this.CreateExpiryTimer(objectId),
|
||||
(i, t) => ExtendExpiryTimer(t));
|
||||
}
|
||||
}
|
||||
|
||||
this.objectData[objectId] = "{\"id\":\"" + objectId + "\", \"node\":" + objectJson + "}";
|
||||
}
|
||||
|
||||
private void RemoveObject(string objectId)
|
||||
{
|
||||
if (this.expiryTimers != null)
|
||||
{
|
||||
this.expiryTimers.TryRemove(objectId, out Timer timer);
|
||||
}
|
||||
|
||||
this.objectData.TryRemove(objectId, out string objectJson);
|
||||
}
|
||||
|
||||
private Timer ExtendExpiryTimer(Timer t)
|
||||
{
|
||||
t.Change(expiryInterval, Timeout.InfiniteTimeSpan);
|
||||
return t;
|
||||
}
|
||||
|
||||
private void OnObjectExpired(object state)
|
||||
{
|
||||
string objectId = (string)state;
|
||||
RemoveObject(objectId);
|
||||
}
|
||||
|
||||
private void RefreshJson(object state)
|
||||
{
|
||||
if (this.objectData.Keys.Count > 0)
|
||||
{
|
||||
this.currentJson = "[" + String.Join(",", this.objectData.Values) + "]";
|
||||
}
|
||||
else
|
||||
{
|
||||
this.currentJson = "[]";
|
||||
}
|
||||
}
|
||||
|
||||
private Timer CreateExpiryTimer(string objectId)
|
||||
{
|
||||
return new Timer(
|
||||
new TimerCallback(OnObjectExpired),
|
||||
objectId,
|
||||
this.expiryInterval,
|
||||
Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27428.2043
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9B56FCB0-63B4-404F-8A09-42EF52B53347}") = "VisualObjects", "VisualObjects\VisualObjects.sfaproj", "{F3713D3C-FEB8-4951-BCFF-63606F992E52}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "web", "web\web.csproj", "{AF13B73E-E661-4BBD-90A5-5FD6752E8ACA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisualObjects.Common", "VisualObjects.Common\VisualObjects.Common.csproj", "{FAAA707B-1F39-4D77-B2FD-0E2BEE6F7D7C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "worker", "worker\worker.csproj", "{DE9FB102-B975-4841-9828-58493D68F2E2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F3713D3C-FEB8-4951-BCFF-63606F992E52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F3713D3C-FEB8-4951-BCFF-63606F992E52}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F3713D3C-FEB8-4951-BCFF-63606F992E52}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F3713D3C-FEB8-4951-BCFF-63606F992E52}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AF13B73E-E661-4BBD-90A5-5FD6752E8ACA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AF13B73E-E661-4BBD-90A5-5FD6752E8ACA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AF13B73E-E661-4BBD-90A5-5FD6752E8ACA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AF13B73E-E661-4BBD-90A5-5FD6752E8ACA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FAAA707B-1F39-4D77-B2FD-0E2BEE6F7D7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FAAA707B-1F39-4D77-B2FD-0E2BEE6F7D7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FAAA707B-1F39-4D77-B2FD-0E2BEE6F7D7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FAAA707B-1F39-4D77-B2FD-0E2BEE6F7D7C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DE9FB102-B975-4841-9828-58493D68F2E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DE9FB102-B975-4841-9828-58493D68F2E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DE9FB102-B975-4841-9828-58493D68F2E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DE9FB102-B975-4841-9828-58493D68F2E2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {0329D31B-D1EA-4A8C-B679-C79993DD4B4A}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,4 @@
|
|||
application:
|
||||
schemaVersion: 0.0.1
|
||||
name: VisualObjects
|
||||
description: VisualObjects description.
|
|
@ -0,0 +1,13 @@
|
|||
## Network definition ##
|
||||
network:
|
||||
schemaVersion: 0.0.1
|
||||
name: VisualObjectsNetwork
|
||||
description: VisualObjectsNetwork description.
|
||||
addressPrefix: 10.0.0.4/22
|
||||
ingressConfig:
|
||||
layer4:
|
||||
- name: webIngress
|
||||
publicPort: 80
|
||||
applicationName: VisualObjects
|
||||
serviceName: web
|
||||
endpointName: webListener
|
|
@ -0,0 +1,3 @@
|
|||
## Visual Studio SFApp Publish Profile ##
|
||||
vsSFAppPublishProfile:
|
||||
schemaVersion: 0.0.1
|
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.SFApp.Sdk">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>f3713d3c-feb8-4951-bcff-63606f992e52</ProjectGuid>
|
||||
<SFApplicationProjectVersion>0.3.0</SFApplicationProjectVersion>
|
||||
<SFApplicationTargetsVersion>0.3.0</SFApplicationTargetsVersion>
|
||||
<SFApplicationToolingVersion>0.6.0.0</SFApplicationToolingVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App Resources\app.yaml" />
|
||||
<None Include="App Resources\network.yaml" />
|
||||
<None Include="Publish Profiles\cloud.yaml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,6 @@
|
|||
docker build . -f web\Dockerfile -t seabreeze/azure-mesh-visualobjects-web:1.0-nanoserver-1709
|
||||
docker build . -f worker\Dockerfile -t seabreeze/azure-mesh-visualobjects-worker:1.0-nanoserver-1709
|
||||
docker build . -f worker\rotate.Dockerfile -t seabreeze/azure-mesh-visualobjects-worker:1.0-rotate-nanoserver-1709
|
||||
docker push seabreeze/azure-mesh-visualobjects-web:1.0-nanoserver-1709
|
||||
docker push seabreeze/azure-mesh-visualobjects-worker:1.0-nanoserver-1709
|
||||
docker push seabreeze/azure-mesh-visualobjects-worker:1.0-rotate-nanoserver-1709
|
|
@ -0,0 +1,3 @@
|
|||
docker build . -f web\Dockerfile
|
||||
docker build . -f worker\Dockerfile
|
||||
docker build . -f worker\rotate.Dockerfile
|
|
@ -0,0 +1,49 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Web.Controllers
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using VisualObjects.Common;
|
||||
|
||||
|
||||
[Route("api/[controller]")]
|
||||
public class ValuesController : Controller
|
||||
{
|
||||
private IVisualObjectsBox objectBox;
|
||||
|
||||
public ValuesController(IVisualObjectsBox objectBox)
|
||||
{
|
||||
this.objectBox = objectBox;
|
||||
}
|
||||
|
||||
// GET api/values/getjson
|
||||
[HttpGet("{id}")]
|
||||
public string getjson()
|
||||
{
|
||||
return this.objectBox.GetJson();
|
||||
}
|
||||
|
||||
// POST api/values
|
||||
[HttpPost]
|
||||
public ActionResult Post([FromQuery]string id)
|
||||
{
|
||||
var host = this.HttpContext.Request.Host;
|
||||
var req = this.HttpContext.Request;
|
||||
var jsonData = string.Empty;
|
||||
|
||||
using (StreamReader reader = new StreamReader(req.Body))
|
||||
{
|
||||
jsonData = reader.ReadToEnd();
|
||||
}
|
||||
|
||||
this.objectBox.SetObject(id, jsonData);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
FROM microsoft/aspnetcore:2.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
|
||||
FROM microsoft/aspnetcore-build:2.0 AS build
|
||||
WORKDIR /src
|
||||
COPY web/web.csproj web/
|
||||
RUN dotnet restore web/web.csproj
|
||||
COPY . .
|
||||
WORKDIR /src/web
|
||||
RUN dotnet build web.csproj -c Release -o /app
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish web.csproj -c Release -o /app
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app .
|
||||
ENTRYPOINT ["dotnet", "web.dll"]
|
|
@ -0,0 +1,24 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Web
|
||||
{
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BuildWebHost(args).Run();
|
||||
}
|
||||
|
||||
public static IWebHost BuildWebHost(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseStartup<Startup>()
|
||||
.UseApplicationInsights()
|
||||
.Build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
## Service definition ##
|
||||
application:
|
||||
schemaVersion: 0.0.1
|
||||
name: VisualObjects
|
||||
services:
|
||||
- name: web
|
||||
description: web description.
|
||||
osType: Windows
|
||||
codePackages:
|
||||
- name: web
|
||||
image: web:dev
|
||||
endpoints:
|
||||
- name: webListener
|
||||
port: 20005
|
||||
environmentVariables:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: http://+:80
|
||||
# - name: ApplicationInsights:InstrumentationKey
|
||||
# value: "<Place AppInsights key here, or reference it via a secret>"
|
||||
resources:
|
||||
requests:
|
||||
cpu: 0.5
|
||||
memoryInGB: 1
|
||||
replicaCount: 1
|
||||
networkRefs:
|
||||
- name: VisualObjectsNetwork
|
|
@ -0,0 +1,128 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Web
|
||||
{
|
||||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
using VisualObjects.Common;
|
||||
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMvc();
|
||||
services.AddSingleton<IVisualObjectsBox>(CreateVisualObjectsBox());
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
app.UseMvc();
|
||||
var webSocketOptions = new WebSocketOptions()
|
||||
{
|
||||
KeepAliveInterval = TimeSpan.FromSeconds(120),
|
||||
};
|
||||
app.UseWebSockets(webSocketOptions);
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
if (context.Request.Path == "/data")
|
||||
{
|
||||
if (context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
await SendData(context, webSocket, app.ApplicationServices.GetService<IVisualObjectsBox>());
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await next();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private async Task SendData(HttpContext context, WebSocket webSocket, IVisualObjectsBox objectBox)
|
||||
{
|
||||
using (webSocket)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(objectBox.GetJson());
|
||||
|
||||
try
|
||||
{
|
||||
await webSocket.SendAsync(
|
||||
new ArraySegment<byte>(buffer, 0, buffer.Length),
|
||||
WebSocketMessageType.Text,
|
||||
true,
|
||||
CancellationToken.None);
|
||||
|
||||
if (webSocket.State != WebSocketState.Open)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (WebSocketException)
|
||||
{
|
||||
// If the browser quit or the socket was closed, exit this loop so we can get a new browser socket.
|
||||
break;
|
||||
}
|
||||
|
||||
// wait a bit and continue. This determines the client refresh rate.
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(10), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static IVisualObjectsBox CreateVisualObjectsBox()
|
||||
{
|
||||
TimeSpan refreshInterval = TimeSpan.FromMilliseconds(10);
|
||||
if (long.TryParse(Environment.GetEnvironmentVariable("BOX_REFRESH_INTERVAL_MILLIS"), out long refreshIntervalMillis))
|
||||
{
|
||||
refreshInterval = TimeSpan.FromMilliseconds(refreshIntervalMillis);
|
||||
}
|
||||
|
||||
TimeSpan expiryInterval = TimeSpan.FromMilliseconds(10000);
|
||||
if (long.TryParse(Environment.GetEnvironmentVariable("BOX_EXPIRY_INTERVAL_MILLIS"), out long expiryIntervalMillis))
|
||||
{
|
||||
expiryInterval = TimeSpan.FromMilliseconds(expiryIntervalMillis);
|
||||
}
|
||||
|
||||
Console.WriteLine("Creating box with Expiry = " + expiryInterval + ", Refresh = " + refreshInterval);
|
||||
return new VisualObjectsBox(expiryInterval, refreshInterval);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"Debug": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"Console": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.SFApp.Targets" Version="0.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\VisualObjects.Common\VisualObjects.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,20 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Microsoft Azure Service Fabric Visual Objects Demo</title>
|
||||
<script type="text/javascript" src="Scripts/visualobjects.js"></script>
|
||||
<script type="text/javascript" src="Scripts/paper-full.js"></script>
|
||||
<style type="text/css">
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: 99%;
|
||||
height: 98%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="startDrawing();">
|
||||
<canvas id="canvas" resize keepalive="true"> </canvas>
|
||||
</body>
|
||||
</html>
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,199 @@
|
|||
var newDataReceived = false;
|
||||
var jsonData = "";
|
||||
|
||||
var triangles = new Array();
|
||||
var triangleHistories = new Array();
|
||||
|
||||
function onNewDataReceived(jsonString) {
|
||||
jsonData = jsonString;
|
||||
newDataReceived = true;
|
||||
}
|
||||
|
||||
function drawScene() {
|
||||
|
||||
if (newDataReceived) {
|
||||
|
||||
var newNodes = null;
|
||||
try {
|
||||
newNodes = JSON.parse(jsonData);
|
||||
}
|
||||
catch (err) {
|
||||
newNodes = null;
|
||||
}
|
||||
|
||||
if (newNodes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var numNewNodes = newNodes.length;
|
||||
var newNodesTracker = new Array();
|
||||
for (newNodesIndex = 0; newNodesIndex < numNewNodes; ++newNodesIndex) {
|
||||
var newNodeEntry = newNodes[newNodesIndex];
|
||||
newNodesTracker[newNodeEntry.id] = newNodeEntry.node;
|
||||
|
||||
var currentTriangle = triangles[newNodeEntry.id];
|
||||
if (currentTriangle == null) {
|
||||
|
||||
// new object
|
||||
// create new triangle
|
||||
createTriangles(newNodeEntry);
|
||||
} else {
|
||||
|
||||
// existing oject
|
||||
// update existing triangle
|
||||
updateTriangles(newNodeEntry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Object.keys(triangles).length != numNewNodes) {
|
||||
// remove the extra nodes
|
||||
var newTriangles = new Array();
|
||||
var newTriangleHistories = new Array();
|
||||
|
||||
|
||||
for (var key in triangles) {
|
||||
if (triangles.hasOwnProperty(key)) {
|
||||
|
||||
if (newNodesTracker[key] == null) {
|
||||
|
||||
removeTriangles(key);
|
||||
} else {
|
||||
|
||||
newTriangles[key] = triangles[key];
|
||||
newTriangleHistories[key] = triangleHistories[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
triangles = newTriangles;
|
||||
triangleHistories = newTriangleHistories;
|
||||
}
|
||||
|
||||
// wait for the new data to be received before repainting
|
||||
newDataReceived = false;
|
||||
}
|
||||
}
|
||||
|
||||
function createTriangles(nodeEntry) {
|
||||
var nodeId = nodeEntry.id;
|
||||
var node = nodeEntry.node;
|
||||
|
||||
var t = new Path.RegularPolygon(new Point(0, 0), 3, 20);
|
||||
t.fillColor = new Color(node.currentColor.r, node.currentColor.g, node.currentColor.b);
|
||||
triangles[nodeId] = t;
|
||||
|
||||
var numHistory = node.history.length;
|
||||
triangleHistories[nodeId] = new Array();
|
||||
|
||||
for (historyEntry = 0; historyEntry < numHistory; ++historyEntry) {
|
||||
var h = new Path.RegularPolygon(new Point(0, 0), 3, (20 - (2 * (numHistory - historyEntry))));
|
||||
h.fillColor = new Color(node.currentColor.r, node.currentColor.g, node.currentColor.b);
|
||||
h.fillColor.alpha = 1 - (0.11 * (numHistory - historyEntry));
|
||||
triangleHistories[nodeId][historyEntry] = h;
|
||||
}
|
||||
}
|
||||
|
||||
function removeTriangles(nodeId) {
|
||||
|
||||
var t = triangles[nodeId];
|
||||
t.fillColor = 'black';
|
||||
t.remove();
|
||||
|
||||
if (triangleHistories[nodeId] != null) {
|
||||
var histories = triangleHistories[nodeId];
|
||||
var numHistory = histories.length;
|
||||
for (historyEntry = 0; historyEntry < numHistory; ++historyEntry) {
|
||||
var h = triangleHistories[nodeId][historyEntry];
|
||||
h.fillColor = 'black';
|
||||
h.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateTriangles(nodeEntry) {
|
||||
var nodeId = nodeEntry.id;
|
||||
var node = nodeEntry.node;
|
||||
|
||||
triangles[nodeId].position = scalePosToViewport(node.current.x, node.current.y);
|
||||
triangles[nodeId].rotation = node.rotation;
|
||||
|
||||
var numHistory = node.history.length;
|
||||
var currentHistoryCount = Object.keys(triangleHistories[nodeId]).length;
|
||||
|
||||
if (numHistory != currentHistoryCount) {
|
||||
|
||||
// remove existing history and recreate the history
|
||||
for (historyEntry = 0; historyEntry < currentHistoryCount; ++historyEntry) {
|
||||
var historyTriangle = triangleHistories[nodeId][historyEntry];
|
||||
historyTriangle.currentColor = 'black';
|
||||
historyTriangle.remove();
|
||||
}
|
||||
|
||||
triangleHistories[nodeId] = new Array();
|
||||
for (historyEntry = 0; historyEntry < numHistory; ++historyEntry) {
|
||||
var h = new Path.RegularPolygon(new Point(0, 0), 3, (20 - (2 * (numHistory - historyEntry))));
|
||||
h.fillColor = new Color(node.currentColor.r, node.currentColor.g, node.currentColor.b);
|
||||
h.fillColor.alpha = 1 - (0.11 * (numHistory - historyEntry));
|
||||
triangleHistories[nodeId][historyEntry] = h;
|
||||
}
|
||||
|
||||
} else {
|
||||
// update history
|
||||
for (historyEntry = 0; historyEntry < numHistory; ++historyEntry) {
|
||||
var historyNodeData = node.history[historyEntry];
|
||||
var historyTriangle = triangleHistories[nodeId][historyEntry];
|
||||
historyTriangle.position = scalePosToViewport(historyNodeData.x, historyNodeData.y);
|
||||
historyTriangle.rotation = node.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scalePosToViewport(nodex, nodey) {
|
||||
var xfactor = view.viewSize.width / 2;
|
||||
var yfactor = view.viewSize.height / 2;
|
||||
|
||||
var xval = nodex + 1;
|
||||
var yval = nodey + 1;
|
||||
|
||||
//scaling factor is width or height over 2.
|
||||
//2 = width or height, 0 = 0;
|
||||
|
||||
return new Point(xval * xfactor, yval * yfactor);
|
||||
}
|
||||
|
||||
function startDrawing() {
|
||||
var canvas = document.getElementById("canvas");
|
||||
|
||||
canvas.style.border = "#00ff00 3px solid";
|
||||
|
||||
paper.install(window);
|
||||
paper.setup('canvas');
|
||||
|
||||
initWebSocket();
|
||||
|
||||
view.onFrame = function (event) {
|
||||
drawScene();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var websocket;
|
||||
|
||||
function initWebSocket() {
|
||||
websocket = new WebSocket("ws://" + window.location.host + "/data");
|
||||
|
||||
websocket.onopen = function () { };
|
||||
|
||||
websocket.onmessage = function (args) {
|
||||
onNewDataReceived(args.data);
|
||||
};
|
||||
|
||||
websocket.onclose = function (args) {
|
||||
setTimeout(initWebSocket, 100);
|
||||
};
|
||||
|
||||
websocket.onerror = function (error) {
|
||||
websocket.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Worker
|
||||
{
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// sends the location of the visual object, until cancelled
|
||||
internal class DataSender
|
||||
{
|
||||
private const string SendAddressEnvVar = "BOX_ADDRESS";
|
||||
private static readonly String SendAddress;
|
||||
private static readonly HttpClient Client;
|
||||
private static bool ReportError = true;
|
||||
|
||||
static DataSender()
|
||||
{
|
||||
if (Environment.GetEnvironmentVariable(SendAddressEnvVar) != null)
|
||||
{
|
||||
SendAddress = Environment.GetEnvironmentVariable(SendAddressEnvVar);
|
||||
}
|
||||
else
|
||||
{
|
||||
SendAddress = "web:80";
|
||||
}
|
||||
|
||||
Client = new HttpClient();
|
||||
}
|
||||
|
||||
public static async Task<bool> SendData(string objectId, string content, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new HttpRequestMessage()
|
||||
{
|
||||
Method = HttpMethod.Post,
|
||||
Content = new StringContent(content, Encoding.UTF8),
|
||||
RequestUri = new Uri($"http://{SendAddress}/api/values?id={objectId}"),
|
||||
};
|
||||
|
||||
request.Content.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("text/plain; charset=utf-8");
|
||||
|
||||
var response = await Client.SendAsync(request, cancellationToken);
|
||||
if (response.StatusCode != System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
if (ReportError)
|
||||
{
|
||||
Console.WriteLine("Error in sending the data " + response);
|
||||
ReportError = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportError = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (ReportError)
|
||||
{
|
||||
Console.WriteLine("Error in sending the data " + e.Message);
|
||||
ReportError = false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
FROM microsoft/dotnet:2.0-runtime AS base
|
||||
WORKDIR /app
|
||||
|
||||
FROM microsoft/dotnet:2.0-sdk AS build
|
||||
WORKDIR /src
|
||||
COPY worker/worker.csproj worker/
|
||||
RUN dotnet restore worker/worker.csproj
|
||||
COPY . .
|
||||
WORKDIR /src/worker
|
||||
RUN dotnet build worker.csproj -c Release -o /app
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish worker.csproj -c Release -o /app
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app .
|
||||
ENV OBJECT_ENABLE_ROTATION=false
|
||||
ENTRYPOINT ["dotnet", "worker.dll"]
|
|
@ -0,0 +1,143 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Worker
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
using VisualObjects.Common;
|
||||
|
||||
internal class FileStateStore : IStateStore
|
||||
{
|
||||
private const string StateFolderName = "Data";
|
||||
private const string SubFolderNameEnvVar = "Fabric_Id";
|
||||
private const string StateFileName = "visualobjects.data.xml";
|
||||
|
||||
private DataContractSerializer serializer;
|
||||
private string stateFilePath;
|
||||
private string stateFolderPath;
|
||||
|
||||
public FileStateStore()
|
||||
: this(GetDefaultFolderPath())
|
||||
{
|
||||
}
|
||||
|
||||
public FileStateStore(string folderPath)
|
||||
{
|
||||
this.stateFolderPath = folderPath;
|
||||
this.stateFilePath = Path.Combine(folderPath, StateFileName);
|
||||
this.serializer = new DataContractSerializer(
|
||||
typeof(VisualObject),
|
||||
new DataContractSerializerSettings()
|
||||
{
|
||||
MaxItemsInObjectGraph = int.MaxValue
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<VisualObject> ReadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!File.Exists(this.stateFilePath))
|
||||
{
|
||||
Console.WriteLine($"State file {this.stateFilePath} does not exist. Creating new object.");
|
||||
return VisualObject.CreateRandom(Guid.NewGuid().ToString());
|
||||
}
|
||||
|
||||
// file exists read it
|
||||
byte[] data = null;
|
||||
using (var stream = new FileStream(
|
||||
this.stateFilePath,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.None,
|
||||
4096,
|
||||
true))
|
||||
{
|
||||
data = new byte[stream.Length];
|
||||
await stream.ReadAsync(data, 0, data.Length, cancellationToken);
|
||||
}
|
||||
|
||||
// deserialize
|
||||
try
|
||||
{
|
||||
return this.Deserialize(data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"Read error. recreating the object. {e.ToString()}");
|
||||
return VisualObject.CreateRandom(Guid.NewGuid().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WriteAsync(VisualObject state, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!Directory.Exists(this.stateFolderPath))
|
||||
{
|
||||
Directory.CreateDirectory(this.stateFolderPath);
|
||||
}
|
||||
|
||||
var data = this.Serialize(state);
|
||||
using (var stream = new FileStream(
|
||||
this.stateFilePath,
|
||||
FileMode.Create,
|
||||
FileAccess.Write,
|
||||
FileShare.None,
|
||||
4096,
|
||||
true))
|
||||
{
|
||||
await stream.WriteAsync(data, 0, data.Length, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] Serialize(VisualObject parameters)
|
||||
{
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
using (var writer = XmlDictionaryWriter.CreateTextWriter(stream))
|
||||
{
|
||||
this.serializer.WriteObject(writer, parameters);
|
||||
writer.Flush();
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private VisualObject Deserialize(byte[] parameters)
|
||||
{
|
||||
if (parameters == null)
|
||||
{
|
||||
throw new ArgumentNullException("parameters", "argument should not be null");
|
||||
}
|
||||
|
||||
using (var stream = new MemoryStream(parameters))
|
||||
{
|
||||
using (var reader = XmlDictionaryReader.CreateTextReader(stream, XmlDictionaryReaderQuotas.Max))
|
||||
{
|
||||
return (VisualObject)this.serializer.ReadObject(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetDefaultFolderPath()
|
||||
{
|
||||
var subFolderName = Environment.GetEnvironmentVariable(SubFolderNameEnvVar);
|
||||
if (null == subFolderName)
|
||||
{
|
||||
subFolderName = String.Empty;
|
||||
}
|
||||
|
||||
var codeFolderFullPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
var baseFolderFullPath = Path.GetDirectoryName(codeFolderFullPath);
|
||||
var stateFolderPath = Path.Combine(baseFolderFullPath, StateFolderName, subFolderName);
|
||||
|
||||
return stateFolderPath;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Worker
|
||||
{
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using VisualObjects.Common;
|
||||
|
||||
internal interface IStateStore
|
||||
{
|
||||
Task<VisualObject> ReadAsync(CancellationToken cancellationToken);
|
||||
|
||||
Task WriteAsync(VisualObject obj, CancellationToken cancellationToken);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Worker
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using VisualObjects.Common;
|
||||
|
||||
// moves a visual object and save it to the store
|
||||
internal class Mover
|
||||
{
|
||||
private const string MoveSpeedEnvVar = "OBJECT_MOVE_INTERVAL_MILLIS";
|
||||
private const string EnableRotationEnv = "OBJECT_ENABLE_ROTATION";
|
||||
|
||||
private static readonly TimeSpan MoveSpeed;
|
||||
private static readonly bool Rotate;
|
||||
|
||||
static Mover()
|
||||
{
|
||||
if (bool.TryParse(
|
||||
Environment.GetEnvironmentVariable(EnableRotationEnv),
|
||||
out bool rotate))
|
||||
{
|
||||
Rotate = rotate;
|
||||
}
|
||||
else
|
||||
{
|
||||
Rotate = false;
|
||||
}
|
||||
|
||||
if (long.TryParse(
|
||||
Environment.GetEnvironmentVariable(MoveSpeedEnvVar),
|
||||
out long millis))
|
||||
{
|
||||
MoveSpeed = TimeSpan.FromMilliseconds(millis);
|
||||
}
|
||||
else
|
||||
{
|
||||
MoveSpeed = TimeSpan.FromMilliseconds(50);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static async Task MoveAsync(
|
||||
IStateStore stateStore,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
long totalMoves = 0;
|
||||
long successfulMoves = 0;
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
totalMoves++;
|
||||
var obj = await ReadObjectAsync(stateStore, cancellationToken);
|
||||
if (obj != null)
|
||||
{
|
||||
if (await DataSender.SendData(obj.Name, obj.ToJson(), cancellationToken))
|
||||
{
|
||||
obj.Move(Rotate);
|
||||
await WriteObjectAsync(stateStore, obj, cancellationToken);
|
||||
successfulMoves++;
|
||||
}
|
||||
}
|
||||
|
||||
if ((totalMoves % 1000) == 0)
|
||||
{
|
||||
Console.WriteLine($"Completed {successfulMoves}/{totalMoves} sucessful moves.");
|
||||
}
|
||||
|
||||
await Task.Delay(MoveSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<VisualObject> ReadObjectAsync(
|
||||
IStateStore stateStore,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await stateStore.ReadAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"Error in reading the object. {e.ToString()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task WriteObjectAsync(
|
||||
IStateStore stateStore,
|
||||
VisualObject obj,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await stateStore.WriteAsync(obj, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"Error in writing the object. {e.ToString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace VisualObjects.Worker
|
||||
{
|
||||
using System.Threading;
|
||||
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Mover.MoveAsync(new FileStateStore(), CancellationToken.None).Wait();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
## Service definition ##
|
||||
application:
|
||||
schemaVersion: 0.0.1
|
||||
name: VisualObjects
|
||||
services:
|
||||
- name: worker
|
||||
description: worker description.
|
||||
osType: Windows
|
||||
codePackages:
|
||||
- name: worker
|
||||
image: worker:dev
|
||||
endpoints:
|
||||
- name: workerListener
|
||||
port: 20009
|
||||
resources:
|
||||
requests:
|
||||
cpu: 0.5
|
||||
memoryInGB: 1
|
||||
replicaCount: 1
|
||||
networkRefs:
|
||||
- name: VisualObjectsNetwork
|
|
@ -0,0 +1,19 @@
|
|||
FROM microsoft/dotnet:2.0-runtime AS base
|
||||
WORKDIR /app
|
||||
|
||||
FROM microsoft/dotnet:2.0-sdk AS build
|
||||
WORKDIR /src
|
||||
COPY worker/worker.csproj worker/
|
||||
RUN dotnet restore worker/worker.csproj
|
||||
COPY . .
|
||||
WORKDIR /src/worker
|
||||
RUN dotnet build worker.csproj -c Release -o /app
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish worker.csproj -c Release -o /app
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app .
|
||||
ENV OBJECT_ENABLE_ROTATION=true
|
||||
ENTRYPOINT ["dotnet", "worker.dll"]
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.SFApp.Targets" Version="0.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\VisualObjects.Common\VisualObjects.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,110 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"defaultValue": "westus",
|
||||
"metadata": {
|
||||
"description": "Location of the resources."
|
||||
}
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "2018-03-01-privatepreview",
|
||||
"name": "visualObjectsNetworkWindows",
|
||||
"type": "Microsoft.ServiceFabric/networks",
|
||||
"location": "[parameters('location')]",
|
||||
"dependsOn": [],
|
||||
"properties": {
|
||||
"schemaVersion": "0.0.1",
|
||||
"addressPrefix": "10.0.0.4/22",
|
||||
"ingressConfig": {
|
||||
"layer4": [
|
||||
{
|
||||
"publicPort": "80",
|
||||
"applicationName": "visualObjectsAppWindows",
|
||||
"serviceName": "web",
|
||||
"endpointName": "webListener"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "2018-03-01-privatepreview",
|
||||
"name": "visualObjectsAppWindows",
|
||||
"type": "Microsoft.ServiceFabric/applications",
|
||||
"location": "[parameters('location')]",
|
||||
"dependsOn": [
|
||||
"Microsoft.ServiceFabric/networks/visualObjectsNetworkWindows"
|
||||
],
|
||||
"properties": {
|
||||
"description": "Service Fabric Mesh Visual Objects Application!",
|
||||
"services": [
|
||||
{
|
||||
"type": "Microsoft.ServiceFabric/services",
|
||||
"location": "[parameters('location')]",
|
||||
"name": "web",
|
||||
"properties": {
|
||||
"description": "Service Fabric Mesh Visual Objects Web Service.",
|
||||
"osType": "windows",
|
||||
"codePackages": [
|
||||
{
|
||||
"name": "code",
|
||||
"image": "seabreeze/azure-mesh-visualobjects-web:1.0-nanoserver-1709",
|
||||
"endpoints": [
|
||||
{
|
||||
"name": "webListener",
|
||||
"port": "80"
|
||||
}
|
||||
],
|
||||
"resources": {
|
||||
"requests": {
|
||||
"cpu": "1",
|
||||
"memoryInGB": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"replicaCount": "1",
|
||||
"networkRefs": [
|
||||
{
|
||||
"name": "[resourceId('Microsoft.ServiceFabric/networks', 'visualObjectsNetworkWindows')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Microsoft.ServiceFabric/services",
|
||||
"location": "[parameters('location')]",
|
||||
"name": "worker",
|
||||
"properties": {
|
||||
"description": "Service Fabric Mesh Visual Objects Worker Service.",
|
||||
"osType": "windows",
|
||||
"codePackages": [
|
||||
{
|
||||
"name": "code",
|
||||
"image": "seabreeze/azure-mesh-visualobjects-worker:1.0-nanoserver-1709",
|
||||
"resources": {
|
||||
"requests": {
|
||||
"cpu": "1",
|
||||
"memoryInGB": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"replicaCount": "1",
|
||||
"networkRefs": [
|
||||
{
|
||||
"name": "[resourceId('Microsoft.ServiceFabric/networks', 'visualObjectsNetworkWindows')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"defaultValue": "westus",
|
||||
"metadata": {
|
||||
"description": "Location of the resources."
|
||||
}
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "2018-03-01-privatepreview",
|
||||
"name": "visualObjectsNetworkWindows",
|
||||
"type": "Microsoft.ServiceFabric/networks",
|
||||
"location": "[parameters('location')]",
|
||||
"dependsOn": [],
|
||||
"properties": {
|
||||
"schemaVersion": "0.0.1",
|
||||
"addressPrefix": "10.0.0.4/22",
|
||||
"ingressConfig": {
|
||||
"layer4": [
|
||||
{
|
||||
"publicPort": "80",
|
||||
"applicationName": "visualObjectsAppWindows",
|
||||
"serviceName": "web",
|
||||
"endpointName": "webListener"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "2018-03-01-privatepreview",
|
||||
"name": "visualObjectsAppWindows",
|
||||
"type": "Microsoft.ServiceFabric/applications",
|
||||
"location": "[parameters('location')]",
|
||||
"dependsOn": [
|
||||
"Microsoft.ServiceFabric/networks/visualObjectsNetworkWindows"
|
||||
],
|
||||
"properties": {
|
||||
"description": "Service Fabric Mesh Visual Objects Application!",
|
||||
"services": [
|
||||
{
|
||||
"type": "Microsoft.ServiceFabric/services",
|
||||
"location": "[parameters('location')]",
|
||||
"name": "web",
|
||||
"properties": {
|
||||
"description": "Service Fabric Mesh Visual Objects Web Service.",
|
||||
"osType": "windows",
|
||||
"codePackages": [
|
||||
{
|
||||
"name": "code",
|
||||
"image": "seabreeze/azure-mesh-visualobjects-web:1.0-nanoserver-1709",
|
||||
"endpoints": [
|
||||
{
|
||||
"name": "webListener",
|
||||
"port": "80"
|
||||
}
|
||||
],
|
||||
"resources": {
|
||||
"requests": {
|
||||
"cpu": "1",
|
||||
"memoryInGB": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"replicaCount": "1",
|
||||
"networkRefs": [
|
||||
{
|
||||
"name": "[resourceId('Microsoft.ServiceFabric/networks', 'visualObjectsNetworkWindows')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Microsoft.ServiceFabric/services",
|
||||
"location": "[parameters('location')]",
|
||||
"name": "worker",
|
||||
"properties": {
|
||||
"description": "Service Fabric Mesh Visual Objects Worker Service.",
|
||||
"osType": "windows",
|
||||
"codePackages": [
|
||||
{
|
||||
"name": "code",
|
||||
"image": "seabreeze/azure-mesh-visualobjects-worker:1.0-nanoserver-1709",
|
||||
"resources": {
|
||||
"requests": {
|
||||
"cpu": "1",
|
||||
"memoryInGB": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"replicaCount": "3",
|
||||
"networkRefs": [
|
||||
{
|
||||
"name": "[resourceId('Microsoft.ServiceFabric/networks', 'visualObjectsNetworkWindows')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"defaultValue": "westus",
|
||||
"metadata": {
|
||||
"description": "Location of the resources."
|
||||
}
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "2018-03-01-privatepreview",
|
||||
"name": "visualObjectsNetworkWindows",
|
||||
"type": "Microsoft.ServiceFabric/networks",
|
||||
"location": "[parameters('location')]",
|
||||
"dependsOn": [],
|
||||
"properties": {
|
||||
"schemaVersion": "0.0.1",
|
||||
"addressPrefix": "10.0.0.4/22",
|
||||
"ingressConfig": {
|
||||
"layer4": [
|
||||
{
|
||||
"publicPort": "80",
|
||||
"applicationName": "visualObjectsAppWindows",
|
||||
"serviceName": "web",
|
||||
"endpointName": "webListener"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "2018-03-01-privatepreview",
|
||||
"name": "visualObjectsAppWindows",
|
||||
"type": "Microsoft.ServiceFabric/applications",
|
||||
"location": "[parameters('location')]",
|
||||
"dependsOn": [
|
||||
"Microsoft.ServiceFabric/networks/visualObjectsNetworkWindows"
|
||||
],
|
||||
"properties": {
|
||||
"description": "Service Fabric Mesh Visual Objects Application!",
|
||||
"services": [
|
||||
{
|
||||
"type": "Microsoft.ServiceFabric/services",
|
||||
"location": "[parameters('location')]",
|
||||
"name": "web",
|
||||
"properties": {
|
||||
"description": "Service Fabric Mesh Visual Objects Web Service.",
|
||||
"osType": "windows",
|
||||
"codePackages": [
|
||||
{
|
||||
"name": "code",
|
||||
"image": "seabreeze/azure-mesh-visualobjects-web:1.0-nanoserver-1709",
|
||||
"endpoints": [
|
||||
{
|
||||
"name": "webListener",
|
||||
"port": "80"
|
||||
}
|
||||
],
|
||||
"resources": {
|
||||
"requests": {
|
||||
"cpu": "1",
|
||||
"memoryInGB": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"replicaCount": "1",
|
||||
"networkRefs": [
|
||||
{
|
||||
"name": "[resourceId('Microsoft.ServiceFabric/networks', 'visualObjectsNetworkWindows')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Microsoft.ServiceFabric/services",
|
||||
"location": "[parameters('location')]",
|
||||
"name": "worker",
|
||||
"properties": {
|
||||
"description": "Service Fabric Mesh Visual Objects Worker Service.",
|
||||
"osType": "windows",
|
||||
"codePackages": [
|
||||
{
|
||||
"name": "code",
|
||||
"image": "seabreeze/azure-mesh-visualobjects-worker:1.0-rotate-nanoserver-1709",
|
||||
"resources": {
|
||||
"requests": {
|
||||
"cpu": "1",
|
||||
"memoryInGB": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"replicaCount": "3",
|
||||
"networkRefs": [
|
||||
{
|
||||
"name": "[resourceId('Microsoft.ServiceFabric/networks', 'visualObjectsNetworkWindows')]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
Загрузка…
Ссылка в новой задаче