Edge Agent: Remove docker mode (#6452)

Removing docker mode from EdgeAgent. Synced with @marianan to confirm docker mode not used in our tooling. It certainly isn't used by the product.

I have removed docker command factory and related test. Reasoning:

> we have this docker command factory here:

https://github.com/Azure/iotedge/blob/main/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerCommandFactory.cs



> This is not used for anything except for this E2E test:

https://github.com/Azure/iotedge/blob/main/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test/AgentTests.cs



> The E2E test doesn't make much sense to keep around for me, since the benefit of it on top of existing integration tests is to test whether the plan being executed manifests in docker containers state being altered. However it is not using the command factory we actually use, but a different one. In my opinion this makes it pointless.



> I'd like to remove both these files so I can fully remove DockerCommandFactory from EdgeAgent. This will also make it easier for the work we are doing now because we won't have to worry about altering DockerCommandFacotry.

## Azure IoT Edge PR checklist:
This commit is contained in:
Andrew Smith 2022-07-13 13:26:03 -07:00 коммит произвёл GitHub
Родитель 6cc0eccc96
Коммит 40824ed28e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 5 добавлений и 838 удалений

Просмотреть файл

@ -47,8 +47,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edg
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Agent.Docker.Test", "edge-agent\test\Microsoft.Azure.Devices.Edge.Agent.Docker.Test\Microsoft.Azure.Devices.Edge.Agent.Docker.Test.csproj", "{6EDCED3D-F7E4-4830-872B-E4D1C24EC591}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test", "edge-agent\test\Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test\Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test.csproj", "{A03ECC50-84E7-49FE-9190-840F8DD64A1B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Agent.IoTHub", "edge-agent\src\Microsoft.Azure.Devices.Edge.Agent.IoTHub\Microsoft.Azure.Devices.Edge.Agent.IoTHub.csproj", "{09391D32-FC84-4532-A658-F2952AFD26AF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test", "edge-agent\test\Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test\Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test.csproj", "{F43E5511-49F1-4EF2-8ACB-B38CFB45F4CB}"
@ -260,12 +258,6 @@ Global
{6EDCED3D-F7E4-4830-872B-E4D1C24EC591}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6EDCED3D-F7E4-4830-872B-E4D1C24EC591}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6EDCED3D-F7E4-4830-872B-E4D1C24EC591}.Release|Any CPU.Build.0 = Release|Any CPU
{A03ECC50-84E7-49FE-9190-840F8DD64A1B}.CheckInBuild|Any CPU.ActiveCfg = CheckInBuild|Any CPU
{A03ECC50-84E7-49FE-9190-840F8DD64A1B}.CheckInBuild|Any CPU.Build.0 = CheckInBuild|Any CPU
{A03ECC50-84E7-49FE-9190-840F8DD64A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A03ECC50-84E7-49FE-9190-840F8DD64A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A03ECC50-84E7-49FE-9190-840F8DD64A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A03ECC50-84E7-49FE-9190-840F8DD64A1B}.Release|Any CPU.Build.0 = Release|Any CPU
{09391D32-FC84-4532-A658-F2952AFD26AF}.CheckInBuild|Any CPU.ActiveCfg = CheckInBuild|Any CPU
{09391D32-FC84-4532-A658-F2952AFD26AF}.CheckInBuild|Any CPU.Build.0 = CheckInBuild|Any CPU
{09391D32-FC84-4532-A658-F2952AFD26AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@ -631,7 +623,6 @@ Global
{840816E2-D691-45B4-9995-0291FB5AADDA} = {373FFF5E-E84C-4789-B768-676FFF51E7A6}
{3B5A4C63-7B33-4E37-9602-29FC8FC7C9C5} = {54351E51-19CB-4DE3-8302-99846AB216CF}
{6EDCED3D-F7E4-4830-872B-E4D1C24EC591} = {F5E37327-3AA9-4CC2-9FE3-B28271ADB5E3}
{A03ECC50-84E7-49FE-9190-840F8DD64A1B} = {F5E37327-3AA9-4CC2-9FE3-B28271ADB5E3}
{09391D32-FC84-4532-A658-F2952AFD26AF} = {54351E51-19CB-4DE3-8302-99846AB216CF}
{F43E5511-49F1-4EF2-8ACB-B38CFB45F4CB} = {F5E37327-3AA9-4CC2-9FE3-B28271ADB5E3}
{592F8A5A-B1C7-4011-9879-B2AEE118DD3F} = {578D5330-2F72-44C6-9DB5-C93B3F42C473}

Просмотреть файл

@ -1,98 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Azure.Devices.Edge.Agent.Docker
{
using System.Collections.Generic;
using System.Threading.Tasks;
using global::Docker.DotNet;
using Microsoft.Azure.Devices.Edge.Agent.Core;
using Microsoft.Azure.Devices.Edge.Agent.Core.Commands;
using Microsoft.Azure.Devices.Edge.Agent.Docker.Commands;
using Microsoft.Azure.Devices.Edge.Util;
public class DockerCommandFactory : ICommandFactory
{
readonly IDockerClient client;
readonly DockerLoggingConfig dockerLoggerConfig;
readonly IConfigSource configSource;
readonly ICombinedConfigProvider<CombinedDockerConfig> combinedConfigProvider;
public DockerCommandFactory(IDockerClient dockerClient, DockerLoggingConfig dockerLoggingConfig, IConfigSource configSource, ICombinedConfigProvider<CombinedDockerConfig> combinedConfigProvider)
{
this.client = Preconditions.CheckNotNull(dockerClient, nameof(dockerClient));
this.dockerLoggerConfig = Preconditions.CheckNotNull(dockerLoggingConfig, nameof(dockerLoggingConfig));
this.configSource = Preconditions.CheckNotNull(configSource, nameof(configSource));
this.combinedConfigProvider = Preconditions.CheckNotNull(combinedConfigProvider, nameof(combinedConfigProvider));
}
public Task<ICommand> UpdateEdgeAgentAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo) => Task.FromResult(NullCommand.Instance as ICommand);
public async Task<ICommand> CreateAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo)
{
if (module.Module is DockerModule dockerModule)
{
CombinedDockerConfig combinedDockerConfig = this.combinedConfigProvider.GetCombinedConfig(dockerModule, runtimeInfo);
var commands = new List<ICommand>();
if (module.Module.ImagePullPolicy != ImagePullPolicy.Never)
{
commands.Add(new PullCommand(this.client, combinedDockerConfig));
}
commands.Add(await CreateCommand.BuildAsync(this.client, dockerModule, module.ModuleIdentity, this.dockerLoggerConfig, this.configSource, module.Module is EdgeHubDockerModule));
return new GroupCommand(commands.ToArray());
}
return NullCommand.Instance;
}
public async Task<ICommand> UpdateAsync(IModule current, IModuleWithIdentity next, IRuntimeInfo runtimeInfo)
{
if (current is DockerModule currentDockerModule && next.Module is DockerModule nextDockerModule)
{
CombinedDockerConfig combinedDockerConfig = this.combinedConfigProvider.GetCombinedConfig(nextDockerModule, runtimeInfo);
var commands = new List<ICommand>();
if (next.Module.ImagePullPolicy != ImagePullPolicy.Never)
{
commands.Add(new PullCommand(this.client, combinedDockerConfig));
}
commands.AddRange(
new ICommand[]
{
new StopCommand(this.client, currentDockerModule),
new RemoveCommand(this.client, currentDockerModule),
await CreateCommand.BuildAsync(this.client, nextDockerModule, next.ModuleIdentity, this.dockerLoggerConfig, this.configSource, next.Module is EdgeHubDockerModule)
});
return new GroupCommand(commands.ToArray());
}
return NullCommand.Instance;
}
public Task<ICommand> RemoveAsync(IModule module) =>
Task.FromResult(
module is DockerModule
? new RemoveCommand(this.client, (DockerModule)module)
: (ICommand)NullCommand.Instance);
public Task<ICommand> StartAsync(IModule module) =>
Task.FromResult(
module is DockerModule
? new StartCommand(this.client, (DockerModule)module)
: (ICommand)NullCommand.Instance);
public Task<ICommand> StopAsync(IModule module) =>
Task.FromResult(
module is DockerModule
? new StopCommand(this.client, (DockerModule)module)
: (ICommand)NullCommand.Instance);
public Task<ICommand> RestartAsync(IModule module) =>
Task.FromResult(
module is DockerRuntimeModule
? new RestartCommand(this.client, (DockerRuntimeModule)module)
: (ICommand)NullCommand.Instance);
public Task<ICommand> WrapAsync(ICommand command) => Task.FromResult(command);
}
}

