Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't stage deployments #3364

Merged
merged 1 commit into from
Aug 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public DaemonConfiguration(string configYamlFile, Option<string> agentImage, Opt
string contents = File.ReadAllText(this.configYamlFile);
this.config = new YamlDocument(contents);
this.UpdateAgentImage(
agentImage.GetOrElse("mcr.microsoft.com/microsoft/azureiotedge-agent:1.0"),
agentImage.GetOrElse("mcr.microsoft.com/azureiotedge-agent:1.0"),
agentRegistry);
}

Expand Down
63 changes: 48 additions & 15 deletions test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Devices.Edge.Test.Common
using Microsoft.Azure.Devices.Shared;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Serilog;

public enum EdgeModuleStatus
Expand Down Expand Up @@ -71,7 +72,7 @@ await Retry.Do(
{
// Retry if iotedged's management endpoint is still starting up,
// and therefore isn't responding to `iotedge list` yet
bool DaemonNotReady(string details) =>
static bool DaemonNotReady(string details) =>
details.Contains("Could not list modules", StringComparison.OrdinalIgnoreCase) ||
details.Contains("Socket file could not be found", StringComparison.OrdinalIgnoreCase);

Expand Down Expand Up @@ -152,26 +153,58 @@ static bool JsonEquals(
{
Dictionary<string, JValue> ProcessJson(object obj, string rootPath)
{
// return all json values under root path, with their relative
// paths as keys
return JObject
// return all json values under root path, with their relative paths as keys
Dictionary<string, JValue> result = JObject
.FromObject(obj)
.SelectToken(rootPath)
.Cast<JContainer>()
.DescendantsAndSelf()
.OfType<JValue>()
.Select(
v =>
{
if (v.Path.EndsWith("settings.createOptions"))
{
// normalize stringized JSON inside "createOptions"
v.Value = JObject.Parse((string)v.Value).ToString(Formatting.None);
}

return v;
})
.ToDictionary(v => v.Path.Substring(rootPath.Length).TrimStart('.'));

var agentKeys = result.Keys
.Where(k => k.EndsWith("edgeAgent.settings.createOptions"));
var otherKeys = result.Keys
.Where(k => k.EndsWith("settings.createOptions"))
.Except(agentKeys);

// normalize stringized JSON inside "createOptions"
foreach (var key in otherKeys)
{
result[key].Value = JObject
.Parse((string)result[key].Value)
.ToString(Formatting.None);
}

// Do some additional processing for edge agent's createOptions...
// Remove "net.azure-devices.edge.*" labels because they can be deeply nested
// stringized JSON, making them difficult to compare. Besides, they're created by
// edge agent and iotedged for internal use; they're not related to the deployment.
foreach (var key in agentKeys)
{
JObject createOptions = JObject.Parse((string)result[key].Value);
if (createOptions.TryGetValue("Labels", out JToken labels))
{
string[] remove = labels
.Children<JProperty>()
.Where(label => label.Name.StartsWith("net.azure-devices.edge."))
.Select(label => label.Name)
.ToArray();
foreach (var name in remove)
{
labels.Value<JObject>().Remove(name);
}

if (!labels.HasValues)
{
createOptions.Remove("Labels");
}
}

result[key].Value = createOptions.ToString(Formatting.None);
}

return result;
}

Dictionary<string, JValue> referenceValues = ProcessJson(reference.obj, reference.rootPath);
Expand Down
25 changes: 9 additions & 16 deletions test/Microsoft.Azure.Devices.Edge.Test.Common/EdgeRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ public EdgeRuntime(string deviceId, Option<string> agentImage, Option<string> hu
// receive it and start up all the modules.
public async Task<EdgeDeployment> DeployConfigurationAsync(
Action<EdgeConfigBuilder> addConfig,
CancellationToken token,
bool stageSystemModules = true)
CancellationToken token)
{
var builder = new EdgeConfigBuilder(this.DeviceId);
builder.AddRegistries(this.registries);
Expand All @@ -52,20 +51,14 @@ public async Task<EdgeDeployment> DeployConfigurationAsync(
addConfig(builder);

DateTime deployTime = DateTime.Now;
var finalModules = new EdgeModule[] { };
IEnumerable<EdgeConfiguration> configs = builder.Build(stageSystemModules).ToArray();
foreach (EdgeConfiguration edgeConfiguration in configs)
{
await edgeConfiguration.DeployAsync(this.iotHub, token);
EdgeModule[] modules = edgeConfiguration.ModuleNames
.Select(id => new EdgeModule(id, this.DeviceId, this.iotHub))
.ToArray();
await EdgeModule.WaitForStatusAsync(modules, EdgeModuleStatus.Running, token);
await edgeConfiguration.VerifyAsync(this.iotHub, token);
finalModules = modules;
}

return new EdgeDeployment(deployTime, finalModules);
EdgeConfiguration edgeConfiguration = builder.Build();
await edgeConfiguration.DeployAsync(this.iotHub, token);
EdgeModule[] modules = edgeConfiguration.ModuleNames
.Select(id => new EdgeModule(id, this.DeviceId, this.iotHub))
.ToArray();
await EdgeModule.WaitForStatusAsync(modules, EdgeModuleStatus.Running, token);
await edgeConfiguration.VerifyAsync(this.iotHub, token);
return new EdgeDeployment(deployTime, modules);
}

public Task<EdgeDeployment> DeployConfigurationAsync(CancellationToken token) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,48 +52,20 @@ public IModuleConfigBuilder AddModule(string name, string image)
return builder;
}

// By default, returns two configurations: one with just the system modules; the other with
// the full configuration (if it contains more than just system modules). The first
// configuration can be deployed in advance to ensure edgeHub's routes are ready before the
// test modules start sending messages, to avoid dropped messages.
// Note: Another option would be to define all possible routes at the beginning of the test
// run, but then module names would need to be statically defined as well (currently they're
// dynamic).
// If stageSystemModules is false, returns one (full) configuration.
public IEnumerable<EdgeConfiguration> Build(bool stageSystemModules = true)
public EdgeConfiguration Build()
{
// Edge agent is not optional; add if necessary
if (!this.moduleBuilders.ContainsKey(ModuleName.EdgeAgent))
{
this.AddEdgeAgent();
}

ILookup<string, ModuleConfiguration> moduleConfigs = this.moduleBuilders
List<ModuleConfiguration> moduleConfigs = this.moduleBuilders
.Where(b => b.Key != ModuleName.EdgeAgent) // delay building edge agent
.Select(b => b.Value.Build())
.ToLookup(m => m.IsSystemModule() ? "system" : "other");

EdgeConfiguration BuildEdgeConfiguration(List<ModuleConfiguration> modules)
{
modules.Insert(0, this.BuildEdgeAgent(modules));
return EdgeConfiguration.Create(this.deviceId, modules);
}

if (stageSystemModules)
{
// Return a configuration for $edgeHub and $edgeAgent
yield return BuildEdgeConfiguration(moduleConfigs["system"].ToList());

if (moduleConfigs.Contains("other"))
{
// Return a configuration for all modules
yield return BuildEdgeConfiguration(moduleConfigs.SelectMany(m => m).ToList());
}
}
else
{
yield return BuildEdgeConfiguration(moduleConfigs.SelectMany(m => m).ToList());
}
.ToList();
moduleConfigs.Insert(0, this.BuildEdgeAgent(moduleConfigs));
return EdgeConfiguration.Create(this.deviceId, moduleConfigs);
}

ModuleConfiguration BuildEdgeAgent(IEnumerable<ModuleConfiguration> configs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,11 @@ public static EdgeConfiguration Create(string deviceId, IEnumerable<ModuleConfig
var reported = new Dictionary<string, object>
{
["systemModules"] = desired
.Value<JObject>("systemModules")
.Children<JProperty>()
.ToDictionary(
p => p.Name,
p => p.Name == ModuleName.EdgeAgent.Substring(1)
? CreateExpectedAgentModuleConfig((JObject)p.Value)
: CreateExpectedModuleConfig((JObject)p.Value))
.Value<JObject>("systemModules")
.Children<JProperty>()
.ToDictionary(
p => p.Name,
p => CreateExpectedModuleConfig((JObject)p.Value))
};

if (desired.ContainsKey("modules"))
Expand Down Expand Up @@ -105,27 +103,6 @@ static object CreateExpectedModuleConfig(JObject source)
return module;
}

static object CreateExpectedAgentModuleConfig(JObject source)
{
source.TryAdd("settings", new JObject());
JObject settings = source.Value<JObject>("settings");

settings.TryAdd("createOptions", new JObject());
JObject createOptions = settings.Value<JObject>("createOptions");
string createOptionsLabel = JsonConvert.SerializeObject(createOptions);

createOptions.TryAdd("Labels", new JObject());
JObject labels = createOptions.Value<JObject>("Labels");

JToken env = source.SelectToken("env") ?? new JObject();

labels.TryAdd("net.azure-devices.edge.create-options", new JValue(createOptionsLabel));
labels.TryAdd("net.azure-devices.edge.env", JsonConvert.SerializeObject(env));
labels.TryAdd("net.azure-devices.edge.owner", new JValue("Microsoft.Azure.Devices.Edge.Agent"));

return CreateExpectedModuleConfig(source);
}

public Task DeployAsync(IotHub iotHub, CancellationToken token) => Profiler.Run(
() => iotHub.DeployDeviceConfigurationAsync(this.deviceId, this.config, token),
"Deployed edge configuration to device with modules:\n {Modules}",
Expand Down
11 changes: 4 additions & 7 deletions test/Microsoft.Azure.Devices.Edge.Test/Metrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Devices.Edge.Test
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Edge.Test.Common;
using Microsoft.Azure.Devices.Edge.Test.Common.Config;
using Microsoft.Azure.Devices.Edge.Test.Helpers;
using Microsoft.Azure.Devices.Edge.Util.Test.Common.NUnit;
Expand All @@ -19,13 +18,12 @@ namespace Microsoft.Azure.Devices.Edge.Test
public class Metrics : SasManualProvisioningFixture
{
public const string ModuleName = "metricsValidator";
const string EdgeAgentBaseImage = "mcr.microsoft.com/azureiotedge-agent:1.0";

[Test]
public async Task ValidateMetrics()
{
CancellationToken token = this.TestToken;
await this.Deploy(token);
await this.DeployAsync(token);

var result = await this.iotHub.InvokeMethodAsync(this.runtime.DeviceId, ModuleName, new CloudToDeviceMethod("ValidateMetrics", TimeSpan.FromSeconds(300), TimeSpan.FromSeconds(300)), token);
Assert.AreEqual(result.Status, (int)HttpStatusCode.OK);
Expand All @@ -41,7 +39,7 @@ class Report
public int Failed { get; set; }
}

async Task Deploy(CancellationToken token)
async Task DeployAsync(CancellationToken token)
{
// First deploy everything needed for this test, including a temporary image that will be removed later to bump the "stopped" metric
string metricsValidatorImage = Context.Current.MetricsValidatorImage.Expect(() => new InvalidOperationException("Missing Metrics Validator image"));
Expand All @@ -55,8 +53,7 @@ await this.runtime.DeployConfigurationAsync(
// Next remove the temporary image from the deployment
await this.runtime.DeployConfigurationAsync(
builder => { builder.AddMetricsValidatorConfig(metricsValidatorImage); },
token,
stageSystemModules: false);
token);
}
}

Expand All @@ -73,7 +70,7 @@ public static void AddMetricsValidatorConfig(this EdgeConfigBuilder builder, str
{
builder.AddModule(Metrics.ModuleName, image);

var edgeHub = builder.GetModule(ConfigModuleName.EdgeHub)
builder.GetModule(ConfigModuleName.EdgeHub)
.WithDesiredProperties(new Dictionary<string, object>
{
{
Expand Down
4 changes: 2 additions & 2 deletions test/Microsoft.Azure.Devices.Edge.Test/PriorityQueues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task PriorityQueueModuleToModuleMessages()
EdgeDeployment deployment = await this.runtime.DeployConfigurationAsync(addInitialConfig, token);
PriorityQueueTestStatus loadGenTestStatus = await this.PollUntilFinishedAsync(LoadGenModuleName, token);
Action<EdgeConfigBuilder> addRelayerConfig = this.BuildAddRelayerConfig(relayerImage, loadGenTestStatus);
deployment = await this.runtime.DeployConfigurationAsync(addInitialConfig + addRelayerConfig, token, false);
deployment = await this.runtime.DeployConfigurationAsync(addInitialConfig + addRelayerConfig, token);
await this.PollUntilFinishedAsync(RelayerModuleName, token);
await this.ValidateResultsAsync();
}
Expand Down Expand Up @@ -120,7 +120,7 @@ public async Task PriorityQueueTimeToLive()
await Task.Delay(testInfo.TtlThreshold * 1000);

Action<EdgeConfigBuilder> addRelayerConfig = this.BuildAddRelayerConfig(relayerImage, loadGenTestStatus);
deployment = await this.runtime.DeployConfigurationAsync(addInitialConfig + addRelayerConfig, token, false);
deployment = await this.runtime.DeployConfigurationAsync(addInitialConfig + addRelayerConfig, token);
await this.PollUntilFinishedAsync(RelayerModuleName, token);
await this.ValidateResultsAsync();
}
Expand Down