Skip to content

Commit

Permalink
Update end-to-end tests to use 'iotedge config apply' (#7037)
Browse files Browse the repository at this point in the history
Today, the end-to-end tests configure IoT Edge by directly writing each individual config TOML file (keyd, certd, identityd, edged).

This change updates the end-to-end tests to write one super-config TOML file and then call `iotedge config apply` to automatically create the service-specific configs. It's less tedious and it gives us more test coverage for the `iotedge config apply` tool.

To test, I made sure the end-to-end and nested end-to-end pipelines pass.

## Azure IoT Edge PR checklist:
  • Loading branch information
damonbarry authored Nov 21, 2023
1 parent 8740a08 commit e45807a
Show file tree
Hide file tree
Showing 14 changed files with 178 additions and 448 deletions.
3 changes: 0 additions & 3 deletions test/Microsoft.Azure.Devices.Edge.Test.Common/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ IEnumerable<Registry> GetAndValidateRegistries()
this.DiagnosticsImage = Option.Maybe(Get("diagnosticsImage"));
this.EventHubEndpoint = Get("EVENT_HUB_ENDPOINT");
Preconditions.CheckArgument(!string.IsNullOrWhiteSpace(this.EventHubEndpoint), $"EVENT_HUB_ENDPOINT is missing from environment or context.json.");
this.InstallerPath = Option.Maybe(Get("installerPath"));
this.LogFile = Option.Maybe(Get("logFile"));
this.MethodReceiverImage = Option.Maybe(Get("methodReceiverImage"));
this.MethodSenderImage = Option.Maybe(Get("methodSenderImage"));
Expand Down Expand Up @@ -151,8 +150,6 @@ IEnumerable<Registry> GetAndValidateRegistries()

public string EventHubEndpoint { get; }

public Option<string> InstallerPath { get; }

public Option<string> LogFile { get; }

public Option<string> MethodReceiverImage { get; }
Expand Down
354 changes: 87 additions & 267 deletions test/Microsoft.Azure.Devices.Edge.Test.Common/DaemonConfiguration.cs

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions test/Microsoft.Azure.Devices.Edge.Test.Common/IOsPlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface IOsPlatform
{
Task<string> CollectDaemonLogsAsync(DateTime testStartTime, string filePrefix, CancellationToken token);

Task<IEdgeDaemon> CreateEdgeDaemonAsync(Option<string> installerPath, CancellationToken token);
Task<IEdgeDaemon> CreateEdgeDaemonAsync(CancellationToken token);

// After calling this function, the following files will be available under {scriptPath}:
// certs/iot-device-{deviceId}-full-chain.cert.pem
Expand All @@ -38,7 +38,5 @@ public interface IOsPlatform
void InstallTrustedCertificates(IEnumerable<X509Certificate2> certs);

void SetOwner(string filePath, string owner, string permissions);

uint GetUid(string user);
}
}
79 changes: 37 additions & 42 deletions test/Microsoft.Azure.Devices.Edge.Test.Common/TomlDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace Microsoft.Azure.Devices.Edge.Test.Common
{
using System.Collections.Generic;
using System.Linq;
using Nett;

class TomlDocument
Expand All @@ -15,7 +13,7 @@ public TomlDocument(string input)
this.document = Toml.ReadString(input);
}

public void ReplaceOrAdd<T>(string dottedKey, T value)
(TomlTable table, string key) TraverseKey(string dottedKey, bool add = false)
{
string[] segments = dottedKey.Split(".");
TomlTable table = this.document;
Expand All @@ -24,67 +22,64 @@ public void ReplaceOrAdd<T>(string dottedKey, T value)
{
string tableKey = segments[i];

try
if (!table.ContainsKey(tableKey))
{
table = (TomlTable)table[tableKey];
}
catch (KeyNotFoundException)
{
// Nett does not provide a function to easily add table subkeys.
// A hack workaround is to serialize the table, append the new table as a string, then deserialize.
// This only needs to be done once to create the new subtable.
// After that, Nett functions can be used to modify the subtable.
string tableName = string.Join(".", segments.Take(segments.Length - 1));
this.AddTable(tableName, segments[segments.Length - 1], value);
return;
if (add)
{
table.Add(tableKey, table.CreateEmptyAttachedTable());
}
else
{
return (table, string.Empty);
}
}

table = (TomlTable)table[tableKey];
}

string key = segments[segments.Length - 1];
return (table, segments[segments.Length - 1]);
}