Просмотреть файл

@ -82,8 +82,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service
bool enableNonPersistentStorageBackup;
Option<string> storageBackupPath = Option.None<string>();
string edgeDeviceHostName;
string dockerLoggingDriver;
Dictionary<string, string> dockerLoggingOptions;
IEnumerable<global::Docker.DotNet.Models.AuthConfig> dockerAuthConfig;
int configRefreshFrequencySecs;
ExperimentalFeatures experimentalFeatures;
@ -93,7 +91,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service
try
{
mode = configuration.GetValue(Constants.ModeKey, "docker");
mode = configuration.GetValue(Constants.ModeKey, "iotedged");
configSourceConfig = configuration.GetValue<string>("ConfigSource");
backupConfigFilePath = configuration.GetValue<string>("BackupConfigFilePath");
maxRestartCount = configuration.GetValue<int>("MaxRestartCount");
@ -114,8 +112,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service
backupConfigFilePath = GetFullBackupFilePath(storagePath, backupConfigFilePath);
edgeDeviceHostName = configuration.GetValue<string>(Constants.EdgeDeviceHostNameKey);
dockerLoggingDriver = configuration.GetValue<string>("DockerLoggingDriver");
dockerLoggingOptions = configuration.GetSection("DockerLoggingOptions").Get<Dictionary<string, string>>() ?? new Dictionary<string, string>();
dockerAuthConfig = configuration.GetSection("DockerRegistryAuth").Get<List<global::Docker.DotNet.Models.AuthConfig>>() ?? new List<global::Docker.DotNet.Models.AuthConfig>();
NestedEdgeParentUriParser parser = new NestedEdgeParentUriParser();
@ -138,7 +134,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service
try
{
var builder = new ContainerBuilder();
builder.RegisterModule(new LoggingModule(dockerLoggingDriver, dockerLoggingOptions));
builder.RegisterModule(new LoggingModule());
string productInfo =
versionInfo != VersionInfo.Empty ?
$"{Constants.IoTEdgeAgentProductInfoIdentifier}/{versionInfo}" :
@ -160,16 +156,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service
switch (mode.ToLowerInvariant())
{
case Constants.DockerMode:
var dockerUri = new Uri(configuration.GetValue<string>("DockerUri"));
string deviceConnectionString = configuration.GetValue<string>("DeviceConnectionString");
IotHubConnectionStringBuilder connectionStringParser = IotHubConnectionStringBuilder.Create(deviceConnectionString);
deviceId = connectionStringParser.DeviceId;
iothubHostname = connectionStringParser.HostName;
builder.RegisterModule(new AgentModule(maxRestartCount, intensiveCareTime, coolOffTimeUnitInSeconds, usePersistentStorage, storagePath, enableNonPersistentStorageBackup, storageBackupPath, storageTotalMaxWalSize, storageMaxManifestFileSize, storageMaxOpenFiles, storageLogLevel));
builder.RegisterModule(new DockerModule(deviceConnectionString, edgeDeviceHostName, dockerUri, dockerAuthConfig, upstreamProtocol, proxy, productInfo, closeOnIdleTimeout, idleTimeout, useServerHeartbeat, backupConfigFilePath));
break;
case Constants.IotedgedMode:
string managementUri = configuration.GetValue<string>(Constants.EdgeletManagementUriVariableName);
string workloadUri = configuration.GetValue<string>(Constants.EdgeletWorkloadUriVariableName);

Просмотреть файл

@ -1,11 +1,8 @@
{
"Mode": "docker",
"DockerUri": "http://localhost:2375",
"Mode": "iotedged",
"ManagementUri": "http://localhost:50002",
"DeviceConnectionString": "<Edge Device Connection String>",
"ConfigSource": "twin",
"DockerLoggingDriver": "json-file",
"DockerLoggingOptions": {},
"BackupConfigFilePath": "backup.json",
"MaxRestartCount": 20,
"IntensiveCareTimeInMinutes": 10,
@ -27,4 +24,4 @@
"EdgeModuleVolumePath": "",
"ConfigRefreshFrequencySecs": "3600",
"DisableDeviceAnalyticsTelemetry": false
}
}

Просмотреть файл

@ -1,164 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Azure.Devices.Edge.Agent.Service.Modules
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Autofac;
using global::Docker.DotNet;
using global::Docker.DotNet.Models;
using Microsoft.Azure.Devices.Edge.Agent.Core;
using Microsoft.Azure.Devices.Edge.Agent.Core.ConfigSources;
using Microsoft.Azure.Devices.Edge.Agent.Core.DeviceManager;
using Microsoft.Azure.Devices.Edge.Agent.Core.Serde;
using Microsoft.Azure.Devices.Edge.Agent.Docker;
using Microsoft.Azure.Devices.Edge.Agent.IoTHub;
using Microsoft.Azure.Devices.Edge.Agent.IoTHub.SdkClient;
using Microsoft.Azure.Devices.Edge.Storage;
using Microsoft.Azure.Devices.Edge.Util;
using Microsoft.Azure.Devices.Edge.Util.Metrics;
using Microsoft.Extensions.Logging;
public class DockerModule : Module
{
readonly string deviceId;
readonly string iotHubHostName;
readonly string edgeDeviceConnectionString;
readonly string gatewayHostname;
readonly Uri dockerHostname;
readonly IEnumerable<AuthConfig> dockerAuthConfig;
readonly Option<UpstreamProtocol> upstreamProtocol;
readonly Option<IWebProxy> proxy;
readonly string productInfo;
readonly bool closeOnIdleTimeout;
readonly TimeSpan idleTimeout;
readonly bool useServerHeartbeat;
readonly string backupConfigFilePath;
public DockerModule(
string edgeDeviceConnectionString,
string gatewayHostname,
Uri dockerHostname,
IEnumerable<AuthConfig> dockerAuthConfig,
Option<UpstreamProtocol> upstreamProtocol,
Option<IWebProxy> proxy,
string productInfo,
bool closeOnIdleTimeout,
TimeSpan idleTimeout,
bool useServerHeartbeat,
string backupConfigFilePath)
{
this.edgeDeviceConnectionString = Preconditions.CheckNonWhiteSpace(edgeDeviceConnectionString, nameof(edgeDeviceConnectionString));
this.gatewayHostname = Preconditions.CheckNonWhiteSpace(gatewayHostname, nameof(gatewayHostname));
IotHubConnectionStringBuilder connectionStringParser = IotHubConnectionStringBuilder.Create(this.edgeDeviceConnectionString);
this.deviceId = connectionStringParser.DeviceId;
this.iotHubHostName = connectionStringParser.HostName;
this.dockerHostname = Preconditions.CheckNotNull(dockerHostname, nameof(dockerHostname));
this.dockerAuthConfig = Preconditions.CheckNotNull(dockerAuthConfig, nameof(dockerAuthConfig));
this.upstreamProtocol = Preconditions.CheckNotNull(upstreamProtocol, nameof(upstreamProtocol));
this.proxy = Preconditions.CheckNotNull(proxy, nameof(proxy));
this.productInfo = Preconditions.CheckNotNull(productInfo, nameof(productInfo));
this.closeOnIdleTimeout = closeOnIdleTimeout;
this.idleTimeout = idleTimeout;
this.useServerHeartbeat = useServerHeartbeat;
this.backupConfigFilePath = Preconditions.CheckNonWhiteSpace(backupConfigFilePath, nameof(backupConfigFilePath));
}
protected override void Load(ContainerBuilder builder)
{
// IModuleClientProvider
string edgeAgentConnectionString = $"{this.edgeDeviceConnectionString};{Constants.ModuleIdKey}={Constants.EdgeAgentModuleIdentityName}";
builder.Register(
c => new ModuleClientProvider(
edgeAgentConnectionString,
c.Resolve<ISdkModuleClientProvider>(),
this.upstreamProtocol,
this.proxy,
this.productInfo,
this.closeOnIdleTimeout,
this.idleTimeout,
this.useServerHeartbeat))
.As<IModuleClientProvider>()
.SingleInstance();
// IServiceClient
builder.Register(c => new RetryingServiceClient(new ServiceClient(this.edgeDeviceConnectionString, this.deviceId)))
.As<IServiceClient>()
.SingleInstance();
// IModuleIdentityLifecycleManager
builder.Register(c => new ModuleIdentityLifecycleManager(c.Resolve<IServiceClient>(), this.iotHubHostName, this.deviceId, this.gatewayHostname))
.As<IModuleIdentityLifecycleManager>()
.SingleInstance();
// IDockerClient
builder.Register(c => new DockerClientConfiguration(this.dockerHostname).CreateClient())
.As<IDockerClient>()
.SingleInstance();
// ICombinedConfigProvider<CombinedDockerConfig>
builder.Register(c => new CombinedDockerConfigProvider(this.dockerAuthConfig))
.As<ICombinedConfigProvider<CombinedDockerConfig>>()
.SingleInstance();
// ICommandFactory
builder.Register(
async c =>
{
var dockerClient = c.Resolve<IDockerClient>();
var dockerLoggingConfig = c.Resolve<DockerLoggingConfig>();
var combinedDockerConfigProvider = c.Resolve<ICombinedConfigProvider<CombinedDockerConfig>>();
IConfigSource configSource = await c.Resolve<Task<IConfigSource>>();
ICommandFactory factory = new DockerCommandFactory(dockerClient, dockerLoggingConfig, configSource, combinedDockerConfigProvider);
factory = new MetricsCommandFactory(factory, c.Resolve<IMetricsProvider>());
return new LoggingCommandFactory(factory, c.Resolve<ILoggerFactory>()) as ICommandFactory;
})
.As<Task<ICommandFactory>>()
.SingleInstance();
// IRuntimeInfoProvider
builder.Register(
async c =>
{
IRuntimeInfoProvider runtimeInfoProvider = await RuntimeInfoProvider.CreateAsync(c.Resolve<IDockerClient>());
return runtimeInfoProvider;
})
.As<Task<IRuntimeInfoProvider>>()
.SingleInstance();
// Task<IEnvironmentProvider>
builder.Register(
async c =>
{
var moduleStateStore = await c.Resolve<Task<IEntityStore<string, ModuleState>>>();
var restartPolicyManager = c.Resolve<IRestartPolicyManager>();
IRuntimeInfoProvider runtimeInfoProvider = await c.Resolve<Task<IRuntimeInfoProvider>>();
IEnvironmentProvider dockerEnvironmentProvider = await DockerEnvironmentProvider.CreateAsync(runtimeInfoProvider, moduleStateStore, restartPolicyManager, CancellationToken.None);
return dockerEnvironmentProvider;
})
.As<Task<IEnvironmentProvider>>()
.SingleInstance();
// Task<IBackupSource>
builder.Register(
async c =>
{
var serde = c.Resolve<ISerde<DeploymentConfigInfo>>();
var encryptionProviderTask = c.Resolve<Task<IEncryptionProvider>>();
IDeploymentBackupSource backupSource = new DeploymentFileBackup(this.backupConfigFilePath, serde, await encryptionProviderTask);
return backupSource;
})
.As<Task<IDeploymentBackupSource>>()
.SingleInstance();
// IDeviceManager
builder.Register(c => new NullDeviceManager())
.As<IDeviceManager>()
.SingleInstance();
}
}
}

Просмотреть файл

@ -9,13 +9,8 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service.Modules
public class LoggingModule : Module
{
readonly string dockerLoggingDriver;
readonly IDictionary<string, string> driverOptions;
public LoggingModule(string dockerLoggingDriver, IDictionary<string, string> loggingDriverOptions)
public LoggingModule()
{
this.dockerLoggingDriver = Preconditions.CheckNotNull(dockerLoggingDriver, nameof(dockerLoggingDriver));
this.driverOptions = Preconditions.CheckNotNull(loggingDriverOptions, nameof(loggingDriverOptions));
}
protected override void Load(ContainerBuilder builder)
@ -25,11 +20,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service.Modules
.As<ILoggerFactory>()
.SingleInstance();
// DockerLoggingConfig
builder.Register(c => new DockerLoggingConfig(this.dockerLoggingDriver, this.driverOptions))
.As<DockerLoggingConfig>()
.SingleInstance();
base.Load(builder);
}
}

Просмотреть файл

@ -1,285 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using global::Docker.DotNet;
using global::Docker.DotNet.Models;
using Microsoft.Azure.Devices.Edge.Agent.Core;
using Microsoft.Azure.Devices.Edge.Agent.Core.Metrics;
using Microsoft.Azure.Devices.Edge.Agent.Core.Planners;
using Microsoft.Azure.Devices.Edge.Agent.Core.PlanRunners;
using Microsoft.Azure.Devices.Edge.Agent.Core.Reporters;
using Microsoft.Azure.Devices.Edge.Agent.Core.Serde;
using Microsoft.Azure.Devices.Edge.Storage;
using Microsoft.Azure.Devices.Edge.Util;
using Microsoft.Azure.Devices.Edge.Util.Test.Common;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using Newtonsoft.Json;
using Xunit;
public class AgentTests
{
const int CoolOffTimeUnitInSeconds = 10;
const int MaxRunCount = 10;
public static IEnumerable<object[]> GenerateStartTestData()
{
IEnumerable<IConfigurationSection> testsToRun = ConfigHelper.TestConfig.GetSection("testSuite").GetChildren();
// Each test in the test suite supports the notion of a "validator". We determine what
// validator to use by looking at the "$type" property in the test configuration JSON.
// Here's an example:
//
// {
// "name": "mongo-server",
// "version": "1.0",
// "image": "mongo:3.4.4",
// "imageCreateOptions": "{\"HostConfig\": {\"PortBindings\": {\"80/tcp\": [{\"HostPort\": \"8080\"}]}}}",
// "imagePullPolicyTestConfig": {
// "imagePullPolicy": "on-create",
// "pullImage": "false"
// },
// "validator": {
// "$type": "RunCommandValidator",
// "command": "docker",
// "args": "run --rm --link mongo-server:mongo-server mongo:3.4.4 sh -c \"exec mongo --quiet --eval 'db.serverStatus().version' mongo-server:27017/test\"",
// "exitCode": 0,
// "outputEquals": "3.4.4"
// }
// }
//
// Here the value "RunCommandValidator" for "$type" means that Newtonsoft JSON will
// de-serialize the "validator" object from the JSON into an instance of type "RunCommandValidator".
// We provide the mapping from the value of "$type" to a fully qualified .NET type name by providing
// a "serialization binder" - in our case this is an instance of TypeNameSerializationBinder. The JSON
// deserializer consults the TypeNameSerializationBinder instance to determine what type to instantiate.
//
// The "pullPolicyTestConfig" configuration is optional. It's intended for cases where we wish to test
// the behavior of the Agent based on the pull policy specified for a module.
//
// The "exitCode" configuration is optional. By default it's expected value is assumed to be 0.
Type agentTestsType = typeof(AgentTests);
string format = $"{agentTestsType.Namespace}.{{0}}, {agentTestsType.Assembly.GetName().Name}";
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
SerializationBinder = new TypeNameSerializationBinder(format)
};
// appSettings.json contains an array property called "testSuite" which is a list of strings
// containing names of JSON files that contain the spec for the tests to run. We process all the
// JSON files and build a flat list of tests (instances of TestConfig).
IEnumerable<object[]> result = testsToRun.SelectMany(
cs =>
{
string json = File.ReadAllText(cs.Value);
return JsonConvert
.DeserializeObject<TestConfig[]>(json, settings)
.Select(config => new object[] { config });
});
return result;
}
[Integration]
[Theory]
[MemberData(nameof(GenerateStartTestData))]
public async Task AgentStartsUpModules(TestConfig testConfig)
{
// Build the docker host URL.
string dockerHostUrl = ConfigHelper.TestConfig["dockerHostUrl"];
DockerClient client = new DockerClientConfiguration(new Uri(dockerHostUrl)).CreateClient();
try
{
// Remove any running containers with the same name that may be a left-over
// from previous test runs.
await RemoveContainer(client, testConfig);
// Remove old images and pull a new image if specified in the test config.
await PullImage(client, testConfig);
// Initialize docker configuration for this module.
DockerConfig dockerConfig = testConfig.ImageCreateOptions != null
? new DockerConfig(testConfig.Image, testConfig.ImageCreateOptions, Option.None<string>())
: new DockerConfig(testConfig.Image);
ImagePullPolicy imagePullPolicy = ImagePullPolicy.OnCreate;
testConfig.ImagePullPolicyTestConfig.ForEach(p => imagePullPolicy = p.ImagePullPolicy);
// Initialize an Edge Agent module object.
var dockerModule = new DockerModule(
testConfig.Name,
testConfig.Version,
ModuleStatus.Running,
global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.OnUnhealthy,
dockerConfig,
imagePullPolicy,
Constants.DefaultStartupOrder,
null,
null);
var modules = new Dictionary<string, IModule> { [testConfig.Name] = dockerModule };
var systemModules = new SystemModules(null, null);
// Start up the agent and run a "reconcile".
var dockerLoggingOptions = new Dictionary<string, string>
{
{ "max-size", "1m" },
{ "max-file", "1" }
};
var loggingConfig = new DockerLoggingConfig("json-file", dockerLoggingOptions);
string sharedAccessKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("test"));
IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection(
new Dictionary<string, string>
{
{ "DeviceConnectionString", $"Hostname=fakeiothub;Deviceid=test;SharedAccessKey={sharedAccessKey}" }
}).Build();
var runtimeConfig = new DockerRuntimeConfig("1.24.0", "{}");
var runtimeInfo = new DockerRuntimeInfo("docker", runtimeConfig);
var deploymentConfigInfo = new DeploymentConfigInfo(1, new DeploymentConfig("1.0", runtimeInfo, systemModules, modules, null));
var configSource = new Mock<IConfigSource>();
configSource.Setup(cs => cs.Configuration).Returns(configRoot);
configSource.Setup(cs => cs.GetDeploymentConfigInfoAsync()).ReturnsAsync(deploymentConfigInfo);
// TODO: Fix this up with a real reporter. But before we can do that we need to use
// the real configuration source that talks to IoT Hub above.
NullReporter reporter = NullReporter.Instance;
var restartStateStore = Mock.Of<IEntityStore<string, ModuleState>>();
var configStore = Mock.Of<IEntityStore<string, string>>();
var deploymentConfigInfoSerde = Mock.Of<ISerde<DeploymentConfigInfo>>();
IRestartPolicyManager restartManager = new Mock<IRestartPolicyManager>().Object;
var dockerCommandFactory = new DockerCommandFactory(client, loggingConfig, configSource.Object, new CombinedDockerConfigProvider(Enumerable.Empty<AuthConfig>()));
IRuntimeInfoProvider runtimeInfoProvider = await RuntimeInfoProvider.CreateAsync(client);
IEnvironmentProvider environmentProvider = await DockerEnvironmentProvider.CreateAsync(runtimeInfoProvider, restartStateStore, restartManager, CancellationToken.None);
var logFactoryMock = new Mock<ILoggerFactory>();
var logMock = new Mock<ILogger<LoggingCommandFactory>>();
logFactoryMock.Setup(l => l.CreateLogger(It.IsAny<string>()))
.Returns(logMock.Object);
var commandFactory = new LoggingCommandFactory(dockerCommandFactory, logFactoryMock.Object);
var credential = new ConnectionStringCredentials("fake");
var identity = new Mock<IModuleIdentity>();
identity.Setup(id => id.Credentials).Returns(credential);
identity.Setup(id => id.ModuleId).Returns(testConfig.Name);
IImmutableDictionary<string, IModuleIdentity> identities = new Dictionary<string, IModuleIdentity>()
{
[testConfig.Name] = identity.Object
}.ToImmutableDictionary();
var moduleIdentityLifecycleManager = new Mock<IModuleIdentityLifecycleManager>();
moduleIdentityLifecycleManager.Setup(m => m.GetModuleIdentitiesAsync(It.IsAny<ModuleSet>(), It.IsAny<ModuleSet>())).Returns(Task.FromResult(identities));
var availabilityMetric = Mock.Of<IDeploymentMetrics>();
var store = new Mock<IEntityStore<string, ModuleState>>();
TimeSpan intensiveCareTime = TimeSpan.FromMinutes(10);
HealthRestartPlanner healthRestartPlanner = new HealthRestartPlanner(commandFactory, store.Object, intensiveCareTime, restartManager);
var planRunner = new OrderedRetryPlanRunner(MaxRunCount, CoolOffTimeUnitInSeconds, new SystemTime());
Agent agent = await Agent.Create(
configSource.Object,
healthRestartPlanner,
planRunner,
reporter,
moduleIdentityLifecycleManager.Object,
environmentProvider,
configStore,
deploymentConfigInfoSerde,
NullEncryptionProvider.Instance,
availabilityMetric);
await agent.ReconcileAsync(CancellationToken.None);
// Sometimes the container is still not ready by the time we run the validator.
// So we attempt validation multiple times and bail only if all of them fail.
bool validated = false;
int attempts = 0;
const int MaxAttempts = 5;
while (!validated && attempts < MaxAttempts)
{
validated = testConfig.Validator.Validate();
if (!validated)
{
Thread.Sleep(TimeSpan.FromSeconds(5));
}
++attempts;
}
Assert.True(validated);
}
finally
{
await RemoveContainer(client, testConfig);
}
}
static async Task RemoveContainer(IDockerClient client, TestConfig testConfig)
{
// get current list of containers (running or otherwise) where their name
// matches what's given in the test settings
IList<ContainerListResponse> containersList = await client.Containers.ListContainersAsync(
new ContainersListParameters
{
All = true
});
IEnumerable<ContainerListResponse> toBeRemoved = containersList
.Where(c => c.Names.Contains($"/{testConfig.Name}"));
// blow them away!
var removeParams = new ContainerRemoveParameters
{
Force = true
};
await Task.WhenAll(toBeRemoved.Select(c => client.Containers.RemoveContainerAsync(c.ID, removeParams)));
}
static async Task PullImage(IDockerClient client, TestConfig testConfig)
{
// First, delete the image if it's already present.
IList<ImagesListResponse> images = await client.Images.ListImagesAsync(
new ImagesListParameters
{
MatchName = testConfig.Image,
});
foreach (ImagesListResponse image in images)
{
await client.Images.DeleteImageAsync(
image.ID,
new ImageDeleteParameters
{
Force = true,
});
}
bool pullImage = false;
testConfig.ImagePullPolicyTestConfig.ForEach(p => pullImage = p.PullImage);
// Pull the image if the test config specifies that the image should be pulled.
if (pullImage)
{
await client.Images.CreateImageAsync(
new ImagesCreateParameters
{
FromImage = testConfig.Image,
},
new AuthConfig(),
new Progress<JSONMessage>());
}
}
}
}

Просмотреть файл

@ -1,22 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test
{
using Microsoft.Azure.Devices.Edge.Agent.Core;
using Newtonsoft.Json;
public class ImagePullPolicyTestConfig
{
[JsonConstructor]
public ImagePullPolicyTestConfig(ImagePullPolicy imagePullPolicy, bool pullImage)
{
this.ImagePullPolicy = imagePullPolicy;
this.PullImage = pullImage;
}
[JsonProperty("imagePullPolicy")]
public ImagePullPolicy ImagePullPolicy { get; set; }
[JsonProperty("pullImage")]
public bool PullImage { get; set; }
}
}

Просмотреть файл

@ -1,45 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\netcoreappVersion.props" />
<PropertyGroup>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<Configurations>Debug;Release;CheckInBuild</Configurations>
<HighEntropyVA>true</HighEntropyVA>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.10.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\edge-util\test\Microsoft.Azure.Devices.Edge.Util.Test.Common\Microsoft.Azure.Devices.Edge.Util.Test.Common.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Azure.Devices.Edge.Agent.Core\Microsoft.Azure.Devices.Edge.Agent.Core.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Azure.Devices.Edge.Agent.Docker\Microsoft.Azure.Devices.Edge.Agent.Docker.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="test-config-x64.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="test-config-windows-x64.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\..\stylecop.json" Link="stylecop.json" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\..\stylecop.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<Import Project="..\..\..\stylecop.props" />
</Project>

Просмотреть файл

@ -1,42 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test
{
using System.Diagnostics;
public class RunCommandValidator : Validator
{
public RunCommandValidator()
{
this.Type = ValidatorType.RunCommand;
}
public string Command { get; set; }
public string Args { get; set; }
public string OutputEquals { get; set; }
public int ExitCode { get; set; }
public override bool Validate()
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = this.Command,
Arguments = this.Args,
RedirectStandardOutput = true
}
};
string output = string.Empty;
process.OutputDataReceived += (sender, args) => output += args.Data;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
return process.ExitCode == this.ExitCode && output == this.OutputEquals;
}
}
}

Просмотреть файл

@ -1,40 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test
{
using Microsoft.Azure.Devices.Edge.Util;
using Microsoft.Azure.Devices.Edge.Util.Json;
using Newtonsoft.Json;
public class TestConfig
{
[JsonConstructor]
public TestConfig(string name, string version, string image, string imageCreateOptions, Validator validator, ImagePullPolicyTestConfig imagePullPolicyTestConfig)
{
this.Name = name;
this.Version = version;
this.Image = image;
this.ImageCreateOptions = imageCreateOptions;
this.Validator = validator;
this.ImagePullPolicyTestConfig = Option.Maybe(imagePullPolicyTestConfig);
}
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "version")]
public string Version { get; set; }
[JsonProperty(PropertyName = "image")]
public string Image { get; set; }
[JsonProperty(PropertyName = "imageCreateOptions")]
public string ImageCreateOptions { get; set; }
[JsonProperty(PropertyName = "validator")]
public Validator Validator { get; set; }
[JsonProperty(PropertyName = "imagePullPolicyTestConfig")]
[JsonConverter(typeof(OptionConverter<ImagePullPolicyTestConfig>))]
public Option<ImagePullPolicyTestConfig> ImagePullPolicyTestConfig { get; set; }
}
}

