Skip to content

Commit

Permalink
Don't stage deployments (#3364)
Browse files Browse the repository at this point in the history
Test-only fix, cherry-picked from master (4b02827).
  • Loading branch information
damonbarry authored Aug 5, 2020
1 parent 3987b9e commit 65d21e1
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 102 deletions.
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

0 comments on commit 65d21e1

Please sign in to comment.