public void ReplaceOrAdd(string dottedKey, bool value)
{
var (table, key) = this.TraverseKey(dottedKey, add: true);

if (table.ContainsKey(key))
{
table.Update(key, value.ToString()); // May need to fix to support other types.
table.Update(key, value);
}
else
{
table.Add(key, value.ToString()); // May need to fix to support other types.
table.Add(key, value);
}
}

public void RemoveIfExists(string dottedKey)
public void ReplaceOrAdd(string dottedKey, string value)
{
string[] segments = dottedKey.Split(".");
TomlTable table = this.document;
var (table, key) = this.TraverseKey(dottedKey, add: true);

for (int i = 0; i < segments.Length - 1; i++)
if (table.ContainsKey(key))
{
try
{
table = (TomlTable)table[segments[i]];
}
catch (KeyNotFoundException)
{
// Key does not exist in table; do nothing.
return;
}
table.Update(key, value);
}
else
{
table.Add(key, value);
}

table.Remove(segments[segments.Length - 1]);
}

public override string ToString()
public void RemoveIfExists(string dottedKey)
{
return this.document.ToString();
var (table, key) = this.TraverseKey(dottedKey);
if (!string.IsNullOrEmpty(key))
{
table.Remove(key);
}
}

public void AddTable<T>(string tableName, string key, T value)
public override string ToString()
{
string v = value is string ? $"\"{value}\"" : value.ToString().ToLower();

this.document = Toml.ReadString(
this.document.ToString() + "\n" +
$"[{tableName}]\n" +
$"{key} = {v}\n");
return Toml.WriteString(this.document);
}
}
}
24 changes: 13 additions & 11 deletions test/Microsoft.Azure.Devices.Edge.Test.Common/linux/EdgeDaemon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Test.Common.Linux
{
using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.ServiceProcess;
Expand All @@ -15,6 +14,7 @@ namespace Microsoft.Azure.Devices.Edge.Test.Common.Linux
public class EdgeDaemon : IEdgeDaemon
{
readonly PackageManagement packageManagement;
readonly bool isCentOs;

public static async Task<EdgeDaemon> CreateAsync(CancellationToken token)
{
Expand Down Expand Up @@ -78,12 +78,13 @@ public static async Task<EdgeDaemon> CreateAsync(CancellationToken token)
throw new NotImplementedException($"Don't know how to install daemon on operating system '{os}'");
}

return new EdgeDaemon(new PackageManagement(os, version, packageExtension));
return new EdgeDaemon(new PackageManagement(os, version, packageExtension), os == "centos");
}

EdgeDaemon(PackageManagement packageManagement)
EdgeDaemon(PackageManagement packageManagement, bool isCentOs)
{
this.packageManagement = packageManagement;
this.isCentOs = isCentOs;
}

public async Task InstallAsync(Option<string> packagesPath, Option<Uri> proxy, CancellationToken token)
Expand Down Expand Up @@ -121,23 +122,24 @@ public Task ConfigureAsync(Func<DaemonConfiguration, Task<(string, object[])>> c
{
await this.InternalStopAsync(token);

ConfigFilePaths paths = new ConfigFilePaths
DaemonConfiguration conf = new DaemonConfiguration("/etc/aziot/config.toml");
if (this.isCentOs)
{
Keyd = "/etc/aziot/keyd/config.toml",
Certd = "/etc/aziot/certd/config.toml",
Identityd = "/etc/aziot/identityd/config.toml",
Edged = "/etc/aziot/edged/config.toml"
};
// The recommended way to set up [listen] sockets in config.toml is to use the 'fd://...' URL
// scheme, which will make use of systemd socket activation. CentOS 7 supports systemd but does
// not support socket activation, so for that platform use the 'unix://...' scheme.
conf.SetConnectSockets("unix:///var/lib/iotedge/workload.sock", "unix:///var/lib/iotedge/mgmt.sock");
conf.SetListenSockets("unix:///var/lib/iotedge/workload.sock", "unix:///var/lib/iotedge/mgmt.sock");
}

DaemonConfiguration conf = new DaemonConfiguration(paths);
(string msg, object[] props) = await config(conf);

message += $" {msg}";
properties = properties.Concat(props).ToArray();

if (restart)
{
await this.InternalStartAsync(token);
await Process.RunAsync("iotedge", "config apply", token);
}
},
message.ToString(),
Expand Down
23 changes: 2 additions & 21 deletions test/Microsoft.Azure.Devices.Edge.Test.Common/linux/OsPlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ public async Task<string> CollectDaemonLogsAsync(DateTime testStartTime, string
return daemonLog;
}

public async Task<IEdgeDaemon> CreateEdgeDaemonAsync(
Option<string> _,
CancellationToken token) => await EdgeDaemon.CreateAsync(token);
public async Task<IEdgeDaemon> CreateEdgeDaemonAsync(CancellationToken token) =>
await EdgeDaemon.CreateAsync(token);

public async Task<IdCertificates> GenerateIdentityCertificatesAsync(string deviceId, string scriptPath, CancellationToken token)
{
Expand Down Expand Up @@ -75,23 +74,5 @@ public void SetOwner(string path, string owner, string permissions)
chmod.WaitForExit();
chmod.Close();
}

public uint GetUid(string user)
{
var id = new System.Diagnostics.Process();

id.StartInfo.FileName = "id";
id.StartInfo.Arguments = $"-u {user}";
id.StartInfo.RedirectStandardOutput = true;

id.Start();
StreamReader reader = id.StandardOutput;
string uid = reader.ReadToEnd().Trim();

id.WaitForExit();
id.Close();

return System.Convert.ToUInt32(uid, 10);
}
}
}
10 changes: 3 additions & 7 deletions test/Microsoft.Azure.Devices.Edge.Test/Image.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
namespace Microsoft.Azure.Devices.Edge.Test
{
using System;
using System.Collections.Generic;
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;
using NUnit.Framework;
Expand Down Expand Up @@ -43,13 +41,11 @@ public async Task ImageGarbageCollection()

// Configure image garbage collection to happen in 2 minutes
await this.daemon.ConfigureAsync(
config =>
async config =>
{
config.SetImageGarbageCollection(2);
config.Update();
return Task.FromResult((
"with non-default image garbage collection settings.",
new object[] { }));
await config.UpdateAsync(token);
return ("with non-default image garbage collection settings.", new object[] { });
},
token,
true);
Expand Down
18 changes: 7 additions & 11 deletions test/Microsoft.Azure.Devices.Edge.Test/Provisioning.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,12 @@ public async Task DpsSymmetricKey()
(TestCertificates testCerts, _) = await TestCertificates.GenerateCertsAsync(registrationId, token);

await this.daemon.ConfigureAsync(
config =>
async config =>
{
testCerts.AddCertsToConfig(config);
config.SetDpsSymmetricKey(idScope, registrationId, deviceKey);
config.Update();
return Task.FromResult((
"with DPS symmetric key attestation for '{Identity}'",
new object[] { registrationId }));
await config.UpdateAsync(token);
return ("with DPS symmetric key attestation for '{Identity}'", new object[] { registrationId });
},
token);

Expand Down Expand Up @@ -114,14 +112,12 @@ public async Task DpsX509()
OsPlatform.Current.SetOwner(keyPath, "aziotks", "600");

await this.daemon.ConfigureAsync(
config =>
async config =>
{
testCerts.AddCertsToConfig(config);
config.SetDpsX509(idScope, registrationId, certPath, keyPath);
config.Update();
return Task.FromResult((
"with DPS X509 attestation for '{Identity}'",
new object[] { registrationId }));
config.SetDpsX509(idScope, certPath, keyPath);
await config.UpdateAsync(token);
return ("with DPS X509 attestation for '{Identity}'", new object[] { registrationId });
},
token);

Expand Down
Loading

0 comments on commit e45807a

Please sign in to comment.