Просмотреть файл

@ -1,15 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test
{
public enum ValidatorType
{
RunCommand
}
public abstract class Validator
{
public ValidatorType Type { get; set; }
public abstract bool Validate();
}
}

Просмотреть файл

@ -1,13 +0,0 @@
[
{
"name": "nano",
"version": "1.0",
"image": "mcr.microsoft.com/windows/nanoserver:1809",
"validator": {
"$type": "RunCommandValidator",
"command": "docker",
"args": "ps -a -f name=nano --format '{{.Names}}'",
"outputEquals": "'nano'"
}
}
]

Просмотреть файл

@ -1,73 +0,0 @@
[
{
"name": "mongo-server",
"version": "1.0",
"image": "mongo:3.4.4",
"validator": {
"$type": "RunCommandValidator",
"command": "docker",
"args": "run --rm --link mongo-server:mongo-server mongo:3.4.4 sh -c \"exec mongo --quiet --eval 'db.serverStatus().version' mongo-server:27017/test\"",
"outputEquals": "3.4.4"
}
},
{
"name": "nginx-server",
"version": "1.0",
"image": "nginx:latest",
"imageCreateOptions": "{\"HostConfig\": {\"PortBindings\": {\"80/tcp\": [{\"HostPort\": \"8080\"}]}}}",
"validator": {
"$type": "RunCommandValidator",
"command": "docker",
"args": "run --rm --link nginx-server:nginx-server appropriate/curl curl -s -o /dev/null -w '%{http_code}' http://nginx-server",
"outputEquals": "'200'"
}
},
{
"name": "bash",
"version": "1.0",
"image": "bash:latest",
"imagePullPolicyTestConfig": {
"imagePullPolicy": "never",
"pullImage": "false"
},
"validator": {
"$type": "RunCommandValidator",
"command": "curl",
"args": "-X GET -s -o /dev/null -w \"%{http_code}\" --unix-socket /var/run/docker.sock http://docker/containers/bash/json",
"exitCode" : 0,
"outputEquals": "404"
}
},
{
"name": "bash",
"version": "1.0",
"image": "bash:latest",
"imagePullPolicyTestConfig": {
"imagePullPolicy": "never",
"pullImage": "true"
},
"validator": {
"$type": "RunCommandValidator",
"command": "curl",
"args": "-X GET -s -o /dev/null -w \"%{http_code}\" --unix-socket /var/run/docker.sock http://docker/containers/bash/json",
"exitCode" : 0,
"outputEquals": "200"
}
},
{
"name": "bash",
"version": "1.0",
"image": "bash:latest",
"imagePullPolicyTestConfig": {
"imagePullPolicy": "on-create",
"pullImage": "false"
},
"validator": {
"$type": "RunCommandValidator",
"command": "curl",
"args": "-X GET -s -o /dev/null -w \"%{http_code}\" --unix-socket /var/run/docker.sock http://docker/containers/bash/json",
"exitCode" : 0,
"outputEquals": "200"
}
}
]