зеркало из https://github.com/Azure/iotedge.git
Create temp sensor end-to-end test using new shared lib (#1118)
This commit is contained in:
Родитель
02eb6e177b
Коммит
d4094df3be
|
@ -171,6 +171,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edg
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DirectMethodCloudSender", "edge-modules\DirectMethodCloudSender\DirectMethodCloudSender.csproj", "{A68ABCD4-5926-4005-9525-559DD74991C9}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2300ED4C-1D5A-460F-8691-7C85E1162E0C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Test.Common", "test\Microsoft.Azure.Devices.Edge.Test.Common\Microsoft.Azure.Devices.Edge.Test.Common.csproj", "{950DACB0-B011-41AF-B0FB-245F749B01AB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Test.TempSensor", "test\Microsoft.Azure.Devices.Edge.Test.TempSensor\Microsoft.Azure.Devices.Edge.Test.TempSensor.csproj", "{FCF94FF4-A608-4CC1-8D1D-215B6F88F203}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
CheckInBuild|Any CPU = CheckInBuild|Any CPU
|
||||
|
@ -547,6 +553,22 @@ Global
|
|||
{A68ABCD4-5926-4005-9525-559DD74991C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A68ABCD4-5926-4005-9525-559DD74991C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A68ABCD4-5926-4005-9525-559DD74991C9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{950DACB0-B011-41AF-B0FB-245F749B01AB}.CheckInBuild|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{950DACB0-B011-41AF-B0FB-245F749B01AB}.CheckInBuild|Any CPU.Build.0 = Debug|Any CPU
|
||||
{950DACB0-B011-41AF-B0FB-245F749B01AB}.CodeCoverage|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{950DACB0-B011-41AF-B0FB-245F749B01AB}.CodeCoverage|Any CPU.Build.0 = Debug|Any CPU
|
||||
{950DACB0-B011-41AF-B0FB-245F749B01AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{950DACB0-B011-41AF-B0FB-245F749B01AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{950DACB0-B011-41AF-B0FB-245F749B01AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{950DACB0-B011-41AF-B0FB-245F749B01AB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FCF94FF4-A608-4CC1-8D1D-215B6F88F203}.CheckInBuild|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FCF94FF4-A608-4CC1-8D1D-215B6F88F203}.CheckInBuild|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FCF94FF4-A608-4CC1-8D1D-215B6F88F203}.CodeCoverage|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FCF94FF4-A608-4CC1-8D1D-215B6F88F203}.CodeCoverage|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FCF94FF4-A608-4CC1-8D1D-215B6F88F203}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FCF94FF4-A608-4CC1-8D1D-215B6F88F203}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FCF94FF4-A608-4CC1-8D1D-215B6F88F203}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FCF94FF4-A608-4CC1-8D1D-215B6F88F203}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -615,6 +637,8 @@ Global
|
|||
{013D53C7-3AB5-41A4-9A8D-0F2C47238773} = {578D5330-2F72-44C6-9DB5-C93B3F42C473}
|
||||
{A58633ED-5302-41DF-A0F6-FDC48E5C6B04} = {578D5330-2F72-44C6-9DB5-C93B3F42C473}
|
||||
{A68ABCD4-5926-4005-9525-559DD74991C9} = {578D5330-2F72-44C6-9DB5-C93B3F42C473}
|
||||
{950DACB0-B011-41AF-B0FB-245F749B01AB} = {2300ED4C-1D5A-460F-8691-7C85E1162E0C}
|
||||
{FCF94FF4-A608-4CC1-8D1D-215B6F88F203} = {2300ED4C-1D5A-460F-8691-7C85E1162E0C}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D71830F5-3AF5-46B4-8A9E-1DCE4F2253AC}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.Common
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
public class DaemonConfiguration
|
||||
{
|
||||
const string ConfigYamlFile = @"C:\ProgramData\iotedge\config.yaml";
|
||||
|
||||
YamlDocument config;
|
||||
|
||||
public DaemonConfiguration()
|
||||
{
|
||||
string contents = File.ReadAllText(ConfigYamlFile);
|
||||
this.config = new YamlDocument(contents);
|
||||
}
|
||||
|
||||
public void AddHttpsProxy(Uri proxy)
|
||||
{
|
||||
this.config.ReplaceOrAdd("agent.env.https_proxy", proxy.ToString());
|
||||
// TODO: When we allow the caller to specify an upstream protocol,
|
||||
// we'll need to honor that if it's WebSocket-based, otherwise
|
||||
// convert to an equivalent WebSocket-based protocol (e.g.,
|
||||
// Mqtt --> MqttWs)
|
||||
this.config.ReplaceOrAdd("agent.env.UpstreamProtocol", "AmqpWs");
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var attr = File.GetAttributes(ConfigYamlFile);
|
||||
File.SetAttributes(ConfigYamlFile, attr & ~FileAttributes.ReadOnly);
|
||||
|
||||
File.WriteAllText(ConfigYamlFile, this.config.ToString());
|
||||
|
||||
if (attr != 0)
|
||||
{
|
||||
File.SetAttributes(ConfigYamlFile, attr);
|
||||
}
|
||||
|
||||
Console.WriteLine($"Updated daemon configuration file '{ConfigYamlFile}'");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.Common
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices;
|
||||
using Microsoft.Azure.Devices.Edge.Util;
|
||||
|
||||
public class EdgeAgent : EdgeModule
|
||||
{
|
||||
public EdgeAgent(string deviceId, IotHub iotHub)
|
||||
: base("edgeAgent", deviceId, iotHub) { }
|
||||
|
||||
public Task PingAsync(CancellationToken token)
|
||||
{
|
||||
return Profiler.Run(
|
||||
"Pinging module 'edgeAgent' from the cloud",
|
||||
() =>
|
||||
{
|
||||
return Retry.Do(
|
||||
() =>
|
||||
{
|
||||
return this.IotHub.InvokeMethodAsync(
|
||||
this.DeviceId,
|
||||
"$edgeAgent",
|
||||
new CloudToDeviceMethod("ping"),
|
||||
token
|
||||
);
|
||||
},
|
||||
result => result.Status == 200,
|
||||
e => true,
|
||||
TimeSpan.FromSeconds(5),
|
||||
token
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.Common
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices;
|
||||
using Microsoft.Azure.Devices.Edge.Util;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
public enum UpstreamProtocolType
|
||||
{
|
||||
Amqp,
|
||||
AmqpWs,
|
||||
Mqtt,
|
||||
MqttWs
|
||||
}
|
||||
|
||||
public class EdgeConfiguration
|
||||
{
|
||||
ConfigurationContent config;
|
||||
string deviceId;
|
||||
IotHub iotHub;
|
||||
|
||||
IReadOnlyCollection<string> Modules
|
||||
{
|
||||
get
|
||||
{
|
||||
var list = new List<string>();
|
||||
this.ForEachModule((name, module) =>
|
||||
{
|
||||
list.Add(name);
|
||||
});
|
||||
return new ReadOnlyCollection<string>(list);
|
||||
}
|
||||
}
|
||||
|
||||
public EdgeConfiguration(string deviceId, string agentImage, IotHub iotHub)
|
||||
{
|
||||
this.config = GetBaseConfig(agentImage);
|
||||
this.iotHub = iotHub;
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public void AddRegistryCredentials(string address, string username, string password)
|
||||
{
|
||||
this.UpdateAgentDesiredProperties(desired =>
|
||||
{
|
||||
JObject settings = desired.Get<JObject>("runtime").Get<JObject>("settings");
|
||||
settings.Add("registryCredentials", JToken.FromObject(new
|
||||
{
|
||||
reg1 = new
|
||||
{
|
||||
username,
|
||||
password,
|
||||
address
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
// Adds proxy information to each module in Edge Agent's desired properties. Call this
|
||||
// method after you've added all the modules that need proxy information.
|
||||
public void AddProxy(Uri proxy)
|
||||
{
|
||||
this.ForEachModule((name, module) =>
|
||||
{
|
||||
JObject env = GetOrAddObject("env", module);
|
||||
env.Add("https_proxy", JToken.FromObject(new
|
||||
{
|
||||
value = proxy.ToString()
|
||||
}));
|
||||
env.Add("UpstreamProtocol", JToken.FromObject(new
|
||||
{
|
||||
value = UpstreamProtocolType.AmqpWs.ToString()
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
public void AddEdgeHub(string image)
|
||||
{
|
||||
this.UpdateAgentDesiredProperties(desired =>
|
||||
{
|
||||
JObject systemModules = desired.Get<JObject>("systemModules");
|
||||
systemModules.Add("edgeHub", JToken.FromObject(new
|
||||
{
|
||||
type = "docker",
|
||||
status = "running",
|
||||
restartPolicy = "always",
|
||||
settings = new
|
||||
{
|
||||
image,
|
||||
createOptions = "{\"HostConfig\":{\"PortBindings\":{\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}],\"5671/tcp\":[{\"HostPort\":\"5671\"}]}}}"
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
// { "modulesContent": { "$edgeHub": { ... } } }
|
||||
this.config.ModulesContent["$edgeHub"] = new Dictionary<string, object>
|
||||
{
|
||||
["properties.desired"] = new
|
||||
{
|
||||
schemaVersion = "1.0",
|
||||
routes = new Dictionary<string, string>
|
||||
{
|
||||
["route1"] = "from /* INTO $upstream",
|
||||
},
|
||||
storeAndForwardConfiguration = new
|
||||
{
|
||||
timeToLiveSecs = 7200
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void AddTempSensor(string image)
|
||||
{
|
||||
this.UpdateAgentDesiredProperties(desired =>
|
||||
{
|
||||
JObject modules = GetOrAddObject("modules", desired);
|
||||
modules.Add("tempSensor", JToken.FromObject(new
|
||||
{
|
||||
type = "docker",
|
||||
status = "running",
|
||||
restartPolicy = "always",
|
||||
settings = new
|
||||
{
|
||||
image
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
public Task DeployAsync(CancellationToken token)
|
||||
{
|
||||
string message = "Deploying edge configuration to device " +
|
||||
$"'{this.deviceId}' with modules ({string.Join(", ", this.Modules)})";
|
||||
|
||||
return Profiler.Run(
|
||||
message,
|
||||
() => this.iotHub.DeployDeviceConfigurationAsync(this.deviceId, this.config, token)
|
||||
);
|
||||
}
|
||||
|
||||
void ForEachModule(Action<string, JObject> action)
|
||||
{
|
||||
this.UpdateAgentDesiredProperties(desired =>
|
||||
{
|
||||
foreach (var key in new[] { "systemModules", "modules" })
|
||||
{
|
||||
if (desired.TryGetValue(key, StringComparison.OrdinalIgnoreCase, out JToken modules))
|
||||
{
|
||||
foreach (var module in modules.Value<JObject>())
|
||||
{
|
||||
action(module.Key, module.Value.Value<JObject>());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateAgentDesiredProperties(Action<JObject> update)
|
||||
{
|
||||
JObject desired = JObject.FromObject(this.config.ModulesContent["$edgeAgent"]["properties.desired"]);
|
||||
update(desired);
|
||||
this.config.ModulesContent["$edgeAgent"]["properties.desired"] = desired;
|
||||
}
|
||||
|
||||
static JObject GetOrAddObject(string name, JObject parent)
|
||||
{
|
||||
if (parent.TryGetValue(name, StringComparison.OrdinalIgnoreCase, out JToken token))
|
||||
{
|
||||
return token.Value<JObject>();
|
||||
}
|
||||
|
||||
parent.Add(name, new JObject());
|
||||
return parent.Get<JObject>(name);
|
||||
}
|
||||
|
||||
static ConfigurationContent GetBaseConfig(string agentImage) => new ConfigurationContent
|
||||
{
|
||||
ModulesContent = new Dictionary<string, IDictionary<string, object>>
|
||||
{
|
||||
["$edgeAgent"] = new Dictionary<string, object>
|
||||
{
|
||||
["properties.desired"] = new
|
||||
{
|
||||
schemaVersion = "1.0",
|
||||
runtime = new
|
||||
{
|
||||
type = "docker",
|
||||
settings = new
|
||||
{
|
||||
minDockerVersion = "v1.25"
|
||||
}
|
||||
},
|
||||
systemModules = new
|
||||
{
|
||||
edgeAgent = new
|
||||
{
|
||||
type = "docker",
|
||||
settings = new
|
||||
{
|
||||
image = agentImage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.Common
|
||||
{
|
||||
using System;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices.Edge.Util;
|
||||
|
||||
public enum EdgeDaemonStatus
|
||||
{
|
||||
Running = ServiceControllerStatus.Running,
|
||||
Stopped = ServiceControllerStatus.Stopped
|
||||
}
|
||||
|
||||
public class EdgeDaemon
|
||||
{
|
||||
private string scriptDir;
|
||||
|
||||
public EdgeDaemon(string scriptDir)
|
||||
{
|
||||
this.scriptDir = scriptDir;
|
||||
}
|
||||
|
||||
public Task InstallAsync(
|
||||
string deviceConnectionString,
|
||||
Option<string> packagesPath,
|
||||
Option<Uri> proxy,
|
||||
CancellationToken token
|
||||
)
|
||||
{
|
||||
string installCommand = "Install-IoTEdge -Manual -ContainerOs Windows " +
|
||||
$"-DeviceConnectionString '{deviceConnectionString}'";
|
||||
packagesPath.ForEach(p => installCommand += $" -OfflineInstallationPath '{p}'");
|
||||
proxy.ForEach(p =>
|
||||
installCommand += $" -InvokeWebRequestParameters @{{ '-Proxy' = '{p}' }}"
|
||||
);
|
||||
|
||||
var commands = new[]
|
||||
{
|
||||
"$ProgressPreference='SilentlyContinue'",
|
||||
$". {this.scriptDir}\\IotEdgeSecurityDaemon.ps1",
|
||||
installCommand
|
||||
};
|
||||
|
||||
string message = "Installing edge daemon";
|
||||
packagesPath.ForEach(p => message += $" from packages in '{p}'");
|
||||
|
||||
return Profiler.Run(
|
||||
message,
|
||||
() => Process.RunAsync("powershell", string.Join(";", commands), token)
|
||||
);
|
||||
}
|
||||
|
||||
public Task UninstallAsync(CancellationToken token)
|
||||
{
|
||||
var commands = new[]
|
||||
{
|
||||
"$ProgressPreference='SilentlyContinue'",
|
||||
$". {this.scriptDir}\\IotEdgeSecurityDaemon.ps1",
|
||||
"Uninstall-IoTEdge -Force"
|
||||
};
|
||||
return Profiler.Run(
|
||||
"Uninstalling edge daemon",
|
||||
() => Process.RunAsync("powershell", string.Join(";", commands), token)
|
||||
);
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken token)
|
||||
{
|
||||
var sc = new ServiceController("iotedge");
|
||||
return Profiler.Run(
|
||||
"Starting edge daemon",
|
||||
async () =>
|
||||
{
|
||||
if (sc.Status != ServiceControllerStatus.Running)
|
||||
{
|
||||
sc.Start();
|
||||
await this._WaitForStatusAsync(sc, ServiceControllerStatus.Running, token);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken token)
|
||||
{
|
||||
var sc = new ServiceController("iotedge");
|
||||
return Profiler.Run(
|
||||
"Stopping edge daemon",
|
||||
async () => {
|
||||
if (sc.Status != ServiceControllerStatus.Stopped)
|
||||
{
|
||||
sc.Stop();
|
||||
await this._WaitForStatusAsync(sc, ServiceControllerStatus.Stopped, token);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public Task WaitForStatusAsync(EdgeDaemonStatus desired, CancellationToken token)
|
||||
{
|
||||
var sc = new ServiceController("iotedge");
|
||||
return Profiler.Run(
|
||||
$"Waiting for edge daemon to enter the '{desired.ToString().ToLower()}' state",
|
||||
() => this._WaitForStatusAsync(sc, (ServiceControllerStatus)desired, token)
|
||||
);
|
||||
}
|
||||
|
||||
async Task _WaitForStatusAsync(ServiceController sc, ServiceControllerStatus desired, CancellationToken token)
|
||||
{
|
||||
while (sc.Status != desired)
|
||||
{
|
||||
await Task.Delay(250, token).ConfigureAwait(false);
|
||||
sc.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.Common
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices;
|
||||
|
||||
public class EdgeDevice
|
||||
{
|
||||
readonly Device device;
|
||||
readonly IotHub iotHub;
|
||||
readonly bool owned;
|
||||
|
||||
public string ConnectionString =>
|
||||
$"HostName={this.iotHub.Hostname};" +
|
||||
$"DeviceId={this.device.Id};" +
|
||||
$"SharedAccessKey={this.device.Authentication.SymmetricKey.PrimaryKey}";
|
||||
|
||||
public string Id => this.device.Id;
|
||||
|
||||
EdgeDevice(Device device, bool owned, IotHub iotHub)
|
||||
{
|
||||
this.device = device;
|
||||
this.iotHub = iotHub;
|
||||
this.owned = owned;
|
||||
}
|
||||
|
||||
public static Task<EdgeDevice> CreateIdentityAsync(
|
||||
string deviceId,
|
||||
IotHub iotHub,
|
||||
CancellationToken token
|
||||
)
|
||||
{
|
||||
return Profiler.Run(
|
||||
$"Creating edge device '{deviceId}' on hub '{iotHub.Hostname}'",
|
||||
async () => {
|
||||
Device device = await iotHub.CreateEdgeDeviceIdentityAsync(deviceId, token);
|
||||
return new EdgeDevice(device, true, iotHub);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static async Task<EdgeDevice> GetOrCreateIdentityAsync(
|
||||
string deviceId,
|
||||
IotHub iotHub,
|
||||
CancellationToken token
|
||||
)
|
||||
{
|
||||
Device device = await iotHub.GetDeviceIdentityAsync(deviceId, token);
|
||||
if (device != null)
|
||||
{
|
||||
if (!device.Capabilities.IotEdge)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Device '{device.Id}' exists, but is not an edge device"
|
||||
);
|
||||
}
|
||||
|
||||
Console.WriteLine($"Device '{device.Id}' already exists on hub '{iotHub.Hostname}'");
|
||||
return new EdgeDevice(device, false, iotHub);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await CreateIdentityAsync(deviceId, iotHub, token);
|
||||
}
|
||||
}
|
||||
|
||||
public Task DeleteIdentityAsync(CancellationToken token)
|
||||
{
|
||||
return Profiler.Run(
|
||||
$"Deleting device '{this.Id}'",
|
||||
() => this.iotHub.DeleteDeviceIdentityAsync(this.device, token)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task MaybeDeleteIdentityAsync(CancellationToken token)
|
||||
{
|
||||
if (this.owned)
|
||||
{
|
||||
await this.DeleteIdentityAsync(token);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Pre-existing device '{this.Id}' was not deleted");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.Common
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices.Edge.Util;
|
||||
|
||||
public enum EdgeModuleStatus
|
||||
{
|
||||
Running,
|
||||
Stopped
|
||||
}
|
||||
|
||||
public class EdgeModule
|
||||
{
|
||||
protected string DeviceId;
|
||||
protected IotHub IotHub;
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public EdgeModule(string id, string deviceId, IotHub iotHub)
|
||||
{
|
||||
this.DeviceId = deviceId;
|
||||
this.IotHub = iotHub;
|
||||
this.Id = id;
|
||||
}
|
||||
|
||||
public Task WaitForStatusAsync(EdgeModuleStatus desired, CancellationToken token)
|
||||
{
|
||||
return WaitForStatusAsync(new []{this}, desired, token);
|
||||
}
|
||||
|
||||
public static Task WaitForStatusAsync(EdgeModule[] modules, EdgeModuleStatus desired, CancellationToken token)
|
||||
{
|
||||
string FormatModulesList() => modules.Length == 1
|
||||
? $"module '{modules.First().Id}'"
|
||||
: $"modules ({string.Join(", ", modules.Select(module => module.Id))})";
|
||||
|
||||
async Task WaitForStatusAsync() {
|
||||
try
|
||||
{
|
||||
await Retry.Do(
|
||||
async () =>
|
||||
{
|
||||
string[] result = await Process.RunAsync("iotedge", "list", token);
|
||||
|
||||
return result
|
||||
.Where(ln => {
|
||||
var columns = ln.Split(null as char[], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var module in modules)
|
||||
{
|
||||
// each line is "name status"
|
||||
if (columns[0] == module.Id &&
|
||||
columns[1].Equals(desired.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}).ToArray();
|
||||
},
|
||||
a => a.Length == modules.Length,
|
||||
e =>
|
||||
{
|
||||
// Retry if iotedged's management endpoint is still starting up,
|
||||
// and therefore isn't responding to `iotedge list` yet
|
||||
bool DaemonNotReady(string details) =>
|
||||
details.Contains("Could not list modules", StringComparison.OrdinalIgnoreCase) ||
|
||||
details.Contains("Socket file could not be found", StringComparison.OrdinalIgnoreCase);
|
||||
return DaemonNotReady(e.ToString());
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
token
|
||||
);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw new Exception($"Error: timed out waiting for {FormatModulesList()} to start");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception($"Error searching for {FormatModulesList()}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
return Profiler.Run(
|
||||
$"Waiting for {FormatModulesList()} to enter the '{desired.ToString().ToLower()}' state",
|
||||
WaitForStatusAsync
|
||||
);
|
||||
}
|
||||
|
||||
public Task WaitForEventsReceivedAsync(CancellationToken token)
|
||||
{
|
||||
return Profiler.Run(
|
||||
$"Receiving events from device '{this.DeviceId}' on Event Hub '{this.IotHub.EntityPath}'",
|
||||
() => this.IotHub.ReceiveEventsAsync(
|
||||
this.DeviceId,
|
||||
data =>
|
||||
{
|
||||
data.SystemProperties.TryGetValue("iothub-connection-device-id", out object devId);
|
||||
data.SystemProperties.TryGetValue("iothub-connection-module-id", out object modId);
|
||||
|
||||
return devId != null && devId.ToString().Equals(this.DeviceId)
|
||||
&& modId != null && modId.ToString().Equals(this.Id);
|
||||
},
|
||||
token
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.Common
|
||||
{
|
||||
using System;
|
||||
using Microsoft.Azure.Devices.Edge.Util;
|
||||
|
||||
public class EnvironmentVariable
|
||||
{
|
||||
public static string Expect(string name) => Preconditions.CheckNonWhiteSpace(
|
||||
Environment.GetEnvironmentVariable(name),
|
||||
name
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.Common
|
||||
{
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices;
|
||||
using Microsoft.Azure.Devices.Common;
|
||||
using Microsoft.Azure.Devices.Edge.Util;
|
||||
using Microsoft.Azure.Devices.Shared;
|
||||
using Microsoft.Azure.EventHubs;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using DeviceTransportType = Microsoft.Azure.Devices.TransportType;
|
||||
using EventHubTransportType = Microsoft.Azure.EventHubs.TransportType;
|
||||
|
||||
public class IotHub
|
||||
{
|
||||
readonly string eventHubEndpoint;
|
||||
readonly string iotHubConnectionString;
|
||||
readonly Lazy<RegistryManager> registryManager;
|
||||
readonly Lazy<ServiceClient> serviceClient;
|
||||
readonly Lazy<EventHubClient> eventHubClient;
|
||||
|
||||
public string Hostname =>
|
||||
IotHubConnectionStringBuilder.Create(this.iotHubConnectionString).HostName;
|
||||
public string EntityPath =>
|
||||
(new EventHubsConnectionStringBuilder(this.eventHubEndpoint)).EntityPath;
|
||||
|
||||
RegistryManager RegistryManager => this.registryManager.Value;
|
||||
ServiceClient ServiceClient => this.serviceClient.Value;
|
||||
EventHubClient EventHubClient => this.eventHubClient.Value;
|
||||
|
||||
public IotHub(string iotHubConnectionString, string eventHubEndpoint, Option<Uri> proxyUri)
|
||||
{
|
||||
this.eventHubEndpoint = eventHubEndpoint;
|
||||
this.iotHubConnectionString = iotHubConnectionString;
|
||||
Option<IWebProxy> proxy = proxyUri.Map(p => new WebProxy(p) as IWebProxy);
|
||||
|
||||
this.registryManager = new Lazy<RegistryManager>(() =>
|
||||
{
|
||||
var settings = new HttpTransportSettings();
|
||||
proxy.ForEach(p => settings.Proxy = p);
|
||||
return RegistryManager.CreateFromConnectionString(
|
||||
this.iotHubConnectionString,
|
||||
settings
|
||||
);
|
||||
});
|
||||
|
||||
this.serviceClient = new Lazy<ServiceClient>(() =>
|
||||
{
|
||||
var settings = new ServiceClientTransportSettings();
|
||||
proxy.ForEach(p => settings.HttpProxy = p);
|
||||
return ServiceClient.CreateFromConnectionString(
|
||||
this.iotHubConnectionString,
|
||||
DeviceTransportType.Amqp_WebSocket_Only,
|
||||
settings
|
||||
);
|
||||
});
|
||||
|
||||
this.eventHubClient = new Lazy<EventHubClient>(() =>
|
||||
{
|
||||
var builder = new EventHubsConnectionStringBuilder(this.eventHubEndpoint)
|
||||
{
|
||||
TransportType = EventHubTransportType.AmqpWebSockets
|
||||
};
|
||||
var client = EventHubClient.CreateFromConnectionString(builder.ToString());
|
||||
proxy.ForEach(p => client.WebProxy = p);
|
||||
return client;
|
||||
});
|
||||
}
|
||||
|
||||
public Task<Device> GetDeviceIdentityAsync(string deviceId, CancellationToken token) =>
|
||||
this.RegistryManager.GetDeviceAsync(deviceId, token);
|
||||
|
||||
public Task<Device> CreateEdgeDeviceIdentityAsync(string deviceId, CancellationToken token)
|
||||
{
|
||||
var device = new Device(deviceId)
|
||||
{
|
||||
Authentication = new AuthenticationMechanism() { Type = AuthenticationType.Sas },
|
||||
Capabilities = new DeviceCapabilities() { IotEdge = true }
|
||||
};
|
||||
return this.RegistryManager.AddDeviceAsync(device, token);
|
||||
}
|
||||
|
||||
public Task DeleteDeviceIdentityAsync(Device device, CancellationToken token) =>
|
||||
this.RegistryManager.RemoveDeviceAsync(device);
|
||||
|
||||
public Task DeployDeviceConfigurationAsync(
|
||||
string deviceId,
|
||||
ConfigurationContent config,
|
||||
CancellationToken token
|
||||
) => this.RegistryManager.ApplyConfigurationContentOnDeviceAsync(deviceId, config, token);
|
||||
|
||||
public Task<Twin> GetTwinAsync(
|
||||
string deviceId,
|
||||
string moduleId,
|
||||
CancellationToken token
|
||||
) => this.RegistryManager.GetTwinAsync(deviceId, moduleId, token);
|
||||
|
||||
public async Task UpdateTwinAsync(
|
||||
string deviceId,
|
||||
string moduleId,
|
||||
object twinPatch,
|
||||
CancellationToken token
|
||||
)
|
||||
{
|
||||
Twin twin = await this.GetTwinAsync(deviceId, moduleId, token);
|
||||
string patch = JsonConvert.SerializeObject(twinPatch);
|
||||
await this.RegistryManager.UpdateTwinAsync(
|
||||
deviceId, moduleId, patch, twin.ETag, token);
|
||||
}
|
||||
|
||||
public Task<CloudToDeviceMethodResult> InvokeMethodAsync(
|
||||
string deviceId,
|
||||
string moduleId,
|
||||
CloudToDeviceMethod method,
|
||||
CancellationToken token
|
||||
) => this.ServiceClient.InvokeDeviceMethodAsync(deviceId, moduleId, method, token);
|
||||
|
||||
public async Task ReceiveEventsAsync(
|
||||
string deviceId,
|
||||
Func<EventData, bool> onEventReceived,
|
||||
CancellationToken token
|
||||
)
|
||||
{
|
||||
EventHubClient client = this.EventHubClient;
|
||||
int count = (await client.GetRuntimeInformationAsync()).PartitionCount;
|
||||
string partition = EventHubPartitionKeyResolver.ResolveToPartition(deviceId, count);
|
||||
PartitionReceiver receiver = client.CreateReceiver("$Default", partition, EventPosition.FromEnd());
|
||||
|
||||
var result = new TaskCompletionSource<bool>();
|
||||
using (token.Register(() => result.TrySetCanceled()))
|
||||
{
|
||||
receiver.SetReceiveHandler(
|
||||
new PartitionReceiveHandler(
|
||||
data =>
|
||||
{
|
||||
bool done = onEventReceived(data);
|
||||
if (done)
|
||||
{
|
||||
result.TrySetResult(true);
|
||||
}
|
||||
return done;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
await result.Task;
|
||||
}
|
||||
|
||||
await receiver.CloseAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup Condition="'$(DotNet_Runtime)' != 'netcoreapp3.0'">
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(OS)|$(DotNet_Runtime)' == 'Unix|netcoreapp3.0'">
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
Normally, the 'Debug' configuration would work for code coverage, but Microsoft.CodeCoverage currently requires '<DebugType>full</DebugType>' for .NET Core.
|
||||
See https://github.com/Microsoft/vstest-docs/blob/06f9dc0aeb47be7204dc4e1a98c110ead3e978c7/docs/analyze.md#setup-a-project.
|
||||
That setting seems to break the "Open Test" context menu in VS IDE, so we'll use a dedicated configuration for code coverage.
|
||||
-->
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeCoverage|AnyCPU' ">
|
||||
<IntermediateOutputPath>obj\CodeCoverage</IntermediateOutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\CodeCoverage</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Devices" Version="1.17.1" />
|
||||
<PackageReference Include="Microsoft.Azure.EventHubs" Version="3.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
<PackageReference Include="RunProcessAsTask" Version="1.2.3" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.5.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\edge-util\src\Microsoft.Azure.Devices.Edge.Util\Microsoft.Azure.Devices.Edge.Util.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<CodeAnalysisRuleSet>..\..\stylecop.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\..\stylecop.props" />
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.Common
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices.Edge.Util;
|
||||
using Microsoft.Azure.Devices.Shared;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
public class ModuleTwin
|
||||
{
|
||||
string deviceId;
|
||||
IotHub iotHub;
|
||||
string moduleId;
|
||||
|
||||
public ModuleTwin(string moduleId, string deviceId, IotHub iotHub)
|
||||
{
|
||||
this.deviceId = deviceId;
|
||||
this.iotHub = iotHub;
|
||||
this.moduleId = moduleId;
|
||||
}
|
||||
|
||||
public Task UpdateDesiredPropertiesAsync(object patch, CancellationToken token)
|
||||
{
|
||||
return Profiler.Run(
|
||||
$"Updating twin for module '{this.moduleId}'",
|
||||
() => this.iotHub.UpdateTwinAsync(this.deviceId, this.moduleId, patch, token)
|
||||
);
|
||||
}
|
||||
|
||||
public Task WaitForReportedPropertyUpdatesAsync(object expectedPatch, CancellationToken token)
|
||||
{
|
||||
return Profiler.Run(
|
||||
$"Waiting for expected twin updates for module '{this.moduleId}'",
|
||||
() => {
|
||||
return Retry.Do(
|
||||
async () => {
|
||||
Twin twin = await this.iotHub.GetTwinAsync(this.deviceId, this.moduleId, token);
|
||||
return twin.Properties.Reported;
|
||||
},
|
||||
reported => {
|
||||
JObject expected = JObject.FromObject(expectedPatch)
|
||||
.Value<JObject>("properties")
|
||||
.Value<JObject>("reported");
|
||||
return expected.Value<JObject>().All<KeyValuePair<string, JToken>>(
|
||||
prop => reported.Contains(prop.Key) && reported[prop.Key] == prop.Value
|
||||
);
|
||||
},
|
||||
null,
|
||||
TimeSpan.FromSeconds(5),
|
||||
token
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.Common
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.EventHubs;
|
||||
|
||||
class PartitionReceiveHandler : IPartitionReceiveHandler
|
||||
{
|
||||
readonly Func<EventData, bool> onEventReceived;
|
||||
|
||||
public PartitionReceiveHandler(Func<EventData, bool> onEventReceived)
|
||||
{
|
||||
this.onEventReceived = onEventReceived;
|
||||
}
|
||||
|
||||
public int MaxBatchSize { get; set; }
|
||||
|
||||
public Task ProcessEventsAsync(IEnumerable<EventData> events)
|
||||
{
|
||||
if (events != null)
|
||||
{
|
||||
foreach (EventData data in events)
|
||||
{
|
||||
if (this.onEventReceived(data))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ProcessErrorAsync(Exception error) => throw error;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.Common
|
||||
{
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using RunProcessAsTask;
|
||||
|
||||
public class Process
|
||||
{
|
||||
public static async Task<string[]> RunAsync(string name, string args, int timeoutSeconds = 15)
|
||||
{
|
||||
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
|
||||
{
|
||||
return await RunAsync(name, args, cts.Token);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string[]> RunAsync(string name, string args, CancellationToken token)
|
||||
{
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
FileName = name,
|
||||
Arguments = args,
|
||||
};
|
||||
|
||||
using (ProcessResults result = await ProcessEx.RunAsync(info, token))
|
||||
{
|
||||
if (result.ExitCode != 0)
|
||||
{
|
||||
throw new Win32Exception(result.ExitCode, $"'{name}' failed with: {string.Join("\n", result.StandardError)}");
|
||||
}
|
||||
|
||||
return result.StandardOutput;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.Common
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class Profiler
|
||||
{
|
||||
public static Task Run(string startMessage, Func<Task> func, string endMessage = "") => Run(
|
||||
startMessage,
|
||||
async () => {
|
||||
await func();
|
||||
return true;
|
||||
},
|
||||
endMessage
|
||||
);
|
||||
|
||||
public static async Task<T> Run<T>(string startMessage, Func<Task<T>> func, string endMessage = "")
|
||||
{
|
||||
Console.Write(startMessage + (string.IsNullOrEmpty(endMessage) ? "..." : "\n"));
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
T t = await func();
|
||||
|
||||
stopwatch.Stop();
|
||||
TimeSpan ts = stopwatch.Elapsed;
|
||||
Console.WriteLine(
|
||||
(string.IsNullOrEmpty(endMessage) ? "done" : endMessage) + $" [{ts}]");
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.Common
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
class YamlDocument
|
||||
{
|
||||
readonly Dictionary<object, object> root;
|
||||
|
||||
public YamlDocument(string input)
|
||||
{
|
||||
var reader = new StringReader(input);
|
||||
var deserializer = new Deserializer();
|
||||
this.root = (Dictionary<object, object>)deserializer.Deserialize(reader);
|
||||
}
|
||||
|
||||
public void ReplaceOrAdd(string dottedKey, string value)
|
||||
{
|
||||
Dictionary<object, object> node = this.root;
|
||||
string[] segments = dottedKey.Split('.');
|
||||
foreach (string key in segments.SkipLast(1))
|
||||
{
|
||||
if (!node.ContainsKey(key))
|
||||
{
|
||||
node.Add(key, new Dictionary<object, object>());
|
||||
}
|
||||
|
||||
node = (Dictionary<object, object>)node[key];
|
||||
}
|
||||
|
||||
string leaf = segments.Last();
|
||||
if (!node.ContainsKey(leaf))
|
||||
{
|
||||
node.Add(leaf, value);
|
||||
}
|
||||
|
||||
node[leaf] = value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var serializer = new Serializer();
|
||||
return serializer.Serialize(this.root);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup Condition="'$(DotNet_Runtime)' != 'netcoreapp3.0'">
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(OS)|$(DotNet_Runtime)' == 'Unix|netcoreapp3.0'">
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Configurations>Debug;Release;CodeCoverage;CheckInBuild</Configurations>
|
||||
<LangVersion>7.1</LangVersion>
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
Normally, the 'Debug' configuration would work for code coverage, but Microsoft.CodeCoverage currently requires '<DebugType>full</DebugType>' for .NET Core.
|
||||
See https://github.com/Microsoft/vstest-docs/blob/06f9dc0aeb47be7204dc4e1a98c110ead3e978c7/docs/analyze.md#setup-a-project.
|
||||
That setting seems to break the "Open Test" context menu in VS IDE, so we'll use a dedicated configuration for code coverage.
|
||||
-->
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeCoverage|AnyCPU' ">
|
||||
<IntermediateOutputPath>obj\CodeCoverage</IntermediateOutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\CodeCoverage</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Azure.Devices.Edge.Test.Common\Microsoft.Azure.Devices.Edge.Test.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.3.4" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<CodeAnalysisRuleSet>..\..\stylecop.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\..\stylecop.props" />
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.TempSensor
|
||||
{
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading.Tasks;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using Microsoft.Azure.Devices.Edge.Test.Common;
|
||||
using Microsoft.Azure.Devices.Edge.Util;
|
||||
|
||||
[Command(
|
||||
Name = "temp-sensor",
|
||||
Description = "tempSensor end-to-end test",
|
||||
ExtendedHelpText = @"
|
||||
|
||||
The following variables must be set in your environment:
|
||||
E2E_IOT_HUB_CONNECTION_STRING
|
||||
E2E_EVENT_HUB_ENDPOINT
|
||||
If you specify `--registry` and `--user`, the following variable must also be set:
|
||||
E2E_CONTAINER_REGISTRY_PASSWORD
|
||||
"
|
||||
)]
|
||||
[HelpOption]
|
||||
[RegistryAndUserOptionsMustBeSpecifiedTogether()]
|
||||
class Program
|
||||
{
|
||||
const string DefaultAgentImage = "mcr.microsoft.com/azureiotedge-agent:1.0";
|
||||
const string DefaultHubImage = "mcr.microsoft.com/azureiotedge-hub:1.0";
|
||||
const string DefaultSensorImage = "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0";
|
||||
|
||||
[Required]
|
||||
[Argument(0, Name = "device-id", Description = "Device ID")]
|
||||
public string DeviceId { get; }
|
||||
|
||||
[Required]
|
||||
[DirectoryExists]
|
||||
[Argument(1, Name = "installer-path", Description = "Path to IotEdgeSecurityDaemon.ps1")]
|
||||
public string InstallerPath { get; }
|
||||
|
||||
[DirectoryExists]
|
||||
[Option("--package-path", Description = "Path to installation packages")]
|
||||
public string PackagesPath { get; }
|
||||
|
||||
[Option("--proxy", Description = "HTTPS proxy for communication with IoT Hub")]
|
||||
public Uri Proxy { get; }
|
||||
|
||||
[Option("--agent", Description = "Edge Agent image name, default is '" + DefaultAgentImage + "'")]
|
||||
public string AgentImage { get; } = DefaultAgentImage;
|
||||
|
||||
[Option("--hub", Description = "Edge Hub image name, default is '" + DefaultHubImage + "'")]
|
||||
public string HubImage { get; } = DefaultHubImage;
|
||||
|
||||
[Option("--temp-sensor", Description = "Simulated temp sensor image name, default is '" + DefaultSensorImage + "'")]
|
||||
public string SensorImage { get; } = DefaultSensorImage;
|
||||
|
||||
[Option("--registry", Description = "Hostname[:port] of the container registry")]
|
||||
public string RegistryAddress { get; }
|
||||
|
||||
[Option("--user", Description = "Username for container registry login")]
|
||||
public string RegistryUser { get; }
|
||||
|
||||
Task<int> OnExecuteAsync()
|
||||
{
|
||||
var test = new Test();
|
||||
return test.RunAsync(new Test.Args
|
||||
{
|
||||
DeviceId = this.DeviceId,
|
||||
ConnectionString = EnvironmentVariable.Expect("E2E_IOT_HUB_CONNECTION_STRING"),
|
||||
Endpoint = EnvironmentVariable.Expect("E2E_EVENT_HUB_ENDPOINT"),
|
||||
InstallerPath = this.InstallerPath,
|
||||
PackagesPath = Option.Maybe(this.PackagesPath),
|
||||
Proxy = Option.Maybe(this.Proxy),
|
||||
AgentImage = this.AgentImage,
|
||||
HubImage = this.HubImage,
|
||||
SensorImage = this.SensorImage,
|
||||
Registry = this.RegistryAddress != null
|
||||
? Option.Some((
|
||||
this.RegistryAddress,
|
||||
this.RegistryUser,
|
||||
EnvironmentVariable.Expect("E2E_CONTAINER_REGISTRY_PASSWORD")))
|
||||
: Option.None<(string, string, string)>()
|
||||
});
|
||||
}
|
||||
|
||||
static Task<int> Main(string[] args) => CommandLineApplication.ExecuteAsync<Program>(args);
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RegistryAndUserOptionsMustBeSpecifiedTogether : ValidationAttribute
|
||||
{
|
||||
protected override ValidationResult IsValid(object value, ValidationContext context)
|
||||
{
|
||||
if (value is Program obj)
|
||||
{
|
||||
if (obj.RegistryAddress == null ^ obj.RegistryUser == null)
|
||||
{
|
||||
return new ValidationResult("--registry and --user must be specified together");
|
||||
}
|
||||
}
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Azure.Devices.Edge.Test.TempSensor
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices.Edge.Test.Common;
|
||||
using Microsoft.Azure.Devices.Edge.Util;
|
||||
|
||||
public class Test
|
||||
{
|
||||
public class Args
|
||||
{
|
||||
public string DeviceId;
|
||||
public string ConnectionString;
|
||||
public string Endpoint;
|
||||
public string InstallerPath;
|
||||
public Option<string> PackagesPath;
|
||||
public Option<Uri> Proxy;
|
||||
public string AgentImage;
|
||||
public string HubImage;
|
||||
public string SensorImage;
|
||||
public Option<(string address, string username, string password)> Registry;
|
||||
}
|
||||
|
||||
public Task<int> RunAsync(Args args) => Profiler.Run(
|
||||
"Running tempSensor test",
|
||||
async () =>
|
||||
{
|
||||
using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)))
|
||||
{
|
||||
CancellationToken token = cts.Token;
|
||||
|
||||
// ** setup
|
||||
var iotHub = new IotHub(args.ConnectionString, args.Endpoint, args.Proxy);
|
||||
var device = await EdgeDevice.GetOrCreateIdentityAsync(
|
||||
args.DeviceId,
|
||||
iotHub,
|
||||
token);
|
||||
|
||||
var daemon = new EdgeDaemon(args.InstallerPath);
|
||||
await daemon.UninstallAsync(token);
|
||||
await daemon.InstallAsync(
|
||||
device.ConnectionString,
|
||||
args.PackagesPath,
|
||||
args.Proxy,
|
||||
token);
|
||||
|
||||
await args.Proxy.Match(
|
||||
async p =>
|
||||
{
|
||||
await daemon.StopAsync(token);
|
||||
var yaml = new DaemonConfiguration();
|
||||
yaml.AddHttpsProxy(p);
|
||||
yaml.Update();
|
||||
await daemon.StartAsync(token);
|
||||
},
|
||||
() => daemon.WaitForStatusAsync(EdgeDaemonStatus.Running, token)
|
||||
);
|
||||
|
||||
var agent = new EdgeAgent(device.Id, iotHub);
|
||||
await agent.WaitForStatusAsync(EdgeModuleStatus.Running, token);
|
||||
await agent.PingAsync(token);
|
||||
|
||||
// ** test
|
||||
var config = new EdgeConfiguration(device.Id, args.AgentImage, iotHub);
|
||||
args.Registry.ForEach(
|
||||
r => config.AddRegistryCredentials(r.address, r.username, r.password)
|
||||
);
|
||||
config.AddEdgeHub(args.HubImage);
|
||||
args.Proxy.ForEach(p => config.AddProxy(p));
|
||||
config.AddTempSensor(args.SensorImage);
|
||||
await config.DeployAsync(token);
|
||||
|
||||
var hub = new EdgeModule("edgeHub", device.Id, iotHub);
|
||||
var sensor = new EdgeModule("tempSensor", device.Id, iotHub);
|
||||
await EdgeModule.WaitForStatusAsync(
|
||||
new[] { hub, sensor },
|
||||
EdgeModuleStatus.Running,
|
||||
token);
|
||||
await sensor.WaitForEventsReceivedAsync(token);
|
||||
|
||||
var sensorTwin = new ModuleTwin(sensor.Id, device.Id, iotHub);
|
||||
await sensorTwin.UpdateDesiredPropertiesAsync(
|
||||
new
|
||||
{
|
||||
properties = new
|
||||
{
|
||||
desired = new
|
||||
{
|
||||
SendData = true,
|
||||
SendInterval = 10
|
||||
}
|
||||
}
|
||||
},
|
||||
token);
|
||||
await sensorTwin.WaitForReportedPropertyUpdatesAsync(
|
||||
new
|
||||
{
|
||||
properties = new
|
||||
{
|
||||
reported = new
|
||||
{
|
||||
SendData = true,
|
||||
SendInterval = 10
|
||||
}
|
||||
}
|
||||
},
|
||||
token);
|
||||
|
||||
// ** teardown
|
||||
await daemon.StopAsync(token);
|
||||
await device.MaybeDeleteIdentityAsync(token);
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
"Completed tempSensor test"
|
||||
);
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче