diff --git a/CodeCoverage.runsettings b/CodeCoverage.runsettings index 460286f146c..e54f290b9d5 100644 --- a/CodeCoverage.runsettings +++ b/CodeCoverage.runsettings @@ -38,7 +38,6 @@ Included items must then not match any entries in the exclude list to remain inc .*moq\..* .*app.metrics.*\..* .*autofac.* - .*bouncycastle\..* .*fluentassertions.* .*microsoft.codeanalysis\..* .*microsoft.identitymodel\..* diff --git a/Microsoft.Azure.Devices.Edge.sln b/Microsoft.Azure.Devices.Edge.sln index d9ea6409349..4adc4968624 100644 --- a/Microsoft.Azure.Devices.Edge.sln +++ b/Microsoft.Azure.Devices.Edge.sln @@ -44,9 +44,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{A9F6AF55-719 EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Agent.Service", "edge-agent\src\Microsoft.Azure.Devices.Edge.Agent.Service\Microsoft.Azure.Devices.Edge.Agent.Service.csproj", "{37129265-046F-4A46-A1BD-C067E6C06264}" - ProjectSection(ProjectDependencies) = postProject - {B63C506E-5CB5-4DE8-8275-F7BA8354799E} = {B63C506E-5CB5-4DE8-8275-F7BA8354799E} - EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Hub.CloudProxy", "edge-hub\src\Microsoft.Azure.Devices.Edge.Hub.CloudProxy\Microsoft.Azure.Devices.Edge.Hub.CloudProxy.csproj", "{F3F1EFBD-CCD0-4579-99EE-C91A1B6D5C5D}" EndProject @@ -164,23 +161,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2300ED4C-1 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.Agent.Kubernetes", "edge-agent\src\Microsoft.Azure.Devices.Edge.Agent.Kubernetes\Microsoft.Azure.Devices.Edge.Agent.Kubernetes.csproj", "{B63C506E-5CB5-4DE8-8275-F7BA8354799E}" - ProjectSection(ProjectDependencies) = postProject - {B0F37919-0D16-490F-B7A1-665BB93B9A62} = {B0F37919-0D16-490F-B7A1-665BB93B9A62} - {3B5A4C63-7B33-4E37-9602-29FC8FC7C9C5} = {3B5A4C63-7B33-4E37-9602-29FC8FC7C9C5} - {84B77973-0092-4DC9-87E3-F16FC2F4E9AE} = {84B77973-0092-4DC9-87E3-F16FC2F4E9AE} - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test", "edge-agent\test\Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test\Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test.csproj", "{A5DFFB52-D8EA-4E1A-BF4F-8B9665C7DAFE}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Test", "test\Microsoft.Azure.Devices.Edge.Test\Microsoft.Azure.Devices.Edge.Test.csproj", "{08986FED-9283-47F1-B938-86D9C6E753E1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Agent.Diagnostics", "edge-agent\src\Microsoft.Azure.Devices.Edge.Agent.Diagnostics\Microsoft.Azure.Devices.Edge.Agent.Diagnostics.csproj", "{47AA5965-5826-489D-B74B-314A61629121}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Agent.Diagnostics.Test", "edge-agent\test\Microsoft.Azure.Devices.Edge.Agent.Diagnostics.Test\Microsoft.Azure.Devices.Edge.Agent.Diagnostics.Test.csproj", "{B736B03C-1185-4AE5-87B7-665C6790F50F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest", "edge-agent\test\Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest\Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.csproj", "{79540E35-45EA-4568-9AB7-C1CC0BFF73EE}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{F921339B-32F9-4BF3-B364-2DB01FA2F1A1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestResultCoordinator", "test\modules\TestResultCoordinator\TestResultCoordinator.csproj", "{8181EB49-62CE-495B-8078-08DCF8C30541}" @@ -484,18 +470,6 @@ Global {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 - {B63C506E-5CB5-4DE8-8275-F7BA8354799E}.CheckInBuild|Any CPU.ActiveCfg = CheckInBuild|Any CPU - {B63C506E-5CB5-4DE8-8275-F7BA8354799E}.CheckInBuild|Any CPU.Build.0 = CheckInBuild|Any CPU - {B63C506E-5CB5-4DE8-8275-F7BA8354799E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B63C506E-5CB5-4DE8-8275-F7BA8354799E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B63C506E-5CB5-4DE8-8275-F7BA8354799E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B63C506E-5CB5-4DE8-8275-F7BA8354799E}.Release|Any CPU.Build.0 = Release|Any CPU - {A5DFFB52-D8EA-4E1A-BF4F-8B9665C7DAFE}.CheckInBuild|Any CPU.ActiveCfg = CheckInBuild|Any CPU - {A5DFFB52-D8EA-4E1A-BF4F-8B9665C7DAFE}.CheckInBuild|Any CPU.Build.0 = CheckInBuild|Any CPU - {A5DFFB52-D8EA-4E1A-BF4F-8B9665C7DAFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A5DFFB52-D8EA-4E1A-BF4F-8B9665C7DAFE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A5DFFB52-D8EA-4E1A-BF4F-8B9665C7DAFE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A5DFFB52-D8EA-4E1A-BF4F-8B9665C7DAFE}.Release|Any CPU.Build.0 = Release|Any CPU {08986FED-9283-47F1-B938-86D9C6E753E1}.CheckInBuild|Any CPU.ActiveCfg = CheckInBuild|Any CPU {08986FED-9283-47F1-B938-86D9C6E753E1}.CheckInBuild|Any CPU.Build.0 = CheckInBuild|Any CPU {08986FED-9283-47F1-B938-86D9C6E753E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -518,14 +492,6 @@ Global {B736B03C-1185-4AE5-87B7-665C6790F50F}.Debug|Any CPU.Build.0 = Debug|Any CPU {B736B03C-1185-4AE5-87B7-665C6790F50F}.Release|Any CPU.ActiveCfg = Release|Any CPU {B736B03C-1185-4AE5-87B7-665C6790F50F}.Release|Any CPU.Build.0 = Release|Any CPU - {79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.CheckInBuild|Any CPU.ActiveCfg = CheckInBuild|Any CPU - {79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.CheckInBuild|Any CPU.Build.0 = CheckInBuild|Any CPU - {79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.CodeCoverage|Any CPU.ActiveCfg = Debug|Any CPU - {79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.CodeCoverage|Any CPU.Build.0 = Debug|Any CPU - {79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {79540E35-45EA-4568-9AB7-C1CC0BFF73EE}.Release|Any CPU.Build.0 = Release|Any CPU {8181EB49-62CE-495B-8078-08DCF8C30541}.CheckInBuild|Any CPU.ActiveCfg = CheckInBuild|Any CPU {8181EB49-62CE-495B-8078-08DCF8C30541}.CheckInBuild|Any CPU.Build.0 = CheckInBuild|Any CPU {8181EB49-62CE-495B-8078-08DCF8C30541}.CodeCoverage|Any CPU.ActiveCfg = Debug|Any CPU @@ -758,12 +724,9 @@ Global {B8D5312A-B37B-4FA3-8B80-2D1A93077DDF} = {C3BDC9FA-B7D8-44F3-970F-D24281335F46} {9BED8F14-63E9-4B4B-88F7-659D5E522CBD} = {63969606-14B2-4D9D-AB72-A5D60D22037C} {950DACB0-B011-41AF-B0FB-245F749B01AB} = {2300ED4C-1D5A-460F-8691-7C85E1162E0C} - {B63C506E-5CB5-4DE8-8275-F7BA8354799E} = {54351E51-19CB-4DE3-8302-99846AB216CF} - {A5DFFB52-D8EA-4E1A-BF4F-8B9665C7DAFE} = {F5E37327-3AA9-4CC2-9FE3-B28271ADB5E3} {08986FED-9283-47F1-B938-86D9C6E753E1} = {2300ED4C-1D5A-460F-8691-7C85E1162E0C} {47AA5965-5826-489D-B74B-314A61629121} = {54351E51-19CB-4DE3-8302-99846AB216CF} {B736B03C-1185-4AE5-87B7-665C6790F50F} = {F5E37327-3AA9-4CC2-9FE3-B28271ADB5E3} - {79540E35-45EA-4568-9AB7-C1CC0BFF73EE} = {F5E37327-3AA9-4CC2-9FE3-B28271ADB5E3} {F921339B-32F9-4BF3-B364-2DB01FA2F1A1} = {2300ED4C-1D5A-460F-8691-7C85E1162E0C} {8181EB49-62CE-495B-8078-08DCF8C30541} = {F921339B-32F9-4BF3-B364-2DB01FA2F1A1} {F3B989E5-E7F5-4A07-AEA1-84B63040A190} = {2300ED4C-1D5A-460F-8691-7C85E1162E0C} diff --git a/THIRDPARTYNOTICES b/THIRDPARTYNOTICES index bca649f6cf2..05ae34d280d 100644 --- a/THIRDPARTYNOTICES +++ b/THIRDPARTYNOTICES @@ -2395,50 +2395,6 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *** - -Portable.BouncyCastle - - - - - License - - -

The Bouncy Castle Cryptographic C#® API

-

License:

-The Bouncy Castle License
-Copyright (c) 2000-2011 The Legion Of The Bouncy Castle -(http://www.bouncycastle.org)
-Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), to deal in the -Software without restriction, including without limitation the rights to use, copy, modify, merge, -publish, distribute, sub license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", -WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-PURPOSE AND NONINFRINGEMENT. IN NO -EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.
-
-
- - - -*** - paholg/typenum The MIT License (MIT) Copyright (c) 2014 Paho Lurie-Gregg diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/AssemblyInfo.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/AssemblyInfo.cs deleted file mode 100644 index 6283060f118..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/AssemblyInfo.cs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test")] -[assembly: InternalsVisibleTo("Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest")] diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/AuthConfig.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/AuthConfig.cs deleted file mode 100644 index a6be53ddcd5..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/AuthConfig.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using Microsoft.Azure.Devices.Edge.Util; - - public class AuthConfig - { - public AuthConfig(string name) - { - this.Name = Preconditions.CheckNonWhiteSpace(name, nameof(name)); - } - - public string Name { get; } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/CombinedKubernetesConfig.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/CombinedKubernetesConfig.cs deleted file mode 100644 index ce0d3e7864a..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/CombinedKubernetesConfig.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util; - - public class CombinedKubernetesConfig - { - public CombinedKubernetesConfig(string image, CreatePodParameters createOptions, Option imagePullSecret) - { - this.Image = Preconditions.CheckNonWhiteSpace(image, nameof(image)).Trim(); - this.CreateOptions = Preconditions.CheckNotNull(createOptions, nameof(createOptions)); - this.ImagePullSecret = imagePullSecret; - } - - public string Image { get; } - - public CreatePodParameters CreateOptions { get; } - - public Option ImagePullSecret { get; } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/CombinedKubernetesConfigProvider.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/CombinedKubernetesConfigProvider.cs deleted file mode 100644 index a8365867a3c..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/CombinedKubernetesConfigProvider.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Runtime.InteropServices; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Agent.Docker.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util; - using Newtonsoft.Json.Linq; - - public class CombinedKubernetesConfigProvider : ICombinedConfigProvider - { - const string CmdKey = "Cmd"; - const string EntrypointKey = "Entrypoint"; - const string WorkingDirKey = "WorkingDir"; - - readonly CombinedDockerConfigProvider dockerConfigProvider; - readonly Uri workloadUri; - readonly Uri managementUri; - readonly bool enableKubernetesExtensions; - - public CombinedKubernetesConfigProvider( - IEnumerable authConfigs, - Uri workloadUri, - Uri managementUri, - bool enableKubernetesExtensions) - { - this.dockerConfigProvider = new CombinedDockerConfigProvider(authConfigs); - this.workloadUri = Preconditions.CheckNotNull(workloadUri, nameof(workloadUri)); - this.managementUri = Preconditions.CheckNotNull(managementUri, nameof(managementUri)); - this.enableKubernetesExtensions = enableKubernetesExtensions; - } - - public CombinedKubernetesConfig GetCombinedConfig(IModule module, IRuntimeInfo runtimeInfo) - { - CombinedDockerConfig dockerConfig = this.dockerConfigProvider.GetCombinedConfig(module, runtimeInfo); - - // if the workload URI is a Unix domain socket then volume mount it into the container - HostConfig hostConfig = this.AddSocketBinds(module, Option.Maybe(dockerConfig.CreateOptions.HostConfig)); - - var otherProperties = Option.Maybe(dockerConfig.CreateOptions.OtherProperties) - .Map(op => new Dictionary(op, StringComparer.OrdinalIgnoreCase)); - - CreatePodParameters createOptions = new CreatePodParameters( - dockerConfig.CreateOptions.Env, - dockerConfig.CreateOptions.ExposedPorts, - hostConfig, - dockerConfig.CreateOptions.Image, - dockerConfig.CreateOptions.Labels, - GetPropertiesStringArray(CmdKey, otherProperties), - GetPropertiesStringArray(EntrypointKey, otherProperties), - GetPropertiesString(WorkingDirKey, otherProperties)); - - if (this.enableKubernetesExtensions) - { - Option experimentalOptions = KubernetesExperimentalCreatePodParameters.Parse(dockerConfig.CreateOptions.OtherProperties); - experimentalOptions.ForEach(parameters => createOptions.Volumes = parameters.Volumes); - experimentalOptions.ForEach(parameters => createOptions.NodeSelector = parameters.NodeSelector); - experimentalOptions.ForEach(parameters => createOptions.Resources = parameters.Resources); - experimentalOptions.ForEach(parameters => createOptions.SecurityContext = parameters.SecurityContext); - experimentalOptions.ForEach(parameters => createOptions.ServiceOptions = parameters.ServiceOptions); - experimentalOptions.ForEach(parameters => createOptions.DeploymentStrategy = parameters.DeploymentStrategy); - } - - Option imagePullSecret = dockerConfig.AuthConfig - .Map(auth => new ImagePullSecret(auth)); - - return new CombinedKubernetesConfig(dockerConfig.Image, createOptions, imagePullSecret); - } - - HostConfig AddSocketBinds(IModule module, Option dockerHostConfig) - { - Option hostConfig = dockerHostConfig; - - // If Workload URI is Unix domain socket, and the module is the EdgeAgent, then mount it ino the container. - if (string.Equals(this.workloadUri.Scheme, "unix", StringComparison.OrdinalIgnoreCase)) - { - string path = BindPath(this.workloadUri); - hostConfig = hostConfig.Else(() => Option.Some(new HostConfig { Binds = new List() })); - hostConfig.ForEach(config => config.Binds.Add($"{path}:{path}")); - } - - // If Management URI is Unix domain socket, and the module is the EdgeAgent, then mount it ino the container. - if (string.Equals(this.managementUri.Scheme, "unix", StringComparison.OrdinalIgnoreCase) - && module.Name.Equals(Core.Constants.EdgeAgentModuleName, StringComparison.OrdinalIgnoreCase)) - { - string path = BindPath(this.managementUri); - hostConfig = hostConfig.Else(() => Option.Some(new HostConfig { Binds = new List() })); - hostConfig.ForEach(config => config.Binds.Add($"{path}:{path}")); - } - - return hostConfig.OrDefault(); - } - - static string BindPath(Uri uri) - { - // On Windows we need to bind to the parent folder. We can't bind - // directly to the socket file. - return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? Path.GetDirectoryName(uri.LocalPath) - : uri.AbsolutePath; - } - - static IReadOnlyList GetPropertiesStringArray(string key, Option> other) => - other.FlatMap(options => options.Get(key).FlatMap(option => Option.Maybe(option.ToObject>()))).OrDefault(); - - static string GetPropertiesString(string key, Option> other) => - other.FlatMap(options => options.Get(key).FlatMap(option => Option.Maybe(option.ToObject()))).OrDefault(); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/Constants.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/Constants.cs deleted file mode 100644 index bd1c8a390c0..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/Constants.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; - - public static class Constants - { - public static class EdgeDeployment - { - public const string ApiVersion = Api + "/" + Version; - - public const string Api = "microsoft.azure.devices.edge"; - - public const string Version = "v1"; - - public const string Kind = "EdgeDeployment"; - - public const string Group = "microsoft.azure.devices.edge"; - - public const string Plural = "edgedeployments"; - } - - public const string CreationString = "net.azure-devices.edge.creationstring"; - - public const string DefaultDeletePropagationPolicy = "Background"; - - public const PortMapServiceType DefaultPortMapServiceType = PortMapServiceType.ClusterIP; - - public const string K8sEdgeModuleLabel = "net.azure-devices.edge.module"; - - public const string K8sEdgeOriginalModuleId = "net.azure-devices.edge.original-moduleid"; - - public const string K8sEdgeDeviceLabel = "net.azure-devices.edge.deviceid"; - - public const string K8sNameDivider = "-"; - - public const string K8sPullSecretType = "kubernetes.io/dockerconfigjson"; - - public const string K8sPullSecretData = ".dockerconfigjson"; - - public const string K8sBackupSecretType = "Opaque"; - - public const string EdgeAgentBackupName = "edgeagent-backup-config"; - - public const string PortMappingServiceType = "PortMappingServiceType"; - - public const string EnableK8sServiceCallTracingName = "EnableK8sServiceCallTracing"; - - public const string K8sNamespaceKey = "K8sNamespace"; - - public const string ProxyImageEnvKey = "ProxyImage"; - - public const string ProxyImagePullSecretNameEnvKey = "ProxyImagePullSecretName"; - - public const string ProxyConfigPathEnvKey = "ProxyConfigPath"; - - public const string ProxyConfigVolumeEnvKey = "ProxyConfigVolume"; - - public const string ProxyConfigMapNameEnvKey = "ProxyConfigMapName"; - - public const string ProxyTrustBundlePathEnvKey = "ProxyTrustBundlePath"; - - public const string ProxyTrustBundleVolumeEnvKey = "ProxyTrustBundleVolume"; - - public const string ProxyTrustBundleConfigMapEnvKey = "ProxyTrustBundleConfigMapName"; - - public const string UseMountSourceForVolumeNameKey = "UseMountSourceForVolumeName"; - - public const string StorageClassNameKey = "StorageClassName"; - - public const string PersistentVolumeClaimDefaultSizeInMbKey = "PersistentVolumeClaimDefaultSizeInMb"; - - public const string EdgeK8sObjectOwnerApiVersionKey = "EdgeK8sObjectOwnerApiVersion"; - - public const string EdgeK8sObjectOwnerKindKey = "EdgeK8sObjectOwnerKind"; - - public const string EdgeK8sObjectOwnerNameKey = "EdgeK8sObjectOwnerName"; - - public const string EdgeK8sObjectOwnerUidKey = "EdgeK8sObjectOwnerUid"; - - public const string RunAsNonRootKey = "RunAsNonRoot"; - - public const string UnknownImage = "unknown"; - - public const string HostIPC = "host"; - public const string HostNetwork = "host"; - public const string HostNetworkDnsPolicy = "ClusterFirstWithHostNet"; - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/CreatePodParameters.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/CreatePodParameters.cs deleted file mode 100644 index b1e104b8603..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/CreatePodParameters.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Docker.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Json; - using Newtonsoft.Json; - using EmptyStruct = global::Docker.DotNet.Models.EmptyStruct; - - public class CreatePodParameters - { - public CreatePodParameters( - IEnumerable env, - IDictionary exposedPorts, - HostConfig hostConfig, - string image, - IDictionary labels, - IEnumerable cmd, - IEnumerable entrypoint, - string workingDir) - : this(env?.ToList(), exposedPorts, hostConfig, image, labels, cmd?.ToList(), entrypoint?.ToList(), workingDir, null, null, null, null, null, null) - { - } - - [JsonConstructor] - CreatePodParameters( - IReadOnlyList env, - IDictionary exposedPorts, - HostConfig hostConfig, - string image, - IDictionary labels, - IReadOnlyList cmd, - IReadOnlyList entrypoint, - string workingDir, - IDictionary nodeSelector, - V1ResourceRequirements resources, - IReadOnlyList volumes, - V1PodSecurityContext securityContext, - KubernetesServiceOptions serviceOptions, - V1DeploymentStrategy strategy) - { - this.Env = Option.Maybe(env); - this.ExposedPorts = Option.Maybe(exposedPorts); - this.HostConfig = Option.Maybe(hostConfig); - this.Image = Option.Maybe(image); - this.Labels = Option.Maybe(labels); - this.Cmd = Option.Maybe(cmd); - this.Entrypoint = Option.Maybe(entrypoint); - this.WorkingDir = Option.Maybe(workingDir); - this.NodeSelector = Option.Maybe(nodeSelector); - this.Resources = Option.Maybe(resources); - this.Volumes = Option.Maybe(volumes); - this.SecurityContext = Option.Maybe(securityContext); - this.ServiceOptions = Option.Maybe(serviceOptions); - this.DeploymentStrategy = Option.Maybe(strategy); - } - - internal static CreatePodParameters Create( - IReadOnlyList env = null, - IDictionary exposedPorts = null, - HostConfig hostConfig = null, - string image = null, - IDictionary labels = null, - IReadOnlyList cmd = null, - IReadOnlyList entrypoint = null, - string workingDir = null, - IDictionary nodeSelector = null, - V1ResourceRequirements resources = null, - IReadOnlyList volumes = null, - V1PodSecurityContext securityContext = null, - KubernetesServiceOptions serviceOptions = null, - V1DeploymentStrategy deploymentStrategy = null) - => new CreatePodParameters(env, exposedPorts, hostConfig, image, labels, cmd, entrypoint, workingDir, nodeSelector, resources, volumes, securityContext, serviceOptions, deploymentStrategy); - - [JsonProperty("env", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter>))] - public Option> Env { get; } - - [JsonProperty("exposedPorts", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter>))] - public Option> ExposedPorts { get; } - - [JsonProperty("hostConfig", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter))] - public Option HostConfig { get; } - - [JsonProperty("image", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter))] - public Option Image { get; } - - [JsonProperty("labels", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter>))] - public Option> Labels { get; } - - [JsonProperty("nodeSelector", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter>))] - public Option> NodeSelector { get; set; } - - [JsonProperty("resources", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter))] - public Option Resources { get; set; } - - [JsonProperty("volumes", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter>))] - public Option> Volumes { get; set; } - - [JsonProperty("securityContext", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter))] - public Option SecurityContext { get; set; } - - [JsonProperty("serviceOptions", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter))] - public Option ServiceOptions { get; set; } - - [JsonProperty("strategy", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter))] - public Option DeploymentStrategy { get; set; } - - [JsonProperty("cmd", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter>))] - public Option> Cmd { get; } - - [JsonProperty("entrypoint", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter>))] - public Option> Entrypoint { get; } - - [JsonProperty("workingDir", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter))] - public Option WorkingDir { get; } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/DeploymentSecretBackup.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/DeploymentSecretBackup.cs deleted file mode 100644 index 6f7abe232eb..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/DeploymentSecretBackup.cs +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Security.Cryptography; - using System.Threading.Tasks; - using k8s; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Core.Serde; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Concurrency; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.Logging; - using Microsoft.Rest; - - public class DeploymentSecretBackup : IDeploymentBackupSource - { - const string BackupData = "backup.json"; - readonly string deviceNamespace; - readonly KubernetesModuleOwner moduleOwner; - readonly ISerde serde; - readonly IKubernetes client; - - public DeploymentSecretBackup(string secretName, string deviceNamespace, KubernetesModuleOwner moduleOwner, ISerde serde, IKubernetes client) - { - this.Name = Preconditions.CheckNonWhiteSpace(secretName, nameof(secretName)); - this.deviceNamespace = Preconditions.CheckNonWhiteSpace(deviceNamespace, nameof(deviceNamespace)); - this.moduleOwner = Preconditions.CheckNotNull(moduleOwner, nameof(moduleOwner)); - this.serde = Preconditions.CheckNotNull(serde, nameof(serde)); - this.client = Preconditions.CheckNotNull(client, nameof(client)); - } - - public string Name { get; } - - public async Task ReadFromBackupAsync() - { - Option config = Option.None(); - - try - { - V1Secret backupSecret = await this.client.ReadNamespacedSecretAsync(this.Name, this.deviceNamespace); - config = Option.Maybe(backupSecret) - .FlatMap( - s => - { - if (s.Data != null && s.Data.TryGetValue(BackupData, out byte[] backupconfig)) - { - string backupJson = System.Text.Encoding.UTF8.GetString(s.Data[BackupData]); - DeploymentConfigInfo deploymentConfigInfo = this.serde.Deserialize(backupJson); - Events.ObtainedDeploymentFromBackup(this.Name); - return Option.Maybe(deploymentConfigInfo); - } - - return Option.None(); - }); - } - catch (Exception e) - { - Events.GetBackupFailed(e, this.Name); - } - - return config.GetOrElse(DeploymentConfigInfo.Empty); - } - - public async Task BackupDeploymentConfigAsync(DeploymentConfigInfo deploymentConfigInfo) - { - try - { - // backup the config info only if there isn't an error in it - if (!deploymentConfigInfo.Exception.HasValue) - { - byte[] json = System.Text.Encoding.UTF8.GetBytes(this.serde.Serialize(deploymentConfigInfo)); - - var secretMeta = new V1ObjectMeta( - name: this.Name, - namespaceProperty: this.deviceNamespace, - ownerReferences: this.moduleOwner.ToOwnerReferences()); - var secretData = new Dictionary { [BackupData] = json }; - var newSecret = new V1Secret("v1", secretData, type: Constants.K8sBackupSecretType, kind: "Secret", metadata: secretMeta); - - Option currentSecret; - try - { - currentSecret = Option.Maybe(await this.client.ReadNamespacedSecretAsync(this.Name, this.deviceNamespace)); - } - catch (HttpOperationException ex) when (!ex.IsFatal()) - { - currentSecret = Option.None(); - } - - var v1Secret = await currentSecret.Match( - async s => - { - if (s.Data != null && s.Data.TryGetValue(BackupData, out byte[] backupSecretData) && - backupSecretData.SequenceEqual(json)) - { - return s; - } - - return await this.client.ReplaceNamespacedSecretAsync( - newSecret, - this.Name, - this.deviceNamespace); - }, - async () => await this.client.CreateNamespacedSecretAsync(newSecret, this.deviceNamespace)); - if (v1Secret == null) - { - throw new InvalidBackupException("backup secret was not properly created"); - } - } - } - catch (Exception e) - { - Events.SetBackupFailed(e, this.Name); - } - } - - static class Events - { - const int IdStart = KubernetesEventIds.SecretBackup; - static readonly ILogger Log = Logger.Factory.CreateLogger(); - - enum EventIds - { - Created = IdStart, - SetBackupFailed, - GetBackupFailed - } - - public static void SetBackupFailed(Exception exception, string secretName) - { - Log.LogError((int)EventIds.SetBackupFailed, exception, $"Error backing up edge agent config to secret {secretName}"); - } - - public static void GetBackupFailed(Exception exception, string secretName) - { - Log.LogError((int)EventIds.GetBackupFailed, exception, $"Failed to read edge agent config from backup secret {secretName}"); - } - - public static void ObtainedDeploymentFromBackup(string secretName) - { - Log.LogInformation((int)EventIds.Created, $"Obtained edge agent config from backup config secret - {secretName}"); - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/IKubernetesEnvironmentOperator.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/IKubernetesEnvironmentOperator.cs deleted file mode 100644 index 0ffafd45fa3..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/IKubernetesEnvironmentOperator.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - public interface IKubernetesEnvironmentOperator : IKubernetesOperator - { - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/IKubernetesOperator.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/IKubernetesOperator.cs deleted file mode 100644 index 366da20b8dc..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/IKubernetesOperator.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - - public interface IKubernetesOperator : IDisposable - { - void Start(); - - void Stop(); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/IRuntimeInfoSource.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/IRuntimeInfoSource.cs deleted file mode 100644 index a5ad6518104..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/IRuntimeInfoSource.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using k8s.Models; - - public interface IRuntimeInfoSource - { - void CreateOrUpdateAddPodInfo(V1Pod pod); - - bool RemovePodInfo(V1Pod pod); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/InvalidBackupException.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/InvalidBackupException.cs deleted file mode 100644 index d133cd28f1c..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/InvalidBackupException.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - - [Serializable] - public class InvalidBackupException : Exception - { - public InvalidBackupException(string message) - : base(message) - { - } - - public InvalidBackupException(string message, Exception inner) - : base(message, inner) - { - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/InvalidIdentityException.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/InvalidIdentityException.cs deleted file mode 100644 index 3a0a823fe6f..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/InvalidIdentityException.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - - [Serializable] - public class InvalidIdentityException : Exception - { - public InvalidIdentityException(string message) - : base(message) - { - } - - public InvalidIdentityException(string message, Exception inner) - : base(message, inner) - { - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/InvalidKubernetesName.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/InvalidKubernetesName.cs deleted file mode 100644 index 343a0592502..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/InvalidKubernetesName.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - - [Serializable] - public class InvalidKubernetesNameException : Exception - { - public InvalidKubernetesNameException(string message) - : base(message) - { - } - - public InvalidKubernetesNameException(string message, Exception inner) - : base(message, inner) - { - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/InvalidModuleException.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/InvalidModuleException.cs deleted file mode 100644 index 26bc23b2faf..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/InvalidModuleException.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - - [Serializable] - public class InvalidModuleException : Exception - { - public InvalidModuleException(string message) - : base(message) - { - } - - public InvalidModuleException(string message, Exception inner) - : base(message, inner) - { - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubeUtils.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubeUtils.cs deleted file mode 100644 index e1420e59070..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubeUtils.cs +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using CoreConstants = Microsoft.Azure.Devices.Edge.Agent.Core.Constants; - - public static class KubeUtils - { - const string Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - const string Numeric = "0123456789"; - const string AllowedCharsLabelValues = "-._"; - const string AllowedCharsDns = "-"; - const string AllowedCharsGeneric = "-."; - const int MaxK8SValueLength = 253; - const int MaxDnsNameLength = 63; - const int MaxLabelValueLength = 63; - static readonly HashSet AlphaHashSet = new HashSet(Alphabet.ToCharArray()); - static readonly HashSet AlphaNumericHashSet = new HashSet((Alphabet + Numeric).ToCharArray()); - static readonly HashSet AllowedLabelsHashSet = new HashSet((Alphabet + Numeric + AllowedCharsLabelValues).ToCharArray()); - static readonly HashSet AllowedDnsHashSet = new HashSet((Alphabet + Numeric + AllowedCharsDns).ToCharArray()); - static readonly HashSet AllowedGenericHashSet = new HashSet((Alphabet + Numeric + AllowedCharsGeneric).ToCharArray()); - - static bool IsAlpha(char ch) => AlphaHashSet.Contains(ch); - - static bool IsAlphaNumeric(char ch) => AlphaNumericHashSet.Contains(ch); - - // Valid annotation keys have two segments: an optional prefix and name, separated by a slash (/). - // The name segment is required and must be 63 characters or less, beginning and ending with an - // alphanumeric character ([a-z0-9A-Z]) with dashes (-), underscores (_), dots (.), and alphanumerics between. - // The prefix is optional. If specified, the prefix must be a DNS subdomain - public static string SanitizeAnnotationKey(string key) - { - if (string.IsNullOrEmpty(key)) - { - throw new InvalidKubernetesNameException($"Key '{key}' is null or empty"); - } - - char[] annotationSplit = { '/' }; - string[] keySegments = key.Split(annotationSplit, 2); - string output; - if (keySegments.Count() == 2) - { - output = SanitizeDNSDomain(keySegments[0]) + "/" + SanitizeNameValue(keySegments[1]); - } - else - { - output = SanitizeNameValue(key); - } - - if (string.IsNullOrEmpty(output)) - { - throw new InvalidKubernetesNameException($"Key '{key}' as sanitized is empty"); - } - - return output; - } - - // Alphanumeric, '-' and '.' up to 253 characters. - public static string SanitizeK8sValue(string name) - { - if (name == null) - { - // Values sometimes may be null, and that's OK. - return name; - } - - name = name.ToLower(); - - var output = new StringBuilder(); - for (int i = 0; i < name.Length; i++) - { - if (AllowedGenericHashSet.Contains(name[i])) - { - output.Append(name[i]); - } - } - - if (output.Length > MaxK8SValueLength) - { - throw new InvalidKubernetesNameException($"Value '{name}' as sanitized exceeded maximum length {MaxK8SValueLength}"); - } - - return output.ToString(); - } - - // DNS label (as per RFC 1035) - public static string SanitizeDNSValue(string name) - { - // The name returned from here must conform to following rules (as per RFC 1035): - // - length must be <= 63 characters - // - must be all lower case alphanumeric characters or '-' - // - must start with an alphabet - // - must end with an alphanumeric character - if (string.IsNullOrEmpty(name)) - { - throw new InvalidKubernetesNameException($"DNS Name '{name}' is null or empty"); - } - - name = name.ToLower(); - - // get index of first character from the left that is an alphabet - int start = 0; - while (start < name.Length && !IsAlpha(name[start])) - { - start++; - } - - if (start == name.Length) - { - throw new InvalidKubernetesNameException($"DNS name '{name}' does not start with a valid character"); - } - - // get index of last character from right that's an alphanumeric - int end = Math.Max(start, name.Length - 1); - while (end > start && !IsAlphaNumeric(name[end])) - { - end--; - } - - // build a new string from start-end (inclusive) excluding characters - // that aren't alphanumeric or the symbol '-' - var output = new StringBuilder(); - for (int i = start; i <= end; i++) - { - if (AllowedDnsHashSet.Contains(name[i])) - { - output.Append(name[i]); - } - } - - if (output.Length > MaxDnsNameLength) - { - throw new InvalidKubernetesNameException($"DNS name '{name}' exceeded maximum length of {MaxDnsNameLength}"); - } - - if (output.Length == 0) - { - throw new InvalidKubernetesNameException($"DNS name '{name}' as sanitized is empty"); - } - - return output.ToString(); - } - - // DNS subdomains are DNS labels separated by '.', max 253 characters. - public static string SanitizeDNSDomain(string name) - { - if (string.IsNullOrEmpty(name)) - { - throw new InvalidKubernetesNameException($"DNS subdomain '{name}' is null or empty"); - } - - char[] nameSplit = { '.' }; - string[] dnsSegments = name.Split(nameSplit); - var output = new StringBuilder(); - bool firstSegment = true; - foreach (var segment in dnsSegments) - { - string sanitized = SanitizeDNSValue(segment); - - if (firstSegment) - { - output.Append(sanitized); - firstSegment = false; - } - else - { - output.Append("."); - output.Append(sanitized); - } - } - - if (output.Length > MaxK8SValueLength) - { - throw new InvalidKubernetesNameException($"DNS subdomain '{name}' as sanitized exceeded maximum length of {MaxK8SValueLength}"); - } - - return output.ToString(); - } - - private static string SanitizeNameValue(string name) - { - // The name returned from here must conform to following rules: - // - length must be <= 63 characters - // - must be all alphanumeric characters or ['-','.','_'] - // - must start with an alphabet - // - must end with an alphanumeric character - - // get index of first character from the left that is an alphabet - int start = 0; - while (start < name.Length && !IsAlphaNumeric(name[start])) - { - start++; - } - - if (start == name.Length) - { - throw new InvalidKubernetesNameException($"Name '{name}' does not start with a valid character"); - } - - // get index of last character from right that's an alphanumeric - int end = Math.Max(start, name.Length - 1); - while (end > start && !IsAlphaNumeric(name[end])) - { - end--; - } - - // build a new string from start-end (inclusive) excluding characters - // that aren't alphanumeric or the symbol '-' - var output = new StringBuilder(); - for (int i = start; i <= end; i++) - { - if (AllowedLabelsHashSet.Contains(name[i])) - { - output.Append(name[i]); - } - } - - if (output.Length > MaxLabelValueLength) - { - throw new InvalidKubernetesNameException($"Name '{name}' exceeded maximum length of {MaxLabelValueLength}"); - } - - if (output.Length == 0) - { - throw new InvalidKubernetesNameException($"Name '{name}' as sanitized is empty"); - } - - return output.ToString(); - } - - public static string SanitizeLabelValue(string name) - { - // The name returned from here must conform to following rules: - // - length must be <= 63 characters - // - must be all lower case alphanumeric characters or ['-','.','_'] - // - must start with an alphabet - // - must end with an alphanumeric character - if (string.IsNullOrEmpty(name)) - { - throw new InvalidKubernetesNameException($"Name '{name}' is null or empty"); - } - - return SanitizeNameValue(name.ToLower()); - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesApplicationSettings.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesApplicationSettings.cs deleted file mode 100644 index e3da433f8a4..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesApplicationSettings.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; - using Microsoft.Azure.Devices.Edge.Util; - - /// - /// Application settings, derived from "appsetting_k8s.json" and environment - /// variables, loaded from autofac. These setting are used to allow edgeAgent - /// to properly construct proxy container settings and for agent to build itself. - /// - public class KubernetesApplicationSettings - { - // Location of "appsetting_k8s.json" in container - public string AgentConfigPath { get; set; } - // Name of configmap containing "appsetting_k8s.json" - public string AgentConfigMapName { get; set; } - // Name of volume to use when mounting configmap, used in PodSpec - public string AgentConfigVolume { get; set; } - // Proxy Image - public string ProxyImage { get; set; } - // Name of volume to use when mounting proxy's configmap - public string ProxyConfigVolume { get; set; } - // Name of configmap containing "config.yaml" - public string ProxyConfigMapName { get; set; } - // Location of "config.yaml" in proxy container - public string ProxyConfigPath { get; set; } - // Location of trustbundle in proxy container - public string ProxyTrustBundlePath { get; set; } - // Name of volume to use when mounting trustbundle - public string ProxyTrustBundleVolume { get; set; } - // Map of configmap containing trustbundle - public string ProxyTrustBundleConfigMapName { get; set; } - // Resource requirements for proxy. - public ResourceSettings ProxyResourceRequests { get; set; } - // Resource requirements for agent. - public ResourceSettings AgentResourceRequests { get; set; } - - public Option GetProxyResourceRequirements() => - Option.Maybe(this.ProxyResourceRequests).Map(rr => rr.ToResourceRequirements()); - public Option GetAgentResourceRequirements() => - Option.Maybe(this.AgentResourceRequests).Map(rr => rr.ToResourceRequirements()); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesCommandFactory.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesCommandFactory.cs deleted file mode 100644 index 7581309737a..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesCommandFactory.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Core.Commands; - - public class KubernetesCommandFactory : ICommandFactory - { - public KubernetesCommandFactory() - { - } - - public Task UpdateEdgeAgentAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo) => Task.FromResult(NullCommand.Instance as ICommand); - - public Task CreateAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo) => - Task.FromResult((ICommand)NullCommand.Instance); - - public Task UpdateAsync(IModule current, IModuleWithIdentity next, IRuntimeInfo runtimeInfo) => - Task.FromResult((ICommand)NullCommand.Instance); - - public Task RemoveAsync(IModule module) => - Task.FromResult((ICommand)NullCommand.Instance); - - public Task StartAsync(IModule module) => - Task.FromResult((ICommand)NullCommand.Instance); - - public Task StopAsync(IModule module) => - Task.FromResult((ICommand)NullCommand.Instance); - - public Task RestartAsync(IModule module) => - Task.FromResult((ICommand)NullCommand.Instance); - - public Task WrapAsync(ICommand command) => Task.FromResult(command); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesConfig.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesConfig.cs deleted file mode 100644 index 6b17b1970f6..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesConfig.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Json; - using Newtonsoft.Json; - - public class KubernetesConfig - { - public KubernetesConfig(string image, CreatePodParameters createOptions, Option authConfig) - { - this.Image = Preconditions.CheckNonWhiteSpace(image, nameof(image)).Trim(); - this.CreateOptions = Preconditions.CheckNotNull(createOptions, nameof(createOptions)); - this.AuthConfig = authConfig; - } - - [JsonConstructor] - KubernetesConfig(string image, CreatePodParameters createOptions, AuthConfig auth) - : this(image, createOptions, Option.Maybe(auth)) - { - } - - [JsonProperty(Required = Required.Always, PropertyName = "image")] - public string Image { get; } - - [JsonProperty(Required = Required.AllowNull, PropertyName = "createOptions")] - public CreatePodParameters CreateOptions { get; } - - [JsonProperty(Required = Required.AllowNull, PropertyName = "auth")] - [JsonConverter(typeof(OptionConverter))] - public Option AuthConfig { get; } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesEnvironment.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesEnvironment.cs deleted file mode 100644 index 5fdd9119d47..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesEnvironment.cs +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Agent.Docker.Models; - using Microsoft.Azure.Devices.Edge.Storage; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Extensions.Logging; - - /// - /// This implementation gets the module runtime information from KubernetesRuntimeInfoProvider and - /// the configuration information from the deploymentConfig. - /// TODO: This could be made generic (not docker specific) and moved to Core. - /// - public class KubernetesEnvironment : IEnvironment - { - readonly IRuntimeInfoProvider moduleStatusProvider; - readonly IEntityStore moduleStateStore; - readonly string operatingSystemType; - readonly string architecture; - readonly string version; - readonly DeploymentConfig deploymentConfig; - - public KubernetesEnvironment( - IRuntimeInfoProvider moduleStatusProvider, - DeploymentConfig deploymentConfig, - IEntityStore moduleStateStore, - string operatingSystemType, - string architecture, - string version) - { - this.moduleStatusProvider = moduleStatusProvider; - this.deploymentConfig = deploymentConfig; - this.moduleStateStore = moduleStateStore; - this.operatingSystemType = operatingSystemType; - this.architecture = architecture; - this.version = version; - } - - public async Task GetModulesAsync(CancellationToken token) - { - IEnumerable moduleStatuses = await this.moduleStatusProvider.GetModules(token); - var modules = new List(); - ModuleSet moduleSet = this.deploymentConfig.GetModuleSet(); - - foreach (ModuleRuntimeInfo moduleRuntimeInfo in moduleStatuses) - { - if (moduleRuntimeInfo.Type != "docker" || !(moduleRuntimeInfo is ModuleRuntimeInfo dockerRuntimeInfo)) - { - Events.InvalidModuleType(moduleRuntimeInfo); - continue; - } - - if (!moduleSet.Modules.TryGetValue(dockerRuntimeInfo.Name, out IModule configModule) || !(configModule is DockerModule dockerModule)) - { - dockerModule = new DockerModule(dockerRuntimeInfo.Name, string.Empty, ModuleStatus.Unknown, Core.RestartPolicy.Unknown, new DockerConfig(Constants.UnknownImage, new CreateContainerParameters(), Option.None()), ImagePullPolicy.OnCreate, Core.Constants.HighestPriority, new ConfigurationInfo(), null); - } - - Option moduleStateOption = await this.moduleStateStore.Get(moduleRuntimeInfo.Name); - ModuleState moduleState = moduleStateOption.GetOrElse(new ModuleState(0, moduleRuntimeInfo.ExitTime.GetOrElse(DateTime.MinValue))); - - string image = !string.IsNullOrWhiteSpace(dockerRuntimeInfo.Config.Image) ? dockerRuntimeInfo.Config.Image : dockerModule.Config.Image; - var dockerReportedConfig = new DockerReportedConfig(image, dockerModule.Config.CreateOptions, dockerRuntimeInfo.Config.ImageHash, dockerModule.Config.NotaryContentTrust); - IModule module; - switch (moduleRuntimeInfo.Name) - { - case Core.Constants.EdgeHubModuleName: - module = new EdgeHubDockerRuntimeModule( - dockerModule.DesiredStatus, - dockerModule.RestartPolicy, - dockerReportedConfig, - (int)dockerRuntimeInfo.ExitCode, - moduleRuntimeInfo.Description, - moduleRuntimeInfo.StartTime.GetOrElse(DateTime.MinValue), - moduleRuntimeInfo.ExitTime.GetOrElse(DateTime.MinValue), - moduleState.RestartCount, - moduleState.LastRestartTimeUtc, - moduleRuntimeInfo.ModuleStatus, - dockerModule.ImagePullPolicy, - dockerModule.StartupOrder, - dockerModule.ConfigurationInfo, - dockerModule.Env); - break; - - case Core.Constants.EdgeAgentModuleName: - module = new EdgeAgentDockerRuntimeModule( - dockerReportedConfig, - moduleRuntimeInfo.ModuleStatus, - (int)dockerRuntimeInfo.ExitCode, - moduleRuntimeInfo.Description, - moduleRuntimeInfo.StartTime.GetOrElse(DateTime.MinValue), - moduleRuntimeInfo.ExitTime.GetOrElse(DateTime.MinValue), - dockerModule.ImagePullPolicy, - dockerModule.ConfigurationInfo, - dockerModule.Env); - break; - - default: - module = new DockerRuntimeModule( - moduleRuntimeInfo.Name, - dockerModule.Version, - dockerModule.DesiredStatus, - dockerModule.RestartPolicy, - dockerReportedConfig, - (int)dockerRuntimeInfo.ExitCode, - moduleRuntimeInfo.Description, - moduleRuntimeInfo.StartTime.GetOrElse(DateTime.MinValue), - moduleRuntimeInfo.ExitTime.GetOrElse(DateTime.MinValue), - moduleState.RestartCount, - moduleState.LastRestartTimeUtc, - moduleRuntimeInfo.ModuleStatus, - dockerModule.ImagePullPolicy, - dockerModule.StartupOrder, - dockerModule.ConfigurationInfo, - dockerModule.Env); - break; - } - - modules.Add(module); - } - - return ModuleSet.Create(modules.ToArray()); - } - - public Task GetRuntimeInfoAsync() - { - IRuntimeInfo runtimeInfo = this.deploymentConfig.Runtime; - if (runtimeInfo?.Type == "docker") - { - var platform = new DockerPlatformInfo(this.operatingSystemType, this.architecture, this.version); - DockerRuntimeConfig config = (runtimeInfo as DockerRuntimeInfo)?.Config; - runtimeInfo = new DockerReportedRuntimeInfo(runtimeInfo.Type, config, platform); - } - else if (runtimeInfo == null || runtimeInfo is UnknownRuntimeInfo) - { - var platform = new DockerPlatformInfo(this.operatingSystemType, this.architecture, this.version); - runtimeInfo = new DockerReportedUnknownRuntimeInfo(platform); - } - - return Task.FromResult(runtimeInfo); - } - - static class Events - { - const int IdStart = AgentEventIds.DockerEnvironment; - static readonly ILogger Log = Logger.Factory.CreateLogger(); - - enum EventIds - { - InvalidModuleType = IdStart - } - - public static void InvalidModuleType(ModuleRuntimeInfo moduleRuntimeInfo) - { - Log.LogWarning((int)EventIds.InvalidModuleType, $"Module {moduleRuntimeInfo.Name} has an invalid module type '{moduleRuntimeInfo.Type}'. Expected type 'docker'"); - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesEnvironmentOperator.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesEnvironmentOperator.cs deleted file mode 100644 index 5be86affc35..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesEnvironmentOperator.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - using System.Threading.Tasks; - using k8s; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Extensions.Logging; - using Microsoft.Rest; - - public class KubernetesEnvironmentOperator : IKubernetesEnvironmentOperator - { - readonly IRuntimeInfoSource moduleStatusSource; - readonly IKubernetes client; - readonly string deviceNamespace; - Option> podWatch; - - public KubernetesEnvironmentOperator( - string deviceNamespace, - IRuntimeInfoSource moduleStatusSource, - IKubernetes client) - { - this.deviceNamespace = deviceNamespace; - this.moduleStatusSource = moduleStatusSource; - this.client = Preconditions.CheckNotNull(client, nameof(client)); - this.podWatch = Option.None>(); - } - - public void Start() => this.StartListPods(); - - public void Stop() - { - // TODO do we need lock here? - this.podWatch.ForEach(watch => watch.Dispose()); - } - - public void Dispose() => this.Stop(); - - void StartListPods() => - this.client.ListNamespacedPodWithHttpMessagesAsync(this.deviceNamespace, watch: true) - .ContinueWith(this.OnListPodsCompleted); - - async Task OnListPodsCompleted(Task> task) - { - HttpOperationResponse podListResp = await task; - - this.podWatch = Option.Some( - podListResp.Watch( - onEvent: (type, item) => - { - try - { - this.HandlePodChangedAsync(type, item); - } - catch (Exception ex) when (!ex.IsFatal()) - { - Events.PodWatchFailed(ex); - } - }, - onClosed: () => - { - Events.PodWatchClosed(); - - // get rid of the current pod watch object since we got closed - this.podWatch.ForEach(watch => watch.Dispose()); - this.podWatch = Option.None>(); - - // kick off a new watch - this.StartListPods(); - }, - onError: (ex) => - { - Events.PodWatchFailed(ex); - throw ex; - })); - } - - void HandlePodChangedAsync(WatchEventType type, V1Pod pod) - { - // if the pod doesn't have the module label set then we are not interested in it - if (!pod.Metadata.Labels.ContainsKey(Constants.K8sEdgeModuleLabel)) - { - return; - } - - Events.PodStatus(type, pod); - switch (type) - { - case WatchEventType.Added: - case WatchEventType.Modified: - case WatchEventType.Error: - this.moduleStatusSource.CreateOrUpdateAddPodInfo(pod); - break; - - case WatchEventType.Deleted: - if (!this.moduleStatusSource.RemovePodInfo(pod)) - { - Events.PodStatusRemoveError(pod); - } - - break; - - default: - throw new ArgumentOutOfRangeException(nameof(type), type, null); - } - } - } - - static class Events - { - const int IdStart = KubernetesEventIds.KubernetesEnvironmentOperator; - static readonly ILogger Log = Logger.Factory.CreateLogger(); - - enum EventIds - { - WatchFailed = IdStart, - PodStatus, - PodStatusRemoveError, - WatchClosed - } - - public static void PodWatchFailed(Exception ex) - { - Log.LogError((int)EventIds.WatchFailed, ex, "Exception caught in Pod Watch task."); - } - - public static void PodStatus(WatchEventType type, V1Pod pod) - { - Log.LogDebug((int)EventIds.PodStatus, $"Pod '{pod.Metadata.Name}', status'{type}'"); - } - - public static void PodStatusRemoveError(V1Pod pod) - { - Log.LogWarning((int)EventIds.PodStatusRemoveError, $"Notified of pod {pod.Metadata.Name} deleted, but not removed from our pod list"); - } - - public static void PodWatchClosed() - { - Log.LogInformation((int)EventIds.WatchClosed, $"K8s closed the pod watch. Attempting to reopen watch."); - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesEnvironmentProvider.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesEnvironmentProvider.cs deleted file mode 100644 index 2baa2cddbb0..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesEnvironmentProvider.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Storage; - using Microsoft.Azure.Devices.Edge.Util; - - public class KubernetesEnvironmentProvider : IEnvironmentProvider - { - readonly IRuntimeInfoProvider moduleStatusProvider; - readonly IEntityStore store; - readonly string operatingSystemType; - readonly string architecture; - readonly string version; - - KubernetesEnvironmentProvider( - IRuntimeInfoProvider runtimeInfoProvider, - IEntityStore store, - string operatingSystemType, - string architecture, - string version) - { - this.moduleStatusProvider = runtimeInfoProvider; - this.store = Preconditions.CheckNotNull(store, nameof(store)); - this.operatingSystemType = operatingSystemType; - this.architecture = architecture; - this.version = version; - } - - public static async Task CreateAsync( - IRuntimeInfoProvider runtimeInfoProvider, - IEntityStore store, - CancellationToken token) - { - SystemInfo systemInfo = await Preconditions.CheckNotNull(runtimeInfoProvider, nameof(runtimeInfoProvider)).GetSystemInfo(token); - return new KubernetesEnvironmentProvider( - runtimeInfoProvider, - store, - systemInfo.OperatingSystemType, - systemInfo.Architecture, - systemInfo.Version); - } - - public IEnvironment Create(DeploymentConfig deploymentConfig) => - new KubernetesEnvironment( - this.moduleStatusProvider, - deploymentConfig, - this.store, - this.operatingSystemType, - this.architecture, - this.version); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesEventIds.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesEventIds.cs deleted file mode 100644 index 19fe22fc68a..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesEventIds.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - public struct KubernetesEventIds - { - public const int KubernetesPlanner = EventIdStart + 100; - public const int KubernetesCommand = EventIdStart + 200; - public const int EdgeDeploymentOperator = EventIdStart + 300; - public const int EdgeDeploymentController = EventIdStart + 400; - public const int KubernetesEnvironmentOperator = EventIdStart + 500; - public const int KubernetesExperimentalCreateOptions = EventIdStart + 600; - public const int KubernetesModelValidation = EventIdStart + 700; - public const int KubernetesProxyHealthProbe = EventIdStart + 800; - public const int SecretBackup = EventIdStart + 900; - const int EventIdStart = 200000; - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesExperimentalCreatePodParameters.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesExperimentalCreatePodParameters.cs deleted file mode 100644 index c0b0655db61..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesExperimentalCreatePodParameters.cs +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Extensions.Logging; - using Newtonsoft.Json.Linq; - - public class KubernetesExperimentalCreatePodParameters - { - public Option> NodeSelector { get; } - - public Option Resources { get; } - - public Option> Volumes { get; } - - public Option SecurityContext { get; } - - public Option ServiceOptions { get; } - - public Option DeploymentStrategy { get; } - - KubernetesExperimentalCreatePodParameters( - Option> nodeSelector, - Option resources, - Option> volumes, - Option securityContext, - Option serviceOptions, - Option deploymentStrategy) - { - this.NodeSelector = nodeSelector; - this.Resources = resources; - this.Volumes = volumes; - this.SecurityContext = securityContext; - this.ServiceOptions = serviceOptions; - this.DeploymentStrategy = deploymentStrategy; - } - - static class ExperimentalParameterNames - { - public const string Section = "k8s-experimental"; - public const string NodeSelector = "NodeSelector"; - public const string Resources = "Resources"; - public const string Volumes = "Volumes"; - public const string SecurityContext = "SecurityContext"; - public const string ServiceOptions = "ServiceOptions"; - public const string DeploymentStrategy = "Strategy"; - } - - public static Option Parse(IDictionary other) - => Option.Maybe(other).FlatMap(options => options.Get(ExperimentalParameterNames.Section).FlatMap(ParseParameters)); - - static Option ParseParameters(JToken experimental) - => Option.Maybe(experimental as JObject) - .Map(ParseParameters) - .Else( - () => - { - Events.UnableToParseExperimentalOptions(experimental); - return Option.None(); - }); - - static KubernetesExperimentalCreatePodParameters ParseParameters(JObject experimental) - { - Dictionary options = PrepareSupportedOptionsStore(experimental); - - Option> nodeSelector = options.Get(ExperimentalParameterNames.NodeSelector) - .FlatMap(option => Option.Maybe(option.ToObject>())); - - var resources = options.Get(ExperimentalParameterNames.Resources) - .FlatMap(option => Option.Maybe(option.ToObject())); - - var volumes = options.Get(ExperimentalParameterNames.Volumes) - .FlatMap(option => Option.Maybe(option.ToObject>())); - - var securityContext = options.Get(ExperimentalParameterNames.SecurityContext) - .FlatMap(option => Option.Maybe(option.ToObject())); - - var serviceOptions = options.Get(ExperimentalParameterNames.ServiceOptions) - .FlatMap(option => Option.Maybe(option.ToObject())); - - var deploymentStrategy = options.Get(ExperimentalParameterNames.DeploymentStrategy) - .FlatMap(option => Option.Maybe(option.ToObject())); - - return new KubernetesExperimentalCreatePodParameters(nodeSelector, resources, volumes, securityContext, serviceOptions, deploymentStrategy); - } - - static Dictionary PrepareSupportedOptionsStore(JObject experimental) - { - var options = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var property in experimental.Properties()) - { - if (!KnownExperimentalOptions.Contains(property.Name)) - { - Events.UnknownExperimentalOption(property.Name); - continue; - } - - options[property.Name] = property.Value; - } - - return options; - } - - static readonly HashSet KnownExperimentalOptions = new HashSet(StringComparer.OrdinalIgnoreCase) - { - ExperimentalParameterNames.NodeSelector, - ExperimentalParameterNames.Resources, - ExperimentalParameterNames.Volumes, - ExperimentalParameterNames.SecurityContext, - ExperimentalParameterNames.ServiceOptions, - ExperimentalParameterNames.DeploymentStrategy - }; - - static class Events - { - const int IdStart = KubernetesEventIds.KubernetesExperimentalCreateOptions; - static readonly ILogger Log = Logger.Factory.CreateLogger(); - - enum EventIds - { - UnknownExperimentalOption = IdStart - } - - public static void UnknownExperimentalOption(string name) - { - Log.LogWarning((int)EventIds.UnknownExperimentalOption, $"Unknown Kubernetes CreateOption {name}."); - } - - public static void UnableToParseExperimentalOptions(JToken experimental) - { - Log.LogWarning((int)EventIds.UnknownExperimentalOption, $"Unable to parse Kubernetes CreateOptions. Expected JObject but: {experimental.Type} found: {experimental}"); - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesModule.cs deleted file mode 100644 index 24dd45b010c..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesModule.cs +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System.Collections.Generic; - using System.Collections.Immutable; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker.Models; - using Microsoft.Azure.Devices.Edge.Util; - using Newtonsoft.Json; - - public class KubernetesModule : IModule - { - static readonly DictionaryComparer EnvDictionaryComparer = new DictionaryComparer(); - - static readonly CombinedKubernetesConfigEqualityComparer ConfigComparer = new CombinedKubernetesConfigEqualityComparer(); - - public KubernetesModule(IModule module, KubernetesConfig config, KubernetesModuleOwner owner) - { - this.Name = module.Name; - this.Version = module.Version; - this.Type = module.Type; - this.DesiredStatus = module.DesiredStatus; - this.RestartPolicy = module.RestartPolicy; - this.ConfigurationInfo = module.ConfigurationInfo ?? new ConfigurationInfo(string.Empty); - this.Env = module.Env?.ToImmutableDictionary() ?? ImmutableDictionary.Empty; - this.ImagePullPolicy = module.ImagePullPolicy; - this.StartupOrder = Core.Constants.DefaultStartupOrder; - this.Config = config; - this.Owner = owner; - } - - [JsonConstructor] - public KubernetesModule( - string name, - string version, - string type, - ModuleStatus status, - Core.RestartPolicy restartPolicy, - ConfigurationInfo configurationInfo, - IDictionary env, - KubernetesConfig settings, - ImagePullPolicy imagePullPolicy, - KubernetesModuleOwner owner) - { - this.Name = name; - this.Version = version; - this.Type = type; - this.DesiredStatus = status; - this.RestartPolicy = restartPolicy; - this.ConfigurationInfo = configurationInfo ?? new ConfigurationInfo(string.Empty); - this.Env = env?.ToImmutableDictionary() ?? ImmutableDictionary.Empty; - this.Config = settings; - this.ImagePullPolicy = imagePullPolicy; - this.StartupOrder = Core.Constants.DefaultStartupOrder; - this.Owner = owner; - } - - [JsonProperty(PropertyName = "name")] - public string Name { get; set; } - - [JsonProperty(PropertyName = "version")] - public string Version { get; } - - [JsonProperty(PropertyName = "type")] - public string Type { get; } - - [JsonProperty(PropertyName = "status")] - public ModuleStatus DesiredStatus { get; } - - [JsonProperty(PropertyName = "restartPolicy")] - public Core.RestartPolicy RestartPolicy { get; } - - [JsonProperty(PropertyName = "imagePullPolicy")] - public ImagePullPolicy ImagePullPolicy { get; } - - [JsonIgnore] - public uint StartupOrder { get; } - - [JsonIgnore] - public ConfigurationInfo ConfigurationInfo { get; } - - [JsonProperty(PropertyName = "env")] - public IDictionary Env { get; } - - [JsonProperty(PropertyName = "settings")] - [JsonConverter(typeof(ObjectToStringConverter))] - public KubernetesConfig Config { get; } - - [JsonProperty(PropertyName = "owner")] - public KubernetesModuleOwner Owner { get; } - - public virtual bool Equals(IModule other) => this.Equals(other as KubernetesModule); - - public bool Equals(IModule other) => this.Equals(other as KubernetesModule); - - public static string PvcName(KubernetesModule module, Mount mount) - { - return KubeUtils.SanitizeK8sValue($"{module.Name}-{mount.Source}"); - } - - public bool Equals(KubernetesModule other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return string.Equals(this.Name, other.Name) && - string.Equals(this.Version, other.Version) && - string.Equals(this.Type, other.Type) && - this.DesiredStatus == other.DesiredStatus && - ConfigComparer.Equals(this.Config, other.Config) && - this.RestartPolicy == other.RestartPolicy && - this.ImagePullPolicy == other.ImagePullPolicy && - this.StartupOrder == other.StartupOrder && - EnvDictionaryComparer.Equals(this.Env, other.Env); - } - - public override int GetHashCode() - { - unchecked - { - // We are ignoring this here because, we only change the name of the module on Creation. This - // is needed because the name is not part of the body of Json equivalent to IModule, it is on the key of the json. - // ReSharper disable NonReadonlyMemberInGetHashCode - int hashCode = this.Name != null ? this.Name.GetHashCode() : 0; - // ReSharper restore NonReadonlyMemberInGetHashCode - hashCode = (hashCode * 397) ^ (this.Version != null ? this.Version.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (this.Type != null ? this.Type.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (int)this.DesiredStatus; - hashCode = (hashCode * 397) ^ (this.Config != null ? this.Config.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ this.RestartPolicy.GetHashCode(); - hashCode = (hashCode * 397) ^ this.ImagePullPolicy.GetHashCode(); - hashCode = (hashCode * 397) ^ this.StartupOrder.GetHashCode(); - hashCode = (hashCode * 397) ^ EnvDictionaryComparer.GetHashCode(this.Env); - return hashCode; - } - } - - public bool IsOnlyModuleStatusChanged(IModule other) - { - return other is KubernetesModule && - string.Equals(this.Name, other.Name) && - string.Equals(this.Version, other.Version) && - string.Equals(this.Type, other.Type) && - this.DesiredStatus != other.DesiredStatus && - ConfigComparer.Equals(this.Config, (other as KubernetesModule).Config) && - this.RestartPolicy == other.RestartPolicy && - this.ImagePullPolicy == other.ImagePullPolicy && - this.StartupOrder == other.StartupOrder && - EnvDictionaryComparer.Equals(this.Env, other.Env); - } - - internal class CombinedKubernetesConfigEqualityComparer : IEqualityComparer - { - static readonly AuthConfigEqualityComparer AuthConfigComparer = new AuthConfigEqualityComparer(); - - public bool Equals(KubernetesConfig a, KubernetesConfig b) - { - if ((ReferenceEquals(null, a) && !ReferenceEquals(null, b)) || - (!ReferenceEquals(null, a) && ReferenceEquals(null, b))) - { - return false; - } - - if (ReferenceEquals(a, b)) - { - return true; - } - - string thisOptions = JsonConvert.SerializeObject(a.CreateOptions); - string otherOptions = JsonConvert.SerializeObject(b.CreateOptions); - - return string.Equals(a.Image, b.Image) - && AuthConfigComparer.Equals(a.AuthConfig, b.AuthConfig) - && string.Equals(thisOptions, otherOptions); - } - - public int GetHashCode(KubernetesConfig obj) => obj.GetHashCode(); - - internal class AuthConfigEqualityComparer : IEqualityComparer> - { - public bool Equals(Option a, Option b) - { - if (!a.HasValue && !b.HasValue) - { - return true; - } - - if (a.HasValue && b.HasValue) - { - var authConfig = a.OrDefault(); - var other = b.OrDefault(); - - return authConfig.Name == other.Name; - } - - return false; - } - - public int GetHashCode(Option obj) => obj.GetHashCode(); - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesModuleIdentityLifecycleManager.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesModuleIdentityLifecycleManager.cs deleted file mode 100644 index c8c5830605e..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesModuleIdentityLifecycleManager.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - using System.Collections.Immutable; - using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Edgelet; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Extensions.Logging; - - public class KubernetesModuleIdentityLifecycleManager : ModuleIdentityLifecycleManager - { - protected override bool ShouldAlwaysReturnIdentities => true; - - public KubernetesModuleIdentityLifecycleManager(IIdentityManager identityManager, ModuleIdentityProviderServiceBuilder identityProviderServiceBuilder, Uri workloadUri) - : base(identityManager, identityProviderServiceBuilder, workloadUri) - { - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesModuleOwner.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesModuleOwner.cs deleted file mode 100644 index a5b98660864..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesModuleOwner.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using Microsoft.Azure.Devices.Edge.Util; - using Newtonsoft.Json; - - public class KubernetesModuleOwner - { - [JsonConstructor] - public KubernetesModuleOwner(string apiVersion, string kind, string name, string uid) - { - this.ApiVersion = Preconditions.CheckNonWhiteSpace(apiVersion, nameof(apiVersion)); - this.Kind = Preconditions.CheckNonWhiteSpace(kind, nameof(kind)); - this.Name = Preconditions.CheckNonWhiteSpace(name, nameof(name)); - this.Uid = Preconditions.CheckNonWhiteSpace(uid, nameof(uid)); - } - - [JsonProperty(PropertyName = "apiVersion")] - public string ApiVersion { get; } - - [JsonProperty(PropertyName = "kind")] - public string Kind { get; } - - [JsonProperty(PropertyName = "name")] - public string Name { get; } - - [JsonProperty(PropertyName = "uid")] - public string Uid { get; } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesModuleVolumeSpec.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesModuleVolumeSpec.cs deleted file mode 100644 index fa6d8d83ca4..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesModuleVolumeSpec.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Json; - using Newtonsoft.Json; - - public class KubernetesModuleVolumeSpec - { - public KubernetesModuleVolumeSpec(V1Volume volume, IReadOnlyList volumeMounts) - { - this.Volume = Option.Maybe(volume); - this.VolumeMounts = Option.Maybe(volumeMounts); - } - - [JsonProperty("Volume", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter))] - public Option Volume { get; } - - [JsonProperty("VolumeMounts", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter>))] - public Option> VolumeMounts { get; } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesResponseException.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesResponseException.cs deleted file mode 100644 index e2853c5fbd6..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesResponseException.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - - [Serializable] - public class KuberenetesResponseException : Exception - { - public KuberenetesResponseException(string message) - : base(message) - { - } - - public KuberenetesResponseException(string message, Exception inner) - : base(message, inner) - { - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesRuntimeInfoProvider.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesRuntimeInfoProvider.cs deleted file mode 100644 index b44b354e272..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/KubernetesRuntimeInfoProvider.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.IO; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using k8s; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Edgelet; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util; - using AgentDocker = Microsoft.Azure.Devices.Edge.Agent.Docker; - public class KubernetesRuntimeInfoProvider : IRuntimeInfoProvider, IRuntimeInfoSource - { - readonly string deviceNamespace; - readonly IKubernetes client; - readonly ConcurrentDictionary>> moduleRuntimeInfo; - readonly IModuleManager moduleManager; - - public KubernetesRuntimeInfoProvider(string deviceNamespace, IKubernetes client, IModuleManager moduleManager) - { - this.deviceNamespace = Preconditions.CheckNonWhiteSpace(deviceNamespace, nameof(deviceNamespace)); - this.client = Preconditions.CheckNotNull(client, nameof(client)); - this.moduleManager = Preconditions.CheckNotNull(moduleManager, nameof(moduleManager)); - this.moduleRuntimeInfo = new ConcurrentDictionary>>(); - } - - public void CreateOrUpdateAddPodInfo(V1Pod pod) - { - string moduleName = pod.Metadata.Labels[Constants.K8sEdgeModuleLabel]; - var newPair = new KeyValuePair(pod.Metadata.Name, pod.ConvertToRuntime(moduleName)); - - this.moduleRuntimeInfo.AddOrUpdate( - moduleName, - ImmutableList.Create(newPair), - (_, existing) => - { - return existing.FirstOption(pair => pair.Key == pod.Metadata.Name) - .Map(pair => existing.Replace(pair, newPair)) - .GetOrElse(() => existing.Add(newPair)); - }); - } - - public bool RemovePodInfo(V1Pod pod) - { - var moduleName = pod.Metadata.Labels[Constants.K8sEdgeModuleLabel]; - if (this.moduleRuntimeInfo.TryGetValue(moduleName, out var existing)) - { - return existing - .FirstOption(pair => pair.Key == pod.Metadata.Name) - .Map( - pair => - { - ImmutableList> list = existing.Remove(pair); - return !list.IsEmpty - ? this.moduleRuntimeInfo.TryUpdate(moduleName, list, existing) - : this.moduleRuntimeInfo.Remove(moduleName, out _); - }) - .OrDefault(); - } - - return false; - } - - public async Task> GetModules(CancellationToken cancellationToken) - { - IEnumerable moduleRuntimeInfoList = this.moduleRuntimeInfo.Values - .Select(list => list.Last().Value) - .ToList(); - return await Task.FromResult(moduleRuntimeInfoList); - } - - public Task GetModuleLogs(string module, bool follow, Option tail, Option since, Option until, Option includeTimestamp, CancellationToken cancellationToken) - { - return Task.FromResult(Stream.Null); - } - - public Task GetSystemInfo(CancellationToken cancellationToken) => this.moduleManager.GetSystemInfoAsync(cancellationToken); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.csproj b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.csproj deleted file mode 100644 index a8539f2a38f..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - True - Debug;Release;CheckInBuild - true - - - - - - - - - - - - - - - - akka - - - - - - - - - ..\..\..\stylecop.ruleset - - - diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ModuleIdentityExtensions.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ModuleIdentityExtensions.cs deleted file mode 100644 index a1f4f055162..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ModuleIdentityExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using Microsoft.Azure.Devices.Edge.Agent.Core; - - public static class ModuleIdentityExtensions - { - public static string DeploymentName(this IModuleIdentity identity) => KubeUtils.SanitizeK8sValue(identity.ModuleId); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ObjectToStringConverter.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ObjectToStringConverter.cs deleted file mode 100644 index c9d1a25d5e9..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ObjectToStringConverter.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - using System.IO; - using Newtonsoft.Json; - - public class ObjectToStringConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer) - { - using (var text = new StringWriter()) - { - serializer.Serialize(text, value); - writer.WriteValue(text.ToString()); - } - } - - public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer) - { - using (var text = new StringReader(reader.Value.ToString())) - { - return (T)serializer.Deserialize(text, typeof(T)); - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/OverrideJsonIgnoreOfBaseClassContractResolver.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/OverrideJsonIgnoreOfBaseClassContractResolver.cs deleted file mode 100644 index ba1d1e521d5..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/OverrideJsonIgnoreOfBaseClassContractResolver.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - using System.Collections.Generic; - using System.Reflection; - using Newtonsoft.Json; - using Newtonsoft.Json.Serialization; - - // TODO add unit tests - public class OverrideJsonIgnoreOfBaseClassContractResolver : CamelCasePropertyNamesContractResolver - { - public OverrideJsonIgnoreOfBaseClassContractResolver(Dictionary names) - { - this.names = new Dictionary>(); - - foreach (var (type, properties) in names) - { - if (!this.names.TryGetValue(type, out var existing)) - { - existing = new HashSet(StringComparer.OrdinalIgnoreCase); - this.names.Add(type, existing); - } - - foreach (var property in properties) - { - existing.Add(property); - } - } - } - - readonly Dictionary> names; - - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - var property = base.CreateProperty(member, memberSerialization); - - if (this.names.TryGetValue(property.DeclaringType, out var properties) && properties.Contains(property.PropertyName)) - { - property.Ignored = false; - property.ShouldSerialize = propInstance => true; - } - - return property; - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ProxyReadinessProbe.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ProxyReadinessProbe.cs deleted file mode 100644 index 2370d4eb751..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ProxyReadinessProbe.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - using System.Net; - using System.Net.Http; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Extensions.Logging; - - public class ProxyReadinessProbe - { - readonly string apiVersion; - - readonly HttpClient client; - - public ProxyReadinessProbe(Uri url, string apiVersion) - { - this.apiVersion = apiVersion; - this.client = new HttpClient { BaseAddress = url }; - } - - async Task CheckAsync(CancellationToken token) - { - try - { - HttpResponseMessage response = await this.client.GetAsync($"/systeminfo?api-version={this.apiVersion}", token); - return response.StatusCode == HttpStatusCode.OK ? ProxyReadiness.Ready : ProxyReadiness.Failed; - } - catch - { - return ProxyReadiness.NotReady; - } - } - - public async Task WaitUntilProxyIsReady(CancellationToken token) - { - while (true) - { - if (token.IsCancellationRequested) - { - throw new ProxyReadinessProbeException("All proxy readiness attempts exhausted."); - } - - CancellationTokenSource tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - ProxyReadiness readiness = await this.CheckAsync(tokenSource.Token); - Events.CheckHealth(readiness); - - if (readiness == ProxyReadiness.Ready) - { - break; - } - } - } - - enum ProxyReadiness - { - Ready, - NotReady, - Failed - } - - static class Events - { - const int IdStart = KubernetesEventIds.KubernetesProxyHealthProbe; - static readonly ILogger Log = Logger.Factory.CreateLogger(); - - enum EventIds - { - CheckHealth = IdStart, - } - - internal static void CheckHealth(ProxyReadiness readiness) - => Log.LogDebug((int)EventIds.CheckHealth, $"Proxy container readiness state: {readiness}"); - } - } - - public class ProxyReadinessProbeException : Exception - { - public ProxyReadinessProbeException(string message) - : base(message) - { - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ResourceName.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ResourceName.cs deleted file mode 100644 index 60ac69c15b9..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ResourceName.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System; - using Microsoft.Azure.Devices.Edge.Util; - - public class ResourceName - { - public readonly string DeviceId; - public readonly string Hostname; - - public ResourceName(string hostname, string deviceId) - { - this.Hostname = Preconditions.CheckNonWhiteSpace(hostname, nameof(hostname)); - this.DeviceId = Preconditions.CheckNonWhiteSpace(deviceId, nameof(deviceId)); - } - - public static implicit operator string(ResourceName name) => name.ToString(); - - public bool Equals(string other) => this.ToString().Equals(other, StringComparison.OrdinalIgnoreCase); - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != this.GetType()) - { - return false; - } - - return this.Equals(obj.ToString()); - } - - public override int GetHashCode() => this.ToString().GetHashCode(); - - public override string ToString() => KubeUtils.SanitizeK8sValue(this.Hostname) + Constants.K8sNameDivider + KubeUtils.SanitizeK8sValue(this.DeviceId); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ResourceSettings.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ResourceSettings.cs deleted file mode 100644 index 69a0d13c9f9..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/ResourceSettings.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes -{ - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Util; - - /// - /// K8s Resource requirements as read in by autofac - /// - public class ResourceSettings - { - public Dictionary Limits { get; set; } - public Dictionary Requests { get; set; } - - public V1ResourceRequirements ToResourceRequirements() - { - Dictionary limits = Option.Maybe(this.Limits).Map(limitMap => - limitMap.ToDictionary(pair => pair.Key, pair => new ResourceQuantity(pair.Value))).OrDefault(); - Dictionary requests = Option.Maybe(this.Requests).Map(limitMap => - limitMap.ToDictionary(pair => pair.Key, pair => new ResourceQuantity(pair.Value))).OrDefault(); - return new V1ResourceRequirements(limits, requests); - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeConditionStatusType.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeConditionStatusType.cs deleted file mode 100644 index f6c4f1dffc0..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeConditionStatusType.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System.Runtime.Serialization; - using Newtonsoft.Json; - using Newtonsoft.Json.Converters; - - [JsonConverter(typeof(StringEnumConverter))] - public enum EdgeConditionStatusType - { - [EnumMember(Value = "True")] - True, - - [EnumMember(Value = "False")] - False, - - [EnumMember(Value = "Unknown")] - Unknown, - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentCommand.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentCommand.cs deleted file mode 100644 index 239010b31b4..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentCommand.cs +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using k8s; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Diff; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Extensions.Logging; - using Microsoft.Rest; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - public class EdgeDeploymentCommand : ICommand - { - readonly IKubernetes client; - readonly IReadOnlyCollection modules; - readonly IRuntimeInfo runtimeInfo; - readonly Lazy id; - readonly ICombinedConfigProvider configProvider; - readonly string deviceSelector; - readonly string deviceNamespace; - readonly ResourceName resourceName; - readonly JsonSerializerSettings serializerSettings; - readonly KubernetesModuleOwner moduleOwner; - readonly Option activeDeployment; - - // We use the sum of the IDs of the underlying commands as the id for this group - // command. - public string Id => this.id.Value; - - public EdgeDeploymentCommand( - ResourceName resourceName, - string deviceSelector, - string deviceNamespace, - IKubernetes client, - IEnumerable desiredModules, - Option activeDeployment, - IRuntimeInfo runtimeInfo, - ICombinedConfigProvider configProvider, - KubernetesModuleOwner moduleOwner) - { - this.resourceName = Preconditions.CheckNotNull(resourceName, nameof(resourceName)); - this.deviceNamespace = KubeUtils.SanitizeK8sValue(Preconditions.CheckNonWhiteSpace(deviceNamespace, nameof(deviceNamespace))); - this.deviceSelector = Preconditions.CheckNonWhiteSpace(deviceSelector, nameof(deviceSelector)); - this.client = Preconditions.CheckNotNull(client, nameof(client)); - this.modules = Preconditions.CheckNotNull(desiredModules, nameof(desiredModules)).ToList(); - this.activeDeployment = activeDeployment; - this.runtimeInfo = Preconditions.CheckNotNull(runtimeInfo, nameof(runtimeInfo)); - this.configProvider = Preconditions.CheckNotNull(configProvider, nameof(configProvider)); - this.id = new Lazy(() => this.modules.Aggregate(string.Empty, (prev, module) => module.Name + prev)); - this.serializerSettings = EdgeDeploymentSerialization.SerializerSettings; - this.moduleOwner = Preconditions.CheckNotNull(moduleOwner, nameof(moduleOwner)); - } - - public async Task ExecuteAsync(CancellationToken token) - { - await this.ManageImagePullSecrets(token); - await this.PushEdgeDeployment(token); - } - - async Task ManageImagePullSecrets(CancellationToken token) - { - var deviceOnlyLabels = new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = KubeUtils.SanitizeLabelValue(this.resourceName.DeviceId), - }; - - // Modules may share an image pull secret, so only pick unique ones to add to the dictionary. - List desiredImagePullSecrets = this.modules - .Select(module => this.configProvider.GetCombinedConfig(module, this.runtimeInfo)) - .Select(config => config.ImagePullSecret) - .FilterMap() - .GroupBy(secret => secret.Name) - .Select(secretGroup => this.CreateSecret(secretGroup.First(), deviceOnlyLabels)) - .ToList(); - - V1SecretList currentImagePullSecrets = await this.client.ListNamespacedSecretAsync(this.deviceNamespace, labelSelector: this.deviceSelector, cancellationToken: token); - - await this.ManageImagePullSecrets(currentImagePullSecrets, desiredImagePullSecrets, token); - } - - V1Secret CreateSecret(ImagePullSecret imagePullSecret, IDictionary labels) => - new V1Secret - { - Data = new Dictionary - { - [KubernetesConstants.K8sPullSecretData] = Encoding.UTF8.GetBytes(imagePullSecret.GenerateSecret()) - }, - Type = KubernetesConstants.K8sPullSecretType, - Metadata = new V1ObjectMeta - { - Name = imagePullSecret.Name, - NamespaceProperty = this.deviceNamespace, - OwnerReferences = this.moduleOwner.ToOwnerReferences(), - Labels = labels - } - }; - - async Task ManageImagePullSecrets(V1SecretList existing, List desired, CancellationToken token) - { - // find difference between desired and existing image pull secrets - var diff = FindImagePullSecretDiff(desired, existing.Items); - - // Update only those image pull secrets if configurations have not matched - var updatingTask = diff.Updated - .Select( - update => - { - Events.UpdateImagePullSecret(update.To); - - update.To.Metadata.ResourceVersion = update.From.Metadata.ResourceVersion; - return this.client.ReplaceNamespacedSecretAsync(update.To, update.To.Metadata.Name, this.deviceNamespace, cancellationToken: token); - }); - await Task.WhenAll(updatingTask); - - // Delete all existing image pull secrets that are not in desired list - var removingTasks = diff.Removed - .Select( - name => - { - Events.DeleteImagePullSecret(name); - return this.client.DeleteNamespacedSecretAsync(name, this.deviceNamespace, cancellationToken: token); - }); - await Task.WhenAll(removingTasks); - - // Create new desired image pull secrets - foreach (V1Secret secret in diff.Added) - { - // Allow user to override image pull secrets even if they were created not by agent (e.g. during installation and/or iotedged) - try - { - Events.CreateImagePullSecret(secret); - await this.client.CreateNamespacedSecretAsync(secret, this.deviceNamespace, cancellationToken: token); - } - catch (HttpOperationException e) when (e.Response.StatusCode == HttpStatusCode.Conflict) - { - Events.UpdateExistingImagePullSecret(secret); - - V1Secret conflictedSecret = await this.client.ReadNamespacedSecretAsync(secret.Metadata.Name, secret.Metadata.NamespaceProperty, cancellationToken: token); - conflictedSecret.Data = secret.Data; - - await this.client.ReplaceNamespacedSecretAsync(conflictedSecret, conflictedSecret.Metadata.Name, conflictedSecret.Metadata.NamespaceProperty, cancellationToken: token); - } - } - } - - static Diff FindImagePullSecretDiff(IEnumerable desired, IEnumerable existing) - { - var desiredSet = new Set(desired.ToDictionary(secret => secret.Metadata.Name)); - var existingSet = new Set(existing.ToDictionary(secret => secret.Metadata.Name)); - - return desiredSet.Diff(existingSet, ImagePullSecretBySecretDataEqualityComparer); - } - - static IEqualityComparer ImagePullSecretBySecretDataEqualityComparer { get; } = new KubernetesImagePullSecretBySecretDataEqualityComparer(); - - async Task PushEdgeDeployment(CancellationToken token) - { - List modulesList = this.modules - .Select( - module => - { - var combinedConfig = this.configProvider.GetCombinedConfig(module, this.runtimeInfo); - string image = combinedConfig.Image; - - // TODO: this is a workaround in preview to keep Edge Agent from updating itself - if (module.Name == Core.Constants.EdgeAgentModuleName) - { - var agentImage = this.FindAgentImageAsync(token).ConfigureAwait(false); - agentImage.GetAwaiter().GetResult().ForEach(foundImage => image = foundImage); - } - - var authConfig = combinedConfig.ImagePullSecret.Map(secret => new AuthConfig(secret.Name)); - return new KubernetesModule(module, new KubernetesConfig(image, combinedConfig.CreateOptions, authConfig), this.moduleOwner); - }) - .ToList(); - - var metadata = new V1ObjectMeta - { - Name = this.resourceName, - NamespaceProperty = this.deviceNamespace, - OwnerReferences = this.moduleOwner.ToOwnerReferences() - }; - - // need resourceVersion for Replace. - this.activeDeployment.ForEach(deployment => metadata.ResourceVersion = deployment.Metadata.ResourceVersion); - - var customObjectDefinition = new EdgeDeploymentDefinition(KubernetesConstants.EdgeDeployment.ApiVersion, KubernetesConstants.EdgeDeployment.Kind, metadata, modulesList); - var crdObject = JObject.FromObject(customObjectDefinition, JsonSerializer.Create(this.serializerSettings)); - - await this.activeDeployment.Match( - async a => - { - Events.ReplaceEdgeDeployment(customObjectDefinition); - await this.client.ReplaceNamespacedCustomObjectWithHttpMessagesAsync( - crdObject, - KubernetesConstants.EdgeDeployment.Group, - KubernetesConstants.EdgeDeployment.Version, - this.deviceNamespace, - KubernetesConstants.EdgeDeployment.Plural, - this.resourceName, - cancellationToken: token); - }, - async () => - { - try - { - Events.CreateEdgeDeployment(customObjectDefinition); - await this.client.CreateNamespacedCustomObjectWithHttpMessagesAsync( - crdObject, - KubernetesConstants.EdgeDeployment.Group, - KubernetesConstants.EdgeDeployment.Version, - this.deviceNamespace, - KubernetesConstants.EdgeDeployment.Plural, - cancellationToken: token); - } - catch (HttpOperationException e) when (e.Response.StatusCode == HttpStatusCode.NotFound) - { - Events.ReportCrdInstallationFailed(e); - throw; - } - }); - } - - Task> FindAgentImageAsync(CancellationToken token) - { - var agentImage = this.activeDeployment.Match( - edgeDeployment => - { - var currentAgent = this.activeDeployment.OrDefault().Spec.First(agentModule => agentModule.Name == Core.Constants.EdgeAgentModuleName); - return Task.FromResult(Option.Some(currentAgent.Config.Image)); - }, - async () => - { - try - { - // When CRD has not been created, use helm chart deployment details - var agentDeployment = await this.client.ReadNamespacedDeploymentAsync( - Core.Constants.EdgeAgentModuleName.ToLower(), - this.deviceNamespace, - cancellationToken: token); - return Option.Some(agentDeployment.Spec.Template.Spec.Containers.First(container => container.Name == Core.Constants.EdgeAgentModuleName.ToLower()).Image); - } - catch (Exception e) - { - Events.FindActiveDeploymentFailed(Core.Constants.EdgeAgentModuleName, e); - return Option.None(); - } - }); - - return agentImage; - } - - public Task UndoAsync(CancellationToken token) => Task.CompletedTask; - - public string Show() => $"Create an EdgeDeployment with modules: [{string.Join(", ", this.modules.Select(m => m.Name))}]"; - - public override string ToString() => this.Show(); - - static class Events - { - const int IdStart = KubernetesEventIds.KubernetesCommand; - static readonly ILogger Log = Logger.Factory.CreateLogger(); - - enum EventIds - { - CreateDeployment = IdStart, - ReplaceDeployment, - CreateImagePullSecret, - DeleteImagePullSecret, - UpdateImagePullSecret, - UpdateExistingImagePullSecret, - FindActiveDeploymentFailed, - ReportCrdInstallationFailed - } - - public static void CreateEdgeDeployment(EdgeDeploymentDefinition deployment) => Log.LogDebug((int)EventIds.CreateDeployment, $"Create edge deployment: {deployment.Metadata.Name}"); - - public static void ReplaceEdgeDeployment(EdgeDeploymentDefinition deployment) => Log.LogDebug((int)EventIds.ReplaceDeployment, $"Replace edge deployment: {deployment.Metadata.Name}"); - - internal static void CreateImagePullSecret(V1Secret secret) => Log.LogDebug((int)EventIds.CreateImagePullSecret, $"Create Image Pull Secret {secret.Metadata.Name}"); - - internal static void DeleteImagePullSecret(string name) => Log.LogDebug((int)EventIds.DeleteImagePullSecret, $"Delete Image Pull Secret {name}"); - - internal static void UpdateImagePullSecret(V1Secret secret) => Log.LogDebug((int)EventIds.UpdateImagePullSecret, $"Update Image Pull Secret {secret.Metadata.Name}"); - - internal static void UpdateExistingImagePullSecret(V1Secret secret) => Log.LogWarning((int)EventIds.UpdateExistingImagePullSecret, $"Update existing Image Pull Secret {secret.Metadata.Name}"); - - internal static void ReportCrdInstallationFailed(Exception ex) => Log.LogError((int)EventIds.ReportCrdInstallationFailed, "EdgeDeployment CustomResourceDefinition(CRD) was not found. Please install the edge-kubernetes-crd Helm chart"); - - internal static void FindActiveDeploymentFailed(string deploymentName, Exception exception) => Log.LogDebug((int)EventIds.FindActiveDeploymentFailed, exception, $"Failed to find active Edge Deployment {deploymentName}"); - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentController.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentController.cs deleted file mode 100755 index 4357a6324e5..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentController.cs +++ /dev/null @@ -1,449 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - using k8s; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Deployment; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Diff; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Pvc; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.ServiceAccount; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Extensions.Logging; - using Microsoft.Rest; - using CoreConstants = Microsoft.Azure.Devices.Edge.Agent.Core.Constants; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - // TODO add unit tests - public class EdgeDeploymentController : IEdgeDeploymentController - { - readonly IKubernetes client; - readonly ResourceName resourceName; - readonly string deviceSelector; - readonly string deviceNamespace; - readonly IModuleIdentityLifecycleManager moduleIdentityLifecycleManager; - readonly IKubernetesServiceMapper serviceMapper; - readonly IKubernetesDeploymentMapper deploymentMapper; - readonly IKubernetesPvcMapper pvcMapper; - readonly IKubernetesServiceAccountMapper serviceAccountMapper; - - public EdgeDeploymentController( - ResourceName resourceName, - string deviceSelector, - string deviceNamespace, - IKubernetes client, - IModuleIdentityLifecycleManager moduleIdentityLifecycleManager, - IKubernetesServiceMapper serviceMapper, - IKubernetesDeploymentMapper deploymentMapper, - IKubernetesPvcMapper pvcMapper, - IKubernetesServiceAccountMapper serviceAccountMapper) - { - this.resourceName = resourceName; - this.deviceSelector = deviceSelector; - this.deviceNamespace = deviceNamespace; - this.moduleIdentityLifecycleManager = moduleIdentityLifecycleManager; - this.client = client; - this.serviceMapper = serviceMapper; - this.deploymentMapper = deploymentMapper; - this.pvcMapper = pvcMapper; - this.serviceAccountMapper = serviceAccountMapper; - } - - public async Task DeployModulesAsync(ModuleSet desiredModules, ModuleSet currentModules) - { - try - { - var moduleIdentities = await this.moduleIdentityLifecycleManager.GetModuleIdentitiesAsync(desiredModules, currentModules); - - // having desired modules an no module identities means that we are unable to obtain a list of module identities - if (desiredModules.Modules.Any() && !moduleIdentities.Any()) - { - Events.NoModuleIdentities(); - return EdgeDeploymentStatus.Failure("Unable to obtain identities for desired modules"); - } - - var labels = desiredModules.Modules - .ToDictionary( - module => module.Key, - module => new Dictionary - { - [KubernetesConstants.K8sEdgeModuleLabel] = moduleIdentities[module.Key].DeploymentName(), - [KubernetesConstants.K8sEdgeDeviceLabel] = KubeUtils.SanitizeLabelValue(this.resourceName.DeviceId), - }); - var deviceOnlyLabels = new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = KubeUtils.SanitizeLabelValue(this.resourceName.DeviceId), - }; - - var desiredServiceAccounts = desiredModules.Modules - .Select(module => this.serviceAccountMapper.CreateServiceAccount((KubernetesModule)module.Value, moduleIdentities[module.Key], labels[module.Key])) - .ToList(); - - V1ServiceAccountList currentServiceAccounts = await this.client.ListNamespacedServiceAccountAsync(this.deviceNamespace, labelSelector: this.deviceSelector); - await this.ManageServiceAccounts(currentServiceAccounts, desiredServiceAccounts); - - var desiredServices = desiredModules.Modules - .Select(module => this.serviceMapper.CreateService(moduleIdentities[module.Key], (KubernetesModule)module.Value, labels[module.Key])) - .FilterMap() - .ToList(); - - V1ServiceList currentServices = await this.client.ListNamespacedServiceAsync(this.deviceNamespace, labelSelector: this.deviceSelector); - await this.ManageServices(currentServices, desiredServices); - - var desiredDeployments = desiredModules.Modules - .Select(module => this.deploymentMapper.CreateDeployment(moduleIdentities[module.Key], (KubernetesModule)module.Value, labels[module.Key])) - .ToList(); - - V1DeploymentList currentDeployments = await this.client.ListNamespacedDeploymentAsync(this.deviceNamespace, labelSelector: this.deviceSelector); - await this.ManageDeployments(currentDeployments, desiredDeployments); - - var desiredPvcs = desiredModules.Modules - .Select(module => this.pvcMapper.CreatePersistentVolumeClaims((KubernetesModule)module.Value, deviceOnlyLabels)) - .FilterMap() - .SelectMany(x => x) - .GroupBy(x => x.Metadata.Name) - .Select(x => x.First()); - - // Modules may use PVCs created by the user, we get all PVCs and then work on ours. - V1PersistentVolumeClaimList currentPvcList = await this.client.ListNamespacedPersistentVolumeClaimAsync(this.deviceNamespace); - await this.ManagePvcs(currentPvcList, desiredPvcs); - - return EdgeDeploymentStatus.Success("Successfully deployed"); - } - catch (HttpOperationException e) - { - Events.DeployModulesException(e); - return EdgeDeploymentStatus.Failure(e); - } - } - - async Task ManageServices(V1ServiceList existing, IEnumerable desired) - { - // find difference between desired and existing services - var diff = FindServiceDiff(desired, existing.Items); - - // Update only those services if configurations have not matched - var updatingTask = diff.Updated - .Select( - update => - { - Events.UpdateService(update.To); - - this.serviceMapper.UpdateService(update.To, update.From); - return this.client.ReplaceNamespacedServiceAsync(update.To, update.To.Metadata.Name, this.deviceNamespace); - }); - await Task.WhenAll(updatingTask); - - // Delete all existing services that are not in desired list - var removingTasks = diff.Removed - .Select( - name => - { - Events.DeleteService(name); - return this.client.DeleteNamespacedServiceAsync(name, this.deviceNamespace); - }); - await Task.WhenAll(removingTasks); - - // Create new desired services - var addingTasks = diff.Added - .Select( - service => - { - Events.CreateService(service); - return this.client.CreateNamespacedServiceAsync(service, this.deviceNamespace); - }); - await Task.WhenAll(addingTasks); - } - - static Diff FindServiceDiff(IEnumerable desired, IEnumerable existing) - { - var desiredSet = new Set(desired.ToDictionary(service => service.Metadata.Name)); - var existingSet = new Set(existing.ToDictionary(service => service.Metadata.Name)); - - return desiredSet.Diff(existingSet, ServiceByCreationStringEqualityComparer); - } - - static IEqualityComparer ServiceByCreationStringEqualityComparer { get; } = new KubernetesServiceByCreationStringEqualityComparer(); - - async Task ManageDeployments(V1DeploymentList existing, IEnumerable desired) - { - // find difference between desired and existing deployments - var diff = FindDeploymentDiff(desired, existing.Items); - - var updatingTask = diff.Updated - .Select( - update => - { - Events.UpdateDeployment(update.To); - - this.deploymentMapper.UpdateDeployment(update.To, update.From); - return this.client.ReplaceNamespacedDeploymentAsync(update.To, update.To.Metadata.Name, this.deviceNamespace); - }); - await Task.WhenAll(updatingTask); - - // Delete all existing deployments that are not in desired list - var removingTasks = diff.Removed - .Select( - name => - { - Events.DeleteDeployment(name); - return this.client.DeleteNamespacedDeploymentAsync( - name, - this.deviceNamespace, - propagationPolicy: KubernetesConstants.DefaultDeletePropagationPolicy, - body: new V1DeleteOptions(propagationPolicy: KubernetesConstants.DefaultDeletePropagationPolicy)); - }); - await Task.WhenAll(removingTasks); - - // Add all new deployments from desired list - var addingTasks = diff.Added - .Select( - deployment => - { - Events.CreateDeployment(deployment); - return this.client.CreateNamespacedDeploymentAsync(deployment, this.deviceNamespace); - }); - await Task.WhenAll(addingTasks); - } - - static Diff FindDeploymentDiff(IEnumerable desired, IEnumerable existing) - { - var desiredSet = new Set(desired.ToDictionary(deployment => deployment.Metadata.Name)); - var existingSet = new Set(existing.ToDictionary(deployment => deployment.Metadata.Name)); - - return desiredSet.Diff(existingSet, DeploymentByCreationStringEqualityComparer); - } - - static IEqualityComparer DeploymentByCreationStringEqualityComparer { get; } = new KubernetesDeploymentByCreationStringEqualityComparer(); - - async Task ManagePvcs(V1PersistentVolumeClaimList existing, IEnumerable desired) - { - // Find the difference between desired and existing PVCs - var diff = this.FindPvcDiff(desired, existing.Items); - - // Update all PVCs that are in both lists, and are labeled (created by Agent) - var updatingTask = diff.Updated - .Select( - update => - { - Events.UpdatePvc(update.To); - this.pvcMapper.UpdatePersistentVolumeClaim(update.To, update.From); - return this.client.ReplaceNamespacedPersistentVolumeClaimAsync(update.To, update.To.Metadata.Name, this.deviceNamespace); - }); - await Task.WhenAll(updatingTask); - - // Remove all PVCs that are not in the desired list, and are labeled (created by Agent) - var removingTasks = diff.Removed - .Select( - name => - { - Events.DeletePvc(name); - return this.client.DeleteNamespacedPersistentVolumeClaimAsync(name, this.deviceNamespace); - }); - await Task.WhenAll(removingTasks); - - // Create all new desired PVCs. - var addingTasks = diff.Added - .Select( - pvc => - { - Events.CreatePvc(pvc); - return this.client.CreateNamespacedPersistentVolumeClaimAsync(pvc, this.deviceNamespace); - }); - await Task.WhenAll(addingTasks); - } - - Diff FindPvcDiff( - IEnumerable desired, - IEnumerable existing) - { - var existingDict = existing.ToDictionary(pvc => pvc.Metadata.Name); - var desiredSet = new Set(desired.ToDictionary(pvc => pvc.Metadata.Name)); - var existingSet = new Set(existingDict); - var fullDiff = desiredSet.Diff(existingSet, KubernetesPvcByValueEqualityComparer); - // In fullDiff: - // Added are `desired` PVCs which are named differently that all existing PVCs. - // - these are all new, - // Removed are all PVCs which are in `existing` and not in `desired` - // - some of these names may be PVCs created by the user, we don't want to delete them. - // Updated are all PVCs which differ between `existing` and `desired` - // - some of the "From" PVCs were created by the user, we shouldn't update these. - // Filter Removed and Updated to only select ones created by controller. - return new Diff( - fullDiff.Added, - fullDiff.Removed.Where(name => this.IsCreatedByController(existingDict[name])), - fullDiff.Updated.Where(update => this.IsCreatedByController(update.From))); - } - - bool IsCreatedByController(V1PersistentVolumeClaim claim) - { - var labels = claim.Metadata?.Labels; - if (labels == null) - { - return false; - } - - if (!labels.ContainsKey(KubernetesConstants.K8sEdgeDeviceLabel)) - { - return false; - } - - return labels[KubernetesConstants.K8sEdgeDeviceLabel] == KubeUtils.SanitizeLabelValue(this.resourceName.DeviceId); - } - - static IEqualityComparer KubernetesPvcByValueEqualityComparer { get; } = new KubernetesPvcByValueEqualityComparer(); - - async Task ManageServiceAccounts(V1ServiceAccountList existing, IReadOnlyCollection desired) - { - // find difference between desired and existing service accounts - var diff = FindServiceAccountDiff(desired, existing.Items); - - // Update all service accounts that are in both lists - var updatingTasks = diff.Updated - .Select( - update => - { - Events.UpdateServiceAccount(update.To); - - this.serviceAccountMapper.UpdateServiceAccount(update.To, update.From); - return this.client.ReplaceNamespacedServiceAccountAsync(update.To, update.To.Metadata.Name, this.deviceNamespace); - }); - await Task.WhenAll(updatingTasks); - - // Delete only those existing service accounts that are not in in desired list of common names - var removingTasks = diff.Removed - .Select( - name => - { - Events.DeleteServiceAccount(name); - return this.client.DeleteNamespacedServiceAccountAsync(name, this.deviceNamespace); - }); - await Task.WhenAll(removingTasks); - - // Add only those desired service account that are not in the list of common names - var addingTasks = diff.Added - .Select( - account => - { - Events.CreateServiceAccount(account); - return this.client.CreateNamespacedServiceAccountAsync(account, this.deviceNamespace); - }); - await Task.WhenAll(addingTasks); - } - - static Diff FindServiceAccountDiff(IEnumerable desired, IEnumerable existing) - { - var desiredSet = new Set(desired.ToDictionary(serviceAccount => serviceAccount.Metadata.Name)); - var existingSet = new Set(existing.ToDictionary(serviceAccount => serviceAccount.Metadata.Name)); - - return desiredSet.Diff(existingSet, KubernetesServiceAccountByValueEqualityComparer); - } - - static IEqualityComparer KubernetesServiceAccountByValueEqualityComparer { get; } = new KubernetesServiceAccountByValueEqualityComparer(); - - static class Events - { - const int IdStart = KubernetesEventIds.EdgeDeploymentController; - static readonly ILogger Log = Logger.Factory.CreateLogger(); - - enum EventIds - { - InvalidCreationString = IdStart, - CreateService, - DeleteService, - UpdateService, - CreateDeployment, - DeleteDeployment, - UpdateDeployment, - CreatePvc, - DeletePvc, - UpdatePvc, - CreateServiceAccount, - DeleteServiceAccount, - UpdateServiceAccount, - DeployModulesException, - NoModuleIdentities - } - - internal static void DeleteService(string name) - { - Log.LogInformation((int)EventIds.DeleteService, $"Delete service {name}"); - } - - internal static void CreateService(V1Service service) - { - Log.LogInformation((int)EventIds.CreateService, $"Create service {service.Metadata.Name}"); - } - - internal static void CreateDeployment(V1Deployment deployment) - { - Log.LogInformation((int)EventIds.CreateDeployment, $"Create deployment {deployment.Metadata.Name}"); - } - - internal static void DeleteDeployment(string name) - { - Log.LogInformation((int)EventIds.DeleteDeployment, $"Delete deployment {name}"); - } - - internal static void UpdateDeployment(V1Deployment deployment) - { - Log.LogInformation((int)EventIds.UpdateDeployment, $"Update deployment {deployment.Metadata.Name}"); - } - - internal static void CreatePvc(V1PersistentVolumeClaim pvc) - { - Log.LogInformation((int)EventIds.CreatePvc, $"Create PVC {pvc.Metadata.Name}"); - } - - internal static void DeletePvc(string name) - { - Log.LogInformation((int)EventIds.DeletePvc, $"Delete PVC {name}"); - } - - internal static void UpdatePvc(V1PersistentVolumeClaim pvc) - { - Log.LogInformation((int)EventIds.UpdatePvc, $"Update PVC {pvc.Metadata.Name}"); - } - - internal static void DeployModulesException(Exception ex) - { - Log.LogWarning((int)EventIds.DeployModulesException, ex, "Module deployment failed"); - } - - internal static void InvalidCreationString(string kind, string name) - { - Log.LogDebug((int)EventIds.InvalidCreationString, $"Expected a valid '{kind}' creation string in k8s Object '{name}'."); - } - - internal static void UpdateService(V1Service service) - { - Log.LogDebug((int)EventIds.UpdateService, $"Update service object '{service.Metadata.Name}'"); - } - - internal static void CreateServiceAccount(V1ServiceAccount serviceAccount) - { - Log.LogDebug((int)EventIds.CreateServiceAccount, $"Create Service Account {serviceAccount.Metadata.Name}"); - } - - internal static void DeleteServiceAccount(string name) - { - Log.LogDebug((int)EventIds.DeleteServiceAccount, $"Delete Service Account {name}"); - } - - internal static void UpdateServiceAccount(V1ServiceAccount serviceAccount) - { - Log.LogDebug((int)EventIds.UpdateServiceAccount, $"Update Service Account {serviceAccount.Metadata.Name}"); - } - - internal static void NoModuleIdentities() - { - Log.LogError((int)EventIds.NoModuleIdentities, "Unable to get identities for desired modules"); - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentDefinition.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentDefinition.cs deleted file mode 100644 index 53e9302b52d..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentDefinition.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Json; - using Newtonsoft.Json; - - public class EdgeDeploymentDefinition : IEdgeDeploymentDefinition - { - [JsonProperty(PropertyName = "apiVersion")] - public string ApiVersion { get; set; } - - [JsonProperty(PropertyName = "kind")] - public string Kind { get; set; } - - [JsonProperty(PropertyName = "metadata")] - public V1ObjectMeta Metadata { get; } - - [JsonProperty(PropertyName = "spec")] - public IReadOnlyList Spec { get; } - - [JsonProperty(PropertyName = "status")] - [JsonConverter(typeof(OptionConverter))] - public Option Status { get; } - - public EdgeDeploymentDefinition(string apiVersion, string kind, V1ObjectMeta metadata, IReadOnlyList spec, EdgeDeploymentStatus status = null) - { - this.ApiVersion = Preconditions.CheckNonWhiteSpace(apiVersion, nameof(apiVersion)); - this.Kind = Preconditions.CheckNonWhiteSpace(kind, nameof(kind)); - this.Metadata = Preconditions.CheckNotNull(metadata, nameof(metadata)); - this.Spec = Preconditions.CheckNotNull(spec, nameof(spec)); - this.Status = Option.Maybe(status); - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentOperator.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentOperator.cs deleted file mode 100644 index fbc4d8d987c..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentOperator.cs +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System; - using System.Linq; - using System.Threading.Tasks; - using k8s; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Concurrency; - using Microsoft.Extensions.Logging; - using Microsoft.Rest; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - // TODO add unit tests - public class EdgeDeploymentOperator : IEdgeDeploymentOperator - { - readonly IKubernetes client; - readonly AsyncLock watchLock = new AsyncLock(); - readonly IEdgeDeploymentController controller; - readonly ResourceName resourceName; - readonly string deviceNamespace; - readonly JsonSerializerSettings serializerSettings = EdgeDeploymentSerialization.SerializerSettings; - Option> operatorWatch; - ModuleSet currentModules = ModuleSet.Empty; - EdgeDeploymentStatus currentStatus = EdgeDeploymentStatus.Default; - - public EdgeDeploymentOperator( - ResourceName resourceName, - string deviceNamespace, - IKubernetes client, - IEdgeDeploymentController controller) - { - this.deviceNamespace = Preconditions.CheckNonWhiteSpace(deviceNamespace, nameof(deviceNamespace)); - this.resourceName = Preconditions.CheckNotNull(resourceName, nameof(resourceName)); - - this.client = Preconditions.CheckNotNull(client, nameof(client)); - this.operatorWatch = Option.None>(); - this.controller = Preconditions.CheckNotNull(controller, nameof(controller)); - } - - public void Start() => this.StartListEdgeDeployments(); - - public void Stop() - { - // TODO do we need lock here? - this.operatorWatch.ForEach(watch => watch.Dispose()); - } - - public void Dispose() => this.Stop(); - - void StartListEdgeDeployments() => - this.client.ListNamespacedCustomObjectWithHttpMessagesAsync(KubernetesConstants.EdgeDeployment.Group, KubernetesConstants.EdgeDeployment.Version, this.deviceNamespace, KubernetesConstants.EdgeDeployment.Plural, watch: true) - .ContinueWith(this.OnListEdgeDeploymentsCompleted); - - async Task OnListEdgeDeploymentsCompleted(Task> task) - { - HttpOperationResponse response = await task; - - this.operatorWatch = Option.Some( - response.Watch( - onEvent: async (type, item) => await this.EdgeDeploymentOnEventHandlerAsync(type, item), - onClosed: () => - { - Events.EdgeDeploymentWatchClosed(); - - // get rid of the current edge deployment watch object since we got closed - this.operatorWatch.ForEach(watch => watch.Dispose()); - this.operatorWatch = Option.None>(); - - // kick off a new watch - this.StartListEdgeDeployments(); - }, - onError: (ex) => - { - Events.EdgeDeploymentWatchFailed(ex); - throw ex; - })); - } - - internal async Task EdgeDeploymentOnEventHandlerAsync(WatchEventType type, EdgeDeploymentDefinition item) - { - using (await this.watchLock.LockAsync()) - { - try - { - await this.HandleEdgeDeploymentChangedAsync(type, item); - } - catch (Exception ex) - { - Events.EdgeDeploymentWatchFailed(ex); - await this.ReportDeploymentFailure(ex, item); - if (ex.IsFatal()) - { - throw; - } - } - } - } - - async Task HandleEdgeDeploymentChangedAsync(WatchEventType type, EdgeDeploymentDefinition edgeDeploymentCrdObject) - { - // only operate on the device that matches this operator. - if (!this.resourceName.Equals(edgeDeploymentCrdObject.Metadata.Name)) - { - Events.DeploymentNameMismatch(edgeDeploymentCrdObject.Metadata.Name, this.resourceName); - return; - } - - Events.DeploymentStatus(type, this.resourceName); - switch (type) - { - case WatchEventType.Added: - case WatchEventType.Modified: - var desiredModules = ModuleSet.Create(edgeDeploymentCrdObject.Spec.ToArray()); - var status = await this.controller.DeployModulesAsync(desiredModules, this.currentModules); - await this.ReportEdgeDeploymentStatus(edgeDeploymentCrdObject, status); - this.currentModules = desiredModules; - this.currentStatus = status; - break; - - case WatchEventType.Deleted: - // Kubernetes garbage collection will handle cleanup of deployment artifacts - this.currentModules = ModuleSet.Empty; - this.currentStatus = EdgeDeploymentStatus.Default; - break; - - case WatchEventType.Error: - Events.DeploymentError(); - break; - - default: - throw new ArgumentOutOfRangeException(nameof(type), type, null); - } - } - - async Task ReportDeploymentFailure(Exception ex, EdgeDeploymentDefinition item) - { - var status = EdgeDeploymentStatus.Failure(ex); - await this.ReportEdgeDeploymentStatus(item, status); - this.currentStatus = status; - } - - async Task ReportEdgeDeploymentStatus(EdgeDeploymentDefinition edgeDeploymentDefinition, EdgeDeploymentStatus status) - { - if (!status.Equals(this.currentStatus)) - { - var edgeDeploymentStatus = new EdgeDeploymentDefinition( - edgeDeploymentDefinition.ApiVersion, - edgeDeploymentDefinition.Kind, - edgeDeploymentDefinition.Metadata, - edgeDeploymentDefinition.Spec, - status); - - var crdObject = JObject.FromObject(edgeDeploymentStatus, JsonSerializer.Create(this.serializerSettings)); - - try - { - await this.client.ReplaceNamespacedCustomObjectStatusWithHttpMessagesAsync( - crdObject, - KubernetesConstants.EdgeDeployment.Group, - KubernetesConstants.EdgeDeployment.Version, - this.deviceNamespace, - KubernetesConstants.EdgeDeployment.Plural, - this.resourceName); - } - catch (HttpOperationException e) - { - Events.DeploymentStatusFailed(e); - } - } - } - - static class Events - { - const int IdStart = KubernetesEventIds.EdgeDeploymentOperator; - static readonly ILogger Log = Logger.Factory.CreateLogger(); - - enum EventIds - { - WatchFailed = IdStart, - DeploymentStatus, - DeploymentError, - DeploymentNameMismatch, - DeploymentStatusFailed, - WatchClosed, - } - - public static void EdgeDeploymentWatchFailed(Exception ex) - { - Log.LogError((int)EventIds.WatchFailed, ex, "Exception caught in edge deployment watch task."); - } - - public static void DeploymentStatus(WatchEventType type, ResourceName name) - { - Log.LogDebug((int)EventIds.DeploymentStatus, $"Deployment '{name}', status'{type}'"); - } - - public static void DeploymentError() - { - Log.LogError((int)EventIds.DeploymentError, "Operator received error on watch type."); - } - - public static void DeploymentNameMismatch(string received, ResourceName expected) - { - Log.LogDebug((int)EventIds.DeploymentNameMismatch, $"Watching for edge deployments for '{expected}', received notification for '{received}'"); - } - - public static void DeploymentStatusFailed(Exception ex) - { - Log.LogWarning((int)EventIds.DeploymentStatusFailed, ex, "Failed to update Deployment status."); - } - - public static void EdgeDeploymentWatchClosed() - { - Log.LogInformation((int)EventIds.WatchClosed, "K8s closed the edge deployment watch. Attempting to reopen watch."); - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentSerialization.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentSerialization.cs deleted file mode 100644 index 858d7b7cd43..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentSerialization.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System; - using System.Collections.Generic; - using Newtonsoft.Json; - using Newtonsoft.Json.Serialization; - - public static class EdgeDeploymentSerialization - { - public static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings - { - ContractResolver = new OverrideJsonIgnoreOfBaseClassContractResolver( - new Dictionary - { - [typeof(KubernetesModule)] = new[] { nameof(KubernetesModule.Name) } - }) - { - // Environment variable (env) property JSON casing should be left alone - NamingStrategy = new CamelCaseNamingStrategy - { - ProcessDictionaryKeys = false - } - } - }; - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentStatus.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentStatus.cs deleted file mode 100644 index fdf0b2e5a47..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentStatus.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System; - using Microsoft.Rest; - using Newtonsoft.Json; - - public class EdgeDeploymentStatus : IEquatable - { - [JsonProperty(PropertyName = "state")] - public EdgeDeploymentStatusType State { get; } - - [JsonProperty(PropertyName = "message")] - public string Message { get; } - - public static EdgeDeploymentStatus Success(string message) - => new EdgeDeploymentStatus(EdgeDeploymentStatusType.Success, message); - - public static EdgeDeploymentStatus Failure(Exception e) - { - string message = e is HttpOperationException httpEx - ? $"{httpEx.Request.Method} [{httpEx.Request.RequestUri}]({httpEx.Message})" - : e.Message; - return Failure(message); - } - - public static EdgeDeploymentStatus Failure(string message) - => new EdgeDeploymentStatus(EdgeDeploymentStatusType.Failure, message); - - public static readonly EdgeDeploymentStatus Default = new EdgeDeploymentStatus(EdgeDeploymentStatusType.Failure, string.Empty); - - [JsonConstructor] - EdgeDeploymentStatus(EdgeDeploymentStatusType state, string message) - { - this.State = state; - this.Message = message; - } - - public bool Equals(EdgeDeploymentStatus other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.State == other.State && string.Equals(this.Message, other.Message); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != this.GetType()) - { - return false; - } - - return this.Equals((EdgeDeploymentStatus)obj); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = (int)this.State; - hashCode = (hashCode * 397) ^ (this.Message != null ? this.Message.GetHashCode() : 0); - return hashCode; - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentStatusCommand.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentStatusCommand.cs deleted file mode 100644 index 982f1db4653..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentStatusCommand.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Core.ConfigSources; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes; - using Microsoft.Azure.Devices.Edge.Util; - - public class EdgeDeploymentStatusCommand : ICommand - { - readonly Option activeStatus; - - public string Id => "EdgeDeploymentStatusCommand"; - - public EdgeDeploymentStatusCommand( - Option activeStatus) - { - this.activeStatus = activeStatus; - } - - public Task ExecuteAsync(CancellationToken token) - { - // If the EdgeDeployment operator fails, report this to the plan executor - // as an exception. - this.activeStatus.ForEach(status => - { - if (status.State != EdgeDeploymentStatusType.Success) - { - throw new ConfigOperationFailureException(status.Message); - } - }); - return Task.CompletedTask; - } - - public Task UndoAsync(CancellationToken token) => Task.CompletedTask; - - EdgeDeploymentStatusType CurrentStatus() => this.activeStatus.Map(s => s.State).GetOrElse(() => EdgeDeploymentStatusType.Success); - - public string Show() => $"Report EdgeDeployment status: [{this.CurrentStatus()}]"; - - public override string ToString() => this.Show(); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentStatusType.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentStatusType.cs deleted file mode 100644 index 931e1defbc8..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/EdgeDeploymentStatusType.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System.Runtime.Serialization; - using Newtonsoft.Json; - using Newtonsoft.Json.Converters; - - [JsonConverter(typeof(StringEnumConverter))] - public enum EdgeDeploymentStatusType - { - [EnumMember(Value = "Success")] - Success, - - [EnumMember(Value = "Failure")] - Failure, - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IEdgeDeploymentController.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IEdgeDeploymentController.cs deleted file mode 100644 index afc934d3a92..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IEdgeDeploymentController.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; - - public interface IEdgeDeploymentController - { - Task DeployModulesAsync(ModuleSet modules, ModuleSet currentModules); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IEdgeDeploymentDefinition.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IEdgeDeploymentDefinition.cs deleted file mode 100644 index 599faaedace..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IEdgeDeploymentDefinition.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System.Collections.Generic; - using k8s; - using k8s.Models; - using Newtonsoft.Json; - - interface IEdgeDeploymentDefinition : IKubernetesObject - { - [JsonProperty(PropertyName = "metadata")] - V1ObjectMeta Metadata { get; } - - [JsonProperty(PropertyName = "spec")] - IReadOnlyList Spec { get; } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IEdgeDeploymentOperator.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IEdgeDeploymentOperator.cs deleted file mode 100644 index 6b091e24cbd..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IEdgeDeploymentOperator.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - public interface IEdgeDeploymentOperator : IKubernetesOperator - { - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IKubernetesDeploymentMapper.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IKubernetesDeploymentMapper.cs deleted file mode 100644 index 1b84c129814..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IKubernetesDeploymentMapper.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - - public interface IKubernetesDeploymentMapper - { - V1Deployment CreateDeployment(IModuleIdentity identity, KubernetesModule module, IDictionary labels); - - void UpdateDeployment(V1Deployment to, V1Deployment from); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IKubernetesPvcMapper.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IKubernetesPvcMapper.cs deleted file mode 100644 index e958ff4e9e1..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IKubernetesPvcMapper.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Util; - - public interface IKubernetesPvcMapper - { - Option> CreatePersistentVolumeClaims(KubernetesModule module, IDictionary labels); - - void UpdatePersistentVolumeClaim(V1PersistentVolumeClaim to, V1PersistentVolumeClaim from); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IKubernetesServiceAccountMapper.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IKubernetesServiceAccountMapper.cs deleted file mode 100644 index d3a16d7d807..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IKubernetesServiceAccountMapper.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - - public interface IKubernetesServiceAccountMapper - { - V1ServiceAccount CreateServiceAccount(KubernetesModule module, IModuleIdentity identity, IDictionary labels); - - void UpdateServiceAccount(V1ServiceAccount to, V1ServiceAccount from); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IKubernetesServiceMapper.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IKubernetesServiceMapper.cs deleted file mode 100644 index ca38e475315..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/IKubernetesServiceMapper.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Util; - - public interface IKubernetesServiceMapper - { - Option CreateService(IModuleIdentity identity, KubernetesModule module, IDictionary labels); - - void UpdateService(V1Service to, V1Service from); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/ImagePullSecret.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/ImagePullSecret.cs deleted file mode 100644 index 9339229ae0c..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/ImagePullSecret.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System; - using System.Collections.Generic; - using System.Text; - using global::Docker.DotNet.Models; - using Newtonsoft.Json; - - public class ImagePullSecret - { - public ImagePullSecret(AuthConfig dockerAuth) - { - this.dockerAuth = dockerAuth; - this.Name = ImagePullSecretName.Create(dockerAuth); - } - - public ImagePullSecretName Name { get; } - - readonly AuthConfig dockerAuth; - - public string GenerateSecret() - { - // JSON struct is - // { "auths": - // { "" : - // { "username":"", - // "password":"", - // "email":"" (not needed) - // "auth":":'>" - // } - // } - // } - var auths = new Auth(this.dockerAuth.ServerAddress, new AuthEntry(this.dockerAuth.Username, this.dockerAuth.Password)); - - return JsonConvert.SerializeObject(auths); - } - - class AuthEntry - { - [JsonProperty(Required = Required.Always, PropertyName = "username")] - public readonly string Username; - - [JsonProperty(Required = Required.Always, PropertyName = "password")] - public readonly string Password; - - [JsonProperty(Required = Required.Always, PropertyName = "auth")] - public readonly string Auth; - - public AuthEntry(string username, string password) - { - this.Username = username; - this.Password = password; - byte[] auth = Encoding.UTF8.GetBytes($"{username}:{password}"); - this.Auth = Convert.ToBase64String(auth); - } - } - - class Auth - { - [JsonProperty(Required = Required.Always, PropertyName = "auths")] - public Dictionary Auths; - - public Auth() - { - this.Auths = new Dictionary(); - } - - public Auth(string registry, AuthEntry entry) - : this() - { - this.Auths.Add(registry, entry); - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/ImagePullSecretName.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/ImagePullSecretName.cs deleted file mode 100644 index e7be85f4f82..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/ImagePullSecretName.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using global::Docker.DotNet.Models; - using Microsoft.Azure.Devices.Edge.Util; - using Newtonsoft.Json; - - [JsonConverter(typeof(ImagePullSecretNameConverter))] - public class ImagePullSecretName - { - readonly string value; - - public ImagePullSecretName(string name) - { - this.value = Preconditions.CheckNonWhiteSpace(name, nameof(name)); - } - - public static ImagePullSecretName Create(AuthConfig auth) => new ImagePullSecretName($"{auth.Username.ToLower()}-{auth.ServerAddress.ToLower()}"); - - public static implicit operator string(ImagePullSecretName name) => name.ToString(); - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != this.GetType()) - { - return false; - } - - return this.Equals((ImagePullSecretName)obj); - } - - bool Equals(ImagePullSecretName other) => this.value.Equals(other.value); - - public override int GetHashCode() => this.ToString().GetHashCode(); - - public override string ToString() => this.value; - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/ImagePullSecretNameConverter.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/ImagePullSecretNameConverter.cs deleted file mode 100644 index 6532f0f55cb..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/ImagePullSecretNameConverter.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System; - using Newtonsoft.Json; - - public class ImagePullSecretNameConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, ImagePullSecretName value, JsonSerializer serializer) => writer.WriteValue(value.ToString()); - - public override ImagePullSecretName ReadJson(JsonReader reader, Type objectType, ImagePullSecretName existingValue, bool hasExistingValue, JsonSerializer serializer) - { - if (reader.TokenType != JsonToken.String) - { - throw new JsonSerializationException($"Unable to deserialize {typeof(ImagePullSecretName)}"); - } - - return new ImagePullSecretName(reader.Value.ToString()); - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/KubernetesImagePullSecretBySecretDataEqualityComparer.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/KubernetesImagePullSecretBySecretDataEqualityComparer.cs deleted file mode 100644 index 93cabb74cfd..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/KubernetesImagePullSecretBySecretDataEqualityComparer.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Util; - - public sealed class KubernetesImagePullSecretBySecretDataEqualityComparer : IEqualityComparer - { - static readonly DictionaryComparer MetadataComparer = DictionaryComparer.StringDictionaryComparer; - - public bool Equals(V1Secret x, V1Secret y) - { - if (ReferenceEquals(x, y)) - { - return true; - } - - if (ReferenceEquals(x, null)) - { - return false; - } - - if (ReferenceEquals(y, null)) - { - return false; - } - - if (x.GetType() != y.GetType()) - { - return false; - } - - if (x.Metadata.Name != y.Metadata.Name) - { - return false; - } - - if (!MetadataComparer.Equals(x.Metadata.Labels, y.Metadata.Labels)) - { - return false; - } - - if (x.Type != y.Type) - { - return false; - } - - if (x.Data == null && y.Data == null) - { - return true; - } - - if (x.Data != null && y.Data != null) - { - if (x.Data.Count != y.Data.Count) - { - return false; - } - - if (x.Data.Keys.Intersect(y.Data.Keys).Count() != x.Data.Count) - { - return false; - } - - return x.Data.Keys.Any(key => x.Data[key].SequenceEqual(y.Data[key])); - } - - return false; - } - - public int GetHashCode(V1Secret obj) => obj.GetHashCode(); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/KubernetesModuleOwnerExtensions.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/KubernetesModuleOwnerExtensions.cs deleted file mode 100644 index 6e6befb2226..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/KubernetesModuleOwnerExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System.Collections.Generic; - using k8s.Models; - - public static class KubernetesModuleOwnerExtensions - { - public static List ToOwnerReferences(this KubernetesModuleOwner kubeModuleOwner) => - new List - { - new V1OwnerReference( - apiVersion: kubeModuleOwner.ApiVersion, - kind: kubeModuleOwner.Kind, - name: kubeModuleOwner.Name, - uid: kubeModuleOwner.Uid) - }; - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/PodExtensions.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/PodExtensions.cs deleted file mode 100644 index 30c5469a78e..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/PodExtensions.cs +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment -{ - using System; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Util; - using AgentDocker = Microsoft.Azure.Devices.Edge.Agent.Docker; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - public static class PodExtensions - { - public static ModuleRuntimeInfo ConvertToRuntime(this V1Pod pod, string name) - { - Option containerStatus = GetContainerByName(name, pod); - ReportedModuleStatus moduleStatus = ConvertPodStatusToModuleStatus(Option.Maybe(pod.Status), containerStatus); - RuntimeData runtimeData = GetRuntimeData(containerStatus.OrDefault()); - - string moduleName = string.Empty; - if (!(pod.Metadata?.Annotations?.TryGetValue(KubernetesConstants.K8sEdgeOriginalModuleId, out moduleName) ?? false)) - { - moduleName = name; - } - - var reportedConfig = new AgentDocker.DockerReportedConfig(runtimeData.ImageName, string.Empty, string.Empty, Option.None()); - return new ModuleRuntimeInfo( - moduleName, - "docker", - moduleStatus.Status, - moduleStatus.Description, - runtimeData.ExitStatus, - runtimeData.StartTime, - runtimeData.EndTime, - reportedConfig); - } - - static Option GetContainerByName(string name, V1Pod pod) - { - string containerName = KubeUtils.SanitizeDNSValue(name); - V1ContainerStatus status = pod.Status?.ContainerStatuses? - .FirstOrDefault(container => string.Equals(container.Name, containerName, StringComparison.OrdinalIgnoreCase)); - return Option.Maybe(status); - } - - static ReportedModuleStatus ConvertPodStatusToModuleStatus(Option podStatus, Option containerStatus) - { - return podStatus.Map( - status => - { - switch (status.Phase) - { - case "Running": - { - return containerStatus.Map(c => - { - if (c.State.Waiting != null) - { - return new ReportedModuleStatus(ModuleStatus.Backoff, $"Module in Back-off reason: {c.State.Waiting.Reason}"); - } - else if (c.State.Terminated != null) - { - if (c.State.Terminated.ExitCode != 0) - { - return new ReportedModuleStatus(ModuleStatus.Failed, $"Module Failed reason: {c.State.Terminated.Reason}"); - } - else - { - return new ReportedModuleStatus(ModuleStatus.Stopped, $"Module Stopped reason: {c.State.Terminated.Reason}"); - } - } - else - { - return new ReportedModuleStatus(ModuleStatus.Running, $"Started at {c.State.Running.StartedAt}"); - } - }).GetOrElse(() => new ReportedModuleStatus(ModuleStatus.Failed, $"Module Failed with container status Unknown More Info: K8s reason: {status.Reason} with message: {status.Message}")); - } - - case "Pending": - { - return containerStatus.Map(c => - { - if (c.State.Waiting != null) - { - return new ReportedModuleStatus(ModuleStatus.Backoff, $"Module in Back-off reason: {c.State.Waiting.Reason}"); - } - else if (c.State.Terminated != null) - { - if (c.State.Terminated.ExitCode != 0) - { - return new ReportedModuleStatus(ModuleStatus.Failed, $"Module Failed reason: {c.State.Terminated.Reason}"); - } - else - { - return new ReportedModuleStatus(ModuleStatus.Stopped, $"Module Stopped reason: {c.State.Terminated.Reason}"); - } - } - else - { - return new ReportedModuleStatus(ModuleStatus.Backoff, $"Started at {c.State.Running.StartedAt}"); - } - }).GetOrElse(() => new ReportedModuleStatus(ModuleStatus.Failed, $"Module Failed with container status Unknown More Info: K8s reason: {status.Reason} with message: {status.Message}")); - } - - case "Unknown": - return new ReportedModuleStatus(ModuleStatus.Unknown, $"Module status Unknown reason: {status.Reason}"); - case "Succeeded": - return new ReportedModuleStatus(ModuleStatus.Stopped, $"Module Stopped reason: {status.Reason} with message: {status.Message}"); - case "Failed": - return new ReportedModuleStatus(ModuleStatus.Failed, $"Module Failed reason: {status.Reason} with message: {status.Message}"); - default: - throw new InvalidOperationException($"Invalid pod status {status.Phase}"); - } - }).GetOrElse(() => new ReportedModuleStatus(ModuleStatus.Unknown, "Unable to get pod status")); - } - - static RuntimeData GetRuntimeData(V1ContainerStatus status) - { - string imageName = "unknown:unknown"; - if (status?.Image != null) - { - imageName = status.Image; - } - - if (status?.State?.Running != null) - { - if (status.State.Running.StartedAt.HasValue) - { - return new RuntimeData(0, Option.Some(status.State.Running.StartedAt.Value), Option.None(), imageName); - } - } - else if (status?.State?.Terminated != null) - { - return GetTerminatedRuntimeData(status.State.Terminated, imageName); - } - else if (status?.LastState?.Terminated != null) - { - return GetTerminatedRuntimeData(status.LastState.Terminated, imageName); - } - - return new RuntimeData(0, Option.None(), Option.None(), imageName); - } - - static RuntimeData GetTerminatedRuntimeData(V1ContainerStateTerminated term, string imageName) - { - if (term.StartedAt.HasValue && - term.FinishedAt.HasValue) - { - return new RuntimeData(term.ExitCode, Option.Some(term.StartedAt.Value), Option.Some(term.FinishedAt.Value), imageName); - } - - return new RuntimeData(0, Option.None(), Option.None(), imageName); - } - } - - class ReportedModuleStatus - { - public readonly ModuleStatus Status; - public readonly string Description; - - public ReportedModuleStatus(ModuleStatus status, string description) - { - this.Status = status; - this.Description = description; - } - } - - class RuntimeData - { - public readonly int ExitStatus; - public readonly Option StartTime; - public readonly Option EndTime; - public readonly string ImageName; - - public RuntimeData(int exitStatus, Option startTime, Option endTime, string image) - { - this.ExitStatus = exitStatus; - this.StartTime = startTime; - this.EndTime = endTime; - this.ImageName = image; - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/deployment/KubernetesDeploymentByCreationStringEqualityComparer.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/deployment/KubernetesDeploymentByCreationStringEqualityComparer.cs deleted file mode 100644 index 05eb83c68f5..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/deployment/KubernetesDeploymentByCreationStringEqualityComparer.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Deployment -{ - using System.Collections.Generic; - using k8s.Models; - using Newtonsoft.Json; - using CoreConstants = Microsoft.Azure.Devices.Edge.Agent.Core.Constants; - - public sealed class KubernetesDeploymentByCreationStringEqualityComparer : IEqualityComparer - { - public bool Equals(V1Deployment x, V1Deployment y) - { - if (ReferenceEquals(x, y)) - { - return true; - } - - if (ReferenceEquals(x, null)) - { - return false; - } - - if (ReferenceEquals(y, null)) - { - return false; - } - - if (x.GetType() != y.GetType()) - { - return false; - } - - if (x.Metadata.Name != y.Metadata.Name) - { - return false; - } - - // EdgeAgent deployments are equal when they have identical image sections - if (x.Metadata.Name == KubeUtils.SanitizeK8sValue(CoreConstants.EdgeAgentModuleName)) - { - return V1DeploymentEx.ImageEquals(x, y); - } - - // compares by creation string - string xCreationString = GetCreationString(x); - string yCreationString = GetCreationString(y); - - return xCreationString == yCreationString; - } - - static string GetCreationString(V1Deployment deployment) - { - if (deployment.Metadata?.Annotations == null || !deployment.Metadata.Annotations.TryGetValue(Constants.CreationString, out string creationString)) - { - var deploymentWithoutStatus = new V1Deployment(deployment.ApiVersion, deployment.Kind, deployment.Metadata, deployment.Spec); - creationString = JsonConvert.SerializeObject(deploymentWithoutStatus); - } - - return creationString; - } - - public int GetHashCode(V1Deployment obj) => obj.GetHashCode(); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/deployment/KubernetesDeploymentMapper.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/deployment/KubernetesDeploymentMapper.cs deleted file mode 100644 index 001f406977f..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/deployment/KubernetesDeploymentMapper.cs +++ /dev/null @@ -1,460 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Deployment -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; - using Microsoft.Azure.Devices.Edge.Util; - using Newtonsoft.Json; - using Serilog.Events; - using AgentDocker = Microsoft.Azure.Devices.Edge.Agent.Docker; - using CoreConstants = Microsoft.Azure.Devices.Edge.Agent.Core.Constants; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - public class KubernetesDeploymentMapper : IKubernetesDeploymentMapper - { - const string EdgeHubHostname = "edgehub"; - - readonly string deviceNamespace; - readonly string edgeHostname; - readonly string proxyImage; - readonly Option proxyImagePullSecretName; - readonly string proxyConfigPath; - readonly string proxyConfigVolumeName; - readonly string proxyConfigMapName; - readonly string proxyTrustBundlePath; - readonly string proxyTrustBundleVolumeName; - readonly string proxyTrustBundleConfigMapName; - readonly Option proxyResourceRequirements; - readonly Option agentConfigMapName; - readonly Option agentConfigPath; - readonly Option agentConfigVolume; - readonly Option agentResourceRequirements; - readonly PortMapServiceType defaultServiceType; - readonly bool useMountSourceForVolumeName; - readonly Option storageClassName; - readonly Option persistentVolumeClaimDefaultSizeMb; - readonly string workloadApiVersion; - readonly Uri workloadUri; - readonly Uri managementUri; - readonly bool runAsNonRoot; - readonly bool enableServiceCallTracing; - readonly IDictionary experimentalFeatures; - - public KubernetesDeploymentMapper( - string deviceNamespace, - string edgeHostname, - string proxyImage, - Option proxyImagePullSecretName, - string proxyConfigPath, - string proxyConfigVolumeName, - string proxyConfigMapName, - string proxyTrustBundlePath, - string proxyTrustBundleVolumeName, - string proxyTrustBundleConfigMapName, - Option proxyResourceRequirements, - Option agentConfigMapName, - Option agentConfigPath, - Option agentConfigVolume, - Option agentResourceRequirements, - PortMapServiceType defaultServiceType, - bool useMountSourceForVolumeName, - string storageClassName, - Option persistentVolumeClaimDefaultSizeMb, - string workloadApiVersion, - Uri workloadUri, - Uri managementUri, - bool runAsNonRoot, - bool enableServiceCallTracing, - IDictionary experimentalFeatures) - { - this.deviceNamespace = deviceNamespace; - this.edgeHostname = edgeHostname; - this.proxyImage = proxyImage; - this.proxyImagePullSecretName = proxyImagePullSecretName; - this.proxyConfigPath = proxyConfigPath; - this.proxyConfigVolumeName = proxyConfigVolumeName; - this.proxyConfigMapName = proxyConfigMapName; - this.proxyTrustBundlePath = proxyTrustBundlePath; - this.proxyTrustBundleVolumeName = proxyTrustBundleVolumeName; - this.proxyTrustBundleConfigMapName = proxyTrustBundleConfigMapName; - this.proxyResourceRequirements = proxyResourceRequirements; - this.agentConfigMapName = agentConfigMapName; - this.agentConfigPath = agentConfigPath; - this.agentConfigVolume = agentConfigVolume; - this.agentResourceRequirements = agentResourceRequirements; - this.defaultServiceType = defaultServiceType; - this.useMountSourceForVolumeName = useMountSourceForVolumeName; - this.storageClassName = Option.Maybe(storageClassName); - this.persistentVolumeClaimDefaultSizeMb = persistentVolumeClaimDefaultSizeMb; - this.workloadApiVersion = workloadApiVersion; - this.workloadUri = workloadUri; - this.managementUri = managementUri; - this.runAsNonRoot = runAsNonRoot; - this.enableServiceCallTracing = enableServiceCallTracing; - this.experimentalFeatures = experimentalFeatures; - } - - public V1Deployment CreateDeployment(IModuleIdentity identity, KubernetesModule module, IDictionary labels) - { - var deployment = this.PrepareDeployment(identity, module, labels); - deployment.Metadata.Annotations[KubernetesConstants.CreationString] = JsonConvert.SerializeObject(deployment); - - return deployment; - } - - public void UpdateDeployment(V1Deployment to, V1Deployment from) - { - to.Metadata.ResourceVersion = from.Metadata.ResourceVersion; - } - - V1Deployment PrepareDeployment(IModuleIdentity identity, KubernetesModule module, IDictionary labels) - { - string name = identity.DeploymentName(); - - var podSpec = this.GetPod(name, identity, module, labels); - - var selector = new V1LabelSelector(matchLabels: labels); - - V1DeploymentStrategy deploymentStrategy = module.Config.CreateOptions.DeploymentStrategy.OrDefault(); - - // Desired status in Deployment should only be Running or Stopped. Assume Running if not Stopped - int replicas = (module.DesiredStatus != ModuleStatus.Stopped) ? 1 : 0; - var deploymentSpec = new V1DeploymentSpec(replicas: replicas, selector: selector, strategy: deploymentStrategy, template: podSpec); - - var deploymentMeta = new V1ObjectMeta( - name: name, - labels: labels, - annotations: new Dictionary(), - ownerReferences: module.Owner.ToOwnerReferences()); - return new V1Deployment(metadata: deploymentMeta, spec: deploymentSpec); - } - - bool? IsHostIpc(CreatePodParameters config) => - config.HostConfig.FlatMap(h => Option.Maybe(h.IpcMode).Map(ipcMode => string.Compare(ipcMode, KubernetesConstants.HostIPC, true) == 0)) - .Match(i => i, () => default(bool?)); - - bool? IsHostNetwork(CreatePodParameters config) => - config.HostConfig.FlatMap(h => Option.Maybe(h.NetworkMode).Map(networkMode => string.Compare(networkMode, KubernetesConstants.HostNetwork, true) == 0)) - .Match(i => i, () => default(bool?)); - - V1PodTemplateSpec GetPod(string name, IModuleIdentity identity, KubernetesModule module, IDictionary labels) - { - // Convert docker labels to annotations because docker labels don't have the same restrictions as Kubernetes labels. - Dictionary annotations = module.Config.CreateOptions.Labels - .Map(dockerLabels => dockerLabels.ToDictionary(label => KubeUtils.SanitizeAnnotationKey(label.Key), label => label.Value)) - .GetOrElse(() => new Dictionary()); - annotations[KubernetesConstants.K8sEdgeOriginalModuleId] = ModuleIdentityHelper.GetModuleName(identity.ModuleId); - - var (proxyContainer, proxyVolumes) = this.PrepareProxyContainer(module); - var (moduleContainer, moduleVolumes) = this.PrepareModuleContainer(name, identity, module); - bool? hostIpc = this.IsHostIpc(module.Config.CreateOptions); - bool? hostNetwork = this.IsHostNetwork(module.Config.CreateOptions); - string dnsPolicy = (hostNetwork ?? false) ? KubernetesConstants.HostNetworkDnsPolicy : default(string); - - var imagePullSecrets = new List> { this.proxyImagePullSecretName, module.Config.AuthConfig.Map(auth => auth.Name) } - .FilterMap() - .Distinct() - .Select(pullSecretName => new V1LocalObjectReference(pullSecretName)) - .ToList(); - - V1PodSecurityContext securityContext = module.Config.CreateOptions.SecurityContext.GetOrElse( - () => this.runAsNonRoot - ? new V1PodSecurityContext { RunAsNonRoot = true, RunAsUser = 1000 } - : null); - - return new V1PodTemplateSpec - { - Metadata = new V1ObjectMeta - { - Name = name, - Labels = labels, - Annotations = annotations - }, - Spec = new V1PodSpec - { - Containers = new List { proxyContainer, moduleContainer }, - Volumes = proxyVolumes.Concat(moduleVolumes).ToList(), - ImagePullSecrets = imagePullSecrets.Any() ? imagePullSecrets : null, - SecurityContext = securityContext, - ServiceAccountName = name, - NodeSelector = module.Config.CreateOptions.NodeSelector.OrDefault(), - HostIPC = hostIpc, - HostNetwork = hostNetwork, - DnsPolicy = dnsPolicy, - } - }; - } - - (V1Container, IReadOnlyList) PrepareModuleContainer(string name, IModuleIdentity identity, KubernetesModule module) - { - List env = this.CollectModuleEnv(module, identity); - - (List volumes, List volumeMounts) = this.CollectModuleVolumes(module, identity); - - Option securityContext = module.Config.CreateOptions.HostConfig - .Filter(config => config.Privileged) - .Map(config => new V1SecurityContext(privileged: true)); - - Option> exposedPorts = module.Config.CreateOptions.ExposedPorts - .Map(PortExtensions.GetContainerPorts); - - Option resourceRequirements = this.PrepareModuleResourceRequirements(module, identity); - - var container = new V1Container - { - Name = name, - Env = env, - Image = module.Config.Image, - VolumeMounts = volumeMounts, - SecurityContext = securityContext.OrDefault(), - Ports = exposedPorts.OrDefault(), - Resources = resourceRequirements.OrDefault(), - Command = module.Config.CreateOptions.Entrypoint.Map(list => list.ToList()).OrDefault(), - Args = module.Config.CreateOptions.Cmd.Map(list => list.ToList()).OrDefault(), - WorkingDir = module.Config.CreateOptions.WorkingDir.OrDefault(), - }; - - return (container, volumes); - } - - List CollectModuleEnv(KubernetesModule module, IModuleIdentity identity) - { - var envList = module.Env.Select(env => new V1EnvVar(env.Key, env.Value.Value)).ToList(); - - module.Config.CreateOptions.Env.Map(ParseEnv) - .ForEach(hostEnv => envList.AddRange(hostEnv)); - - envList.Add(new V1EnvVar(CoreConstants.IotHubHostnameVariableName, identity.IotHubHostname)); - envList.Add(new V1EnvVar(CoreConstants.EdgeletAuthSchemeVariableName, "sasToken")); - if (!envList.Exists(e => e.Name == Logger.RuntimeLogLevelEnvKey)) - { - envList.Add(new V1EnvVar(Logger.RuntimeLogLevelEnvKey, Logger.GetLogLevel().ToString())); - } - - envList.Add(new V1EnvVar(CoreConstants.EdgeletWorkloadUriVariableName, this.workloadUri.ToString())); - if (identity.Credentials is IdentityProviderServiceCredentials creds) - { - envList.Add(new V1EnvVar(CoreConstants.EdgeletModuleGenerationIdVariableName, creds.ModuleGenerationId)); - } - - envList.Add(new V1EnvVar(CoreConstants.DeviceIdVariableName, identity.DeviceId)); - envList.Add(new V1EnvVar(CoreConstants.ModuleIdVariableName, identity.ModuleId)); - envList.Add(new V1EnvVar(CoreConstants.EdgeletApiVersionVariableName, this.workloadApiVersion)); - - if (string.Equals(identity.ModuleId, CoreConstants.EdgeAgentModuleIdentityName)) - { - envList.Add(new V1EnvVar(CoreConstants.ModeKey, CoreConstants.KubernetesMode)); - envList.Add(new V1EnvVar(CoreConstants.EdgeletManagementUriVariableName, this.managementUri.ToString())); - envList.Add(new V1EnvVar(CoreConstants.NetworkIdKey, "azure-iot-edge")); - envList.Add(new V1EnvVar(KubernetesConstants.ProxyImageEnvKey, this.proxyImage)); - this.proxyImagePullSecretName.ForEach(ips => envList.Add(new V1EnvVar(KubernetesConstants.ProxyImagePullSecretNameEnvKey, ips))); - envList.Add(new V1EnvVar(KubernetesConstants.ProxyConfigPathEnvKey, this.proxyConfigPath)); - envList.Add(new V1EnvVar(KubernetesConstants.ProxyConfigVolumeEnvKey, this.proxyConfigVolumeName)); - envList.Add(new V1EnvVar(KubernetesConstants.ProxyConfigMapNameEnvKey, this.proxyConfigMapName)); - envList.Add(new V1EnvVar(KubernetesConstants.ProxyTrustBundlePathEnvKey, this.proxyTrustBundlePath)); - envList.Add(new V1EnvVar(KubernetesConstants.ProxyTrustBundleVolumeEnvKey, this.proxyTrustBundleVolumeName)); - envList.Add(new V1EnvVar(KubernetesConstants.ProxyTrustBundleConfigMapEnvKey, this.proxyTrustBundleConfigMapName)); - envList.Add(new V1EnvVar(KubernetesConstants.K8sNamespaceKey, this.deviceNamespace)); - envList.Add(new V1EnvVar(KubernetesConstants.RunAsNonRootKey, this.runAsNonRoot.ToString())); - envList.Add(new V1EnvVar(KubernetesConstants.EdgeK8sObjectOwnerApiVersionKey, module.Owner.ApiVersion)); - envList.Add(new V1EnvVar(KubernetesConstants.EdgeK8sObjectOwnerKindKey, module.Owner.Kind)); - envList.Add(new V1EnvVar(KubernetesConstants.EdgeK8sObjectOwnerNameKey, module.Owner.Name)); - envList.Add(new V1EnvVar(KubernetesConstants.EdgeK8sObjectOwnerUidKey, module.Owner.Uid)); - envList.Add(new V1EnvVar(KubernetesConstants.PortMappingServiceType, this.defaultServiceType.ToString())); - envList.Add(new V1EnvVar(KubernetesConstants.EnableK8sServiceCallTracingName, this.enableServiceCallTracing.ToString())); - envList.Add(new V1EnvVar(KubernetesConstants.UseMountSourceForVolumeNameKey, this.useMountSourceForVolumeName.ToString())); - this.storageClassName.ForEach(scName => envList.Add(new V1EnvVar(KubernetesConstants.StorageClassNameKey, scName))); - this.persistentVolumeClaimDefaultSizeMb.ForEach(size => envList.Add(new V1EnvVar(KubernetesConstants.PersistentVolumeClaimDefaultSizeInMbKey, size.ToString()))); - envList.AddRange(this.experimentalFeatures.Select(env => new V1EnvVar(env.Key, env.Value.ToString()))); - } - - if (string.Equals(identity.ModuleId, CoreConstants.EdgeAgentModuleIdentityName) || - string.Equals(identity.ModuleId, CoreConstants.EdgeHubModuleIdentityName)) - { - envList.Add(new V1EnvVar(CoreConstants.EdgeDeviceHostNameKey, this.edgeHostname)); - } - else - { - envList.Add(new V1EnvVar(CoreConstants.GatewayHostnameVariableName, EdgeHubHostname)); - } - - return envList; - } - - static IEnumerable ParseEnv(IReadOnlyList env) => - env.Select(hostEnv => - { - int index = hostEnv.IndexOf('='); - return (index <= 0) ? - Array.Empty() : - new string[] { hostEnv.Substring(0, index), hostEnv.Substring(index + 1) }; - }) - .Where(keyValue => keyValue.Length == 2) - .Select(keyValue => new V1EnvVar(keyValue[0], keyValue[1])); - - (List, List) CollectModuleVolumes(KubernetesModule module, IModuleIdentity identity) - { - var volumeList = new List(); - var volumeMountList = new List(); - - if (string.Equals(identity.ModuleId, CoreConstants.EdgeAgentModuleIdentityName)) - { - // If iotedged is new enough, edgeAgent now has a config map which needs - // to be mounted. - if (this.agentConfigMapName.HasValue && - this.agentConfigPath.HasValue && - this.agentConfigVolume.HasValue) - { - var agentConfigVolume = this.agentConfigVolume.OrDefault(); - volumeList.Add(new V1Volume { Name = agentConfigVolume, ConfigMap = new V1ConfigMapVolumeSource(name: this.agentConfigMapName.OrDefault(), optional: true) }); - volumeMountList.Add(new V1VolumeMount { MountPath = this.agentConfigPath.OrDefault(), Name = agentConfigVolume, ReadOnlyProperty = true }); - } - } - - // collect volumes and volume mounts from HostConfig.Binds section - var binds = module.Config.CreateOptions.HostConfig - .FlatMap(config => Option.Maybe(config.Binds)) - .Map( - hostBinds => hostBinds - .Select(bind => bind.Split(':')) - .Where(bind => bind.Length >= 2) - .Select(bind => new { Name = KubeUtils.SanitizeDNSValue(bind[0]), HostPath = bind[0], MountPath = bind[1], IsReadOnly = bind.Length > 2 && bind[2].Contains("ro") }) - .ToList()); - - binds.Map(hostBinds => hostBinds.Select(bind => new V1Volume(bind.Name, hostPath: new V1HostPathVolumeSource(bind.HostPath, "DirectoryOrCreate")))) - .ForEach(volumes => volumeList.AddRange(volumes)); - - binds.Map(hostBinds => hostBinds.Select(bind => new V1VolumeMount(bind.MountPath, bind.Name, readOnlyProperty: bind.IsReadOnly))) - .ForEach(mounts => volumeMountList.AddRange(mounts)); - - // collect volumes and volume mounts from HostConfig.Mounts section for binds to host path - var bindMounts = module.Config.CreateOptions.HostConfig - .FlatMap(config => Option.Maybe(config.Mounts)) - .Map(mounts => mounts.Where(mount => mount.Type.Equals("bind", StringComparison.InvariantCultureIgnoreCase)).ToList()); - - bindMounts.Map(mounts => mounts.Select(mount => new V1Volume(KubeUtils.SanitizeDNSValue(mount.Source), hostPath: new V1HostPathVolumeSource(mount.Source, "DirectoryOrCreate")))) - .ForEach(volumes => volumeList.AddRange(volumes)); - - bindMounts.Map(mounts => mounts.Select(mount => new V1VolumeMount(mount.Target, KubeUtils.SanitizeDNSValue(mount.Source), readOnlyProperty: mount.ReadOnly))) - .ForEach(mounts => volumeMountList.AddRange(mounts)); - - // collect volumes and volume mounts from HostConfig.Mounts section for volumes - var volumeMounts = module.Config.CreateOptions.HostConfig - .FlatMap(config => Option.Maybe(config.Mounts)) - .Map(mounts => mounts.Where(mount => mount.Type.Equals("volume", StringComparison.InvariantCultureIgnoreCase)).ToList()); - - volumeMounts.Map(mounts => mounts.Select(mount => this.GetVolume(module, mount)) - .GroupBy(x => x.Name) - .Select(x => x.FirstOrDefault())) - .ForEach(volumes => volumeList.AddRange(volumes)); - - volumeMounts.Map(mounts => mounts.Select(mount => this.GetVolumeMount(mount))) - .ForEach(mounts => volumeMountList.AddRange(mounts)); - - // collect volume and volume mounts from CreateOption.Volumes section @kubernetes extended feature - module.Config.CreateOptions.Volumes - .Map(volumes => volumes.Select(volume => volume.Volume).FilterMap()) - .ForEach(volumes => volumeList.AddRange(volumes)); - - module.Config.CreateOptions.Volumes - .Map(volumes => volumes.Select(volume => volume.VolumeMounts).FilterMap()) - .ForEach(mounts => volumeMountList.AddRange(mounts.SelectMany(x => x))); - - return (volumeList, volumeMountList); - } - - Option PrepareModuleResourceRequirements(KubernetesModule module, IModuleIdentity identity) - { - // All modules get their resource requirements from createOptions, - // but Agent has default resources to guarantee QoS if the user didn't set it. - if (!module.Config.CreateOptions.Resources.HasValue && - string.Equals(identity.ModuleId, CoreConstants.EdgeAgentModuleIdentityName)) - { - return this.agentResourceRequirements; - } - - return module.Config.CreateOptions.Resources; - } - - (V1Container, List) PrepareProxyContainer(KubernetesModule module) - { - var env = new List - { - new V1EnvVar("PROXY_LOG", ToProxyLogLevel(Logger.GetLogLevel())) - }; - - var volumeMounts = new List - { - new V1VolumeMount { MountPath = this.proxyConfigPath, Name = this.proxyConfigVolumeName, ReadOnlyProperty = true }, - new V1VolumeMount { MountPath = this.proxyTrustBundlePath, Name = this.proxyTrustBundleVolumeName } - }; - - var volumes = new List - { - new V1Volume { Name = this.proxyConfigVolumeName, ConfigMap = new V1ConfigMapVolumeSource(name: this.proxyConfigMapName) }, - new V1Volume { Name = this.proxyTrustBundleVolumeName, ConfigMap = new V1ConfigMapVolumeSource(name: this.proxyTrustBundleConfigMapName) } - }; - - var resources = this.proxyResourceRequirements.OrDefault(); - - var container = new V1Container - { - Name = "proxy", - Env = env, - Image = this.proxyImage, - Resources = resources, - VolumeMounts = volumeMounts - }; - - return (container, volumes); - } - - static readonly Dictionary ProxyLogLevel = new Dictionary - { - [LogEventLevel.Verbose] = "Trace", - [LogEventLevel.Debug] = "Debug", - [LogEventLevel.Information] = "Info", - [LogEventLevel.Warning] = "Warn", - [LogEventLevel.Error] = "Error", - [LogEventLevel.Fatal] = "Error", - }; - - static string ToProxyLogLevel(LogEventLevel level) - { - if (!ProxyLogLevel.TryGetValue(level, out string proxyLevel)) - { - throw new ArgumentOutOfRangeException(nameof(level), $"Unknown log level: {level}"); - } - - return proxyLevel; - } - - V1Volume GetVolume(KubernetesModule module, Mount mount) - { - // Volume name will be mount.Source - string volumeName = KubeUtils.SanitizeDNSValue(mount.Source); - // PVC name will be volume name - string pvcName = volumeName; - - return new V1Volume - { - Name = volumeName, - PersistentVolumeClaim = new V1PersistentVolumeClaimVolumeSource(pvcName, mount.ReadOnly) - }; - } - - V1VolumeMount GetVolumeMount(Mount mount) - { - // Volume name will be mount.Source - string volumeName = KubeUtils.SanitizeDNSValue(mount.Source); - - return new V1VolumeMount(mount.Target, volumeName, readOnlyProperty: mount.ReadOnly); - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/deployment/KubernetesExtensions.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/deployment/KubernetesExtensions.cs deleted file mode 100644 index 025b8779c0a..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/deployment/KubernetesExtensions.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Deployment -{ - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - - public static class V1PodSpecEx - { - public static bool ImageEquals(this V1PodSpec self, V1PodSpec other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(self, other)) - { - return true; - } - - List otherList = other.Containers.ToList(); - - foreach (V1Container selfContainer in self.Containers) - { - if (otherList.Exists(c => string.Equals(c.Name, selfContainer.Name))) - { - V1Container otherContainer = otherList.Find(c => string.Equals(c.Name, selfContainer.Name)); - if (!string.Equals(selfContainer.Image, otherContainer.Image)) - { - // Container has a new image name. - return false; - } - } - else - { - // container names don't match - return false; - } - } - - return true; - } - } - - public static class V1PodTemplateSpecEx - { - public static bool ImageEquals(this V1PodTemplateSpec self, V1PodTemplateSpec other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(self, other)) - { - return true; - } - - return self.Spec.ImageEquals(other.Spec); - } - } - - public static class V1DeploymentSpecEx - { - public static bool ImageEquals(this V1DeploymentSpec self, V1DeploymentSpec other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(self, other)) - { - return true; - } - - return self.Template.ImageEquals(other.Template); - } - } - - public static class V1DeploymentEx - { - public static bool ImageEquals(V1Deployment self, V1Deployment other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(self, other)) - { - return true; - } - - return string.Equals(self.Kind, other.Kind) && self.Spec.ImageEquals(other.Spec); - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/diff/Diff.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/diff/Diff.cs deleted file mode 100644 index 6a53fa6c5ab..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/diff/Diff.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Diff -{ - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - using Microsoft.Azure.Devices.Edge.Util; - - public class Diff - { - public readonly IImmutableSet Added; - - public readonly IImmutableSet Removed; - - public readonly IImmutableSet> Updated; - - public Diff(IEnumerable added, IEnumerable removed, IEnumerable> updated) - { - this.Added = Preconditions.CheckNotNull(added, nameof(added)).ToImmutableHashSet(); - this.Removed = Preconditions.CheckNotNull(removed, nameof(removed)).ToImmutableHashSet(); - this.Updated = Preconditions.CheckNotNull(updated, nameof(updated)).ToImmutableHashSet(); - } - - public static Diff Empty { get; } = new Diff(ImmutableList.Empty, ImmutableList.Empty, ImmutableList>.Empty); - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - return obj is Diff diffObj && this.Equals(diffObj); - } - - public override int GetHashCode() - { - unchecked - { - int hash = 17; - hash = this.Updated.Aggregate(hash, (acc, item) => acc * 31 + item.GetHashCode()); - hash = this.Removed.Aggregate(hash, (acc, item) => acc * 31 + item.GetHashCode()); - hash = this.Added.Aggregate(hash, (acc, item) => acc * 31 + item.GetHashCode()); - return hash; - } - } - - protected bool Equals(Diff other) => - this.Updated.SetEquals(other.Updated) - && this.Added.SetEquals(other.Added) - && this.Removed.SetEquals(other.Removed); - - public class Builder - { - IReadOnlyList added; - IReadOnlyList> updated; - IReadOnlyList removed; - - public Builder WithAdded(params T[] added) - { - this.added = added; - return this; - } - - public Builder WithUpdated(params Update[] updated) - { - this.updated = updated; - return this; - } - - public Builder WithRemoved(params string[] removed) - { - this.removed = removed; - return this; - } - - public Diff Build() => - new Diff( - this.added ?? ImmutableList.Empty, - this.removed ?? ImmutableList.Empty, - this.updated ?? ImmutableList>.Empty); - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/diff/Set.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/diff/Set.cs deleted file mode 100644 index e8de9816340..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/diff/Set.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Diff -{ - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - using Microsoft.Azure.Devices.Edge.Util; - - public class Set - { - readonly IReadOnlyDictionary items; - - public Set(IReadOnlyDictionary items) - { - this.items = Preconditions.CheckNotNull(items, nameof(items)); - } - - public static readonly Set Empty = new Set(new Dictionary()); - - public Diff Diff(Set other) => this.Diff(other, EqualityComparer.Default); - - public Diff Diff(Set other, IEqualityComparer comparer) - { - // build list of items that are available in "other" set but not available in "this" set; - // this represents items that may need to be created - IEnumerable added = this.items.Keys - .Except(other.items.Keys) - .Select(key => this.items[key]); - - // build list of items that are available in "this" set but not available in "other" set; - // this represents items that may need to be removed - IEnumerable removed = other.items.Keys - .Except(this.items.Keys); - - // build list of items that are available in both sets; - // this represents items that may be updated - IEnumerable> updated = this.items.Keys - .Intersect(other.items.Keys) - .Where(key => !comparer.Equals(this.items[key], other.items[key])) - .Select(key => new Update(other.items[key], this.items[key])); - - return new Diff(added.ToImmutableList(), removed.ToImmutableList(), updated.ToImmutableList()); - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/diff/Update.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/diff/Update.cs deleted file mode 100644 index 78f511d9fe9..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/diff/Update.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Diff -{ - using System.Collections.Generic; - - public sealed class Update - { - public T From { get; } - - public T To { get; } - - public Update(T from, T to) - { - this.From = from; - this.To = to; - } - - bool Equals(Update other) => EqualityComparer.Default.Equals(this.From, other.From) && EqualityComparer.Default.Equals(this.To, other.To); - - public override bool Equals(object obj) - { - if (ReferenceEquals(this, obj)) - { - return true; - } - - return obj is Update other && this.Equals(other); - } - - public override int GetHashCode() - { - unchecked - { - return (EqualityComparer.Default.GetHashCode(this.From) * 397) ^ EqualityComparer.Default.GetHashCode(this.To); - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/pvc/KubernetesPvcByValueEqualityComparer.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/pvc/KubernetesPvcByValueEqualityComparer.cs deleted file mode 100644 index 0f07c4cd793..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/pvc/KubernetesPvcByValueEqualityComparer.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Pvc -{ - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Util; - - public sealed class KubernetesPvcByValueEqualityComparer : IEqualityComparer - { - const string Storage = "storage"; - static readonly DictionaryComparer LabelComparer = DictionaryComparer.StringDictionaryComparer; - - public bool Equals(V1PersistentVolumeClaim x, V1PersistentVolumeClaim y) - { - if (ReferenceEquals(x, y)) - { - return true; - } - - if (ReferenceEquals(x, null)) - { - return false; - } - - if (ReferenceEquals(y, null)) - { - return false; - } - - if (x.GetType() != y.GetType()) - { - return false; - } - - // This should be set for all valid V1PersistentVolumeClaim in this application. - // (Make this check here because SequenceEquals below requires non-null objects.) - if (x.Spec?.AccessModes == null || y.Spec?.AccessModes == null) - { - return false; - } - - // For storage class name and volume name, k8s api fills out the missing fields. - // for equivalence here, either VolumeName or StorageClassName have to be the same. - return x.Metadata?.Name == y.Metadata?.Name && - LabelComparer.Equals(x.Metadata?.Labels, y.Metadata?.Labels) && - (x.Spec?.VolumeName == y.Spec?.VolumeName || - x.Spec?.StorageClassName == y.Spec?.StorageClassName) && - x.Spec.AccessModes.SequenceEqual(y.Spec.AccessModes) && - this.GetStorage(x) == this.GetStorage(y); - } - - Option GetStorage(V1PersistentVolumeClaim claim) => Option.Maybe(claim.Spec?.Resources?.Requests).FlatMap(requests => requests.Get(Storage)); - - public int GetHashCode(V1PersistentVolumeClaim obj) - { - unchecked - { - int hashCode = obj.Spec?.AccessModes != null ? this.GetAccessHash(obj.Spec.AccessModes) : 0; - hashCode = (hashCode * 397) ^ (obj.Metadata?.Name != null ? obj.Metadata.Name.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (obj.Metadata?.Labels != null ? this.GetLabelHash(obj.Metadata.Labels) : 0); - hashCode = (hashCode * 397) ^ (obj.Spec?.StorageClassName != null ? obj.Spec.StorageClassName.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (obj.Spec?.VolumeName != null ? obj.Spec.VolumeName.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ this.GetStorage(obj).GetHashCode(); - return hashCode; - } - } - - int GetAccessHash(IList access) - { - unchecked - { - return access.Aggregate(0, (current, item) => (current * 397) ^ (item != null ? item.GetHashCode() : 0)); - } - } - - int GetLabelHash(IDictionary labels) - { - unchecked - { - return labels.Aggregate(0, (current, item) => - { - int itemHash = current; - itemHash = (itemHash * 397) ^ item.Key.GetHashCode(); - itemHash = (itemHash * 397) ^ ((item.Value != null) ? item.Value.GetHashCode() : 0); - return itemHash; - }); - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/pvc/KubernetesPvcMapper.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/pvc/KubernetesPvcMapper.cs deleted file mode 100644 index fdeaf4c1901..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/pvc/KubernetesPvcMapper.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Pvc -{ - using System; - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Docker.Models; - using Microsoft.Azure.Devices.Edge.Util; - - public class KubernetesPvcMapper : IKubernetesPvcMapper - { - readonly bool useMountSourceForVolumeName; - readonly Option storageClassName; - readonly uint persistentVolumeClaimSizeMb; - - public KubernetesPvcMapper( - bool useMountSourceForVolumeName, - string storageClassName, - uint persistentVolumeClaimSizeMb) - { - this.useMountSourceForVolumeName = useMountSourceForVolumeName; - this.storageClassName = Option.Maybe(storageClassName); - this.persistentVolumeClaimSizeMb = persistentVolumeClaimSizeMb; - } - - public Option> CreatePersistentVolumeClaims(KubernetesModule module, IDictionary labels) => - module.Config.CreateOptions.HostConfig - .FlatMap(hostConfig => Option.Maybe(hostConfig.Mounts)) - .Map(mounts => mounts.Where(this.ShouldCreatePvc).Select(mount => this.ExtractPvc(module, mount, labels)).ToList()) - .Filter(mounts => mounts.Any()); - - bool ShouldCreatePvc(Mount mount) - { - if (!mount.Type.Equals("volume", StringComparison.InvariantCultureIgnoreCase)) - { - return false; - } - - return this.storageClassName.HasValue; - } - - V1PersistentVolumeClaim ExtractPvc(KubernetesModule module, Mount mount, IDictionary labels) - { - string volumeName = KubeUtils.SanitizeK8sValue(mount.Source); - string pvcName = volumeName; - bool readOnly = mount.ReadOnly; - - var persistentVolumeClaimSpec = new V1PersistentVolumeClaimSpec() - { - // What happens if the PV access mode is not compatible with the access we're requesting? - // Deployment will be created and will be in a failed state. The user will see this as - // module running == false. - AccessModes = new List { readOnly ? "ReadOnlyMany" : "ReadWriteMany" }, - Resources = new V1ResourceRequirements() - { - Requests = new Dictionary() { { "storage", new ResourceQuantity($"{this.persistentVolumeClaimSizeMb}Mi") } } - }, - }; - if (this.useMountSourceForVolumeName) - { - persistentVolumeClaimSpec.VolumeName = volumeName; - } - - if (this.storageClassName.HasValue) - { - persistentVolumeClaimSpec.StorageClassName = this.storageClassName.OrDefault(); - } - - var pvcMeta = new V1ObjectMeta( - name: pvcName, - labels: labels, - ownerReferences: module.Owner.ToOwnerReferences()); - - return new V1PersistentVolumeClaim(metadata: pvcMeta, spec: persistentVolumeClaimSpec); - } - - public void UpdatePersistentVolumeClaim(V1PersistentVolumeClaim to, V1PersistentVolumeClaim from) - { - to.Metadata.ResourceVersion = from.Metadata.ResourceVersion; - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/KubernetesServiceByCreationStringEqualityComparer.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/KubernetesServiceByCreationStringEqualityComparer.cs deleted file mode 100644 index 4557a3cc4a2..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/KubernetesServiceByCreationStringEqualityComparer.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service -{ - using System.Collections.Generic; - using k8s.Models; - using Newtonsoft.Json; - - public sealed class KubernetesServiceByCreationStringEqualityComparer : IEqualityComparer - { - public bool Equals(V1Service x, V1Service y) - { - if (ReferenceEquals(x, y)) - { - return true; - } - - if (ReferenceEquals(x, null)) - { - return false; - } - - if (ReferenceEquals(y, null)) - { - return false; - } - - if (x.GetType() != y.GetType()) - { - return false; - } - - string xCreationString = GetCreationString(x); - string yCreationString = GetCreationString(y); - - return xCreationString == yCreationString; - } - - static string GetCreationString(V1Service service) - { - if (service.Metadata?.Annotations == null || !service.Metadata.Annotations.TryGetValue(Constants.CreationString, out string creationString)) - { - var serviceWithoutStatus = new V1Service(service.ApiVersion, service.Kind, service.Metadata, service.Spec); - creationString = JsonConvert.SerializeObject(serviceWithoutStatus); - } - - return creationString; - } - - public int GetHashCode(V1Service obj) => obj.GetHashCode(); - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/KubernetesServiceMapper.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/KubernetesServiceMapper.cs deleted file mode 100644 index afedc64ab5d..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/KubernetesServiceMapper.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service -{ - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Util; - using Newtonsoft.Json; - using AgentDocker = Microsoft.Azure.Devices.Edge.Agent.Docker; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - public class KubernetesServiceMapper : IKubernetesServiceMapper - { - readonly PortMapServiceType defaultMapServiceType; - - public KubernetesServiceMapper(PortMapServiceType defaultMapServiceType) - { - this.defaultMapServiceType = defaultMapServiceType; - } - - public Option CreateService(IModuleIdentity identity, KubernetesModule module, IDictionary labels) - { - Option service = this.PrepareService(identity, module, labels); - service.ForEach(s => s.Metadata.Annotations[KubernetesConstants.CreationString] = JsonConvert.SerializeObject(s)); - - return service; - } - - public void UpdateService(V1Service to, V1Service from) - { - to.Metadata.ResourceVersion = from.Metadata.ResourceVersion; - to.Spec.ClusterIP = from.Spec.ClusterIP; - } - - Option PrepareService(IModuleIdentity identity, KubernetesModule module, IDictionary labels) - { - // Add annotations from Docker labels. This provides the customer a way to assign annotations to services if they want - // to tie backend services to load balancers via an Ingress Controller. - var annotations = module.Config.CreateOptions.Labels - .Map(dockerLabels => dockerLabels.ToDictionary(label => KubeUtils.SanitizeAnnotationKey(label.Key), label => label.Value)) - .GetOrElse(() => new Dictionary()); - - // Entries in the Exposed Port list just tell Docker that this container wants to listen on that port. - // We interpret this as a "ClusterIP" service type listening on that exposed port, backed by this module. - // Users of this Module's exposed port should be able to find the service by connecting to ":" - Option> exposedPorts = module.Config.CreateOptions.ExposedPorts - .Map(ports => ports.GetExposedPorts()); - - // Entries in Docker portMap wants to expose a port on the host (hostPort) and map it to the container's port (port) - // We interpret that as the pod wants the cluster to expose a port on a public IP (hostPort), and target it to the container's port (port) - Option> hostPorts = module.Config.CreateOptions.HostConfig - .FlatMap(config => Option.Maybe(config.PortBindings).Map(ports => ports.GetHostPorts())); - - bool onlyExposedPorts = !hostPorts.HasValue; - - // override exposed ports with host ports - var servicePorts = new Dictionary(); - exposedPorts.ForEach(ports => ports.ForEach(port => servicePorts[port.Port] = port)); - hostPorts.ForEach(ports => ports.ForEach(port => servicePorts[port.Port] = port)); - - if (servicePorts.Any()) - { - // Selector: by module name and device name, also how we will label this puppy. - string name = identity.DeploymentName(); - var serviceMeta = new V1ObjectMeta( - name: name, - labels: labels, - annotations: annotations, - ownerReferences: module.Owner.ToOwnerReferences()); - - // How we manage this service is dependent on the port mappings user asks for. - // If the user tells us to only use ClusterIP ports, we will always set the type to ClusterIP. - // If all we had were exposed ports, we will assume ClusterIP. Otherwise, we use the given value as the default service type - // - // If the user wants to expose the ClusterIPs port externally, they should manually create a service to expose it. - // This gives the user more control as to how they want this to work. - var serviceType = this.GetServiceType(module, onlyExposedPorts); - var loadBalancerIP = module.Config.CreateOptions.ServiceOptions.FlatMap(so => so.LoadBalancerIP).OrDefault(); - - return Option.Some(new V1Service(metadata: serviceMeta, spec: new V1ServiceSpec(type: serviceType.ToString(), loadBalancerIP: loadBalancerIP, ports: servicePorts.Values.ToList(), selector: labels))); - } - - return Option.None(); - } - - PortMapServiceType GetServiceType(KubernetesModule module, bool onlyExposedPorts) - { - var serviceType = module.Config.CreateOptions.ServiceOptions.FlatMap(so => so.Type); - return serviceType.GetOrElse(() => onlyExposedPorts - ? PortMapServiceType.ClusterIP - : this.defaultMapServiceType); - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/KubernetesServiceOptions.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/KubernetesServiceOptions.cs deleted file mode 100644 index de9c78f668b..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/KubernetesServiceOptions.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service -{ - using System; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Json; - using Newtonsoft.Json; - - public class KubernetesServiceOptions - { - public KubernetesServiceOptions(string loadBalancerIP, string type) - { - PortMapServiceType serviceType; - this.LoadBalancerIP = Option.Maybe(loadBalancerIP); - this.Type = Enum.TryParse(type, true, out serviceType) ? Option.Some(serviceType) : Option.None(); - } - - [JsonProperty("LoadBalancerIP", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter))] - public Option LoadBalancerIP { get; } - - [JsonProperty("Type", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [JsonConverter(typeof(OptionConverter))] - public Option Type { get; } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/PortAndProtocol.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/PortAndProtocol.cs deleted file mode 100644 index 804bba236f3..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/PortAndProtocol.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service -{ - using System; - using System.Collections.Generic; - using Microsoft.Azure.Devices.Edge.Util; - - public class PortAndProtocol - { - static Option empty = Option.None(); - static string defaultProtocol = "TCP"; - - public int Port { get; } - - public string Protocol { get; } - - PortAndProtocol(int port, string protocol) - { - this.Port = port; - this.Protocol = protocol; - } - - public static Option Parse(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - return empty; - } - - string[] portProtocol = name.Split('/', StringSplitOptions.RemoveEmptyEntries); - if (portProtocol.Length > 2) - { - return empty; - } - - if (!int.TryParse(portProtocol[0], out int port)) - { - return empty; - } - - // Docker defaults to TCP if not specified. - string protocol = defaultProtocol; - if (portProtocol.Length > 1) - { - if (!SupportedProtocols.TryGetValue(portProtocol[1], out protocol)) - { - return empty; - } - } - - return Option.Some(new PortAndProtocol(port, protocol)); - } - - static readonly Dictionary SupportedProtocols = new Dictionary(StringComparer.InvariantCultureIgnoreCase) - { - ["TCP"] = "TCP", - ["UDP"] = "UDP", - ["SCTP"] = "SCTP", - }; - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/PortExtensions.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/PortExtensions.cs deleted file mode 100644 index 89f3be4a3c7..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/PortExtensions.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service -{ - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Docker.Models; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Extensions.Logging; - - public static class PortExtensions - { - public static List GetContainerPorts(this IDictionary ports) - => ports.Select(port => ExtractContainerPort(port.Key)).FilterMap().ToList(); - - static Option ExtractContainerPort(string exposedPort) - => PortAndProtocol.Parse(exposedPort) - .Map(portAndProtocol => new V1ContainerPort(portAndProtocol.Port, protocol: portAndProtocol.Protocol)); - - public static List GetExposedPorts(this IDictionary ports) - => ports.Select(port => ExtractExposedPort(port.Key)).FilterMap().ToList(); - - static Option ExtractExposedPort(string exposedPort) => - PortAndProtocol.Parse(exposedPort) - .Map(portAndProtocol => new V1ServicePort(portAndProtocol.Port, name: $"ExposedPort-{portAndProtocol.Port}-{portAndProtocol.Protocol}".ToLowerInvariant(), protocol: portAndProtocol.Protocol)); - - public static List GetHostPorts(this IDictionary> ports) - => ports.SelectMany(port => ExtractHostPorts(port.Key, port.Value)).ToList(); - - static IEnumerable ExtractHostPorts(string name, IEnumerable bindings) - => - PortAndProtocol.Parse(name) - .Map( - portAndProtocol => - bindings.Select( - hostBinding => - { - if (int.TryParse(hostBinding.HostPort, out int hostPort)) - { - return Option.Some(new V1ServicePort(hostPort, name: $"HostPort-{portAndProtocol.Port}-{portAndProtocol.Protocol}".ToLowerInvariant(), protocol: portAndProtocol.Protocol, targetPort: portAndProtocol.Port)); - } - - Events.PortBindingValue(name); - return Option.None(); - }) - .FilterMap() - .ToList()) - .GetOrElse(() => new List()); - - static class Events - { - const int IdStart = KubernetesEventIds.KubernetesModelValidation; - static readonly ILogger Log = Logger.Factory.CreateLogger(); - - enum EventIds - { - InvalidExposedPortValue = IdStart, - PortBindingValue - } - - public static void InvalidExposedPortValue(string portEntry) - { - Log.LogWarning((int)EventIds.InvalidExposedPortValue, $"Received an invalid exposed port value '{portEntry}'."); - } - - public static void PortBindingValue(string portEntry) - { - Log.LogWarning((int)EventIds.PortBindingValue, $"Received invalid port binding value '{portEntry}'."); - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/PortMapServiceType.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/PortMapServiceType.cs deleted file mode 100644 index 6616f21dcc3..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/service/PortMapServiceType.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service -{ - using System.Runtime.Serialization; - using Newtonsoft.Json; - using Newtonsoft.Json.Converters; - - [JsonConverter(typeof(StringEnumConverter))] - public enum PortMapServiceType - { - [EnumMember(Value = "ClusterIP")] - ClusterIP, - - [EnumMember(Value = "LoadBalancer")] - LoadBalancer, - - [EnumMember(Value = "NodePort")] - NodePort, - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/serviceaccount/KubernetesServiceAccountByValueEqualityComparer.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/serviceaccount/KubernetesServiceAccountByValueEqualityComparer.cs deleted file mode 100644 index 04b3d0c7c8e..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/serviceaccount/KubernetesServiceAccountByValueEqualityComparer.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.ServiceAccount -{ - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Util; - - public sealed class KubernetesServiceAccountByValueEqualityComparer : IEqualityComparer - { - static readonly DictionaryComparer MetadataComparer = DictionaryComparer.StringDictionaryComparer; - - public bool Equals(V1ServiceAccount x, V1ServiceAccount y) - { - if (ReferenceEquals(x, y)) - { - return true; - } - - if (ReferenceEquals(x, null)) - { - return false; - } - - if (ReferenceEquals(y, null)) - { - return false; - } - - if (x.GetType() != y.GetType()) - { - return false; - } - - return x.Metadata.Name == y.Metadata.Name && - MetadataComparer.Equals(x.Metadata.Labels, y.Metadata.Labels) && - MetadataComparer.Equals(x.Metadata.Annotations, y.Metadata.Annotations); - } - - public int GetHashCode(V1ServiceAccount obj) - { - unchecked - { - int hashCode = obj.Metadata?.Name != null ? obj.Metadata.Name.GetHashCode() : 0; - hashCode = (hashCode * 397) ^ (obj.Metadata?.Labels != null ? obj.Metadata.Labels.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (obj.Metadata?.Annotations != null ? obj.Metadata.Annotations.GetHashCode() : 0); - - return hashCode; - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/serviceaccount/KubernetesServiceAccountMapper.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/serviceaccount/KubernetesServiceAccountMapper.cs deleted file mode 100644 index 2cfdf1a76eb..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/edgedeployment/serviceaccount/KubernetesServiceAccountMapper.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.ServiceAccount -{ - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - public class KubernetesServiceAccountMapper : IKubernetesServiceAccountMapper - { - public V1ServiceAccount CreateServiceAccount(KubernetesModule module, IModuleIdentity identity, IDictionary labels) - { - string name = identity.DeploymentName(); - var annotations = new Dictionary - { - [KubernetesConstants.K8sEdgeOriginalModuleId] = ModuleIdentityHelper.GetModuleName(identity.ModuleId) - }; - - var metadata = new V1ObjectMeta( - annotations, - name: name, - labels: labels, - ownerReferences: module.Owner.ToOwnerReferences()); - return new V1ServiceAccount(metadata: metadata); - } - - public void UpdateServiceAccount(V1ServiceAccount to, V1ServiceAccount from) - { - to.Metadata.ResourceVersion = from.Metadata.ResourceVersion; - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/planners/KubernetesPlanner.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/planners/KubernetesPlanner.cs deleted file mode 100644 index 33064d9f917..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Kubernetes/planners/KubernetesPlanner.cs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Planners -{ - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - using System.Threading.Tasks; - using k8s; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Extensions.Logging; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - public class KubernetesPlanner : IPlanner - { - readonly IKubernetes client; - readonly ICommandFactory commandFactory; - readonly string deviceSelector; - readonly string deviceNamespace; - readonly ResourceName resourceName; - readonly ICombinedConfigProvider configProvider; - readonly JsonSerializerSettings serializerSettings; - - readonly KubernetesModuleOwner moduleOwner; - - public KubernetesPlanner( - ResourceName resourceName, - string deviceSelector, - string deviceNamespace, - IKubernetes client, - ICommandFactory commandFactory, - ICombinedConfigProvider configProvider, - KubernetesModuleOwner moduleOwner) - { - this.resourceName = Preconditions.CheckNotNull(resourceName, nameof(resourceName)); - this.deviceSelector = Preconditions.CheckNonWhiteSpace(deviceSelector, nameof(deviceSelector)); - this.deviceNamespace = Preconditions.CheckNonWhiteSpace(deviceNamespace, nameof(deviceNamespace)); - this.client = Preconditions.CheckNotNull(client, nameof(client)); - this.commandFactory = Preconditions.CheckNotNull(commandFactory, nameof(commandFactory)); - this.configProvider = Preconditions.CheckNotNull(configProvider, nameof(configProvider)); - this.moduleOwner = Preconditions.CheckNotNull(moduleOwner); - this.serializerSettings = EdgeDeploymentSerialization.SerializerSettings; - } - - public async Task PlanAsync( - ModuleSet desired, - ModuleSet current, - IRuntimeInfo runtimeInfo, - IImmutableDictionary moduleIdentities) - { - Events.LogDesired(desired); - - // We receive current ModuleSet from Agent based on what it reports (i.e. pods). - // We need to rebuild the current ModuleSet based on deployments (i.e. CRD). - Option activeDeployment = await this.GetCurrentEdgeDeploymentDefinitionAsync(); - ModuleSet currentModules = - activeDeployment.Match( - a => ModuleSet.Create(a.Spec.ToArray()), - () => ModuleSet.Empty); - - Events.LogCurrent(currentModules); - - // Check that module names sanitize and remain unique. - var groupedModules = desired.Modules.ToLookup(pair => KubeUtils.SanitizeK8sValue(pair.Key)); - if (groupedModules.Any(c => c.Count() > 1)) - { - string nameList = groupedModules - .Where(c => c.Count() > 1) - .SelectMany(g => g, (pairs, pair) => pair.Key) - .Join(","); - throw new InvalidIdentityException($"Deployment will cause a name collision in Kubernetes namespace, modules: [{nameList}]"); - } - - // TODO: improve this so it is generic for all potential module types. - if (!desired.Modules.Values.All(p => p is IModule)) - { - throw new InvalidModuleException($"Kubernetes deployment currently only handles type={typeof(DockerConfig).FullName}"); - } - - Diff moduleDifference = desired.Diff(currentModules); - - Plan plan; - if (!moduleDifference.IsEmpty) - { - // The "Plan" here is very simple - if we have any change, publish all desired modules to a EdgeDeployment CRD. - var crdCommand = new EdgeDeploymentCommand(this.resourceName, this.deviceSelector, this.deviceNamespace, this.client, desired.Modules.Values, activeDeployment, runtimeInfo, this.configProvider, this.moduleOwner); - var planCommand = await this.commandFactory.WrapAsync(crdCommand); - var planList = new List - { - planCommand - }; - await activeDeployment.ForEachAsync(async edgeDeployment => - { - var deploymentStatusCommand = new EdgeDeploymentStatusCommand(edgeDeployment.Status); - var statusCommand = await this.commandFactory.WrapAsync(deploymentStatusCommand); - planList.Add(statusCommand); - }); - Events.PlanCreated(planList); - plan = new Plan(planList); - } - else - { - plan = Plan.Empty; - } - - return plan; - } - - async Task> GetCurrentEdgeDeploymentDefinitionAsync() - { - Option activeDeployment; - try - { - JObject currentDeployment = await this.client.GetNamespacedCustomObjectAsync( - KubernetesConstants.EdgeDeployment.Group, - KubernetesConstants.EdgeDeployment.Version, - this.deviceNamespace, - KubernetesConstants.EdgeDeployment.Plural, - this.resourceName) as JObject; - - activeDeployment = Option.Maybe(currentDeployment) - .Map(deployment => deployment.ToObject(JsonSerializer.Create(this.serializerSettings))); - } - catch (Exception parseException) - { - Events.UnableToGetEdgeDeploymentDefinition(this.deviceNamespace, this.resourceName, parseException); - activeDeployment = Option.None(); - } - - return activeDeployment; - } - - public Task CreateShutdownPlanAsync(ModuleSet current) => Task.FromResult(Plan.Empty); - - static class Events - { - const int IdStart = KubernetesEventIds.KubernetesPlanner; - static readonly ILogger Log = Logger.Factory.CreateLogger(); - - enum EventIds - { - PlanCreated = IdStart, - DesiredModules, - CurrentModules, - ListModules, - } - - public static void PlanCreated(IReadOnlyList commands) - { - Log.LogDebug((int)EventIds.PlanCreated, $"KubernetesPlanner created Plan, with {commands.Count} command(s)."); - } - - public static void LogDesired(ModuleSet desired) - { - Log.LogDebug((int)EventIds.DesiredModules, $"List of desired modules is: [{string.Join(", ", desired.Modules.Keys)}]"); - } - - public static void LogCurrent(ModuleSet current) - { - Log.LogDebug((int)EventIds.CurrentModules, $"List of current modules is: [{string.Join(", ", current.Modules.Keys)}]"); - } - - public static void UnableToGetEdgeDeploymentDefinition(string deviceNamespace, ResourceName name, Exception exception) - { - Log.LogDebug((int)EventIds.ListModules, exception, $"Unable to get edge deployment definition: {name} in namespace {deviceNamespace}"); - } - } - } -} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Microsoft.Azure.Devices.Edge.Agent.Service.csproj b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Microsoft.Azure.Devices.Edge.Agent.Service.csproj index 21e3805ffee..0f41e2f8b1b 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Microsoft.Azure.Devices.Edge.Agent.Service.csproj +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Microsoft.Azure.Devices.Edge.Agent.Service.csproj @@ -22,7 +22,6 @@ - diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Program.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Program.cs index a1757ce2662..8a7d9a4d0f1 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Program.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Program.cs @@ -15,9 +15,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service using Microsoft.Azure.Devices.Edge.Agent.Diagnostics; using Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker; using Microsoft.Azure.Devices.Edge.Agent.IoTHub.Stream; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; using Microsoft.Azure.Devices.Edge.Agent.Service.Modules; using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util; @@ -26,8 +23,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Constants = Microsoft.Azure.Devices.Edge.Agent.Core.Constants; - using K8sConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - using KubernetesModule = Microsoft.Azure.Devices.Edge.Agent.Service.Modules.KubernetesModule; using StorageLogLevel = Microsoft.Azure.Devices.Edge.Storage.StorageLogLevel; public class Program @@ -181,67 +176,6 @@ public static async Task MainAsync(IConfiguration configuration) break; case Constants.KubernetesMode: - managementUri = configuration.GetValue(Constants.EdgeletManagementUriVariableName); - workloadUri = configuration.GetValue(Constants.EdgeletWorkloadUriVariableName); - moduleId = configuration.GetValue(Constants.ModuleIdVariableName, Constants.EdgeAgentModuleIdentityName); - moduleGenerationId = configuration.GetValue(Constants.EdgeletModuleGenerationIdVariableName); - apiVersion = configuration.GetValue(Constants.EdgeletApiVersionVariableName); - iothubHostname = configuration.GetValue(Constants.IotHubHostnameVariableName); - deviceId = configuration.GetValue(Constants.DeviceIdVariableName); - // Get additional k8s configuration from the configmap and environment. - IConfigurationRoot k8sConfiguration = new ConfigurationBuilder() - .AddJsonFile(K8sConfigFileName, true) - .AddEnvironmentVariables() - .Build(); - // k8s options - KubernetesApplicationSettings k8sSettings = k8sConfiguration.Get(); - Option proxyImagePullSecretName = Option.Maybe(k8sConfiguration.GetValue(K8sConstants.ProxyImagePullSecretNameEnvKey)); - PortMapServiceType mappedServiceDefault = GetDefaultServiceType(k8sConfiguration); - bool enableServiceCallTracing = k8sConfiguration.GetValue(K8sConstants.EnableK8sServiceCallTracingName); - bool useMountSourceForVolumeName = k8sConfiguration.GetValue(K8sConstants.UseMountSourceForVolumeNameKey, false); - string storageClassName = k8sConfiguration.GetValue(K8sConstants.StorageClassNameKey); - Option persistentVolumeClaimDefaultSizeMb = Option.Maybe(k8sConfiguration.GetValue(K8sConstants.PersistentVolumeClaimDefaultSizeInMbKey)); - string deviceNamespace = k8sConfiguration.GetValue(K8sConstants.K8sNamespaceKey); - var kubernetesExperimentalFeatures = KubernetesExperimentalFeatures.Create(k8sConfiguration.GetSection("experimentalFeatures"), logger); - var moduleOwner = new KubernetesModuleOwner( - k8sConfiguration.GetValue(K8sConstants.EdgeK8sObjectOwnerApiVersionKey), - k8sConfiguration.GetValue(K8sConstants.EdgeK8sObjectOwnerKindKey), - k8sConfiguration.GetValue(K8sConstants.EdgeK8sObjectOwnerNameKey), - k8sConfiguration.GetValue(K8sConstants.EdgeK8sObjectOwnerUidKey)); - bool runAsNonRoot = k8sConfiguration.GetValue(K8sConstants.RunAsNonRootKey); - - builder.RegisterModule(new AgentModule(maxRestartCount, intensiveCareTime, coolOffTimeUnitInSeconds, usePersistentStorage, storagePath, Option.Some(new Uri(workloadUri)), Option.Some(apiVersion), moduleId, Option.Some(moduleGenerationId), enableNonPersistentStorageBackup, storageBackupPath, storageTotalMaxWalSize, storageMaxOpenFiles, storageLogLevel)); - builder.RegisterModule( - new KubernetesModule( - iothubHostname, - deviceId, - edgeDeviceHostName, - k8sSettings, - proxyImagePullSecretName, - apiVersion, - deviceNamespace, - new Uri(managementUri), - new Uri(workloadUri), - dockerAuthConfig, - upstreamProtocol, - Option.Some(productInfo), - mappedServiceDefault, - enableServiceCallTracing, - useMountSourceForVolumeName, - storageClassName, - persistentVolumeClaimDefaultSizeMb, - proxy, - closeOnIdleTimeout, - idleTimeout, - useServerHeartbeat, - kubernetesExperimentalFeatures, - moduleOwner, - runAsNonRoot)); - - trustBundle = await CertificateHelper.GetTrustBundleFromEdgelet(new Uri(workloadUri), apiVersion, Constants.WorkloadApiVersion, moduleId, moduleGenerationId); - CertificateHelper.InstallCertificates(trustBundle, logger); - break; - default: throw new InvalidOperationException($"Mode '{mode}' not supported."); } @@ -287,26 +221,6 @@ public static async Task MainAsync(IConfiguration configuration) return 1; } - // TODO move this code to Agent - if (mode.ToLowerInvariant().Equals(Constants.KubernetesMode)) - { - // Block agent startup routine until proxy sidecar container is ready - string managementUri = configuration.GetValue(Constants.EdgeletManagementUriVariableName); - string apiVersion = configuration.GetValue(Constants.EdgeletApiVersionVariableName); - ProxyReadinessProbe probe = new ProxyReadinessProbe(new Uri(managementUri), apiVersion); - - CancellationTokenSource tokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(5)); - await probe.WaitUntilProxyIsReady(tokenSource.Token); - - // Start environment operator - IKubernetesEnvironmentOperator environmentOperator = container.Resolve(); - environmentOperator.Start(); - - // Start the edge deployment operator - IEdgeDeploymentOperator edgeDeploymentOperator = container.Resolve(); - edgeDeploymentOperator.Start(); - } - // Initialize metrics if (metricsConfig.Enabled) { @@ -458,11 +372,6 @@ static string GetFullBackupFilePath(string storageFolder, string backupFilePath) return Path.Join(storageFolder, backupFilePath); } - static PortMapServiceType GetDefaultServiceType(IConfiguration configuration) => - Enum.TryParse(configuration.GetValue(K8sConstants.PortMappingServiceType, string.Empty), true, out PortMapServiceType defaultServiceType) - ? defaultServiceType - : Kubernetes.Constants.DefaultPortMapServiceType; - static async Task CloseDbStoreProviderAsync(IContainer container) { IDbStoreProvider dbStoreProvider = await container.Resolve>(); diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/KubernetesModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/KubernetesModule.cs deleted file mode 100644 index c8685c33702..00000000000 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/KubernetesModule.cs +++ /dev/null @@ -1,409 +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.Net.Http; - using System.Threading; - using System.Threading.Tasks; - using Autofac; - using k8s; - using Microsoft.Azure.Devices.Edge.Agent.Core; - 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.Edgelet; - using Microsoft.Azure.Devices.Edge.Agent.IoTHub; - using Microsoft.Azure.Devices.Edge.Agent.IoTHub.SdkClient; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Deployment; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Pvc; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.ServiceAccount; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Planners; - using Microsoft.Azure.Devices.Edge.Storage; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Metrics; - using Microsoft.Extensions.Logging; - using Microsoft.Rest; - using Constants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - public class KubernetesModule : Module - { - static readonly TimeSpan SystemInfoTimeout = TimeSpan.FromSeconds(120); - readonly ResourceName resourceName; - readonly string edgeDeviceHostName; - readonly KubernetesApplicationSettings k8sSettings; - readonly Option proxyImagePullSecretName; - readonly string apiVersion; - readonly string deviceNamespace; - readonly string deviceSelector; - readonly Uri managementUri; - readonly Uri workloadUri; - readonly IEnumerable dockerAuthConfig; - readonly Option upstreamProtocol; - readonly Option productInfo; - readonly PortMapServiceType defaultMapServiceType; - readonly bool enableServiceCallTracing; - readonly bool useMountSourceForVolumeName; - readonly string storageClassName; - readonly Option persistentVolumeClaimSizeMb; - readonly Option proxy; - readonly bool closeOnIdleTimeout; - readonly TimeSpan idleTimeout; - readonly bool useServerHeartbeat; - readonly KubernetesExperimentalFeatures experimentalFeatures; - readonly KubernetesModuleOwner moduleOwner; - readonly bool runAsNonRoot; - - public KubernetesModule( - string iotHubHostname, - string deviceId, - string edgeDeviceHostName, - KubernetesApplicationSettings k8sSettings, - Option proxyImagePullSecretName, - string apiVersion, - string deviceNamespace, - Uri managementUri, - Uri workloadUri, - IEnumerable dockerAuthConfig, - Option upstreamProtocol, - Option productInfo, - PortMapServiceType defaultMapServiceType, - bool enableServiceCallTracing, - bool useMountSourceForVolumeName, - string storageClassName, - Option persistentVolumeClaimSizeMb, - Option proxy, - bool closeOnIdleTimeout, - TimeSpan idleTimeout, - bool useServerHeartbeat, - KubernetesExperimentalFeatures experimentalFeatures, - KubernetesModuleOwner moduleOwner, - bool runAsNonRoot) - { - this.resourceName = new ResourceName(iotHubHostname, deviceId); - this.edgeDeviceHostName = Preconditions.CheckNonWhiteSpace(edgeDeviceHostName, nameof(edgeDeviceHostName)); - this.k8sSettings = Preconditions.CheckNotNull(k8sSettings, nameof(k8sSettings)); - Preconditions.CheckNonWhiteSpace(k8sSettings.ProxyImage, nameof(k8sSettings.ProxyImage)); - Preconditions.CheckNonWhiteSpace(k8sSettings.ProxyConfigPath, nameof(k8sSettings.ProxyConfigPath)); - Preconditions.CheckNonWhiteSpace(k8sSettings.ProxyConfigVolume, nameof(k8sSettings.ProxyConfigVolume)); - Preconditions.CheckNonWhiteSpace(k8sSettings.ProxyConfigMapName, nameof(k8sSettings.ProxyConfigMapName)); - Preconditions.CheckNonWhiteSpace(k8sSettings.ProxyTrustBundlePath, nameof(k8sSettings.ProxyTrustBundlePath)); - Preconditions.CheckNonWhiteSpace(k8sSettings.ProxyTrustBundleVolume, nameof(k8sSettings.ProxyTrustBundleVolume)); - Preconditions.CheckNonWhiteSpace(k8sSettings.ProxyTrustBundleConfigMapName, nameof(k8sSettings.ProxyTrustBundleConfigMapName)); - this.proxyImagePullSecretName = proxyImagePullSecretName; - this.apiVersion = Preconditions.CheckNonWhiteSpace(apiVersion, nameof(apiVersion)); - this.deviceSelector = $"{Constants.K8sEdgeDeviceLabel}={KubeUtils.SanitizeLabelValue(this.resourceName.DeviceId)}"; - this.deviceNamespace = Preconditions.CheckNonWhiteSpace(deviceNamespace, nameof(deviceNamespace)); - this.managementUri = Preconditions.CheckNotNull(managementUri, nameof(managementUri)); - this.workloadUri = Preconditions.CheckNotNull(workloadUri, nameof(workloadUri)); - this.dockerAuthConfig = Preconditions.CheckNotNull(dockerAuthConfig, nameof(dockerAuthConfig)); - this.upstreamProtocol = Preconditions.CheckNotNull(upstreamProtocol, nameof(upstreamProtocol)); - this.productInfo = productInfo.Map(p => $"{p} (Kubernetes)"); - this.defaultMapServiceType = defaultMapServiceType; - this.enableServiceCallTracing = enableServiceCallTracing; - this.useMountSourceForVolumeName = useMountSourceForVolumeName; - this.storageClassName = storageClassName; - this.persistentVolumeClaimSizeMb = persistentVolumeClaimSizeMb; - this.proxy = proxy; - this.closeOnIdleTimeout = closeOnIdleTimeout; - this.idleTimeout = idleTimeout; - this.useServerHeartbeat = useServerHeartbeat; - this.experimentalFeatures = experimentalFeatures; - this.moduleOwner = moduleOwner; - this.runAsNonRoot = runAsNonRoot; - } - - protected override void Load(ContainerBuilder builder) - { - // IKubernetesClient - builder.Register( - c => - { - if (this.enableServiceCallTracing) - { - // enable tracing of k8s requests made by the client - var loggerFactory = c.Resolve(); - ILogger logger = loggerFactory.CreateLogger(typeof(Kubernetes)); - ServiceClientTracing.IsEnabled = true; - ServiceClientTracing.AddTracingInterceptor(new DebugTracer(logger)); - } - - // load the k8s config from KUBECONFIG or $HOME/.kube/config or in-cluster if its available - KubernetesClientConfiguration kubeConfig = Option.Maybe(Environment.GetEnvironmentVariable("KUBECONFIG")) - .Else(() => Option.Maybe(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".kube", "config"))) - .Filter(File.Exists) - .Map(path => KubernetesClientConfiguration.BuildConfigFromConfigFile(path)) - .GetOrElse(KubernetesClientConfiguration.InClusterConfig); - - return new Kubernetes(kubeConfig); - }) - .As() - .SingleInstance(); - - // IModuleClientProvider - builder.Register( - c => new ModuleClientProvider( - c.Resolve(), - this.upstreamProtocol, - this.proxy, - this.productInfo.OrDefault(), - this.closeOnIdleTimeout, - this.idleTimeout, - this.useServerHeartbeat)) - .As() - .SingleInstance(); - - // IModuleManager - builder.Register(c => new ModuleManagementHttpClient(this.managementUri, this.apiVersion, Core.Constants.EdgeletClientApiVersion)) - .As() - .As() - .As() - .SingleInstance(); - - // IModuleIdentityLifecycleManager - var identityBuilder = new ModuleIdentityProviderServiceBuilder(this.resourceName.Hostname, this.resourceName.DeviceId, this.edgeDeviceHostName); - builder.Register(c => new KubernetesModuleIdentityLifecycleManager(c.Resolve(), identityBuilder, this.workloadUri)) - .As() - .SingleInstance(); - - // CombinedKubernetesConfigProvider - builder.Register( - c => - { - bool enableKubernetesExtensions = this.experimentalFeatures.Enabled && this.experimentalFeatures.EnableExtensions; - return new CombinedKubernetesConfigProvider(this.dockerAuthConfig, this.workloadUri, this.managementUri, enableKubernetesExtensions); - }) - .As>() - .SingleInstance(); - - // ICommandFactory - builder.Register( - c => - { - var metricsProvider = c.Resolve(); - var loggerFactory = c.Resolve(); - ICommandFactory factory = new KubernetesCommandFactory(); - factory = new MetricsCommandFactory(factory, metricsProvider); - factory = new LoggingCommandFactory(factory, loggerFactory); - return Task.FromResult(factory); - }) - .As>() - .SingleInstance(); - - // IPlanner - builder.Register( - async c => - { - var configProvider = c.Resolve>(); - ICommandFactory commandFactory = await c.Resolve>(); - IPlanner planner = new KubernetesPlanner( - this.resourceName, - this.deviceSelector, - this.deviceNamespace, - c.Resolve(), - commandFactory, - configProvider, - this.moduleOwner); - return planner; - }) - .As>() - .SingleInstance(); - - // KubernetesRuntimeInfoProvider - builder.Register(c => new KubernetesRuntimeInfoProvider(this.deviceNamespace, c.Resolve(), c.Resolve())) - .As() - .SingleInstance(); - - builder.Register(c => Task.FromResult(c.Resolve() as IRuntimeInfoProvider)) - .As>() - .SingleInstance(); - - builder.Register(c => c.Resolve() ) - .As() - .SingleInstance(); - - // Task - builder.Register( - c => - { - var serde = c.Resolve>(); - IDeploymentBackupSource backupSource = new DeploymentSecretBackup(Constants.EdgeAgentBackupName, this.deviceNamespace, this.moduleOwner, serde, c.Resolve()); - return Task.FromResult(backupSource); - }) - .As>() - .SingleInstance(); - - // KubernetesDeploymentProvider - builder.Register( - c => new KubernetesDeploymentMapper( - this.deviceNamespace, - this.edgeDeviceHostName, - this.k8sSettings.ProxyImage, - this.proxyImagePullSecretName, - this.k8sSettings.ProxyConfigPath, - this.k8sSettings.ProxyConfigVolume, - this.k8sSettings.ProxyConfigMapName, - this.k8sSettings.ProxyTrustBundlePath, - this.k8sSettings.ProxyTrustBundleVolume, - this.k8sSettings.ProxyTrustBundleConfigMapName, - this.k8sSettings.GetProxyResourceRequirements(), - Option.Maybe(this.k8sSettings.AgentConfigMapName), - Option.Maybe(this.k8sSettings.AgentConfigPath), - Option.Maybe(this.k8sSettings.AgentConfigVolume), - this.k8sSettings.GetAgentResourceRequirements(), - this.defaultMapServiceType, - this.useMountSourceForVolumeName, - this.storageClassName, - this.persistentVolumeClaimSizeMb, - this.apiVersion, - this.workloadUri, - this.managementUri, - this.runAsNonRoot, - this.enableServiceCallTracing, - this.experimentalFeatures.GetEnvVars())) - .As(); - - // KubernetesServiceMapper - builder.Register(c => new KubernetesServiceMapper(this.defaultMapServiceType)) - .As(); - - // KubernetesPvcMapper - builder.Register(c => new KubernetesPvcMapper(this.useMountSourceForVolumeName, this.storageClassName, this.persistentVolumeClaimSizeMb.OrDefault())) - .As(); - - // KubernetesServiceAccountProvider - builder.Register(c => new KubernetesServiceAccountMapper()) - .As(); - - // EdgeDeploymentController - builder.Register( - c => - { - IEdgeDeploymentController watchOperator = new EdgeDeploymentController( - this.resourceName, - this.deviceSelector, - this.deviceNamespace, - c.Resolve(), - c.Resolve(), - c.Resolve(), - c.Resolve(), - c.Resolve(), - c.Resolve()); - - return watchOperator; - }) - .As() - .SingleInstance(); - - // IEdgeDeploymentOperator - builder.Register( - c => - { - IEdgeDeploymentOperator watchOperator = new EdgeDeploymentOperator( - this.resourceName, - this.deviceNamespace, - c.Resolve(), - c.Resolve()); - - return watchOperator; - }) - .As() - .SingleInstance(); - - // IKubernetesEnvironmentOperator - builder.Register( - c => - { - IKubernetesEnvironmentOperator watchOperator = new KubernetesEnvironmentOperator( - this.deviceNamespace, - c.Resolve(), - c.Resolve()); - - return watchOperator; - }) - .As() - .SingleInstance(); - - // Task - builder.Register( - async c => - { - CancellationTokenSource tokenSource = new CancellationTokenSource(SystemInfoTimeout); - var moduleStateStoreTask = c.Resolve>>(); - var runtimeInfoProviderTask = c.Resolve>(); - var moduleStateStore = await moduleStateStoreTask; - var runtimeInfoProvider = await runtimeInfoProviderTask; - IEnvironmentProvider kubernetesEnvironmentProvider = await KubernetesEnvironmentProvider.CreateAsync(runtimeInfoProvider, moduleStateStore, tokenSource.Token); - return kubernetesEnvironmentProvider; - }) - .As>() - .SingleInstance(); - - // ISystemResourcesMetrics - builder.Register(c => new Edgelet.Docker.NullSystemResourcesMetrics()) - .As() - .SingleInstance(); - } - } - - class DebugTracer : IServiceClientTracingInterceptor - { - readonly ILogger logger; - - public DebugTracer(ILogger logger) - { - this.logger = logger; - } - - public void Information(string message) - { - this.logger.LogInformation(message); - } - - public void TraceError(string invocationId, Exception exception) - { - this.logger.LogError("Exception in {0}: {1}", invocationId, exception); - } - - public void ReceiveResponse(string invocationId, HttpResponseMessage response) - { - string requestAsString = response == null ? string.Empty : response.AsFormattedString(); - this.logger.LogInformation("invocationId: {0}\r\nresponse: {1}", invocationId, requestAsString); - } - - public void SendRequest(string invocationId, HttpRequestMessage request) - { - string requestAsString = request == null ? string.Empty : request.AsFormattedString(); - this.logger.LogInformation("invocationId: {0}\r\nrequest: {1}", invocationId, requestAsString); - } - - public void Configuration(string source, string name, string value) - { - this.logger.LogInformation("Configuration: source={0}, name={1}, value={2}", source, name, value); - } - - public void EnterMethod(string invocationId, object instance, string method, IDictionary parameters) - { - this.logger.LogInformation( - "invocationId: {0}\r\ninstance: {1}\r\nmethod: {2}\r\nparameters: {3}", - invocationId, - instance, - method, - parameters.AsFormattedString()); - } - - public void ExitMethod(string invocationId, object returnValue) - { - string returnValueAsString = returnValue == null ? string.Empty : returnValue.ToString(); - this.logger.LogInformation( - "Exit with invocation id {0}, the return value is {1}", - invocationId, - returnValueAsString); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/DummyModuleIdentityLifecycleManager.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/DummyModuleIdentityLifecycleManager.cs deleted file mode 100644 index fffc3ab05b7..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/DummyModuleIdentityLifecycleManager.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest -{ - using System.Collections.Immutable; - using System.Linq; - using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; - - public class DummyModuleIdentityLifecycleManager : IModuleIdentityLifecycleManager - { - readonly string hostName; - readonly string gatewayHostname; - readonly string deviceId; - readonly string moduleId; - readonly ICredentials credentials; - private IImmutableDictionary identites = ImmutableDictionary.Empty; - - public DummyModuleIdentityLifecycleManager(string hostName, string gatewayHostname, string deviceId, string moduleId, ICredentials credentials) - { - this.hostName = hostName; - this.gatewayHostname = gatewayHostname; - this.deviceId = deviceId; - this.moduleId = moduleId; - this.credentials = credentials; - } - - public Task> GetModuleIdentitiesAsync(ModuleSet desired, ModuleSet current) => Task.FromResult(this.identites); - - public void SetModules(params string[] moduleNames) => this.identites = moduleNames - .Select(name => new { Name = name, ModuleId = this.CreateModuleIdentity() }) - .ToImmutableDictionary(id => id.Name, id => id.ModuleId); - - IModuleIdentity CreateModuleIdentity() => new ModuleIdentity(this.hostName, this.gatewayHostname, this.deviceId, this.moduleId, this.credentials); - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/DummyModuleManager.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/DummyModuleManager.cs deleted file mode 100644 index f934805b955..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/DummyModuleManager.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Core.Metrics; - using Microsoft.Azure.Devices.Edge.Agent.Edgelet; - using Microsoft.Azure.Devices.Edge.Agent.Edgelet.Models; - using Microsoft.Azure.Devices.Edge.Util; - - public class DummyModuleManager : IModuleManager - { - public Task CreateModuleAsync(ModuleSpec moduleSpec) => throw new NotImplementedException(); - - public Task StartModuleAsync(string name) => throw new NotImplementedException(); - - public Task StopModuleAsync(string name) => throw new NotImplementedException(); - - public Task DeleteModuleAsync(string name) => throw new NotImplementedException(); - - public Task RestartModuleAsync(string name) => throw new NotImplementedException(); - - public Task UpdateModuleAsync(ModuleSpec moduleSpec) => throw new NotImplementedException(); - - public Task UpdateAndStartModuleAsync(ModuleSpec moduleSpec) => throw new NotImplementedException(); - - public Task GetSystemInfoAsync(CancellationToken token) => Task.FromResult(new SystemInfo("kubernetes", "amd64", "v1")); - - public Task GetSystemResourcesAsync() => throw new NotImplementedException(); - - public Task> GetModules(CancellationToken token) => throw new NotImplementedException(); - - public Task PrepareUpdateAsync(ModuleSpec moduleSpec) => throw new NotImplementedException(); - - public Task GetModuleLogs(string name, bool follow, Option tail, Option since, Option until, Option includeTimestamp, CancellationToken cancellationToken) => throw new NotImplementedException(); - - public Task GetSupportBundle(Option since, Option until, Option iothubHostname, Option edgeRuntimeOnly, CancellationToken token) => throw new NotImplementedException(); - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/EdgeDeploymentControllerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/EdgeDeploymentControllerTest.cs deleted file mode 100644 index bb8c4a623c7..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/EdgeDeploymentControllerTest.cs +++ /dev/null @@ -1,470 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest -{ - using System; - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Agent.Docker.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Deployment; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Pvc; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.ServiceAccount; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.Client; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - using DockerEmptyStruct = global::Docker.DotNet.Models.EmptyStruct; - - [Integration] - [Kubernetes] - public class EdgeDeploymentControllerTest : IClassFixture, IAsyncLifetime - { - readonly KubernetesClient client; - - static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(3); - - public EdgeDeploymentControllerTest(KubernetesClusterFixture fixture) - { - string deviceNamespace = $"device-{Guid.NewGuid()}"; - this.client = new KubernetesClient(deviceNamespace, fixture.Client); - } - - public async Task InitializeAsync() => await this.client.AddNamespaceAsync(); - - public Task DisposeAsync() => Task.CompletedTask; - - [Fact] - public async Task CheckIfCreateDeploymentIsSuccessfulWithNoResources() - { - var moduleName = "module-a"; - var deviceSelector = $"{Kubernetes.Constants.K8sEdgeDeviceLabel}=deviceid"; - var moduleLifeCycleManager = this.CreateModuleLifeCycleManager(moduleName); - var controller = this.CreateDeploymentController(deviceSelector, moduleLifeCycleManager, string.Empty); - KubernetesModule km1 = this.CreateDefaultKubernetesModule(moduleName); - moduleLifeCycleManager.SetModules(moduleName); - - await controller.DeployModulesAsync(ModuleSet.Create(km1), ModuleSet.Empty); - - this.AssertNoMatchingDeployments(deviceSelector, moduleName); - this.AssertNoMatchingServiceAccounts(deviceSelector, moduleName); - this.AssertNoServicesExist(deviceSelector); - this.AssertNoPvcsExist(); - } - - [Fact] - public async Task CheckIfCreateDeploymentNoServiceWithPvcIsSuccessful() - { - var moduleName = "module-a"; - var deviceSelector = $"{Kubernetes.Constants.K8sEdgeDeviceLabel}=deviceid"; - var moduleLifeCycleManager = this.CreateModuleLifeCycleManager(moduleName); - var persistentVolumeName = "pvname"; - var controller = this.CreateDeploymentController(deviceSelector, moduleLifeCycleManager, "storagename"); - KubernetesModule km1 = this.CreateKubernetesModuleWithHostConfig(moduleName, persistentVolumeName); - var tokenSource = new CancellationTokenSource(DefaultTimeout * 3); - moduleLifeCycleManager.SetModules(moduleName); - - await controller.DeployModulesAsync(ModuleSet.Create(km1), ModuleSet.Empty); - await this.client.WaitUntilAnyPersistentVolumeClaimAsync(tokenSource.Token); - - this.AssertNoMatchingDeployments(deviceSelector, moduleName); - this.AssertNoMatchingServiceAccounts(deviceSelector, moduleName); - this.AssertNoServicesExist(deviceSelector); - this.AssertNoMatchingPvcs(persistentVolumeName); - } - - [Fact] - public async Task CheckIfCreateDeploymentWithServiceNoPvcIsSuccessful() - { - var moduleName = "module-a"; - var deviceSelector = $"{Kubernetes.Constants.K8sEdgeDeviceLabel}=deviceid"; - var moduleLifeCycleManager = this.CreateModuleLifeCycleManager(moduleName); - var controller = this.CreateDeploymentController(deviceSelector, moduleLifeCycleManager, string.Empty); - KubernetesModule km1 = this.CreateKubernetesModuleWithExposedPorts(moduleName); - moduleLifeCycleManager.SetModules(moduleName); - - await controller.DeployModulesAsync(ModuleSet.Create(km1), ModuleSet.Empty); - - this.AssertNoMatchingDeployments(deviceSelector, moduleName); - this.AssertNoMatchingServiceAccounts(deviceSelector, moduleName); - this.AssertNoMatchingService(deviceSelector, moduleName); - this.AssertNoPvcsExist(); - } - - [Fact] - public async Task CheckIfDeleteDeploymentIsSuccessful() - { - var moduleName = "module-a"; - var deviceSelector = $"{Kubernetes.Constants.K8sEdgeDeviceLabel}=deviceid"; - var moduleLifeCycleManager = this.CreateModuleLifeCycleManager(moduleName); - var persistentVolumeName = "pvname"; - var controller = this.CreateDeploymentController(deviceSelector, moduleLifeCycleManager, "storagename"); - KubernetesModule km1 = this.CreateKubernetesModuleWithHostconfigAndExposedPorts(moduleName, persistentVolumeName); - var labels = this.CreateDefaultLabels(moduleName); - moduleLifeCycleManager.SetModules(moduleName); - - await this.client.AddModuleDeploymentAsync(moduleName, labels, null); - await controller.DeployModulesAsync(ModuleSet.Empty, ModuleSet.Create(km1)); - - this.AssertNoDeploymentsExist(deviceSelector); - this.AssertNoServiceAccountsExist(deviceSelector); - this.AssertNoServicesExist(deviceSelector); - this.AssertNoPvcsExist(); - } - - [Fact] - public async Task CheckIfDeploymentIsHealthyWithDeploymentDeletion() - { - var moduleName = "module-a"; - var deviceSelector = $"{Kubernetes.Constants.K8sEdgeDeviceLabel}=deviceid"; - var moduleLifeCycleManager = this.CreateModuleLifeCycleManager(moduleName); - var controller = this.CreateDeploymentController(deviceSelector, moduleLifeCycleManager, string.Empty); - KubernetesModule km1 = this.CreateDefaultKubernetesModule(moduleName); - var labels = this.CreateDefaultLabels(moduleName); - moduleLifeCycleManager.SetModules(moduleName); - - await this.client.AddModuleDeploymentAsync(moduleName, labels, null); - await this.client.DeleteModuleDeploymentAsync(moduleName); - await controller.DeployModulesAsync(ModuleSet.Create(km1), ModuleSet.Create(km1)); - - this.AssertNoMatchingDeployments(deviceSelector, moduleName); - this.AssertNoMatchingServiceAccounts(deviceSelector, moduleName); - this.AssertNoServicesExist(deviceSelector); - this.AssertNoPvcsExist(); - } - - [Fact] - public async Task CheckIfDeploymentIsHealthyWithMissingPvc() - { - var moduleName = "module-a"; - var deviceSelector = $"{Kubernetes.Constants.K8sEdgeDeviceLabel}=deviceid"; - var moduleLifeCycleManager = this.CreateModuleLifeCycleManager(moduleName); - var persistentVolumeName = "pvname"; - var controller = this.CreateDeploymentController(deviceSelector, moduleLifeCycleManager, "storagename"); - KubernetesModule km1 = this.CreateKubernetesModuleWithHostConfig(moduleName, persistentVolumeName); - var labels = this.CreateDefaultLabels(moduleName); - var tokenSource = new CancellationTokenSource(DefaultTimeout * 3); - moduleLifeCycleManager.SetModules(moduleName); - - await this.client.AddModuleDeploymentAsync(moduleName, labels, null); - await controller.DeployModulesAsync(ModuleSet.Create(km1), ModuleSet.Create(km1)); - await this.client.WaitUntilAnyPersistentVolumeClaimAsync(tokenSource.Token); - - this.AssertNoMatchingDeployments(deviceSelector, moduleName); - this.AssertNoMatchingServiceAccounts(deviceSelector, moduleName); - this.AssertNoServicesExist(deviceSelector); - this.AssertNoMatchingPvcs(persistentVolumeName); - } - - [Fact] - public async Task CheckIfDeploymentIsHealthyWithMissingServiceAccount() - { - var moduleName = "module-a"; - var deviceSelector = $"{Kubernetes.Constants.K8sEdgeDeviceLabel}=deviceid"; - var moduleLifeCycleManager = this.CreateModuleLifeCycleManager(moduleName); - var controller = this.CreateDeploymentController(deviceSelector, moduleLifeCycleManager, string.Empty); - KubernetesModule km1 = this.CreateDefaultKubernetesModule(moduleName); - var labels = this.CreateDefaultLabels(moduleName); - moduleLifeCycleManager.SetModules(moduleName); - - await this.client.AddModuleDeploymentAsync(moduleName, labels, null); - await controller.DeployModulesAsync(ModuleSet.Create(km1), ModuleSet.Create(km1)); - - this.AssertNoMatchingDeployments(deviceSelector, moduleName); - this.AssertNoMatchingServiceAccounts(deviceSelector, moduleName); - this.AssertNoServicesExist(deviceSelector); - this.AssertNoPvcsExist(); - } - - [Fact] - public async Task CheckIfDeploymentIsHealthyWithMissingService() - { - var moduleName = "module-a"; - var deviceSelector = $"{Kubernetes.Constants.K8sEdgeDeviceLabel}=deviceid"; - var moduleLifeCycleManager = this.CreateModuleLifeCycleManager(moduleName); - var controller = this.CreateDeploymentController(deviceSelector, moduleLifeCycleManager, string.Empty); - KubernetesModule km1 = this.CreateKubernetesModuleWithExposedPorts(moduleName); - var labels = this.CreateDefaultLabels(moduleName); - moduleLifeCycleManager.SetModules(moduleName); - - await this.client.AddModuleDeploymentAsync(moduleName, labels, null); - await controller.DeployModulesAsync(ModuleSet.Create(km1), ModuleSet.Create(km1)); - - this.AssertNoMatchingDeployments(deviceSelector, moduleName); - this.AssertNoMatchingServiceAccounts(deviceSelector, moduleName); - this.AssertNoMatchingService(deviceSelector, moduleName); - this.AssertNoPvcsExist(); - } - - [Fact] - public async Task CheckIfUpdateDeploymentWithAddedPvcIsSuccessful() - { - var moduleName = "module-a"; - var deviceSelector = $"{Kubernetes.Constants.K8sEdgeDeviceLabel}=deviceid"; - var moduleLifeCycleManager = this.CreateModuleLifeCycleManager(moduleName); - var persistentVolumeName = "pvname"; - var controller = this.CreateDeploymentController(deviceSelector, moduleLifeCycleManager, "storagename"); - KubernetesModule km1 = this.CreateDefaultKubernetesModule(moduleName); - KubernetesModule km1updated = this.CreateKubernetesModuleWithHostConfig(moduleName, persistentVolumeName); - var labels = this.CreateDefaultLabels(moduleName); - var tokenSource = new CancellationTokenSource(DefaultTimeout * 3); - moduleLifeCycleManager.SetModules(moduleName); - - await this.client.AddModuleDeploymentAsync(moduleName, labels, null); - await controller.DeployModulesAsync(ModuleSet.Create(km1updated), ModuleSet.Create(km1)); - await this.client.WaitUntilAnyPersistentVolumeClaimAsync(tokenSource.Token); - - this.AssertNoMatchingDeployments(deviceSelector, moduleName); - this.AssertNoMatchingServiceAccounts(deviceSelector, moduleName); - this.AssertNoServicesExist(deviceSelector); - this.AssertNoMatchingPvcs(persistentVolumeName); - } - - [Fact] - public async Task CheckIfUpdateDeploymentWithAddedServiceIsSuccessful() - { - var moduleName = "module-a"; - var deviceSelector = $"{Kubernetes.Constants.K8sEdgeDeviceLabel}=deviceid"; - var moduleLifeCycleManager = this.CreateModuleLifeCycleManager(moduleName); - var controller = this.CreateDeploymentController(deviceSelector, moduleLifeCycleManager, string.Empty); - KubernetesModule km1 = this.CreateDefaultKubernetesModule(moduleName); - KubernetesModule km1updated = this.CreateKubernetesModuleWithExposedPorts(moduleName); - var labels = this.CreateDefaultLabels(moduleName); - moduleLifeCycleManager.SetModules(moduleName); - - await this.client.AddModuleDeploymentAsync(moduleName, labels, null); - await controller.DeployModulesAsync(ModuleSet.Create(km1updated), ModuleSet.Create(km1)); - - this.AssertNoMatchingDeployments(deviceSelector, moduleName); - this.AssertNoMatchingServiceAccounts(deviceSelector, moduleName); - this.AssertNoMatchingService(deviceSelector, moduleName); - this.AssertNoPvcsExist(); - } - - [Fact] - public async Task CheckIfUpdateDeploymentWithImageUpdateIsSuccessful() - { - var moduleName = "module-a"; - var deviceSelector = $"{Kubernetes.Constants.K8sEdgeDeviceLabel}=deviceid"; - var moduleLifeCycleManager = this.CreateModuleLifeCycleManager(moduleName); - var controller = this.CreateDeploymentController(deviceSelector, moduleLifeCycleManager, string.Empty); - KubernetesModule km1 = this.CreateDefaultKubernetesModule(moduleName); - string newImage = "test-image:2"; - KubernetesModule km1updated = this.CreateKubernetesModuleWithImageName(moduleName, newImage); - var labels = this.CreateDefaultLabels(moduleName); - moduleLifeCycleManager.SetModules(moduleName); - - await this.client.AddModuleDeploymentAsync(moduleName, labels, null); - await this.client.ReplaceModuleImageAsync(moduleName, newImage); - await controller.DeployModulesAsync(ModuleSet.Create(km1updated), ModuleSet.Create(km1)); - - this.AssertNoMatchingDeployments(deviceSelector, moduleName); - this.AssertNoMatchingServiceAccounts(deviceSelector, moduleName); - this.AssertNoServicesExist(deviceSelector); - this.AssertNoPvcsExist(); - } - - static Dictionary proxyLimits = new Dictionary - { - ["cpu"] = new ResourceQuantity("20m"), - ["memory"] = new ResourceQuantity("1000M"), - }; - static Dictionary agentLimits = new Dictionary - { - ["cpu"] = new ResourceQuantity("150m"), - ["memory"] = new ResourceQuantity("1500Mi"), - }; - - static V1ResourceRequirements proxyReqs = new V1ResourceRequirements(proxyLimits, proxyLimits); - static V1ResourceRequirements agentReqs = new V1ResourceRequirements(agentLimits, agentLimits); - - private EdgeDeploymentController CreateDeploymentController(string deviceSelector, IModuleIdentityLifecycleManager moduleLifeCycleManager, string storageClassName) - { - var resourceName = new ResourceName("hostname", "deviceid"); - var kubernetesServiceMapper = new KubernetesServiceMapper(PortMapServiceType.ClusterIP); - string proxyImagePullSecretName = null; - IDictionary experimentalFeatures = null; - var deploymentMapper = new KubernetesDeploymentMapper( - this.client.DeviceNamespace, - "edgehub", - "proxy", - Option.Maybe(proxyImagePullSecretName), - "configPath", - "config-volume", - "configMapName", - "trustBundlePath", - "trust-bundle-volume", - "trustBundleConfigMapName", - Option.Some(proxyReqs), - Option.Some("agentConfigMapName"), - Option.Some("agentConfigPath"), - Option.Some("agentConfigVolume"), - Option.Some(agentReqs), - PortMapServiceType.ClusterIP, - true, - storageClassName, - Option.Some(100), - "apiVersion", - new Uri("http://localhost:35001"), - new Uri("http://localhost:35000"), - false, - false, - experimentalFeatures == null ? new Dictionary() : experimentalFeatures); - var pvcMapper = new KubernetesPvcMapper(true, storageClassName, 100); - var serviceAccountMapper = new KubernetesServiceAccountMapper(); - return new EdgeDeploymentController( - resourceName, - deviceSelector, - this.client.DeviceNamespace, - this.client.Kubernetes, - moduleLifeCycleManager, - kubernetesServiceMapper, - deploymentMapper, - pvcMapper, - serviceAccountMapper); - } - - private KubernetesModule CreateDefaultKubernetesModule(string moduleName) - { - var createOptions = CreatePodParameters.Create(); - KubernetesConfig config = new KubernetesConfig("image", createOptions, Option.None()); - IModule m1 = new DockerModule(moduleName, "v1", ModuleStatus.Running, Core.RestartPolicy.Always, new DockerConfig("test-image:1"), ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, null, null); - return new KubernetesModule(m1, config, new KubernetesModuleOwner("v1", "Deployment", "iotedged", "123")); - } - - private KubernetesModule CreateKubernetesModuleWithHostConfig(string moduleName, string persistentVolumeName) - { - var hostConfig = new HostConfig - { - Mounts = new List - { - new Mount - { - Type = "volume", - ReadOnly = true, - Source = persistentVolumeName, - Target = "/tmp/volume" - } - } - }; - var createOptions = CreatePodParameters.Create(hostConfig: hostConfig); - KubernetesConfig config = new KubernetesConfig("image", createOptions, Option.None()); - IModule m1 = new DockerModule(moduleName, "v1", ModuleStatus.Running, Core.RestartPolicy.Always, new DockerConfig("test-image:1"), ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, null, null); - return new KubernetesModule(m1, config, new KubernetesModuleOwner("v1", "Deployment", "iotedged", "123")); - } - - private KubernetesModule CreateKubernetesModuleWithExposedPorts(string moduleName) - { - var exposedPorts = new Dictionary - { - ["80/tcp"] = default(DockerEmptyStruct) - }; - var createOptions = CreatePodParameters.Create(exposedPorts: exposedPorts); - KubernetesConfig config = new KubernetesConfig("image", createOptions, Option.None()); - IModule m1 = new DockerModule(moduleName, "v1", ModuleStatus.Running, Core.RestartPolicy.Always, new DockerConfig("test-image:1"), ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, null, null); - return new KubernetesModule(m1, config, new KubernetesModuleOwner("v1", "Deployment", "iotedged", "123")); - } - - private KubernetesModule CreateKubernetesModuleWithHostconfigAndExposedPorts(string moduleName, string persistentVolumeName) - { - var hostConfig = new HostConfig - { - Mounts = new List - { - new Mount - { - Type = "volume", - ReadOnly = true, - Source = persistentVolumeName, - Target = "/tmp/volume" - } - } - }; - var exposedPorts = new Dictionary - { - ["80/tcp"] = default(DockerEmptyStruct) - }; - var createOptions = CreatePodParameters.Create(hostConfig: hostConfig, exposedPorts: exposedPorts); - KubernetesConfig config = new KubernetesConfig("image", createOptions, Option.None()); - IModule m1 = new DockerModule(moduleName, "v1", ModuleStatus.Running, Core.RestartPolicy.Always, new DockerConfig("test-image:1"), ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, null, null); - return new KubernetesModule(m1, config, new KubernetesModuleOwner("v1", "Deployment", "iotedged", "123")); - } - - private KubernetesModule CreateKubernetesModuleWithImageName(string moduleName, string newImage) - { - var createOptions = CreatePodParameters.Create(); - KubernetesConfig config = new KubernetesConfig("image", createOptions, Option.None()); - IModule m1 = new DockerModule(moduleName, "v1", ModuleStatus.Running, Core.RestartPolicy.Always, new DockerConfig(newImage), ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, null, null); - return new KubernetesModule(m1, config, new KubernetesModuleOwner("v1", "Deployment", "iotedged", "123")); - } - - private Dictionary CreateDefaultLabels(string moduleName) - { - return new Dictionary - { - [Kubernetes.Constants.K8sEdgeDeviceLabel] = "deviceid", - [Kubernetes.Constants.K8sEdgeModuleLabel] = moduleName - }; - } - - private DummyModuleIdentityLifecycleManager CreateModuleLifeCycleManager(string moduleName) => new DummyModuleIdentityLifecycleManager( - "hostname", - "gatewayhostname", - "deviceid", - moduleName, - new ConnectionStringCredentials("connectionString")); - - private async void AssertNoDeploymentsExist(string deviceSelector) - { - V1DeploymentList currentDeployments = await this.client.ListDeploymentsAsync(deviceSelector); - Assert.Empty(currentDeployments.Items); - } - - private async void AssertNoMatchingDeployments(string deviceSelector, string moduleName) - { - V1DeploymentList currentDeployments = await this.client.ListDeploymentsAsync(deviceSelector); - Assert.Single(currentDeployments.Items, d => d.Metadata.Name == moduleName); - } - - private async void AssertNoServiceAccountsExist(string deviceSelector) - { - V1ServiceAccountList currentServiceAccounts = await this.client.ListServiceAccountsAsync(deviceSelector); - Assert.Empty(currentServiceAccounts.Items); - } - - private async void AssertNoMatchingServiceAccounts(string deviceSelector, string moduleName) - { - V1ServiceAccountList currentServiceAccounts = await this.client.ListServiceAccountsAsync(deviceSelector); - Assert.Single(currentServiceAccounts.Items, sa => sa.Metadata.Name == moduleName); - } - - private async void AssertNoServicesExist(string deviceSelector) - { - V1ServiceList currentServices = await this.client.ListServicesAsync(deviceSelector); - Assert.Empty(currentServices.Items); - } - - private async void AssertNoMatchingService(string deviceSelector, string moduleName) - { - V1ServiceList currentServiceList = await this.client.ListServicesAsync(deviceSelector); - Assert.Single(currentServiceList.Items, s => s.Metadata.Name == moduleName); - } - - private async void AssertNoPvcsExist() - { - V1PersistentVolumeClaimList currentPvcList = await this.client.ListPeristentVolumeClaimsAsync(); - Assert.Empty(currentPvcList.Items); - } - - private async void AssertNoMatchingPvcs(string pvcName) - { - V1PersistentVolumeClaimList currentPvcList = await this.client.ListPeristentVolumeClaimsAsync(); - Assert.Single(currentPvcList.Items, p => p.Metadata.Name == pvcName); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/KubernetesClusterFixture.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/KubernetesClusterFixture.cs deleted file mode 100644 index 11bc777e988..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/KubernetesClusterFixture.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest -{ - using System; - using System.Threading.Tasks; - using k8s; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.Cluster; - using Xunit; - - public class KubernetesClusterFixture : IAsyncLifetime - { - readonly IKubernetesClientProvider clientProvider; - - public KubernetesClusterFixture() - { - this.clientProvider = Create(); - } - - static IKubernetesClientProvider Create() => - Environment.GetEnvironmentVariable("USE_EXISTING_KUBERNETES_CLUSTER") != null - ? new KubernetesClientProvider() - : (IKubernetesClientProvider)new KindClusterManager($"ea-{Guid.NewGuid()}"); - - public async Task InitializeAsync() - { - if (this.clientProvider is IKubernetesClusterManager clusterManager) - { - await clusterManager.CreateAsync(); - } - - this.Client = await this.clientProvider.GetClientAsync(); - } - - public async Task DisposeAsync() - { - if (this.clientProvider is IKubernetesClusterManager clusterManager) - { - await clusterManager.DeleteAsync(); - } - } - - public IKubernetes Client { get; private set; } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/KubernetesEnvironmentOperatorTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/KubernetesEnvironmentOperatorTest.cs deleted file mode 100644 index c4dca616512..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/KubernetesEnvironmentOperatorTest.cs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using FluentAssertions; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.Client; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - - [Integration] - [Kubernetes] - public class KubernetesEnvironmentOperatorTest : IClassFixture, IAsyncLifetime - { - readonly KubernetesClient client; - - readonly KubernetesEnvironmentOperator environmentOperator; - - readonly KubernetesRuntimeInfoProvider runtimeInfoProvider; - - static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(1); - - public KubernetesEnvironmentOperatorTest(KubernetesClusterFixture fixture) - { - string deviceNamespace = $"device-{Guid.NewGuid()}"; - this.client = new KubernetesClient(deviceNamespace, fixture.Client); - - this.runtimeInfoProvider = new KubernetesRuntimeInfoProvider(deviceNamespace, fixture.Client, new DummyModuleManager()); - this.environmentOperator = new KubernetesEnvironmentOperator(deviceNamespace, this.runtimeInfoProvider, fixture.Client); - } - - public async Task InitializeAsync() - { - await this.client.AddNamespaceAsync(); - this.environmentOperator.Start(); - } - - public Task DisposeAsync() - { - this.environmentOperator?.Dispose(); - return Task.CompletedTask; - } - - [Fact] - public async Task CollectsModuleRuntimeInfoWhenModuleDeploymentAdded() - { - var tokenSource = new CancellationTokenSource(DefaultTimeout); - - await this.AddEdgeModule("Module-A"); - await this.client.WaitUntilAnyPodsAsync("status.phase=Running", tokenSource.Token); - - ModuleRuntimeInfo moduleInfo = await this.GetModule("Module-A"); - - moduleInfo.Should().NotBeNull(); - moduleInfo.ModuleStatus.Should().Be(ModuleStatus.Running); - moduleInfo.Name.Should().Be("Module-A"); - moduleInfo.Description.Should().StartWith("Started at"); - moduleInfo.Type.Should().Be("docker"); - moduleInfo.ExitCode.Should().Be(0); - moduleInfo.StartTime.Should().NotBeNone(); - moduleInfo.ExitTime.Should().BeNone(); - } - - [Fact] - public async Task DoesNotCollectModuleRuntimeInfoForUnknownModules() - { - var tokenSource = new CancellationTokenSource(DefaultTimeout); - - var labels = new Dictionary { ["a"] = "b" }; - await this.client.AddModuleServiceAccountAsync("foreign-pod", labels, null); - await this.client.AddModuleDeploymentAsync("foreign-pod", labels, null); - await this.client.WaitUntilAnyPodsAsync("status.phase=Running", tokenSource.Token); - - ModuleRuntimeInfo moduleInfo = await this.GetModule("foreign-pod"); - - moduleInfo.Should().BeNull(); - } - - [Fact] - public async Task DeletesModuleRuntimeInfoForDeletedModules() - { - var tokenSource = new CancellationTokenSource(DefaultTimeout * 3); - - await this.AddEdgeModule("Module-A"); - await this.client.WaitUntilAnyPodsAsync("status.phase=Running", tokenSource.Token); - - IEnumerable modules = await this.runtimeInfoProvider.GetModules(CancellationToken.None); - modules.Should().HaveCount(1); - - await this.client.DeleteModuleDeploymentAsync("module-a"); - await this.client.WaitUntilPodsExactNumberAsync(0, tokenSource.Token); - - modules = await this.runtimeInfoProvider.GetModules(CancellationToken.None); - modules.Should().BeEmpty(); - } - - [Fact] - public async Task UpdatesModuleRuntimeInfoWhenModuleDeploymentUpdated() - { - var tokenSource = new CancellationTokenSource(DefaultTimeout * 3); - - await this.AddEdgeModule("Module-A"); - await this.client.WaitUntilAnyPodsAsync("status.phase=Running", tokenSource.Token); - ModuleRuntimeInfo initialModuleInfo = await this.GetModule("Module-A"); - - await this.client.ReplaceModuleImageAsync("module-a", "alpine:latest"); - await this.client.WaitUntilPodsExactNumberAsync(2, tokenSource.Token); - await this.client.WaitUntilPodsExactNumberAsync(1, tokenSource.Token); - ModuleRuntimeInfo updatedModuleInfo = await this.GetModule("Module-A"); - - initialModuleInfo.Should().NotBeNull(); - initialModuleInfo.StartTime.Should().NotBeNone(); - initialModuleInfo.ModuleStatus.Should().Be(ModuleStatus.Running); - - updatedModuleInfo.Should().NotBeNull(); - updatedModuleInfo.StartTime.Should().NotBeNone(); - initialModuleInfo.ModuleStatus.Should().Be(ModuleStatus.Running); - updatedModuleInfo.StartTime.OrDefault().Should().BeAfter(initialModuleInfo.StartTime.OrDefault()); - } - - async Task GetModule(string name) - { - IEnumerable modules = await this.runtimeInfoProvider.GetModules(CancellationToken.None); - return modules.SingleOrDefault(module => module.Name == name); - } - - async Task AddEdgeModule(string moduleName) - { - string name = moduleName.ToLowerInvariant(); - - var labels = new Dictionary - { - ["net.azure-devices.edge.deviceid"] = "edgy", - ["net.azure-devices.edge.hub"] = "edgy-iothub.azure-devices.net", - ["net.azure-devices.edge.module"] = name - }; - var annotations = new Dictionary - { - ["net.azure-devices.edge.original-moduleid"] = moduleName - }; - - await this.client.AddModuleServiceAccountAsync(name, labels, annotations); - await this.client.AddModuleDeploymentAsync(name, labels, annotations); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.csproj b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.csproj deleted file mode 100644 index d5a4bfeb30d..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - True - Debug;Release;CheckInBuild - true - - - - - - - - - - - - - - - - - - - - - ..\..\..\stylecop.ruleset - - - - diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/OptionAssertions.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/OptionAssertions.cs deleted file mode 100644 index 01c5ff55d26..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/OptionAssertions.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest -{ - using System.Diagnostics.Contracts; - using FluentAssertions; - using FluentAssertions.Execution; - using FluentAssertions.Primitives; - using Microsoft.Azure.Devices.Edge.Util; - - public static class OptionAssertionExtensions - { - [Pure] - public static OptionAssertions Should(this Option actualValue) => new OptionAssertions(actualValue); - } - - public class OptionAssertions : ReferenceTypeAssertions, OptionAssertions> - { - public OptionAssertions(Option actualValue) - : base(actualValue) - { - } - - protected override string Identifier => typeof(Option).ToString(); - - public AndConstraint> BeNone( - string because = "", - params object[] becauseArgs) - { - Execute.Assertion - .ForCondition(this.Subject != null) - .BecauseOf(because, becauseArgs).WithDefaultIdentifier(this.Identifier) - .FailWith("Expected {context} to be {reason}, but found {0}.", (object)this.Subject); - - Execute.Assertion - .ForCondition(this.Subject.Equals(Option.None())) - .BecauseOf(because, becauseArgs).WithDefaultIdentifier(this.Identifier) - .FailWith("Expected {context} to be {reason}, but found {0}.", (object)this.Subject); - - return new AndConstraint>(this); - } - - public AndConstraint> NotBeNone( - string because = "", - params object[] becauseArgs) - { - Execute.Assertion - .ForCondition(this.Subject != null) - .BecauseOf(because, becauseArgs).WithDefaultIdentifier(this.Identifier) - .FailWith("Expected {context} not to be {reason}, but found {0}.", (object)this.Subject); - - Execute.Assertion - .ForCondition(!this.Subject.Equals(Option.None())) - .BecauseOf(because, becauseArgs).WithDefaultIdentifier(this.Identifier) - .FailWith("Expected {context} not to be {reason}, but found {0}.", (object)this.Subject); - - return new AndConstraint>(this); - } - - public AndConstraint> Be( - Option expected, - string because = "", - params object[] becauseArgs) - { - Execute.Assertion - .ForCondition(this.Subject != null) - .BecauseOf(because, becauseArgs).WithDefaultIdentifier(this.Identifier) - .FailWith("Expected {context} to be <{0}>{reason}, but found {1}.", expected, (object)this.Subject); - - Execute.Assertion - .ForCondition(this.Subject.Equals(expected)) - .BecauseOf(because, becauseArgs).WithDefaultIdentifier(this.Identifier) - .FailWith("Expected {context} to be <{0}>{reason}, but found {1}.", expected, (object)this.Subject); - - return new AndConstraint>(this); - } - - public AndConstraint> NotBe( - Option expected, - string because = "", - params object[] becauseArgs) - { - Execute.Assertion - .ForCondition(this.Subject != null) - .BecauseOf(because, becauseArgs).WithDefaultIdentifier(this.Identifier) - .FailWith("Expected {context} not to be <{0}>{reason}, but found {1}.", expected, (object)this.Subject); - - Execute.Assertion - .ForCondition(!this.Subject.Equals(expected)) - .BecauseOf(because, becauseArgs).WithDefaultIdentifier(this.Identifier) - .FailWith("Expected {context} not to be <{0}>{reason}, but found {1}.", expected, (object)this.Subject); - - return new AndConstraint>(this); - } - - public AndConstraint> BeSome( - T expected, - string because = "", - params object[] becauseArgs) - { - Execute.Assertion - .ForCondition(this.Subject == null) - .BecauseOf(because, becauseArgs).WithDefaultIdentifier(this.Identifier) - .FailWith("Expected {context} to be {reason}, but found {1}.", expected, (object)this.Subject); - - Execute.Assertion - .ForCondition(this.Subject.Equals(Option.Some(expected))) - .BecauseOf(because, becauseArgs).WithDefaultIdentifier(this.Identifier) - .FailWith("Expected {context} to be {reason}, but found {1}.", expected, (object)this.Subject); - - return new AndConstraint>(this); - } - - public AndConstraint> NotBeSome( - T expected, - string because = "", - params object[] becauseArgs) - { - Execute.Assertion - .ForCondition(this.Subject == null) - .BecauseOf(because, becauseArgs).WithDefaultIdentifier(this.Identifier) - .FailWith("Expected {context} not to be {reason}, but found {1}.", expected, (object)this.Subject); - - Execute.Assertion - .ForCondition(this.Subject.Equals(Option.Some(expected))) - .BecauseOf(because, becauseArgs).WithDefaultIdentifier(this.Identifier) - .FailWith("Expected {context} not to be {reason}, but found {1}.", expected, (object)this.Subject); - - return new AndConstraint>(this); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/DeploymentKubernetesClientExtensions.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/DeploymentKubernetesClientExtensions.cs deleted file mode 100644 index 99c24a6435c..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/DeploymentKubernetesClientExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.Client -{ - using System.Collections.Generic; - using System.Threading.Tasks; - using k8s; - using k8s.Models; - - public static class DeploymentKubernetesClientExtensions - { - public static async Task AddModuleDeploymentAsync(this KubernetesClient client, string name, IDictionary labels, IDictionary annotations) - { - var deployment = new V1Deployment - { - Metadata = new V1ObjectMeta - { - Name = name, - NamespaceProperty = client.DeviceNamespace, - Labels = labels - }, - Spec = new V1DeploymentSpec - { - Template = new V1PodTemplateSpec - { - Metadata = new V1ObjectMeta - { - Name = name, - Labels = labels, - Annotations = annotations - }, - Spec = new V1PodSpec - { - Containers = new[] - { - new V1Container - { - Image = "busybox:latest", - Name = name, - Command = new[] { "/bin/sh" }, - Args = new[] { "-c", "while true; do echo hello; sleep 10;done" } - } - }, - ServiceAccountName = name - } - }, - Selector = new V1LabelSelector - { - MatchLabels = labels - } - } - }; - await client.Kubernetes.CreateNamespacedDeploymentAsync(deployment, client.DeviceNamespace); - } - - public static async Task ReplaceModuleImageAsync(this KubernetesClient client, string name, string image) - { - V1Deployment deployment = await client.Kubernetes.ReadNamespacedDeploymentAsync(name, client.DeviceNamespace); - deployment.Spec.Template.Spec.Containers[0].Image = image; - - await client.Kubernetes.ReplaceNamespacedDeploymentAsync(deployment, name, client.DeviceNamespace); - } - - public static async Task DeleteModuleDeploymentAsync(this KubernetesClient client, string name) => await client.Kubernetes.DeleteNamespacedDeploymentAsync(name, client.DeviceNamespace); - - public static async Task ListDeploymentsAsync(this KubernetesClient client, string deviceSelector) => await client.Kubernetes.ListNamespacedDeploymentAsync(client.DeviceNamespace, labelSelector: deviceSelector); - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/KubernetesClient.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/KubernetesClient.cs deleted file mode 100644 index b793002c2ab..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/KubernetesClient.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.Client -{ - using System; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using k8s; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Util; - - public class KubernetesClient - { - public string DeviceNamespace { get; } - - public IKubernetes Kubernetes { get; } - - public KubernetesClient(string deviceNamespace, IKubernetes client) - { - this.DeviceNamespace = deviceNamespace; - this.Kubernetes = client; - } - - public async Task> WaitUntilAnyPodsAsync(string fieldSelector, CancellationToken token) => - await WaitUntilAsync( - () => this.Kubernetes.ListNamespacedPodAsync(this.DeviceNamespace, fieldSelector: fieldSelector, cancellationToken: token), - pods => pods.Items.Any(), - token); - - public async Task> WaitUntilPodsExactNumberAsync(int count, CancellationToken token) => - await WaitUntilAsync( - () => this.Kubernetes.ListNamespacedPodAsync(this.DeviceNamespace, cancellationToken: token), - pods => pods.Items.Count == count, - token); - - public static async Task> WaitUntilAsync(Func> action, Func predicate, CancellationToken token) - { - while (!token.IsCancellationRequested) - { - T result = await action(); - if (predicate(result)) - { - return Option.Some(result); - } - - await Task.Delay(TimeSpan.FromMilliseconds(100), token); - } - - return Option.None(); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/NamespaceKubernetesClientExtensions.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/NamespaceKubernetesClientExtensions.cs deleted file mode 100644 index 27351eb8eb1..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/NamespaceKubernetesClientExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.Client -{ - using System.Threading.Tasks; - using k8s; - using k8s.Models; - - public static class NamespaceKubernetesClientExtensions - { - public static async Task AddNamespaceAsync(this KubernetesClient client) - { - var @namespace = new V1Namespace - { - Metadata = new V1ObjectMeta - { - Name = client.DeviceNamespace - } - }; - - await client.Kubernetes.CreateNamespaceAsync(@namespace); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/PvcKubernetesClientExtensions.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/PvcKubernetesClientExtensions.cs deleted file mode 100644 index 88add173595..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/PvcKubernetesClientExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.Client -{ - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using k8s; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Util; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - public static class PvcKubernetesClientExtensions - { - public static async Task> WaitUntilAnyPersistentVolumeClaimAsync(this KubernetesClient client, CancellationToken token) => - await KubernetesClient.WaitUntilAsync( - () => client.Kubernetes.ListNamespacedPersistentVolumeClaimAsync(client.DeviceNamespace, cancellationToken: token), - p => p.Items.Any(), - token); - - public static async Task ListPeristentVolumeClaimsAsync(this KubernetesClient client) => await client.Kubernetes.ListNamespacedPersistentVolumeClaimAsync(client.DeviceNamespace); - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/ServiceAccountKubernetesClientExtensions.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/ServiceAccountKubernetesClientExtensions.cs deleted file mode 100644 index 9548071faa6..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/ServiceAccountKubernetesClientExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.Client -{ - using System.Collections.Generic; - using System.Threading.Tasks; - using k8s; - using k8s.Models; - - public static class ServiceAccountKubernetesClientExtensions - { - public static async Task AddModuleServiceAccountAsync(this KubernetesClient client, string name, IDictionary labels, IDictionary annotations) - { - var serviceAccount = new V1ServiceAccount - { - Metadata = new V1ObjectMeta - { - Name = name, - Annotations = annotations, - Labels = labels - } - }; - - await client.Kubernetes.CreateNamespacedServiceAccountAsync(serviceAccount, client.DeviceNamespace); - } - - public static async Task ListServiceAccountsAsync(this KubernetesClient client, string deviceSelector) => await client.Kubernetes.ListNamespacedServiceAccountAsync(client.DeviceNamespace, labelSelector: deviceSelector); - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/ServiceKubernetesClientExtensions.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/ServiceKubernetesClientExtensions.cs deleted file mode 100644 index ac5d8d3dab9..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/client/ServiceKubernetesClientExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.Client -{ - using System.Threading.Tasks; - using k8s; - using k8s.Models; - - public static class ServiceKubernetesClientExtensions - { - public static async Task ListServicesAsync(this KubernetesClient client, string deviceSelector) => await client.Kubernetes.ListNamespacedServiceAsync(client.DeviceNamespace, labelSelector: deviceSelector); - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/cluster/IKubernetesClientProvider.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/cluster/IKubernetesClientProvider.cs deleted file mode 100644 index 1f957bbe511..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/cluster/IKubernetesClientProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.Cluster -{ - using System.Threading.Tasks; - using k8s; - - public interface IKubernetesClientProvider - { - Task GetClientAsync(); - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/cluster/IKubernetesClusterManager.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/cluster/IKubernetesClusterManager.cs deleted file mode 100644 index 031d1ac5f28..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/cluster/IKubernetesClusterManager.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.Cluster -{ - using System.Threading.Tasks; - - public interface IKubernetesClusterManager : IKubernetesClientProvider - { - Task CreateAsync(); - - Task DeleteAsync(); - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/cluster/KindClusterManager.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/cluster/KindClusterManager.cs deleted file mode 100644 index 1b57cc9029f..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/cluster/KindClusterManager.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.Cluster -{ - using System; - using System.Threading.Tasks; - using CliWrap; - using k8s; - - public class KindClusterManager : IKubernetesClusterManager - { - readonly string name; - - public KindClusterManager(string name) - { - this.name = name; - } - - public async Task CreateAsync() - { - await BashCommand($@"kind create cluster --name ""{this.name}""") - .SetStandardOutputCallback(Console.WriteLine) - .SetStandardErrorCallback(Console.WriteLine) - .ExecuteAsync(); - } - - public async Task DeleteAsync() - { - await BashCommand($@"kind delete cluster --name ""{this.name}""") - .SetStandardOutputCallback(Console.WriteLine) - .SetStandardErrorCallback(Console.WriteLine) - .ExecuteAsync(); - } - - public async Task GetClientAsync() - { - string path = string.Empty; - - await BashCommand($@"kind get kubeconfig-path --name ""{this.name}""") - .SetStandardOutputCallback(output => path = output) - .SetStandardErrorCallback(Console.WriteLine) - .ExecuteAsync(); - - return new Kubernetes(KubernetesClientConfiguration.BuildConfigFromConfigFile(path)); - } - - static ICli BashCommand(string command) => Cli.Wrap("/bin/bash").SetArguments($@"-c ""{command}"""); - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/cluster/KubernetesClientProvider.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/cluster/KubernetesClientProvider.cs deleted file mode 100644 index 4052f23ae56..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest/cluster/KubernetesClientProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.IntegrationTest.Cluster -{ - using System; - using System.IO; - using System.Threading.Tasks; - using k8s; - using Microsoft.Azure.Devices.Edge.Util; - - public class KubernetesClientProvider : IKubernetesClientProvider - { - public Task GetClientAsync() - { - // load the k8s config from KUBECONFIG or $HOME/.kube/config or in-cluster if its available - KubernetesClientConfiguration kubeConfig = Option.Maybe(Environment.GetEnvironmentVariable("KUBECONFIG")) - .Else(() => Option.Maybe(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".kube", "config"))) - .Filter(File.Exists) - .Map(path => KubernetesClientConfiguration.BuildConfigFromConfigFile(path)) - .GetOrElse(KubernetesClientConfiguration.InClusterConfig); - - IKubernetes client = new Kubernetes(kubeConfig); - return Task.FromResult(client); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/CombinedKubernetesConfigProviderTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/CombinedKubernetesConfigProviderTest.cs deleted file mode 100644 index 8cf521b7fcd..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/CombinedKubernetesConfigProviderTest.cs +++ /dev/null @@ -1,424 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System; - using System.Linq; - using System.Runtime.InteropServices; - using global::Docker.DotNet.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Moq; - using Newtonsoft.Json; - using Xunit; - using CoreConstants = Microsoft.Azure.Devices.Edge.Agent.Core.Constants; - - [Unit] - public class CombinedKubernetesConfigProviderTest - { - [Fact] - public void TestCreateValidation() - { - Assert.Throws(() => new CombinedKubernetesConfigProvider(null, new Uri("http://workload"), new Uri("http://management"), false)); - Assert.Throws(() => new CombinedKubernetesConfigProvider(new[] { new AuthConfig(), }, null, new Uri("http://management"), false)); - Assert.Throws(() => new CombinedKubernetesConfigProvider(new[] { new AuthConfig(), }, new Uri("http://workload"), null, false)); - } - - [Fact] - public void TestVolMount() - { - // Arrange - var runtimeInfo = new Mock>(); - runtimeInfo.SetupGet(ri => ri.Config).Returns(new DockerRuntimeConfig("1.24", string.Empty)); - - var module = new Mock>(); - module.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest")); - module.SetupGet(m => m.Name).Returns(CoreConstants.EdgeAgentModuleName); - - (Uri workloadUri, Uri managementUri) = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? (new Uri("unix:///C:/path/to/workload/sock"), new Uri("unix:///C:/path/to/mgmt/sock")) - : (new Uri("unix:///path/to/workload.sock"), new Uri("unix:///path/to/mgmt.sock")); - - CombinedKubernetesConfigProvider provider = new CombinedKubernetesConfigProvider(new[] { new AuthConfig() }, workloadUri, managementUri, false); - - // Act - CombinedKubernetesConfig config = provider.GetCombinedConfig(module.Object, runtimeInfo.Object); - - // Assert - Assert.NotNull(config.CreateOptions); - Assert.True(config.CreateOptions.HostConfig.HasValue); - config.CreateOptions.HostConfig.ForEach(hostConfig => Assert.NotNull(hostConfig.Binds)); - config.CreateOptions.HostConfig.ForEach(hostConfig => Assert.Equal(2, hostConfig.Binds.Count)); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - config.CreateOptions.HostConfig.ForEach(hostConfig => Assert.Equal(@"C:\path\to\workload:C:\path\to\workload", hostConfig.Binds[0])); - config.CreateOptions.HostConfig.ForEach(hostConfig => Assert.Equal(@"C:\path\to\mgmt:C:\path\to\mgmt", hostConfig.Binds[1])); - } - else - { - config.CreateOptions.HostConfig.ForEach(hostConfig => Assert.Equal("/path/to/workload.sock:/path/to/workload.sock", hostConfig.Binds[0])); - config.CreateOptions.HostConfig.ForEach(hostConfig => Assert.Equal("/path/to/mgmt.sock:/path/to/mgmt.sock", hostConfig.Binds[1])); - } - } - - [Fact] - public void TestNoVolMountForNonUds() - { - // Arrange - var runtimeInfo = new Mock>(); - runtimeInfo.SetupGet(ri => ri.Config).Returns(new DockerRuntimeConfig("1.24", string.Empty)); - - var module = new Mock>(); - module.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest")); - module.SetupGet(m => m.Name).Returns(CoreConstants.EdgeAgentModuleName); - - CombinedKubernetesConfigProvider provider = new CombinedKubernetesConfigProvider(new[] { new AuthConfig() }, new Uri("http://localhost:2375/"), new Uri("http://localhost:2376/"), false); - - // Act - CombinedKubernetesConfig config = provider.GetCombinedConfig(module.Object, runtimeInfo.Object); - - // Assert - Assert.NotNull(config.CreateOptions); - Assert.False(config.CreateOptions.HostConfig.HasValue); - } - - [Fact] - public void IgnoresKubernetesCreateOptionsWhenExperimentalDisabled() - { - // Arrange - var runtimeInfo = new Mock>(); - runtimeInfo.SetupGet(ri => ri.Config).Returns(new DockerRuntimeConfig("1.24", string.Empty)); - - var module = new Mock>(); - module.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest", ExperimentalCreateOptions, Option.None())); - module.SetupGet(m => m.Name).Returns("mod1"); - - CombinedKubernetesConfigProvider provider = new CombinedKubernetesConfigProvider(new[] { new AuthConfig() }, new Uri("unix:///var/run/iotedgedworkload.sock"), new Uri("unix:///var/run/iotedgedmgmt.sock"), false); - - // Act - CombinedKubernetesConfig config = provider.GetCombinedConfig(module.Object, runtimeInfo.Object); - - // Assert - Assert.False(config.CreateOptions.Volumes.HasValue); - Assert.False(config.CreateOptions.NodeSelector.HasValue); - Assert.False(config.CreateOptions.Resources.HasValue); - } - - [Fact] - public void ParsesKubernetesCreateOptionsWhenExperimentalEnabled() - { - // Arrange - var runtimeInfo = new Mock>(); - runtimeInfo.SetupGet(ri => ri.Config).Returns(new DockerRuntimeConfig("1.24", string.Empty)); - - var module = new Mock>(); - module.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest", ExperimentalCreateOptions, Option.None())); - module.SetupGet(m => m.Name).Returns("mod1"); - - CombinedKubernetesConfigProvider provider = new CombinedKubernetesConfigProvider(new[] { new AuthConfig() }, new Uri("unix:///var/run/iotedgedworkload.sock"), new Uri("unix:///var/run/iotedgedmgmt.sock"), true); - - // Act - CombinedKubernetesConfig config = provider.GetCombinedConfig(module.Object, runtimeInfo.Object); - - // Assert - Assert.True(config.CreateOptions.Volumes.HasValue); - config.CreateOptions.Volumes.ForEach(volumes => Assert.Equal(1, volumes.Count)); - config.CreateOptions.Volumes.ForEach(volumes => Assert.NotNull(volumes.First())); - - Assert.True(config.CreateOptions.NodeSelector.HasValue); - config.CreateOptions.NodeSelector.ForEach(selector => Assert.Equal(2, selector.Count)); - - Assert.True(config.CreateOptions.Resources.HasValue); - config.CreateOptions.Resources.ForEach(resources => Assert.Equal(3, resources.Limits.Count)); - config.CreateOptions.Resources.ForEach(resources => Assert.Equal(3, resources.Requests.Count)); - } - - const string ExperimentalCreateOptions = - @"{ - ""k8s-experimental"": { - ""volumes"": [ - { - ""volume"": { - ""name"": ""ModuleA"", - ""configMap"": { - ""optional"": ""true"", - ""defaultMode"": 420, - ""items"": [ - { - ""key"": ""config-file"", - ""path"": ""config.yaml"", - ""mode"": 420 - } - ], - ""name"": ""module-config"" - } - }, - ""volumeMounts"": [ - { - ""name"": ""module-config"", - ""mountPath"": ""/etc/module/config.yaml"", - ""mountPropagation"": ""None"", - ""readOnly"": ""true"", - ""subPath"": """" - } - ] - } - ], - ""resources"": { - ""limits"": { - ""memory"": ""128Mi"", - ""cpu"": ""500m"", - ""hardware-vendor.example/foo"": 2 - }, - ""requests"": { - ""memory"": ""64Mi"", - ""cpu"": ""250m"", - ""hardware-vendor.example/foo"": 2 - } - }, - ""nodeSelector"": { - ""disktype"": ""ssd"", - ""gpu"": ""true"" - } - } -}"; - - [Fact] - public void MakesKubernetesAwareAuthConfig() - { - // Arrange - var runtimeInfo = new Mock>(); - runtimeInfo.SetupGet(ri => ri.Config).Returns(new DockerRuntimeConfig("1.24", string.Empty)); - - var module = new Mock>(); - module.SetupGet(m => m.Config).Returns(new DockerConfig("docker.io/nginx:latest", (string)null, Option.None())); - module.SetupGet(m => m.Name).Returns("mod1"); - - var authConfig = new AuthConfig { Username = "user", Password = "password", ServerAddress = "docker.io" }; - - CombinedKubernetesConfigProvider provider = new CombinedKubernetesConfigProvider(new[] { authConfig }, new Uri("unix:///var/run/iotedgedworkload.sock"), new Uri("unix:///var/run/iotedgedmgmt.sock"), false); - - // Act - CombinedKubernetesConfig config = provider.GetCombinedConfig(module.Object, runtimeInfo.Object); - - // Assert - Assert.True(config.ImagePullSecret.HasValue); - config.ImagePullSecret.ForEach(secret => Assert.Equal("user-docker.io", secret.Name)); - } - - [Fact] - public void NoExecArgumentMeansNoExecArguments() - { - var runtimeInfo = new Mock>(); - runtimeInfo.SetupGet(ri => ri.Config).Returns(new DockerRuntimeConfig("1.24", string.Empty)); - - var module = new Mock>(); - module.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest", string.Empty, Option.None())); - module.SetupGet(m => m.Name).Returns("mod1"); - - CombinedKubernetesConfigProvider provider = new CombinedKubernetesConfigProvider(new[] { new AuthConfig() }, new Uri("unix:///var/run/iotedgedworkload.sock"), new Uri("unix:///var/run/iotedgedmgmt.sock"), true); - - // Act - CombinedKubernetesConfig config = provider.GetCombinedConfig(module.Object, runtimeInfo.Object); - - // Assert - Assert.False(config.CreateOptions.Cmd.HasValue); - Assert.False(config.CreateOptions.Entrypoint.HasValue); - Assert.False(config.CreateOptions.WorkingDir.HasValue); - } - - const string CmdCreateOptions = - @"{ -""Cmd"" : [ - ""argument1"", - ""argument2"" -] -}"; - - const string CmdCreateOptionsLC = - @"{ -""cmd"" : [ - ""argument3"", - ""argument4"" -] -}"; - - [Fact] - public void CmdEntryOptionsWillExist() - { - var runtimeInfo = new Mock>(); - runtimeInfo.SetupGet(ri => ri.Config).Returns(new DockerRuntimeConfig("1.24", string.Empty)); - - var module1 = new Mock>(); - module1.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest", CmdCreateOptions, Option.None())); - module1.SetupGet(m => m.Name).Returns("mod1"); - var module2 = new Mock>(); - module2.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest", CmdCreateOptionsLC, Option.None())); - module2.SetupGet(m => m.Name).Returns("mod1"); - - CombinedKubernetesConfigProvider provider = new CombinedKubernetesConfigProvider(new[] { new AuthConfig() }, new Uri("unix:///var/run/iotedgedworkload.sock"), new Uri("unix:///var/run/iotedgedmgmt.sock"), true); - - // Act - CombinedKubernetesConfig config1 = provider.GetCombinedConfig(module1.Object, runtimeInfo.Object); - CombinedKubernetesConfig config2 = provider.GetCombinedConfig(module2.Object, runtimeInfo.Object); - - // Assert - Assert.True(config1.CreateOptions.Cmd.HasValue); - config1.CreateOptions.Cmd.ForEach(cmd => - { - Assert.Equal("argument1", cmd[0]); - Assert.Equal("argument2", cmd[1]); - }); - Assert.True(config2.CreateOptions.Cmd.HasValue); - config2.CreateOptions.Cmd.ForEach(cmd => - { - Assert.Equal("argument3", cmd[0]); - Assert.Equal("argument4", cmd[1]); - }); - } - - const string EntryPointCreateOptions = - @"{ -""Entrypoint"" : [ - ""a-command"" -] -}"; - const string EntryPointCreateOptionsLC = - @"{ -""entrypoint"" : [ - ""a-command2"" -] -}"; - - [Fact] - public void EntrypointOptionsWillExist() - { - var runtimeInfo = new Mock>(); - runtimeInfo.SetupGet(ri => ri.Config).Returns(new DockerRuntimeConfig("1.24", string.Empty)); - - var module1 = new Mock>(); - module1.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest", EntryPointCreateOptions, Option.None())); - module1.SetupGet(m => m.Name).Returns("mod1"); - var module2 = new Mock>(); - module2.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest", EntryPointCreateOptionsLC, Option.None())); - module2.SetupGet(m => m.Name).Returns("mod1"); - - CombinedKubernetesConfigProvider provider = new CombinedKubernetesConfigProvider(new[] { new AuthConfig() }, new Uri("unix:///var/run/iotedgedworkload.sock"), new Uri("unix:///var/run/iotedgedmgmt.sock"), true); - - // Act - CombinedKubernetesConfig config1 = provider.GetCombinedConfig(module1.Object, runtimeInfo.Object); - CombinedKubernetesConfig config2 = provider.GetCombinedConfig(module2.Object, runtimeInfo.Object); - - // Assert - Assert.True(config1.CreateOptions.Entrypoint.HasValue); - config1.CreateOptions.Entrypoint.ForEach(ep => Assert.Equal("a-command", ep[0])); - Assert.True(config2.CreateOptions.Entrypoint.HasValue); - config2.CreateOptions.Entrypoint.ForEach(ep => Assert.Equal("a-command2", ep[0])); - } - - const string WorkingDirCreateOptions = - @"{ -""WorkingDir"" : ""a-directory"" -}"; - const string WorkingDirCreateOptionsLC = - @"{ -""workingdir"" : ""a-directory2"" -}"; - - [Fact] - public void WorkingDirOptionsWillExist() - { - var runtimeInfo = new Mock>(); - runtimeInfo.SetupGet(ri => ri.Config).Returns(new DockerRuntimeConfig("1.24", string.Empty)); - - var module1 = new Mock>(); - module1.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest", WorkingDirCreateOptions, Option.None())); - module1.SetupGet(m => m.Name).Returns("mod1"); - var module2 = new Mock>(); - module2.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest", WorkingDirCreateOptionsLC, Option.None())); - module2.SetupGet(m => m.Name).Returns("mod1"); - - CombinedKubernetesConfigProvider provider = new CombinedKubernetesConfigProvider(new[] { new AuthConfig() }, new Uri("unix:///var/run/iotedgedworkload.sock"), new Uri("unix:///var/run/iotedgedmgmt.sock"), true); - - // Act - CombinedKubernetesConfig config1 = provider.GetCombinedConfig(module1.Object, runtimeInfo.Object); - CombinedKubernetesConfig config2 = provider.GetCombinedConfig(module2.Object, runtimeInfo.Object); - - // Assert - Assert.True(config1.CreateOptions.WorkingDir.HasValue); - config1.CreateOptions.WorkingDir.ForEach(wd => Assert.Equal("a-directory", wd)); - Assert.True(config2.CreateOptions.WorkingDir.HasValue); - config2.CreateOptions.WorkingDir.ForEach(wd => Assert.Equal("a-directory2", wd)); - } - - const string InvalidCmdCreateOptions = - @"{ -""Cmd"" : { - ""argument1"": ""argument2"" - } -}"; - - [Fact] - public void InvalidCmdEntryOptionsThrows() - { - var runtimeInfo = new Mock>(); - runtimeInfo.SetupGet(ri => ri.Config).Returns(new DockerRuntimeConfig("1.24", string.Empty)); - - var module = new Mock>(); - module.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest", InvalidCmdCreateOptions, Option.None())); - module.SetupGet(m => m.Name).Returns("mod1"); - - CombinedKubernetesConfigProvider provider = new CombinedKubernetesConfigProvider(new[] { new AuthConfig() }, new Uri("unix:///var/run/iotedgedworkload.sock"), new Uri("unix:///var/run/iotedgedmgmt.sock"), true); - - // Act - // Assert - Assert.Throws(() => provider.GetCombinedConfig(module.Object, runtimeInfo.Object)); - } - - const string InvalidEntryPointCreateOptions = - @"{ -""Entrypoint"" : ""a-command"" -}"; - - [Fact] - public void InvalidEntrypointOptionsThrows() - { - var runtimeInfo = new Mock>(); - runtimeInfo.SetupGet(ri => ri.Config).Returns(new DockerRuntimeConfig("1.24", string.Empty)); - - var module = new Mock>(); - module.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest", InvalidEntryPointCreateOptions, Option.None())); - module.SetupGet(m => m.Name).Returns("mod1"); - - CombinedKubernetesConfigProvider provider = new CombinedKubernetesConfigProvider(new[] { new AuthConfig() }, new Uri("unix:///var/run/iotedgedworkload.sock"), new Uri("unix:///var/run/iotedgedmgmt.sock"), true); - - // Act - // Assert - Assert.Throws(() => provider.GetCombinedConfig(module.Object, runtimeInfo.Object)); - } - - const string InvalidWorkingDirCreateOptions = - @"{ -""WorkingDir"" : [ ""/tmp/working"" ] -}"; - - [Fact] - public void InvalidWorkingDirOptionsThrows() - { - var runtimeInfo = new Mock>(); - runtimeInfo.SetupGet(ri => ri.Config).Returns(new DockerRuntimeConfig("1.24", string.Empty)); - - var module = new Mock>(); - module.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest", InvalidWorkingDirCreateOptions, Option.None())); - module.SetupGet(m => m.Name).Returns("mod1"); - - CombinedKubernetesConfigProvider provider = new CombinedKubernetesConfigProvider(new[] { new AuthConfig() }, new Uri("unix:///var/run/iotedgedworkload.sock"), new Uri("unix:///var/run/iotedgedmgmt.sock"), true); - - // Act - // Assert - Assert.Throws(() => provider.GetCombinedConfig(module.Object, runtimeInfo.Object)); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/DeploymentSecretBackupTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/DeploymentSecretBackupTest.cs deleted file mode 100644 index e4d0db28cd8..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/DeploymentSecretBackupTest.cs +++ /dev/null @@ -1,464 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System; - using System.Collections.Generic; - using System.Net; - using System.Net.Http; - using System.Threading; - using k8s; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Core.Serde; - using Microsoft.Azure.Devices.Edge.Agent.Core.Test; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Microsoft.Rest; - using Moq; - using Xunit; - - using KubeConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - [Unit] - public class DeploymentSecretBackupTest - { - const string TestType = "test"; - const string DefaultSecretName = "default-secret-name"; - const string DefaultNamespace = "default-namespace"; - static readonly KubernetesModuleOwner DefaultOwner = new KubernetesModuleOwner("v1", "Deployment", "Owner", "UID"); - - static readonly IDictionary EnvVars = new Dictionary(); - static readonly ConfigurationInfo ConfigurationInfo = new ConfigurationInfo(); - static readonly IEdgeAgentModule EdgeAgentModule = new TestAgentModule("edgeAgent", "test", new TestConfig("edge-agent"), ImagePullPolicy.OnCreate, ConfigurationInfo, EnvVars); - static readonly TestRuntimeInfo TestRuntimeInfo = new TestRuntimeInfo("test"); - static readonly TestConfig Config1 = new TestConfig("image1"); - static readonly IModule ValidModule1 = new TestModule("mod1", "version1", "test", ModuleStatus.Running, Config1, RestartPolicy.OnUnhealthy, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, ConfigurationInfo, EnvVars); - static readonly IEdgeHubModule EdgeHubModule = new TestHubModule("edgeHub", "test", ModuleStatus.Running, new TestConfig("edge-hub:latest"), RestartPolicy.Always, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, ConfigurationInfo, EnvVars); - static readonly IDictionary Modules1 = new Dictionary { ["mod1"] = ValidModule1 }; - static readonly IDictionary Modules2 = new Dictionary { ["mod2"] = ValidModule1 }; - static readonly DeploymentConfig ValidConfig1 = new DeploymentConfig("1.0", TestRuntimeInfo, new SystemModules(EdgeAgentModule, EdgeHubModule), Modules1); - static readonly DeploymentConfig ValidConfig2 = new DeploymentConfig("1.0", TestRuntimeInfo, new SystemModules(EdgeAgentModule, EdgeHubModule), Modules2); - static readonly DeploymentConfigInfo ValidConfigInfo1 = new DeploymentConfigInfo(0, ValidConfig1); - static readonly DeploymentConfigInfo ValidConfigInfo2 = new DeploymentConfigInfo(0, ValidConfig2); - - [Fact] - public void CreateFailsOnEmptyInput() - { - var serde = Mock.Of>(); - var client = Mock.Of(); - - Assert.Throws(() => new DeploymentSecretBackup(null, DefaultNamespace, DefaultOwner, serde, client)); - Assert.Throws(() => new DeploymentSecretBackup(" ", DefaultNamespace, DefaultOwner, serde, client)); - Assert.Throws(() => new DeploymentSecretBackup(DefaultSecretName, null, DefaultOwner, serde, client)); - Assert.Throws(() => new DeploymentSecretBackup(DefaultSecretName, " ", DefaultOwner, serde, client)); - Assert.Throws(() => new DeploymentSecretBackup(DefaultSecretName, DefaultNamespace, null, serde, client)); - Assert.Throws(() => new DeploymentSecretBackup(DefaultSecretName, DefaultNamespace, DefaultOwner, null, client)); - Assert.Throws(() => new DeploymentSecretBackup(DefaultSecretName, DefaultNamespace, DefaultOwner, serde, null)); - } - - [Fact] - public void NameIsSecretName() - { - var serde = Mock.Of>(); - var client = Mock.Of(); - - var backupSource = new DeploymentSecretBackup(DefaultSecretName, DefaultNamespace, DefaultOwner, serde, client); - - Assert.Equal(DefaultSecretName, backupSource.Name); - } - - [Fact] - public async void ReadDeploymentConfigFromSecret() - { - ISerde serde = this.GetSerde(); - - var secretData = new Dictionary - { - ["backup.json"] = System.Text.Encoding.UTF8.GetBytes(serde.Serialize(ValidConfigInfo1)) - }; - var secret = new V1Secret(data: secretData); - var response = new HttpOperationResponse() - { - Body = secret, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - var client = new Mock(MockBehavior.Strict); - client.Setup(c => c.ReadNamespacedSecretWithHttpMessagesAsync( - DefaultSecretName, - DefaultNamespace, - It.IsAny(), // pretty - null, // customHeaders - It.IsAny())) - .ReturnsAsync(response); - - var backupSource = new DeploymentSecretBackup(DefaultSecretName, DefaultNamespace, DefaultOwner, serde, client.Object); - var deploymentConfigInfo = await backupSource.ReadFromBackupAsync(); - - string returnedJson = serde.Serialize(deploymentConfigInfo); - string expectedJson = serde.Serialize(ValidConfigInfo1); - - Assert.Equal(expectedJson, returnedJson, ignoreCase: true); - client.VerifyAll(); - } - - [Fact] - public async void NullSecretreturnsEmptyConfig() - { - ISerde serde = this.GetSerde(); - - var response = new HttpOperationResponse() - { - Body = null, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - var client = new Mock(MockBehavior.Strict); - client.Setup(c => c.ReadNamespacedSecretWithHttpMessagesAsync( - DefaultSecretName, - DefaultNamespace, - It.IsAny(), // pretty - null, // customHeaders - It.IsAny())) - .ReturnsAsync(response); - - var backupSource = new DeploymentSecretBackup(DefaultSecretName, DefaultNamespace, DefaultOwner, serde, client.Object); - var deploymentConfigInfo = await backupSource.ReadFromBackupAsync(); - - Assert.NotNull(deploymentConfigInfo); - Assert.Equal(DeploymentConfigInfo.Empty, deploymentConfigInfo); - client.VerifyAll(); - } - - [Fact] - public async void SecretDataNoKeyReturnsEmptyConfig() - { - ISerde serde = this.GetSerde(); - - var secretData = new Dictionary - { - ["no match"] = System.Text.Encoding.UTF8.GetBytes(serde.Serialize(ValidConfigInfo1)) - }; - var secret = new V1Secret(data: secretData); - var response = new HttpOperationResponse() - { - Body = secret, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - var client = new Mock(MockBehavior.Strict); - client.Setup(c => c.ReadNamespacedSecretWithHttpMessagesAsync( - DefaultSecretName, - DefaultNamespace, - It.IsAny(), // pretty - null, // customHeaders - It.IsAny())) - .ReturnsAsync(response); - - var backupSource = new DeploymentSecretBackup(DefaultSecretName, DefaultNamespace, DefaultOwner, serde, client.Object); - var deploymentConfigInfo = await backupSource.ReadFromBackupAsync(); - - Assert.NotNull(deploymentConfigInfo); - Assert.Equal(DeploymentConfigInfo.Empty, deploymentConfigInfo); - client.VerifyAll(); - } - - [Fact] - public async void DeserializeFaillureReturnsEmptyConfig() - { - ISerde serde = this.GetSerde(); - - var secretData = new Dictionary - { - ["backup.json"] = System.Text.Encoding.UTF8.GetBytes("{}") - }; - var secret = new V1Secret(data: secretData); - var response = new HttpOperationResponse() - { - Body = secret, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - var client = new Mock(MockBehavior.Strict); - client.Setup(c => c.ReadNamespacedSecretWithHttpMessagesAsync( - DefaultSecretName, - DefaultNamespace, - It.IsAny(), // pretty - null, // customHeaders - It.IsAny())) - .ReturnsAsync(response); - - var backupSource = new DeploymentSecretBackup(DefaultSecretName, DefaultNamespace, DefaultOwner, serde, client.Object); - var deploymentConfigInfo = await backupSource.ReadFromBackupAsync(); - - Assert.NotNull(deploymentConfigInfo); - Assert.Equal(DeploymentConfigInfo.Empty, deploymentConfigInfo); - client.VerifyAll(); - } - - [Fact] - public async void BackupDeploymentConfigCreatesNewSecret() - { - ISerde serde = this.GetSerde(); - - string expectedJson = serde.Serialize(ValidConfigInfo1); - var secretData = new Dictionary - { - ["backup.json"] = System.Text.Encoding.UTF8.GetBytes(expectedJson) - }; - var secret = new V1Secret(data: secretData); - var createResponse = new HttpOperationResponse() - { - Body = secret, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - byte[] receivedData = default(byte[]); - var client = new Mock(MockBehavior.Strict); - client.Setup(c => c.ReadNamespacedSecretWithHttpMessagesAsync( - DefaultSecretName, - DefaultNamespace, - It.IsAny(), // pretty - null, // customHeaders - It.IsAny())) - .ThrowsAsync(new HttpOperationException("Not Found")); - client.Setup(c => c.CreateNamespacedSecretWithHttpMessagesAsync(It.IsAny(), DefaultNamespace, null, null, null, null, It.IsAny())) - .Callback((V1Secret body, string namespaceParameter, string dryRun, string fieldManager, string pretty, Dictionary> customHeaders, CancellationToken cancellationToken) => - { - Assert.True(body.Data != null); - Assert.True(body.Data.TryGetValue("backup.json", out receivedData)); - }) - .ReturnsAsync(createResponse); - - var backupSource = new DeploymentSecretBackup(DefaultSecretName, DefaultNamespace, DefaultOwner, serde, client.Object); - await backupSource.BackupDeploymentConfigAsync(ValidConfigInfo1); - - string backupJson = System.Text.Encoding.UTF8.GetString(receivedData); - - Assert.Equal(expectedJson, backupJson, ignoreCase: true); - client.VerifyAll(); - } - - [Fact] - public async void BackupDeploymentConfigReplacesSecret() - { - ISerde serde = this.GetSerde(); - - string expectedJson = serde.Serialize(ValidConfigInfo1); - var readSecretData = new Dictionary - { - ["backup.json"] = System.Text.Encoding.UTF8.GetBytes(serde.Serialize(ValidConfigInfo2)) - }; - var readSecret = new V1Secret(data: readSecretData); - var replaceSecretData = new Dictionary - { - ["backup.json"] = System.Text.Encoding.UTF8.GetBytes(expectedJson) - }; - var replaceSecret = new V1Secret(data: readSecretData); - var readResponse = new HttpOperationResponse() - { - Body = readSecret, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - var replaceResponse = new HttpOperationResponse() - { - Body = replaceSecret, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - byte[] receivedData = default(byte[]); - var client = new Mock(MockBehavior.Strict); - client.Setup(c => c.ReadNamespacedSecretWithHttpMessagesAsync( - DefaultSecretName, - DefaultNamespace, - It.IsAny(), // pretty - null, // customHeaders - It.IsAny())) - .ReturnsAsync(readResponse); - client.Setup(c => c.ReplaceNamespacedSecretWithHttpMessagesAsync( - It.IsAny(), - DefaultSecretName, - DefaultNamespace, - null, // dryRun - null, // fieldmanager - null, // pretty - null, // customHeaders - It.IsAny())) - .Callback((V1Secret body, string name, string namespaceParameter, string dryRun, string fieldManager, string pretty, Dictionary> customHeaders, CancellationToken cancellationToken) => - { - Assert.True(body.Data != null); - Assert.True(body.Data.TryGetValue("backup.json", out receivedData)); - }) - .ReturnsAsync(replaceResponse); - - var backupSource = new DeploymentSecretBackup(DefaultSecretName, DefaultNamespace, DefaultOwner, serde, client.Object); - await backupSource.BackupDeploymentConfigAsync(ValidConfigInfo1); - - string backupJson = System.Text.Encoding.UTF8.GetString(receivedData); - - Assert.Equal(expectedJson, backupJson, ignoreCase: true); - client.VerifyAll(); - } - - [Fact] - public async void BackupDeploymentConfigDoesNotReplacesSameSecret() - { - ISerde serde = this.GetSerde(); - - string expectedJson = serde.Serialize(ValidConfigInfo1); - var readSecretData = new Dictionary - { - ["backup.json"] = System.Text.Encoding.UTF8.GetBytes(expectedJson) - }; - var readSecret = new V1Secret(data: readSecretData); - var readResponse = new HttpOperationResponse() - { - Body = readSecret, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - var client = new Mock(MockBehavior.Strict); - client.Setup(c => c.ReadNamespacedSecretWithHttpMessagesAsync( - DefaultSecretName, - DefaultNamespace, - It.IsAny(), // pretty - null, // customHeaders - It.IsAny())) - .ReturnsAsync(readResponse); - - var backupSource = new DeploymentSecretBackup(DefaultSecretName, DefaultNamespace, DefaultOwner, serde, client.Object); - await backupSource.BackupDeploymentConfigAsync(ValidConfigInfo1); - - client.VerifyAll(); - } - - [Fact] - public async void BackupDeploymentDoesNotThrowOnFailure() - { - ISerde serde = this.GetSerde(); - - string expectedJson = serde.Serialize(ValidConfigInfo1); - var readSecretData = new Dictionary - { - ["backup.json"] = System.Text.Encoding.UTF8.GetBytes(serde.Serialize(ValidConfigInfo2)) - }; - var readSecret = new V1Secret(data: readSecretData); - var replaceSecretData = new Dictionary - { - ["backup.json"] = System.Text.Encoding.UTF8.GetBytes(expectedJson) - }; - var replaceSecret = new V1Secret(data: readSecretData); - var readResponse = new HttpOperationResponse() - { - Body = readSecret, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - var client = new Mock(MockBehavior.Strict); - client.Setup(c => c.ReadNamespacedSecretWithHttpMessagesAsync( - DefaultSecretName, - DefaultNamespace, - It.IsAny(), // pretty - null, // customHeaders - It.IsAny())) - .ReturnsAsync(readResponse); - client.Setup(c => c.ReplaceNamespacedSecretWithHttpMessagesAsync( - It.IsAny(), - DefaultSecretName, - DefaultNamespace, - null, // dryRun - null, // fieldmanager - null, // pretty - null, // customHeaders - It.IsAny())) - .ThrowsAsync(new HttpOperationException("Not Permitted")); - - var backupSource = new DeploymentSecretBackup(DefaultSecretName, DefaultNamespace, DefaultOwner, serde, client.Object); - await backupSource.BackupDeploymentConfigAsync(ValidConfigInfo1); - - client.VerifyAll(); - } - - ISerde GetSerde() - { - var moduleDeserializerTypes = new Dictionary - { - [TestType] = typeof(TestModule) - }; - - var edgeAgentDeserializerTypes = new Dictionary - { - [TestType] = typeof(TestAgentModule) - }; - - var edgeHubDeserializerTypes = new Dictionary - { - [TestType] = typeof(TestHubModule) - }; - - var runtimeInfoDeserializerTypes = new Dictionary - { - [TestType] = typeof(TestRuntimeInfo) - }; - - var deserializerTypesMap = new Dictionary> - { - [typeof(IModule)] = moduleDeserializerTypes, - [typeof(IEdgeAgentModule)] = edgeAgentDeserializerTypes, - [typeof(IEdgeHubModule)] = edgeHubDeserializerTypes, - [typeof(IRuntimeInfo)] = runtimeInfoDeserializerTypes, - }; - - ISerde serde = new TypeSpecificSerDe(deserializerTypesMap); - return serde; - } - } - - class TestRuntimeInfo : IRuntimeInfo - { - public TestRuntimeInfo(string type) - { - this.Type = type; - } - - public string Type { get; } - - public static bool operator ==(TestRuntimeInfo left, TestRuntimeInfo right) => Equals(left, right); - - public static bool operator !=(TestRuntimeInfo left, TestRuntimeInfo right) => !Equals(left, right); - - public bool Equals(IRuntimeInfo other) => other is TestRuntimeInfo otherRuntimeInfo - && this.Equals(otherRuntimeInfo); - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; - if (ReferenceEquals(this, obj)) - return true; - if (obj.GetType() != this.GetType()) - return false; - return this.Equals((TestRuntimeInfo)obj); - } - - public override int GetHashCode() - { - return this.Type != null ? this.Type.GetHashCode() : 0; - } - - public bool Equals(TestRuntimeInfo other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return string.Equals(this.Type, other.Type); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/EdgeDeploymentCommandTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/EdgeDeploymentCommandTest.cs deleted file mode 100644 index 8e9563dc62b..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/EdgeDeploymentCommandTest.cs +++ /dev/null @@ -1,347 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Net.Http; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using global::Docker.DotNet.Models; - using k8s; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Microsoft.Rest; - using Moq; - using Moq.Language.Flow; - using Newtonsoft.Json.Linq; - using Xunit; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - [Unit] - public class EdgeDeploymentCommandTest - { - const string Selector = "selector"; - const string Namespace = "namespace"; - static readonly ResourceName ResourceName = new ResourceName("hostname", "deviceId"); - static readonly IDictionary EnvVars = new Dictionary(); - static readonly AuthConfig DockerAuth = new AuthConfig { Username = "username", Password = "password", ServerAddress = "docker.io" }; - static readonly ImagePullSecret ImagePullSecret = new ImagePullSecret(DockerAuth); - static readonly DockerConfig Config1 = new DockerConfig("test-image:1"); - static readonly DockerConfig Config2 = new DockerConfig("test-image:2"); - static readonly ConfigurationInfo DefaultConfigurationInfo = new ConfigurationInfo("1"); - static readonly KubernetesModuleOwner EdgeletModuleOwner = new KubernetesModuleOwner("v1", "Deployment", "iotedged", "123"); - static readonly IKubernetes DefaultClient = Mock.Of(); - static readonly ICombinedConfigProvider ConfigProvider = Mock.Of>(); - static readonly IRuntimeInfo Runtime = Mock.Of(); - - [Fact] - public void Constructor_ThrowsException_OnInvalidParams() - { - KubernetesConfig config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.None()); - IModule m1 = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - KubernetesModule km1 = new KubernetesModule(m1, config, EdgeletModuleOwner); - KubernetesModule[] modules = { km1 }; - EdgeDeploymentDefinition edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List()); - Assert.Throws(() => new EdgeDeploymentCommand(null, Selector, Namespace, DefaultClient, modules, Option.Maybe(edgeDefinition), Runtime, ConfigProvider, EdgeletModuleOwner)); - Assert.Throws(() => new EdgeDeploymentCommand(ResourceName, null, Namespace, DefaultClient, modules, Option.Maybe(edgeDefinition), Runtime, ConfigProvider, EdgeletModuleOwner)); - Assert.Throws(() => new EdgeDeploymentCommand(ResourceName, Selector, null, DefaultClient, modules, Option.Maybe(edgeDefinition), Runtime, ConfigProvider, EdgeletModuleOwner)); - Assert.Throws(() => new EdgeDeploymentCommand(ResourceName, Selector, Namespace, null, modules, Option.Maybe(edgeDefinition), Runtime, ConfigProvider, EdgeletModuleOwner)); - Assert.Throws(() => new EdgeDeploymentCommand(ResourceName, Selector, Namespace, DefaultClient, null, Option.Maybe(edgeDefinition), Runtime, ConfigProvider, EdgeletModuleOwner)); - Assert.Throws(() => new EdgeDeploymentCommand(ResourceName, Selector, Namespace, DefaultClient, modules, Option.Maybe(edgeDefinition), null, ConfigProvider, EdgeletModuleOwner)); - Assert.Throws(() => new EdgeDeploymentCommand(ResourceName, Selector, Namespace, DefaultClient, modules, Option.Maybe(edgeDefinition), Runtime, null, EdgeletModuleOwner)); - Assert.Throws(() => new EdgeDeploymentCommand(ResourceName, Selector, Namespace, DefaultClient, modules, Option.Maybe(edgeDefinition), Runtime, ConfigProvider, null)); - } - - [Fact] - public async void Execute_CreatesNewImagePullSecret_WhenEmpty() - { - IModule dockerModule = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var dockerConfigProvider = new Mock>(); - dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) - .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.None(), Option.Maybe(DockerAuth))); - var configProvider = new Mock>(); - configProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) - .Returns(() => new CombinedKubernetesConfig("test-image:1", CreatePodParameters.Create(image: "test-image:1"), Option.Maybe(ImagePullSecret))); - - var client = new Mock(); - client.SetupListSecrets() - .ReturnsAsync( - () => - new HttpOperationResponse - { - Response = new HttpResponseMessage(), - Body = new V1SecretList { Items = new List() } - }); - V1Secret createdSecret = null; - client.SetupCreateSecret() - .Callback( - (V1Secret body, string ns, string dryRun, string fieldManager, string pretty, Dictionary> customHeaders, CancellationToken token) => { createdSecret = body; }) - .ReturnsAsync(() => CreateResponse(HttpStatusCode.Created, new V1Secret())); - client.SetupCreateEdgeDeploymentDefinition().ReturnsAsync(() => CreateResponse(HttpStatusCode.Created, new object())); - var cmd = new EdgeDeploymentCommand(ResourceName, Selector, Namespace, client.Object, new[] { dockerModule }, Option.None(), Runtime, configProvider.Object, EdgeletModuleOwner); - - await cmd.ExecuteAsync(CancellationToken.None); - - Assert.NotNull(createdSecret); - client.VerifyAll(); - } - - [Fact] - public async void Execute_UpdatesImagePullSecret_WhenExistsWithSameName() - { - string secretName = "username-docker.io"; - var secretData = new Dictionary { [KubernetesConstants.K8sPullSecretData] = Encoding.UTF8.GetBytes("Invalid Secret Data") }; - var secretMeta = new V1ObjectMeta(name: secretName, namespaceProperty: Namespace); - var existingSecret = new V1Secret("v1", secretData, type: KubernetesConstants.K8sPullSecretType, kind: "Secret", metadata: secretMeta); - IModule dockerModule = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var dockerConfigProvider = new Mock>(); - dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) - .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.None(), Option.Maybe(DockerAuth))); - var configProvider = new Mock>(); - configProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) - .Returns(() => new CombinedKubernetesConfig("test-image:1", CreatePodParameters.Create(image: "test-image:1"), Option.Maybe(ImagePullSecret))); - - var client = new Mock(); - client.SetupListSecrets().ReturnsAsync(() => CreateResponse(new V1SecretList { Items = new List { existingSecret } })); - V1Secret updatedSecret = null; - client.SetupUpdateSecret() - .Callback( - (V1Secret body, string name, string ns, string dryRun, string fieldManager, string pretty, Dictionary> customHeaders, CancellationToken token) => { updatedSecret = body; }) - .ReturnsAsync(() => CreateResponse(new V1Secret())); - client.SetupCreateEdgeDeploymentDefinition().ReturnsAsync(() => CreateResponse(HttpStatusCode.Created, new object())); - var cmd = new EdgeDeploymentCommand(ResourceName, Selector, Namespace, client.Object, new[] { dockerModule }, Option.None(), Runtime, configProvider.Object, EdgeletModuleOwner); - - await cmd.ExecuteAsync(CancellationToken.None); - - Assert.NotNull(updatedSecret); - client.VerifyAll(); - } - - [Fact] - public async void Execute_UpdatesEdgeDeploymentDefinition_WhenExistsWithSameName() - { - IModule dockerModule = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var dockerConfigProvider = new Mock>(); - dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) - .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.None(), Option.Maybe(DockerAuth))); - var configProvider = new Mock>(); - configProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) - .Returns(() => new CombinedKubernetesConfig("test-image:1", CreatePodParameters.Create(image: "test-image:1"), Option.Maybe(ImagePullSecret))); - var existingDeployment = new EdgeDeploymentDefinition(KubernetesConstants.EdgeDeployment.ApiVersion, KubernetesConstants.EdgeDeployment.Kind, new V1ObjectMeta(name: ResourceName), new List()); - - var client = new Mock(); - client.SetupListSecrets().ReturnsAsync(() => CreateResponse(new V1SecretList { Items = new List() })); - client.SetupCreateSecret().ReturnsAsync(() => CreateResponse(HttpStatusCode.Created, new V1Secret())); - client.SetupUpdateEdgeDeploymentDefinition().ReturnsAsync(CreateResponse(HttpStatusCode.Created, new object())); - var cmd = new EdgeDeploymentCommand(ResourceName, Selector, Namespace, client.Object, new[] { dockerModule }, Option.Some(existingDeployment), Runtime, configProvider.Object, EdgeletModuleOwner); - - await cmd.ExecuteAsync(CancellationToken.None); - - client.VerifyAll(); - } - - [Fact] - public async void Execute_CreatesOnlyOneImagePullSecret_When2ModulesWithSameSecret() - { - IModule dockerModule1 = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - IModule dockerModule2 = new DockerModule("module2", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config2, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var dockerConfigProvider = new Mock>(); - dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(It.IsAny(), Runtime)) - .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.None(), Option.Maybe(DockerAuth))); - var configProvider = new Mock>(); - configProvider.Setup(cp => cp.GetCombinedConfig(It.IsAny(), Runtime)) - .Returns(() => new CombinedKubernetesConfig("test-image:1", CreatePodParameters.Create(image: "test-image:1"), Option.Maybe(ImagePullSecret))); - - var client = new Mock(); - client.SetupListSecrets().ReturnsAsync(() => CreateResponse(new V1SecretList { Items = new List() })); - client.SetupCreateSecret().ReturnsAsync(() => CreateResponse(HttpStatusCode.Created, new V1Secret())); - client.SetupCreateEdgeDeploymentDefinition().ReturnsAsync(CreateResponse(HttpStatusCode.Created, new object())); - var cmd = new EdgeDeploymentCommand(ResourceName, Selector, Namespace, client.Object, new[] { dockerModule1, dockerModule2 }, Option.None(), Runtime, configProvider.Object, EdgeletModuleOwner); - - await cmd.ExecuteAsync(CancellationToken.None); - - client.VerifyAll(); - client.VerifyCreateSecret(Times.Once()); - } - - [Fact] - public async void Execute_PreservesCaseOfEnvVars_WhenModuleDeployed() - { - IDictionary moduleEnvVars = new Dictionary { { "ACamelCaseEnvVar", new EnvVal("ACamelCaseEnvVarValue") } }; - IModule dockerModule = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, moduleEnvVars); - var dockerConfigProvider = new Mock>(); - dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) - .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.None(), Option.Maybe(DockerAuth))); - var configProvider = new Mock>(); - configProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) - .Returns(() => new CombinedKubernetesConfig("test-image:1", CreatePodParameters.Create(image: "test-image:1"), Option.Maybe(ImagePullSecret))); - - var client = new Mock(); - client.SetupListSecrets().ReturnsAsync(() => CreateResponse(new V1SecretList { Items = new List() })); - client.SetupCreateSecret().ReturnsAsync(() => CreateResponse(HttpStatusCode.Created, new V1Secret())); - - EdgeDeploymentDefinition edgeDeploymentDefinition = null; - client.SetupCreateEdgeDeploymentDefinition() - .Callback( - (object body, string group, string version, string ns, string plural, string dryRun, string fieldManager, string pretty, Dictionary> headers, CancellationToken token) => { edgeDeploymentDefinition = ((JObject)body).ToObject(); }) - .ReturnsAsync(() => CreateResponse(edgeDeploymentDefinition)); - - var cmd = new EdgeDeploymentCommand(ResourceName, Selector, Namespace, client.Object, new[] { dockerModule }, Option.None(), Runtime, configProvider.Object, EdgeletModuleOwner); - - await cmd.ExecuteAsync(CancellationToken.None); - - Assert.Equal("module1", edgeDeploymentDefinition.Spec[0].Name); - Assert.Equal("test-image:1", edgeDeploymentDefinition.Spec[0].Config.Image); - Assert.True(edgeDeploymentDefinition.Spec[0].Env.Contains(new KeyValuePair("ACamelCaseEnvVar", new EnvVal("ACamelCaseEnvVarValue")))); - } - - [Fact] - public async void Execute_UpdatesSecretData_WhenImagePullSecretCreatedNotByAgent() - { - string secretName = "username-docker.io"; - var existingSecret = new V1Secret - { - Data = new Dictionary { [KubernetesConstants.K8sPullSecretData] = Encoding.UTF8.GetBytes("Invalid Secret Data") }, - Type = KubernetesConstants.K8sPullSecretType, - Metadata = new V1ObjectMeta { Name = secretName, NamespaceProperty = Namespace, ResourceVersion = "1" } - }; - - IModule dockerModule = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var dockerConfigProvider = new Mock>(); - dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) - .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.None(), Option.Maybe(DockerAuth))); - var configProvider = new Mock>(); - configProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) - .Returns(() => new CombinedKubernetesConfig("test-image:1", CreatePodParameters.Create(image: "test-image:1"), Option.Maybe(ImagePullSecret))); - - var client = new Mock(); - client.SetupListSecrets().ReturnsAsync(() => CreateResponse(new V1SecretList { Items = new List() })); - client.SetupCreateSecret().ThrowsAsync(new HttpOperationException { Response = new HttpResponseMessageWrapper(new HttpResponseMessage(HttpStatusCode.Conflict), "Conflict") }); - V1Secret updatedSecret = null; - client.SetupUpdateSecret() - .Callback( - (V1Secret body, string name, string ns, string dryRun, string fieldManager, string pretty, Dictionary> customHeaders, CancellationToken token) => { updatedSecret = body; }) - .ReturnsAsync(() => CreateResponse(updatedSecret)); - client.SetupGetSecret(secretName).ReturnsAsync(() => CreateResponse(existingSecret)); - var cmd = new EdgeDeploymentCommand(ResourceName, Selector, Namespace, client.Object, new[] { dockerModule }, Option.None(), Runtime, configProvider.Object, EdgeletModuleOwner); - - await cmd.ExecuteAsync(CancellationToken.None); - - Assert.True(Encoding.UTF8.GetBytes(ImagePullSecret.GenerateSecret()).SequenceEqual(updatedSecret.Data[KubernetesConstants.K8sPullSecretData])); - Assert.Equal("1", updatedSecret.Metadata.ResourceVersion); - client.VerifyAll(); - } - - static HttpOperationResponse CreateResponse(TResult value) => - CreateResponse(HttpStatusCode.OK, value); - - static HttpOperationResponse CreateResponse(HttpStatusCode statusCode, TResult value) => - new HttpOperationResponse - { - Response = new HttpResponseMessage(statusCode), - Body = value - }; - } - - public static class IKubernetesEdgeDeploymentDefinitionMockExtensions - { - public static ISetup>> SetupCreateEdgeDeploymentDefinition(this Mock client) => - client.Setup( - c => c.CreateNamespacedCustomObjectWithHttpMessagesAsync( - It.IsAny(), // body - KubernetesConstants.EdgeDeployment.Group, - KubernetesConstants.EdgeDeployment.Version, - It.IsAny(), // namespace - KubernetesConstants.EdgeDeployment.Plural, - It.IsAny(), // dryRun - It.IsAny(), // fieldManager - It.IsAny(), // pretty - It.IsAny>>(), // customHeaders - It.IsAny())); - - public static ISetup>> SetupUpdateEdgeDeploymentDefinition(this Mock client) => - client.Setup( - c => c.ReplaceNamespacedCustomObjectWithHttpMessagesAsync( - It.IsAny(), // body - KubernetesConstants.EdgeDeployment.Group, - KubernetesConstants.EdgeDeployment.Version, - It.IsAny(), // namespace - KubernetesConstants.EdgeDeployment.Plural, - It.IsAny(), // name - It.IsAny(), // dryRun - It.IsAny(), // fieldManager - It.IsAny>>(), // customHeaders - It.IsAny())); - } - - public static class IKubernetesSecretMockExtensions - { - public static ISetup>> SetupListSecrets(this Mock client) => - client.Setup( - c => c.ListNamespacedSecretWithHttpMessagesAsync( - It.IsAny(), // namespace - It.IsAny(), // allowWatchBookmarks - It.IsAny(), // continueParameter - It.IsAny(), // fieldSelector - It.IsAny(), // labelSelector - It.IsAny(), // limit - It.IsAny(), // resourceVersion - It.IsAny(), // resourceVersionMatch - It.IsAny(), // timeoutSeconds - It.IsAny(), // watch - It.IsAny(), // pretty - It.IsAny>>(), // customHeaders - It.IsAny())); - - public static ISetup>> SetupCreateSecret(this Mock client) => - client.Setup( - c => c.CreateNamespacedSecretWithHttpMessagesAsync( - It.IsAny(), - It.IsAny(), // namespace - It.IsAny(), // dryRun - It.IsAny(), // fieldManager - It.IsAny(), // pretty - It.IsAny>>(), // customHeaders - It.IsAny())); - - public static void VerifyCreateSecret(this Mock client, Times times) => - client.Verify( - c => c.CreateNamespacedSecretWithHttpMessagesAsync( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny>>(), - It.IsAny()), - times); - - public static ISetup>> SetupUpdateSecret(this Mock client) => - client.Setup( - c => c.ReplaceNamespacedSecretWithHttpMessagesAsync( - It.IsAny(), - It.IsAny(), // name - It.IsAny(), // namespace - It.IsAny(), // dryRun - It.IsAny(), // fieldmanager - It.IsAny(), // pretty - It.IsAny>>(), // customHeaders - It.IsAny())); - - public static ISetup>> SetupGetSecret(this Mock client, string name) => - client.Setup( - c => c.ReadNamespacedSecretWithHttpMessagesAsync( - name, - It.IsAny(), - It.IsAny(), - It.IsAny>>(), - It.IsAny())); - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/EdgeDeploymentStatusCommandTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/EdgeDeploymentStatusCommandTest.cs deleted file mode 100644 index f8f34bc9ba9..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/EdgeDeploymentStatusCommandTest.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System.Threading; - using Microsoft.Azure.Devices.Edge.Agent.Core.ConfigSources; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - - using Xunit; - - [Unit] - public class EdgeDeploymentStatusCommandTest - { - [Fact] - public async void ExecuteAyncSuccessWithNoStatus() - { - EdgeDeploymentStatusCommand noStatus = new EdgeDeploymentStatusCommand(Option.None()); - - await noStatus.ExecuteAsync(CancellationToken.None); - } - - [Fact] - public async void ExecuteAyncSuccessWithSuccessStatus() - { - EdgeDeploymentStatus success = EdgeDeploymentStatus.Success("200 OK"); - EdgeDeploymentStatusCommand successStatus = new EdgeDeploymentStatusCommand(Option.Some(success)); - - await successStatus.ExecuteAsync(CancellationToken.None); - } - - [Fact] - public async void ExecuteAsyncThrowsWithFailureStatus() - { - EdgeDeploymentStatus failure = EdgeDeploymentStatus.Failure("failure message"); - EdgeDeploymentStatusCommand failStatus = new EdgeDeploymentStatusCommand(Option.Some(failure)); - - ConfigOperationFailureException ex = await Assert.ThrowsAsync(() => failStatus.ExecuteAsync(CancellationToken.None)); - Assert.Equal("failure message", ex.Message); - } - - [Fact] - public void CurrentStatusReflectsDeploymentStatus() - { - EdgeDeploymentStatusCommand noStatus = new EdgeDeploymentStatusCommand(Option.None()); - EdgeDeploymentStatus success = EdgeDeploymentStatus.Success("200 OK"); - EdgeDeploymentStatusCommand successStatus = new EdgeDeploymentStatusCommand(Option.Some(success)); - EdgeDeploymentStatus failure = EdgeDeploymentStatus.Failure("failure message"); - EdgeDeploymentStatusCommand failStatus = new EdgeDeploymentStatusCommand(Option.Some(failure)); - - Assert.Equal($"Report EdgeDeployment status: [{EdgeDeploymentStatusType.Success}]", noStatus.Show()); - Assert.Equal($"Report EdgeDeployment status: [{EdgeDeploymentStatusType.Success}]", successStatus.Show()); - Assert.Equal($"Report EdgeDeployment status: [{EdgeDeploymentStatusType.Failure}]", failStatus.Show()); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/HttpContextExtensions.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/HttpContextExtensions.cs deleted file mode 100644 index 906a1e33aff..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/HttpContextExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Newtonsoft.Json; - using Newtonsoft.Json.Converters; - - public static class HttpContextExtensions - { - const string EndLine = "\r\n"; - - public static async Task Write(this HttpContext context, T item) - { - string line = JsonConvert.SerializeObject(item, new StringEnumConverter()); - await context.WriteStreamLine(line); - } - - public static async Task WriteStreamLine(this HttpContext context, string line) - { - await context.Response.WriteAsync(line.Replace(EndLine, string.Empty)); - await context.Response.WriteAsync(EndLine); - await context.Response.Body.FlushAsync(); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ImagePullSecretNameConverterTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ImagePullSecretNameConverterTest.cs deleted file mode 100644 index 1f355eba1c2..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ImagePullSecretNameConverterTest.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Newtonsoft.Json; - using Xunit; - - [Unit] - public class ImagePullSecretNameConverterTest - { - [Fact] - public void SerializesImagePullSecretNameAsString() - { - var name = new ImagePullSecretName("secret"); - - string json = JsonConvert.SerializeObject(name); - - Assert.Equal("\"secret\"", json); - } - - [Fact] - public void SerializesImagePullSecretNameInsideWrapperAsString() - { - var name = new ImagePullSecretName("secret"); - var auth = new AuthWrapper { Name = name }; - - string json = JsonConvert.SerializeObject(auth); - - Assert.Equal("{\"Name\":\"secret\"}", json); - } - - [Fact] - public void DeserializesImagePullSecretNameFromInsideWrapper() - { - string json = "{\"Name\":\"secret\"}"; - - AuthWrapper auth = JsonConvert.DeserializeObject(json); - - Assert.Equal("secret", auth.Name); - } - - [Fact] - public void DeserializesImagePullSecretNameFromString() - { - string json = "\"secret\""; - - ImagePullSecretName auth = JsonConvert.DeserializeObject(json); - - Assert.Equal(new ImagePullSecretName("secret"), auth); - } - - [Theory] - [InlineData("\"\"")] - [InlineData("\" \"")] - public void UnableToDeserializeImagePullSecretNameFromEmptyString(string json) - { - Assert.Throws(() => JsonConvert.DeserializeObject(json)); - } - - class AuthWrapper - { - public ImagePullSecretName Name { get; set; } - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ImagePullSecretNameTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ImagePullSecretNameTest.cs deleted file mode 100644 index c1ebc1b3a49..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ImagePullSecretNameTest.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using global::Docker.DotNet.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - - [Unit] - public class ImagePullSecretNameTest - { - [Fact] - public void CreatesImagePullSecretName() - { - var auth = new AuthConfig { Username = "name", Password = "password", ServerAddress = "server" }; - - var name = ImagePullSecretName.Create(auth); - - Assert.Equal("name-server", name); - } - - [Fact] - public void EqualsImagePullSecretsByValue() - { - var name = new ImagePullSecretName("secret"); - - Assert.True(name.Equals(name)); - Assert.True(name.Equals(new ImagePullSecretName("secret"))); - - Assert.False(name.Equals(null)); - Assert.False(name.Equals(new ImagePullSecretName("not a secret"))); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ImagePullSecretTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ImagePullSecretTest.cs deleted file mode 100644 index 43c3b2e3c0e..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ImagePullSecretTest.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System; - using System.Collections.Generic; - using System.Text; - using global::Docker.DotNet.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Newtonsoft.Json.Linq; - using Xunit; - - public class ImagePullSecretTest - { - public static IEnumerable GenerateAuthConfig() - { - yield return new object[] { new AuthConfig { Username = "one", Password = "two", ServerAddress = "three" } }; - } - - [Theory] - [Unit] - [MemberData(nameof(GenerateAuthConfig))] - public void ImagePullSecretTestGeneration(AuthConfig auth) - { - var ips = new ImagePullSecret(auth); - Assert.Equal($"{auth.Username.ToLower()}-{auth.ServerAddress.ToLower()}", ips.Name); - var generated = JObject.Parse(ips.GenerateSecret()); - - // Validate Json structure - Assert.NotNull(generated["auths"][auth.ServerAddress]); - Assert.Equal(generated["auths"][auth.ServerAddress]["username"], auth.Username); - Assert.Equal(generated["auths"][auth.ServerAddress]["password"], auth.Password); - Assert.Equal(generated["auths"][auth.ServerAddress]["auth"], Convert.ToBase64String(Encoding.UTF8.GetBytes($"{auth.Username}:{auth.Password}"))); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubeUtilsTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubeUtilsTest.cs deleted file mode 100644 index 4ff21ee6e39..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubeUtilsTest.cs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System.Collections.Generic; - using System.Text; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - - [Unit] - public class KubeUtilsTest - { - public static IEnumerable GetLongDnsDomain() - { - var raw_output = new StringBuilder(); - for (int i = 0; i <= 255; i += 2) - { - raw_output.Append((char)('a' + (i % 26))); - raw_output.Append('.'); - } - - yield return new object[] { raw_output.ToString() }; - } - - [Theory] - [InlineData("edgeAgent", "$edgeAgent")] - [InlineData("edgeHub", "$edgeHub")] - [InlineData("iothub.azure-device.net/edgeAgent", "iothub.azure-device.net/$edgeAgent")] - [InlineData("iothub.azure-device.net/edgeHub", "iothub.azure-device.net/$edgeHub")] - [InlineData("k8s.io/a-0", "K8S.IO/---a-0--")] - public void SanitizeAnnotationKeyTest(string expected, string raw) => Assert.Equal(expected, KubeUtils.SanitizeAnnotationKey(raw)); - - [Theory] - [InlineData("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij")] - [InlineData("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijab-------j.com/a")] - [InlineData("iothub.azure-device.net/abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij")] - [InlineData(null)] - [InlineData("!!!")] - [InlineData("!!!!/abc")] - [InlineData("123.com/!!!!")] - public void SanitizeAnnotationKeyFailTest(string raw) => Assert.Throws(() => KubeUtils.SanitizeAnnotationKey(raw)); - - [Theory] - [InlineData("edgeagent", "$edgeAgent")] - [InlineData("edgehub", "$edgeHub")] - [InlineData("---a-0---", "---a-0---")] - [InlineData(null, null)] - public void SanitizeK8sValueTest(string expected, string raw) => Assert.Equal(expected, KubeUtils.SanitizeK8sValue(raw)); - - [Theory] - [MemberData(nameof(GetLongDnsDomain))] - public void SanitizeK8sValueFailTest(string raw) => Assert.Throws(() => KubeUtils.SanitizeK8sValue(raw)); - - [Theory] - [InlineData("edgeagent", "$edgeAgent")] - [InlineData("edgehub", "$edgeHub")] - // all characters are forced lowercase. - [InlineData("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabc", "ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABC")] - // Allow '-' - [InlineData("abcdefghi-abcdefghi-abcdefghi-abcdefghi-abcdefghi-abcdefghi-abc", "ABCDEFGHI-ABCDEFGHI-ABCDEFGHI-ABCDEFGHI-ABCDEFGHI-ABCDEFGHI-ABC")] - // Must start with alphabet and end with alphanumeric - [InlineData("a-0", "---a-0---")] - [InlineData("z-9", "---z-9---")] - [InlineData("a-0", "---A-0---")] - [InlineData("z-9", "---Z-9---")] - [InlineData("a-z", "---a-z---")] - [InlineData("a-z---1", "---a-z-/--1")] - [InlineData("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijab----------c", "ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB----------C")] - public void SanitizeDnsValueTest(string expected, string raw) => Assert.Equal(expected, KubeUtils.SanitizeDNSValue(raw)); - - [Theory] - // length is <= 63 characters - [InlineData("ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ")] - [InlineData("ABCDEFGHI-ABCDEFGHI-ABCDEFGHI-ABCDEFGHI-ABCDEFGHI-ABCDEFGHI-ABCDEFGHIJ")] - // Must start with alphabet and end with alphanumeric - [InlineData("ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB-------J")] - [InlineData(null)] - [InlineData("$$$$$$")] - public void SanitizeDnsValueFailTest(string raw) => Assert.Throws(() => KubeUtils.SanitizeDNSValue(raw)); - - [Theory] - // must be a one or more DNS labels separated by dots (.), not longer than 253 characters in total - [InlineData("edgeagent", "$edgeAgent")] - [InlineData("edgehub", "$edgeHub")] - [InlineData("a-0.org", "---a-0---.org")] - [InlineData("a-0---b.org", "---a-0---b.org")] - [InlineData("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijab----------c.com", "ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB----------C.com")] - public void SanitizeDNSDomainTest(string expected, string raw) => Assert.Equal(expected, KubeUtils.SanitizeDNSDomain(raw)); - - [Theory] - // must be a one or more DNS labels (< 63 chars) separated by dots (.), not longer than 253 characters in total - [InlineData("ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB-------J.com")] - [MemberData(nameof(GetLongDnsDomain))] - [InlineData(null)] - [InlineData(" ")] - [InlineData("$$$$.com")] - [InlineData("a.&&&&.org")] - public void SanitizeDNSDomainFailTest(string raw) => Assert.Throws(() => KubeUtils.SanitizeDNSDomain(raw)); - - [Theory] - [InlineData("edgeagent", "$edgeAgent")] - [InlineData("edgehub", "$edgeHub")] - [InlineData("12device", "12device")] - [InlineData("345hub-name.org", "345hub-name.org")] - // length is <= 63 characters, lowercase - [InlineData("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabc", "ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABC")] - // must be all alphanumeric characters or ['-','.','_'] - [InlineData("a-b_c.d", "a$?/-b#@_c=+.d")] - // must start with an alphabet - // must end with an alphanumeric character - [InlineData("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijab----------c", "ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB----------C")] - [InlineData("zz", "$-._/zz$-._/")] - [InlineData("z9", "$-._/z9$-._/")] - public void SanitizeLabelValueTest(string expected, string raw) => Assert.Equal(expected, KubeUtils.SanitizeLabelValue(raw)); - - [Theory] - // length is <= 63 characters, lowercase - [InlineData("ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCD")] - // must start with an alphabet - // must end with an alphanumeric character - [InlineData("ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB-------J")] - [InlineData(null)] - [InlineData(" ")] - [InlineData("$%^&")] - public void SanitizeLabelValueFailTest(string raw) => Assert.Throws(() => KubeUtils.SanitizeLabelValue(raw)); - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesApiServer.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesApiServer.cs deleted file mode 100644 index e4a780eea34..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesApiServer.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Threading.Tasks; - using k8s; - using Microsoft.AspNetCore; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Hosting.Server.Features; - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Server.Kestrel.Core; - - public class KubernetesApiServer : IDisposable - { - readonly IWebHost webHost; - - public KubernetesApiServer(Func> shouldNext, string resp = null, Action listenConfigure = null) - { - shouldNext = shouldNext ?? (_ => Task.FromResult(true)); - listenConfigure = listenConfigure ?? (_ => { }); - - this.webHost = WebHost.CreateDefaultBuilder() - .Configure( - app => app.Run( - async httpContext => - { - if (await shouldNext(httpContext)) - { - await httpContext.Response.WriteAsync(resp); - } - })) - .UseKestrel(options => { options.Listen(IPAddress.Loopback, 0, listenConfigure); }) - .Build(); - - this.webHost.Start(); - } - - public string Uri - { - get - { - return this.webHost.ServerFeatures.Get().Addresses.First(); - } - } - - public void Dispose() - { - this.webHost.StopAsync(); - this.webHost.WaitForShutdown(); - } - - public static KubernetesApiServer Watch(IEnumerable.WatchEvent> events) => - new KubernetesApiServer( - async context => - { - foreach (var @event in events) - { - await context.Write(@event); - } - - return false; - }); - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesApplicationsSettingsTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesApplicationsSettingsTest.cs deleted file mode 100644 index 54ef88ce1f6..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesApplicationsSettingsTest.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System; - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - - [Unit] - public class KubernetesApplicationSettingsTest - { - [Fact] - public void NullSettingsYieldNoneResourceRequirements() - { - var appSettings = new KubernetesApplicationSettings - { - ProxyResourceRequests = null, - AgentResourceRequests = null, - }; - - var proxyReqs = appSettings.GetProxyResourceRequirements(); - var agentReqs = appSettings.GetAgentResourceRequirements(); - - Assert.False(proxyReqs.HasValue); - Assert.False(agentReqs.HasValue); - } - - [Fact] - public void NonNullSettingsYieldResourceRequirements() - { - var resources = new ResourceSettings - { - Limits = new Dictionary(), - Requests = new Dictionary(), - }; - var appSettings = new KubernetesApplicationSettings - { - ProxyResourceRequests = resources, - AgentResourceRequests = resources, - }; - - var proxyReqs = appSettings.GetProxyResourceRequirements(); - var agentReqs = appSettings.GetAgentResourceRequirements(); - - Assert.True(proxyReqs.HasValue); - Assert.True(agentReqs.HasValue); - } - } -} \ No newline at end of file diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesCommandFactoryTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesCommandFactoryTest.cs deleted file mode 100644 index 29cc3411893..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesCommandFactoryTest.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Core.Commands; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Moq; - using Xunit; - - public class KubernetesCommandFactoryTest - { - [Fact] - [Unit] - public async void KubernetesCommandFactoryIsReallyBasic() - { - var mockModule = Mock.Of(); - var mockModuleIdentity = Mock.Of(); - var mockRuntime = Mock.Of(); - var mockCommand = Mock.Of(c => c.ExecuteAsync(It.IsAny()) == Task.CompletedTask); - var kcf = new KubernetesCommandFactory(); - CancellationToken ct = CancellationToken.None; - - Assert.Equal(NullCommand.Instance, await kcf.UpdateEdgeAgentAsync(mockModuleIdentity, mockRuntime)); - Assert.Equal(NullCommand.Instance, await kcf.CreateAsync(mockModuleIdentity, mockRuntime)); - Assert.Equal(NullCommand.Instance, await kcf.UpdateAsync(mockModule, mockModuleIdentity, mockRuntime)); - Assert.Equal(NullCommand.Instance, await kcf.RemoveAsync(mockModule)); - Assert.Equal(NullCommand.Instance, await kcf.StartAsync(mockModule)); - Assert.Equal(NullCommand.Instance, await kcf.StopAsync(mockModule)); - Assert.Equal(NullCommand.Instance, await kcf.RestartAsync(mockModule)); - var newCommand = await kcf.WrapAsync(mockCommand); - await newCommand.ExecuteAsync(ct); - Mock.Get(mockCommand).Verify(c => c.ExecuteAsync(ct), Times.Once); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesDeploymentMapperTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesDeploymentMapperTest.cs deleted file mode 100644 index fe10e1be6ac..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesDeploymentMapperTest.cs +++ /dev/null @@ -1,1026 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System; - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Agent.Docker.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Deployment; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Moq; - using Newtonsoft.Json.Linq; - using Xunit; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - [Unit] - public class KubernetesDeploymentMapperTest - { - static readonly ConfigurationInfo DefaultConfigurationInfo = new ConfigurationInfo("1"); - - static readonly IDictionary EnvVarsDict = new Dictionary(); - - static readonly DockerConfig Config1 = new DockerConfig("test-image:1"); - - static readonly HostConfig VolumeMountHostConfig = new HostConfig - { - Mounts = new List - { - new Mount - { - Type = "volume", - ReadOnly = true, - Source = "a-volume", - Target = "/tmp/volume" - } - } - }; - - static readonly HostConfig HostIpcModeHostConfig = new HostConfig - { - IpcMode = "host" - }; - - static readonly HostConfig PrivateIpcModeHostConfig = new HostConfig - { - IpcMode = "private" - }; - - static readonly HostConfig HostNetworkModeHostConfig = new HostConfig - { - NetworkMode = "host" - }; - - static readonly HostConfig BridgeNetworkModeHostConfig = new HostConfig - { - NetworkMode = "bridge" - }; - - static readonly KubernetesModuleOwner EdgeletModuleOwner = new KubernetesModuleOwner("v1", "Deployment", "iotedged", "123"); - - [Fact] - public void EmptyIsNotAllowedAsPodAnnotation() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var labels = new Dictionary - { - // string.Empty is an invalid label name - { string.Empty, "test" } - }; - var config = new KubernetesConfig("image", CreatePodParameters.Create(labels: labels), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var moduleLabels = new Dictionary(); - var mapper = CreateMapper(); - - Assert.Throws(() => mapper.CreateDeployment(identity, module, moduleLabels)); - } - - [Fact] - public void SimpleDeploymentCreationHappyPath() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.NotNull(deployment); - Assert.Equal(1, deployment.Metadata.OwnerReferences.Count); - Assert.Equal(V1Deployment.KubeKind, deployment.Metadata.OwnerReferences[0].Kind); - Assert.Equal(EdgeletModuleOwner.Name, deployment.Metadata.OwnerReferences[0].Name); - Assert.Equal(1, deployment.Spec.Replicas); - } - - [Fact] - public void SimpleDeploymentStoppedHasZeroReplicas() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Stopped, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.NotNull(deployment); - Assert.Equal(1, deployment.Metadata.OwnerReferences.Count); - Assert.Equal(V1Deployment.KubeKind, deployment.Metadata.OwnerReferences[0].Kind); - Assert.Equal(EdgeletModuleOwner.Name, deployment.Metadata.OwnerReferences[0].Name); - Assert.Equal(0, deployment.Spec.Replicas); - } - - [Fact] - public void ValidatePodPropertyTranslation() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var labels = new Dictionary - { - // Add a label - { "demo", "test" } - }; - var hostConfig = new HostConfig - { - // Make container privileged - Privileged = true, - // Add a readonly mount - Binds = new List { "/home/blah:/home/blah2:ro" } - }; - var config = new KubernetesConfig("image", CreatePodParameters.Create(labels: labels, hostConfig: hostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var moduleLabels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, moduleLabels); - var pod = deployment.Spec.Template; - - Assert.NotNull(pod); - // Validate annotation - Assert.True(pod.Metadata.Annotations.ContainsKey("demo")); - // Two containers should exist - proxy and the module - Assert.Equal(2, pod.Spec.Containers.Count); - - // There should only be one module container - var moduleContainer = pod.Spec.Containers.Single(p => p.Name != "proxy"); - // We made this container privileged - Assert.True(moduleContainer.SecurityContext.Privileged); - // Validate that there are 1 mounts for module container - Assert.Equal(1, moduleContainer.VolumeMounts.Count); - // Validate the custom mount that we added - Assert.Contains(moduleContainer.VolumeMounts, vm => vm.Name.Equals("homeblah")); - var mount = moduleContainer.VolumeMounts.Single(vm => vm.Name.Equals("homeblah")); - // Lets make sure that it is read only - Assert.True(mount.ReadOnlyProperty); - - // Validate proxy container - var proxyContainer = pod.Spec.Containers.Single(p => p.Name == "proxy"); - // Validate that there are 2 mounts for proxy container: config and trust-bundle - Assert.Equal(2, proxyContainer.VolumeMounts.Count); - Assert.Contains(proxyContainer.VolumeMounts, vm => vm.Name.Equals("configVolumeName")); - Assert.Contains(proxyContainer.VolumeMounts, vm => vm.Name.Equals("trustBundleVolumeName")); - - // Validate pod volumes - Assert.Equal(3, pod.Spec.Volumes.Count); - Assert.Contains(pod.Spec.Volumes, v => v.Name.Equals("homeblah")); - Assert.Contains(pod.Spec.Volumes, v => v.Name.Equals("configVolumeName")); - Assert.Contains(pod.Spec.Volumes, v => v.Name.Equals("trustBundleVolumeName")); - - // Validate no image pull secrets for public images - Assert.Null(pod.Spec.ImagePullSecrets); - - // Validate null pod security context by default - Assert.Null(pod.Spec.SecurityContext); - } - - [Fact] - public void PvcMappingByDefault() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "ModuleId", Mock.Of()); - var labels = new Dictionary(); - var hostConfig = VolumeMountHostConfig; - var config = new KubernetesConfig("image", CreatePodParameters.Create(labels: labels, hostConfig: hostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var mapper = CreateMapper(storageClassName: null); - var deployment = mapper.CreateDeployment(identity, module, labels); - var pod = deployment.Spec.Template; - - Assert.True(pod != null); - var podVolume = pod.Spec.Volumes.Single(v => v.Name == "a-volume"); - Assert.NotNull(podVolume.PersistentVolumeClaim); - Assert.Equal("a-volume", podVolume.PersistentVolumeClaim.ClaimName); - Assert.True(podVolume.PersistentVolumeClaim.ReadOnlyProperty); - var podVolumeMount = pod.Spec.Containers.Single(p => p.Name != "proxy").VolumeMounts.Single(vm => vm.Name == "a-volume"); - Assert.Equal("/tmp/volume", podVolumeMount.MountPath); - Assert.True(podVolumeMount.ReadOnlyProperty); - } - - [Fact] - public void PvcMappingForVolumeNameVolume() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "ModuleId", Mock.Of()); - var labels = new Dictionary(); - var hostConfig = VolumeMountHostConfig; - var config = new KubernetesConfig("image", CreatePodParameters.Create(labels: labels, hostConfig: hostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var mapper = CreateMapper(true, null); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - var pod = deployment.Spec.Template; - - Assert.True(pod != null); - var podVolume = pod.Spec.Volumes.Single(v => v.Name == "a-volume"); - Assert.NotNull(podVolume.PersistentVolumeClaim); - Assert.Equal("a-volume", podVolume.PersistentVolumeClaim.ClaimName); - Assert.True(podVolume.PersistentVolumeClaim.ReadOnlyProperty); - var podVolumeMount = pod.Spec.Containers.Single(p => p.Name != "proxy").VolumeMounts.Single(vm => vm.Name == "a-volume"); - Assert.Equal("/tmp/volume", podVolumeMount.MountPath); - Assert.True(podVolumeMount.ReadOnlyProperty); - } - - [Fact] - public void PvcMappingForStorageClassVolume() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "ModuleId", Mock.Of()); - var labels = new Dictionary(); - var hostConfig = VolumeMountHostConfig; - var config = new KubernetesConfig("image", CreatePodParameters.Create(labels: labels, hostConfig: hostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - var pod = deployment.Spec.Template; - - Assert.True(pod != null); - var podVolume = pod.Spec.Volumes.Single(v => v.Name == "a-volume"); - Assert.NotNull(podVolume.PersistentVolumeClaim); - Assert.Equal("a-volume", podVolume.PersistentVolumeClaim.ClaimName); - Assert.True(podVolume.PersistentVolumeClaim.ReadOnlyProperty); - var podVolumeMount = pod.Spec.Containers.Single(p => p.Name != "proxy").VolumeMounts.Single(vm => vm.Name == "a-volume"); - Assert.Equal("/tmp/volume", podVolumeMount.MountPath); - Assert.True(podVolumeMount.ReadOnlyProperty); - } - - [Fact] - public void PvcMappingForDefaultStorageClassVolume() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "ModuleId", Mock.Of()); - var labels = new Dictionary(); - var hostConfig = VolumeMountHostConfig; - var config = new KubernetesConfig("image", CreatePodParameters.Create(labels: labels, hostConfig: hostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - var pod = deployment.Spec.Template; - - Assert.True(pod != null); - var podVolume = pod.Spec.Volumes.Single(v => v.Name == "a-volume"); - Assert.NotNull(podVolume.PersistentVolumeClaim); - Assert.Equal("a-volume", podVolume.PersistentVolumeClaim.ClaimName); - Assert.True(podVolume.PersistentVolumeClaim.ReadOnlyProperty); - var podVolumeMount = pod.Spec.Containers.Single(p => p.Name != "proxy").VolumeMounts.Single(vm => vm.Name == "a-volume"); - Assert.Equal("/tmp/volume", podVolumeMount.MountPath); - Assert.True(podVolumeMount.ReadOnlyProperty); - } - - [Fact] - public void PvcMappingForPVVolumeExtended() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "ModuleId", Mock.Of()); - var labels = new Dictionary(); - var hostConfig = VolumeMountHostConfig; - - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse( - @"{ - ""volumes"": [ - { - ""volume"": { - ""name"": ""module-config"", - }, - - ""volumeMounts"": [ - { - ""name"": ""module-config"", - ""mountPath"": ""/etc/module"", - ""mountPropagation"": ""None"", - ""readOnly"": ""true"", - ""subPath"": """" - } - ] - } - ]}") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - var volumes = new[] - { - parameters.Volumes.OrDefault().Single(), - }; - - var config = new KubernetesConfig("image", CreatePodParameters.Create(volumes: volumes, hostConfig: hostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - var pod = deployment.Spec.Template; - - Assert.True(pod != null); - var podVolume = pod.Spec.Volumes.Single(v => v.Name == "module-config"); - var podVolumeMount = pod.Spec.Containers.Single(p => p.Name != "proxy").VolumeMounts.Single(vm => vm.Name == "module-config"); - Assert.Equal("/etc/module", podVolumeMount.MountPath); - } - - [Fact] - public void AppliesNodeSelectorFromCreateOptionsToPodSpec() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - IDictionary nodeSelector = new Dictionary - { - ["disktype"] = "ssd" - }; - var config = new KubernetesConfig("image", CreatePodParameters.Create(nodeSelector: nodeSelector), Option.None()); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.Equal(nodeSelector, deployment.Spec.Template.Spec.NodeSelector, new DictionaryComparer()); - } - - [Fact] - public void LeaveNodeSelectorEmptyWhenNothingProvidedInCreateOptions() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.None()); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.Null(deployment.Spec.Template.Spec.NodeSelector); - } - - [Fact] - public void AppliesResourcesFromCreateOptionsToContainerSpec() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var resources = new V1ResourceRequirements( - new Dictionary - { - ["memory"] = new ResourceQuantity("128Mi"), - ["cpu"] = new ResourceQuantity("500M"), - ["hardware-vendor.example/foo"] = 2 - }, - new Dictionary - { - ["memory"] = new ResourceQuantity("64Mi"), - ["cpu"] = new ResourceQuantity("250M"), - ["hardware-vendor.example/foo"] = 1 - }); - var config = new KubernetesConfig("image", CreatePodParameters.Create(resources: resources), Option.None()); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - var moduleContainer = deployment.Spec.Template.Spec.Containers.Single(container => container.Name == "module1"); - Assert.Equal(resources.Limits, moduleContainer.Resources.Limits); - Assert.Equal(resources.Requests, moduleContainer.Resources.Requests); - } - - [Fact] - public void AppliesDefaultResourcesForAgent() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "$edgeAgent", Mock.Of()); - var docker = new DockerModule("edgeAgent", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var agentResources = new V1ResourceRequirements( - new Dictionary - { - ["memory"] = new ResourceQuantity("1500Mi"), - ["cpu"] = new ResourceQuantity("150m"), - }, - new Dictionary - { - ["memory"] = new ResourceQuantity("1500Mi"), - ["cpu"] = new ResourceQuantity("150m"), - }); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.None()); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - var moduleContainer = deployment.Spec.Template.Spec.Containers.Single(container => container.Name == "edgeagent"); - Assert.Equal(agentResources.Limits, moduleContainer.Resources.Limits); - Assert.Equal(agentResources.Requests, moduleContainer.Resources.Requests); - } - - [Fact] - public void AppliesDefaultResourcesForProxy() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var proxyResources = new V1ResourceRequirements( - new Dictionary - { - ["memory"] = new ResourceQuantity("1000M"), - ["cpu"] = new ResourceQuantity("20m"), - }, - new Dictionary - { - ["memory"] = new ResourceQuantity("1000M"), - ["cpu"] = new ResourceQuantity("20m"), - }); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.None()); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - var moduleContainer = deployment.Spec.Template.Spec.Containers.Single(container => container.Name == "proxy"); - Assert.Equal(proxyResources.Limits, moduleContainer.Resources.Limits); - Assert.Equal(proxyResources.Requests, moduleContainer.Resources.Requests); - } - - [Fact] - public void LeaveResourcesEmptyWhenNothingProvidedInCreateOptions() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.None()); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - var moduleContainer = deployment.Spec.Template.Spec.Containers.Single(container => container.Name == "module1"); - Assert.Null(moduleContainer.Resources); - } - - [Fact] - public void AppliesAgentConfigMapVolumeToContainerSpec() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "$edgeAgent", Mock.Of()); - var docker = new DockerModule("edgeagent", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var volumes = new[] - { - new KubernetesModuleVolumeSpec( - new V1Volume("additional-volume", configMap: new V1ConfigMapVolumeSource(name: "additional-config-map")), - new[] { new V1VolumeMount(name: "additional-volume", mountPath: "/etc") }) - }; - var config = new KubernetesConfig("image", CreatePodParameters.Create(volumes: volumes), Option.None()); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - // Validate module volume mounts - var agentContainer = deployment.Spec.Template.Spec.Containers.Single(container => container.Name == "edgeagent"); - Assert.Equal(2, agentContainer.VolumeMounts.Count); - Assert.Contains(agentContainer.VolumeMounts, vm => vm.Name.Equals("agentConfigVolume")); - Assert.Contains(agentContainer.VolumeMounts, vm => vm.Name.Equals("additional-volume")); - } - - [Fact] - public void DoesNotApplyAgentConfigMapVolumeToContainerSpecWhenNoSettings() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "$edgeAgent", Mock.Of()); - var docker = new DockerModule("edgeagent", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var volumes = new[] - { - new KubernetesModuleVolumeSpec( - new V1Volume("additional-volume", configMap: new V1ConfigMapVolumeSource(name: "additional-config-map")), - new[] { new V1VolumeMount(name: "additional-volume", mountPath: "/etc") }) - }; - var config = new KubernetesConfig("image", CreatePodParameters.Create(volumes: volumes), Option.None()); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper1 = CreateMapper(agentConfigMapName: null); - var mapper2 = CreateMapper(agentConfigPath: null); - var mapper3 = CreateMapper(agentConfigVolume: null); - - var deployment1 = mapper1.CreateDeployment(identity, module, labels); - var deployment2 = mapper2.CreateDeployment(identity, module, labels); - var deployment3 = mapper3.CreateDeployment(identity, module, labels); - - // Validate module volume mounts - var agentContainer1 = deployment1.Spec.Template.Spec.Containers.Single(container => container.Name == "edgeagent"); - Assert.Equal(1, agentContainer1.VolumeMounts.Count); - Assert.Contains(agentContainer1.VolumeMounts, vm => vm.Name.Equals("additional-volume")); - var agentContainer2 = deployment2.Spec.Template.Spec.Containers.Single(container => container.Name == "edgeagent"); - Assert.Equal(1, agentContainer2.VolumeMounts.Count); - Assert.Contains(agentContainer2.VolumeMounts, vm => vm.Name.Equals("additional-volume")); - var agentContainer3 = deployment3.Spec.Template.Spec.Containers.Single(container => container.Name == "edgeagent"); - Assert.Equal(1, agentContainer3.VolumeMounts.Count); - Assert.Contains(agentContainer3.VolumeMounts, vm => vm.Name.Equals("additional-volume")); - } - - [Fact] - public void AppliesVolumesFromCreateOptionsToContainerSpec() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var volumes = new[] - { - new KubernetesModuleVolumeSpec( - new V1Volume("additional-volume", configMap: new V1ConfigMapVolumeSource(name: "additional-config-map")), - new[] { new V1VolumeMount(name: "additional-volume", mountPath: "/etc") }) - }; - var config = new KubernetesConfig("image", CreatePodParameters.Create(volumes: volumes), Option.None()); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - // Validate module volume mounts - var moduleContainer = deployment.Spec.Template.Spec.Containers.Single(container => container.Name == "module1"); - Assert.Equal(1, moduleContainer.VolumeMounts.Count); - Assert.Contains(moduleContainer.VolumeMounts, vm => vm.Name.Equals("additional-volume")); - - // Validate proxy volume mounts - var proxyContainer = deployment.Spec.Template.Spec.Containers.Single(p => p.Name == "proxy"); - Assert.Equal(2, proxyContainer.VolumeMounts.Count); - Assert.Contains(proxyContainer.VolumeMounts, vm => vm.Name.Equals("configVolumeName")); - Assert.Contains(proxyContainer.VolumeMounts, vm => vm.Name.Equals("trustBundleVolumeName")); - - // Validate pod volumes - Assert.Equal(3, deployment.Spec.Template.Spec.Volumes.Count); - Assert.Contains(deployment.Spec.Template.Spec.Volumes, v => v.Name.Equals("additional-volume")); - Assert.Contains(deployment.Spec.Template.Spec.Volumes, v => v.Name.Equals("configVolumeName")); - Assert.Contains(deployment.Spec.Template.Spec.Volumes, v => v.Name.Equals("trustBundleVolumeName")); - } - - [Fact] - public void AddsVolumesFromCreateOptionsToContainerSpecEvenIfTheyOverrideExistingOnes() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var volumes = new[] - { - new KubernetesModuleVolumeSpec( - new V1Volume("homeblah", configMap: new V1ConfigMapVolumeSource(name: "additional-config-map")), - new[] { new V1VolumeMount(name: "homeblah", mountPath: "/home/blah") }) - }; - var hostConfig = new HostConfig { Binds = new List { "/home/blah:/home/blah2:ro" } }; - var config = new KubernetesConfig("image", CreatePodParameters.Create(volumes: volumes, hostConfig: hostConfig), Option.None()); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - // Validate module volume mounts - var moduleContainer = deployment.Spec.Template.Spec.Containers.Single(container => container.Name == "module1"); - Assert.Equal(2, moduleContainer.VolumeMounts.Count(vm => vm.Name.Equals("homeblah"))); - - // Validate proxy volume mounts - var proxyContainer = deployment.Spec.Template.Spec.Containers.Single(p => p.Name == "proxy"); - Assert.Equal(2, proxyContainer.VolumeMounts.Count); - Assert.Contains(proxyContainer.VolumeMounts, vm => vm.Name.Equals("configVolumeName")); - Assert.Contains(proxyContainer.VolumeMounts, vm => vm.Name.Equals("trustBundleVolumeName")); - - // Validate pod volumes - Assert.Equal(4, deployment.Spec.Template.Spec.Volumes.Count); - Assert.Equal(2, deployment.Spec.Template.Spec.Volumes.Count(v => v.Name.Equals("homeblah"))); - Assert.Contains(deployment.Spec.Template.Spec.Volumes, v => v.Name.Equals("configVolumeName")); - Assert.Contains(deployment.Spec.Template.Spec.Volumes, v => v.Name.Equals("trustBundleVolumeName")); - } - - [Fact] - public void LeaveVolumesIntactWhenNothingProvidedInCreateOptions() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.None()); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - // 2 volumes for proxy by default - Assert.Equal(2, deployment.Spec.Template.Spec.Volumes.Count); - var moduleContainer = deployment.Spec.Template.Spec.Containers.Single(container => container.Name == "module1"); - Assert.Equal(0, moduleContainer.VolumeMounts.Count); - var proxyContainer = deployment.Spec.Template.Spec.Containers.Single(container => container.Name == "proxy"); - Assert.Equal(2, proxyContainer.VolumeMounts.Count); - } - - [Fact] - public void PassImagePullSecretsInPodSpecForProxyAndModuleContainers() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.Some(new AuthConfig("user-registry1"))); - var module = new KubernetesModule("module1", "v1", "docker", ModuleStatus.Running, Core.RestartPolicy.Always, DefaultConfigurationInfo, EnvVarsDict, config, ImagePullPolicy.OnCreate, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(proxyImagePullSecretName: "user-registry2"); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.Equal(2, deployment.Spec.Template.Spec.ImagePullSecrets.Count); - Assert.Contains(deployment.Spec.Template.Spec.ImagePullSecrets, secret => secret.Name == "user-registry1"); - Assert.Contains(deployment.Spec.Template.Spec.ImagePullSecrets, secret => secret.Name == "user-registry2"); - } - - [Fact] - public void PassOnlyOneImagePullSecretInPodSpecIfProxyAndModuleContainersHasTheSameImagePullSecrets() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.Some(new AuthConfig("user-registry1"))); - var module = new KubernetesModule("module1", "v1", "docker", ModuleStatus.Running, Core.RestartPolicy.Always, DefaultConfigurationInfo, EnvVarsDict, config, ImagePullPolicy.OnCreate, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(proxyImagePullSecretName: "user-registry1"); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.Equal(1, deployment.Spec.Template.Spec.ImagePullSecrets.Count); - Assert.Contains(deployment.Spec.Template.Spec.ImagePullSecrets, secret => secret.Name == "user-registry1"); - } - - [Fact] - public void RunAsNonRootAndRunAsUser1000SecurityPolicyWhenSettingSet() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.Some(new AuthConfig("user-registry1"))); - var module = new KubernetesModule("module1", "v1", "docker", ModuleStatus.Running, Core.RestartPolicy.Always, DefaultConfigurationInfo, EnvVarsDict, config, ImagePullPolicy.OnCreate, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(runAsNonRoot: true); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.Equal(1, deployment.Spec.Template.Spec.ImagePullSecrets.Count); - Assert.Equal(true, deployment.Spec.Template.Spec.SecurityContext.RunAsNonRoot); - Assert.Equal(1000, deployment.Spec.Template.Spec.SecurityContext.RunAsUser); - } - - [Fact] - public void PodSecurityContextFromCreateOptionsOverridesDefaultRunAsNonRootOptionsWhenProvided() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var securityContext = new V1PodSecurityContext { RunAsNonRoot = true, RunAsUser = 0 }; - var config = new KubernetesConfig("image", CreatePodParameters.Create(securityContext: securityContext), Option.Some(new AuthConfig("user-registry1"))); - var module = new KubernetesModule("module1", "v1", "docker", ModuleStatus.Running, Core.RestartPolicy.Always, DefaultConfigurationInfo, EnvVarsDict, config, ImagePullPolicy.OnCreate, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(runAsNonRoot: true); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.Equal(1, deployment.Spec.Template.Spec.ImagePullSecrets.Count); - Assert.Equal(true, deployment.Spec.Template.Spec.SecurityContext.RunAsNonRoot); - Assert.Equal(0, deployment.Spec.Template.Spec.SecurityContext.RunAsUser); - } - - [Fact] - public void ApplyPodSecurityContextFromCreateOptionsWhenProvided() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var securityContext = new V1PodSecurityContext { RunAsNonRoot = true, RunAsUser = 20001 }; - var config = new KubernetesConfig("image", CreatePodParameters.Create(securityContext: securityContext), Option.Some(new AuthConfig("user-registry1"))); - var module = new KubernetesModule("module1", "v1", "docker", ModuleStatus.Running, Core.RestartPolicy.Always, DefaultConfigurationInfo, EnvVarsDict, config, ImagePullPolicy.OnCreate, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.Equal(1, deployment.Spec.Template.Spec.ImagePullSecrets.Count); - Assert.Equal(true, deployment.Spec.Template.Spec.SecurityContext.RunAsNonRoot); - Assert.Equal(20001, deployment.Spec.Template.Spec.SecurityContext.RunAsUser); - } - - [Fact] - public void LeavesStrategyEmptyWhenNotProvided() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.Some(new AuthConfig("user-registry1"))); - var module = new KubernetesModule("module1", "v1", "docker", ModuleStatus.Running, Core.RestartPolicy.Always, DefaultConfigurationInfo, EnvVarsDict, config, ImagePullPolicy.OnCreate, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.Null(deployment.Spec.Strategy); - } - - [Fact] - public void ApplyDeploymentStrategyWhenProvided() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var deploymentStrategy = new V1DeploymentStrategy { Type = "Recreate" }; - var config = new KubernetesConfig("image", CreatePodParameters.Create(deploymentStrategy: deploymentStrategy), Option.Some(new AuthConfig("user-registry1"))); - var module = new KubernetesModule("module1", "v1", "docker", ModuleStatus.Running, Core.RestartPolicy.Always, DefaultConfigurationInfo, EnvVarsDict, config, ImagePullPolicy.OnCreate, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.Equal("Recreate", deployment.Spec.Strategy.Type); - } - - [Fact] - public void NoCmdOptionsNoContainerArgs() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.Some(new AuthConfig("user-registry1"))); - var module = new KubernetesModule("module1", "v1", "docker", ModuleStatus.Running, Core.RestartPolicy.Always, DefaultConfigurationInfo, EnvVarsDict, config, ImagePullPolicy.OnCreate, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - var container = deployment.Spec.Template.Spec.Containers.Single(c => c.Name == "module1"); - Assert.Null(container.Args); - Assert.Null(container.Command); - Assert.Null(container.WorkingDir); - } - - [Fact] - public void CmdOptionsContainerArgs() - { - var cmd = new List { "argument1", "argument2" }; - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(cmd: cmd), Option.Some(new AuthConfig("user-registry1"))); - var module = new KubernetesModule("module1", "v1", "docker", ModuleStatus.Running, Core.RestartPolicy.Always, DefaultConfigurationInfo, EnvVarsDict, config, ImagePullPolicy.OnCreate, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - var container = deployment.Spec.Template.Spec.Containers.Single(c => c.Name == "module1"); - Assert.NotNull(container.Args); - Assert.Equal(2, container.Args.Count); - Assert.Equal("argument1", container.Args[0]); - Assert.Equal("argument2", container.Args[1]); - } - - [Fact] - public void EntrypointOptionsContainerCommands() - { - var entrypoint = new List { "command", "argument-a" }; - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(entrypoint: entrypoint), Option.Some(new AuthConfig("user-registry1"))); - var module = new KubernetesModule("module1", "v1", "docker", ModuleStatus.Running, Core.RestartPolicy.Always, DefaultConfigurationInfo, EnvVarsDict, config, ImagePullPolicy.OnCreate, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - var container = deployment.Spec.Template.Spec.Containers.Single(c => c.Name == "module1"); - Assert.NotNull(container.Command); - Assert.Equal(2, container.Command.Count); - Assert.Equal("command", container.Command[0]); - Assert.Equal("argument-a", container.Command[1]); - } - - [Fact] - public void WorkingDirOptionsContainerWorkingDir() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(workingDir: "/tmp/working"), Option.Some(new AuthConfig("user-registry1"))); - var module = new KubernetesModule("module1", "v1", "docker", ModuleStatus.Running, Core.RestartPolicy.Always, DefaultConfigurationInfo, EnvVarsDict, config, ImagePullPolicy.OnCreate, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - var container = deployment.Spec.Template.Spec.Containers.Single(c => c.Name == "module1"); - Assert.NotNull(container.WorkingDir); - Assert.Equal("/tmp/working", container.WorkingDir); - } - - [Fact] - public void EnvModuleSettingsParseCorrectly() - { - var env = new List - { - "a=b", - "ALL_EQUALS=====", - "HAS_SPACES=this variable has spaces", - "B=b=c", - "BASE64_TEXT=YmFzZTY0Cg==", - "==not a valid env var", - }; - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(env: env), Option.Some(new AuthConfig("user-registry1"))); - var module = new KubernetesModule("module1", "v1", "docker", ModuleStatus.Running, Core.RestartPolicy.Always, DefaultConfigurationInfo, EnvVarsDict, config, ImagePullPolicy.OnCreate, EdgeletModuleOwner); - var labels = new Dictionary(); - var features = new Dictionary - { - ["feature1"] = true, - ["feature2"] = false - }; - var mapper = CreateMapper(runAsNonRoot: true, useMountSourceForVolumeName: true, storageClassName: "scname", proxyImagePullSecretName: "secret name", experimentalFeatures: features); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - var container = deployment.Spec.Template.Spec.Containers.Single(c => c.Name == "module1"); - Assert.Equal("b", container.Env.Single(e => e.Name == "a").Value); - Assert.Equal("====", container.Env.Single(e => e.Name == "ALL_EQUALS").Value); - Assert.Equal("this variable has spaces", container.Env.Single(e => e.Name == "HAS_SPACES").Value); - Assert.Equal("b=c", container.Env.Single(e => e.Name == "B").Value); - Assert.Equal("YmFzZTY0Cg==", container.Env.Single(e => e.Name == "BASE64_TEXT").Value); - Assert.Null(container.Env.SingleOrDefault(e => e.Value.EndsWith("valid env var"))); - } - - [Fact] - public void EdgeAgentEnvSettingsHaveLotsOfStuff() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "$edgeAgent", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.Some(new AuthConfig("user-registry1"))); - var module = new KubernetesModule("edgeAgent", "v1", "docker", ModuleStatus.Running, Core.RestartPolicy.Always, DefaultConfigurationInfo, EnvVarsDict, config, ImagePullPolicy.OnCreate, EdgeletModuleOwner); - var labels = new Dictionary(); - var features = new Dictionary - { - ["feature1"] = true, - ["feature2"] = false - }; - var mapper = CreateMapper(runAsNonRoot: true, useMountSourceForVolumeName: true, storageClassName: "scname", proxyImagePullSecretName: "secret name", experimentalFeatures: features); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - var container = deployment.Spec.Template.Spec.Containers.Single(c => c.Name == "edgeagent"); - Assert.Equal(Constants.KubernetesMode, container.Env.Single(e => e.Name == Constants.ModeKey).Value); - var managementUri = container.Env.Single(e => e.Name == Constants.EdgeletManagementUriVariableName); - Assert.Equal("http://management/", container.Env.Single(e => e.Name == Constants.EdgeletManagementUriVariableName).Value); - Assert.Equal("azure-iot-edge", container.Env.Single(e => e.Name == Constants.NetworkIdKey).Value); - Assert.Equal("proxy", container.Env.Single(e => e.Name == KubernetesConstants.ProxyImageEnvKey).Value); - Assert.Equal("secret name", container.Env.Single(e => e.Name == KubernetesConstants.ProxyImagePullSecretNameEnvKey).Value); - Assert.Equal("configPath", container.Env.Single(e => e.Name == KubernetesConstants.ProxyConfigPathEnvKey).Value); - Assert.Equal("configVolumeName", container.Env.Single(e => e.Name == KubernetesConstants.ProxyConfigVolumeEnvKey).Value); - Assert.Equal("configMapName", container.Env.Single(e => e.Name == KubernetesConstants.ProxyConfigMapNameEnvKey).Value); - Assert.Equal("trustBundlePath", container.Env.Single(e => e.Name == KubernetesConstants.ProxyTrustBundlePathEnvKey).Value); - Assert.Equal("trustBundleVolumeName", container.Env.Single(e => e.Name == KubernetesConstants.ProxyTrustBundleVolumeEnvKey).Value); - Assert.Equal("trustBundleConfigMapName", container.Env.Single(e => e.Name == KubernetesConstants.ProxyTrustBundleConfigMapEnvKey).Value); - Assert.Equal("namespace", container.Env.Single(e => e.Name == KubernetesConstants.K8sNamespaceKey).Value); - Assert.Equal("True", container.Env.Single(e => e.Name == KubernetesConstants.RunAsNonRootKey).Value); - Assert.Equal("v1", container.Env.Single(e => e.Name == KubernetesConstants.EdgeK8sObjectOwnerApiVersionKey).Value); - Assert.Equal("Deployment", container.Env.Single(e => e.Name == KubernetesConstants.EdgeK8sObjectOwnerKindKey).Value); - Assert.Equal("iotedged", container.Env.Single(e => e.Name == KubernetesConstants.EdgeK8sObjectOwnerNameKey).Value); - Assert.Equal("123", container.Env.Single(e => e.Name == KubernetesConstants.EdgeK8sObjectOwnerUidKey).Value); - Assert.Equal("ClusterIP", container.Env.Single(e => e.Name == KubernetesConstants.PortMappingServiceType).Value); - Assert.Equal("False", container.Env.Single(e => e.Name == KubernetesConstants.EnableK8sServiceCallTracingName).Value); - Assert.Equal("True", container.Env.Single(e => e.Name == KubernetesConstants.UseMountSourceForVolumeNameKey).Value); - Assert.Equal("scname", container.Env.Single(e => e.Name == KubernetesConstants.StorageClassNameKey).Value); - Assert.Equal("100", container.Env.Single(e => e.Name == KubernetesConstants.PersistentVolumeClaimDefaultSizeInMbKey).Value); - Assert.Equal("True", container.Env.Single(e => e.Name == "feature1").Value); - Assert.Equal("False", container.Env.Single(e => e.Name == "feature2").Value); - } - - [Fact] - public void NoIpcModeDeploymentCreation() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.NotNull(deployment); - Assert.Null(deployment.Spec.Template.Spec.HostIPC); - } - - [Fact] - public void HostIpcModeDeploymentCreation() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(hostConfig: HostIpcModeHostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.NotNull(deployment); - Assert.Equal(true, deployment.Spec.Template.Spec.HostIPC); - } - - [Fact] - public void PrivateIpcModeDeploymentCreation() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(hostConfig: PrivateIpcModeHostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.NotNull(deployment); - Assert.Equal(false, deployment.Spec.Template.Spec.HostIPC); - } - - [Fact] - public void NoNetworkModeDeploymentCreation() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.NotNull(deployment); - Assert.Null(deployment.Spec.Template.Spec.HostNetwork); - Assert.Null(deployment.Spec.Template.Spec.DnsPolicy); - } - - [Fact] - public void HostNetworkModeDeploymentCreation() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(hostConfig: HostNetworkModeHostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.NotNull(deployment); - Assert.Equal(true, deployment.Spec.Template.Spec.HostNetwork); - Assert.Equal("ClusterFirstWithHostNet", deployment.Spec.Template.Spec.DnsPolicy); - } - - [Fact] - public void BridgeNetworkModeDeploymentCreation() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "Module1", Mock.Of()); - var config = new KubernetesConfig("image", CreatePodParameters.Create(hostConfig: BridgeNetworkModeHostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var labels = new Dictionary(); - var mapper = CreateMapper(); - - var deployment = mapper.CreateDeployment(identity, module, labels); - - Assert.NotNull(deployment); - Assert.Equal(false, deployment.Spec.Template.Spec.HostNetwork); - Assert.Null(deployment.Spec.Template.Spec.DnsPolicy); - } - - static Dictionary proxyLimits = new Dictionary - { - ["cpu"] = new ResourceQuantity("20m"), - ["memory"] = new ResourceQuantity("1000M"), - }; - static Dictionary agentLimits = new Dictionary - { - ["cpu"] = new ResourceQuantity("150m"), - ["memory"] = new ResourceQuantity("1500Mi"), - }; - - static V1ResourceRequirements proxyReqs = new V1ResourceRequirements(proxyLimits, proxyLimits); - static V1ResourceRequirements agentReqs = new V1ResourceRequirements(agentLimits, agentLimits); - - static KubernetesDeploymentMapper CreateMapper( - bool useMountSourceForVolumeName = false, - string storageClassName = "", - string proxyImagePullSecretName = null, - bool runAsNonRoot = false, - IDictionary experimentalFeatures = null, - string agentConfigMapName = "agentConfigMapName", - string agentConfigPath = "agentConfigPath", - string agentConfigVolume = "agentConfigVolume") - => new KubernetesDeploymentMapper( - "namespace", - "edgehub", - "proxy", - Option.Maybe(proxyImagePullSecretName), - "configPath", - "configVolumeName", - "configMapName", - "trustBundlePath", - "trustBundleVolumeName", - "trustBundleConfigMapName", - Option.Some(proxyReqs), - Option.Maybe(agentConfigMapName), - Option.Maybe(agentConfigPath), - Option.Maybe(agentConfigVolume), - Option.Some(agentReqs), - PortMapServiceType.ClusterIP, - useMountSourceForVolumeName, - storageClassName, - Option.Some(100), - "apiVersion", - new Uri("http://workload"), - new Uri("http://management"), - runAsNonRoot, - false, - experimentalFeatures == null ? new Dictionary() : experimentalFeatures); - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesExperimentalPodParametersTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesExperimentalPodParametersTest.cs deleted file mode 100644 index 100ce91f940..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesExperimentalPodParametersTest.cs +++ /dev/null @@ -1,423 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Newtonsoft.Json.Linq; - using Xunit; - - [Unit] - public class KubernetesExperimentalPodParametersTest - { - [Theory] - [MemberData(nameof(EmptyOptions))] - public void ReturnsNoneWhenParseMissedOptions(IDictionary experimental) - { - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental); - - Assert.Equal(Option.None(), parameters); - } - - public static IEnumerable EmptyOptions => - new List - { - new object[] { null }, - new object[] { new Dictionary() } - }; - - [Fact] - public void ReturnsEmptySectionsWhenNoSectionsProvided() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{}") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.False(parameters.NodeSelector.HasValue); - } - - [Fact] - public void ReturnsEmptySectionsWhenWrongOptionsProvided() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("\"\"") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental); - - Assert.False(parameters.HasValue); - } - - [Fact] - public void IgnoresUnsupportedOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ a: { a: \"b\" } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.False(parameters.Volumes.HasValue); - Assert.False(parameters.NodeSelector.HasValue); - Assert.False(parameters.Resources.HasValue); - Assert.False(parameters.SecurityContext.HasValue); - Assert.False(parameters.ServiceOptions.HasValue); - Assert.False(parameters.DeploymentStrategy.HasValue); - } - - [Fact] - public void ParsesNoneNodeSelectorExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ nodeSelector: null }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.False(parameters.NodeSelector.HasValue); - } - - [Fact] - public void ParsesEmptyNodeSelectorExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ nodeSelector: { } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.NodeSelector.HasValue); - parameters.NodeSelector.ForEach(Assert.Empty); - } - - [Fact] - public void ParsesSomeNodeSelectorExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ nodeSelector: { disktype: \"ssd\", gpu: \"true\" } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.NodeSelector.HasValue); - parameters.NodeSelector.ForEach(selector => Assert.Equal(2, selector.Count)); - parameters.NodeSelector.ForEach(selector => Assert.Equal("ssd", selector["disktype"])); - parameters.NodeSelector.ForEach(selector => Assert.Equal("true", selector["gpu"])); - } - - [Fact] - public void ParsesNoneResourcesExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ resources: null }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.False(parameters.Resources.HasValue); - } - - [Fact] - public void ParsesEmptyResourcesExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ resources: { } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.Resources.HasValue); - parameters.Resources.ForEach(resources => Assert.Null(resources.Limits)); - parameters.Resources.ForEach(resources => Assert.Null(resources.Requests)); - } - - [Fact] - public void ParsesEmptyRequirementsResourcesExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ resources: { limits: {}, requests: {} } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.Resources.HasValue); - parameters.Resources.ForEach(resources => Assert.Empty(resources.Limits)); - parameters.Resources.ForEach(resources => Assert.Empty(resources.Requests)); - } - - [Fact] - public void ParsesSomeResourcesExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ resources: { limits: { \"memory\": \"128Mi\" }, requests: { \"cpu\": \"250m\" } } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.Resources.HasValue); - parameters.Resources.ForEach(resources => Assert.Equal(new Dictionary { ["memory"] = new ResourceQuantity("128Mi") }, resources.Limits)); - parameters.Resources.ForEach(resources => Assert.Equal(new Dictionary { ["cpu"] = new ResourceQuantity("250m") }, resources.Requests)); - } - - [Fact] - public void ParsesNoneVolumesExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ volumes: null }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.False(parameters.Volumes.HasValue); - } - - [Fact] - public void ParsesEmptyVolumesExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ volumes: [ ] }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.Volumes.HasValue); - parameters.Volumes.ForEach(Assert.Empty); - } - - [Fact] - public void ParsesVolumesExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse( - @"{ - ""volumes"": [ - { - ""volume"": { - ""name"": ""ModuleA"", - ""configMap"": { - ""optional"": ""true"", - ""defaultMode"": 420, - ""items"": [{ - ""key"": ""config-file"", - ""path"": ""config.yaml"", - ""mode"": 420 - }], - ""name"": ""module-config"" - } - }, - ""volumeMounts"": [ - { - ""name"": ""module-config"", - ""mountPath"": ""/etc/module/config.yaml"", - ""mountPropagation"": ""None"", - ""readOnly"": ""true"", - ""subPath"": """" - } - ] - } - ] - }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.Volumes.HasValue); - var volumeSpec = parameters.Volumes.OrDefault().Single(); - - Assert.True(volumeSpec.Volume.HasValue); - volumeSpec.Volume.ForEach(volume => Assert.Equal("ModuleA", volume.Name)); - volumeSpec.Volume.ForEach(volume => Assert.Equal(true, volume.ConfigMap.Optional)); - volumeSpec.Volume.ForEach(volume => Assert.Equal(420, volume.ConfigMap.DefaultMode)); - volumeSpec.Volume.ForEach(volume => Assert.Equal(1, volume.ConfigMap.Items.Count)); - volumeSpec.Volume.ForEach(volume => Assert.Equal("config-file", volume.ConfigMap.Items[0].Key)); - volumeSpec.Volume.ForEach(volume => Assert.Equal("config.yaml", volume.ConfigMap.Items[0].Path)); - volumeSpec.Volume.ForEach(volume => Assert.Equal(420, volume.ConfigMap.Items[0].Mode)); - volumeSpec.Volume.ForEach(volume => Assert.Equal("module-config", volume.ConfigMap.Name)); - - Assert.True(volumeSpec.VolumeMounts.HasValue); - volumeSpec.VolumeMounts.ForEach(mounts => Assert.Equal(1, mounts.Count)); - volumeSpec.VolumeMounts.ForEach(mounts => Assert.Equal("module-config", mounts[0].Name)); - volumeSpec.VolumeMounts.ForEach(mounts => Assert.Equal("/etc/module/config.yaml", mounts[0].MountPath)); - volumeSpec.VolumeMounts.ForEach(mounts => Assert.Equal("None", mounts[0].MountPropagation)); - volumeSpec.VolumeMounts.ForEach(mounts => Assert.Equal(true, mounts[0].ReadOnlyProperty)); - volumeSpec.VolumeMounts.ForEach(mounts => Assert.Equal(string.Empty, mounts[0].SubPath)); - } - - [Fact] - public void ParsesNoneSecurityContextExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ securityContext: null }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.False(parameters.SecurityContext.HasValue); - } - - [Fact] - public void ParsesEmptySecurityContextExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ securityContext: { } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.SecurityContext.HasValue); - parameters.SecurityContext.ForEach(securityContext => Assert.Null(securityContext.RunAsGroup)); - parameters.SecurityContext.ForEach(securityContext => Assert.Null(securityContext.RunAsUser)); - parameters.SecurityContext.ForEach(securityContext => Assert.Null(securityContext.RunAsNonRoot)); - parameters.SecurityContext.ForEach(securityContext => Assert.Null(securityContext.Sysctls)); - parameters.SecurityContext.ForEach(securityContext => Assert.Null(securityContext.FsGroup)); - parameters.SecurityContext.ForEach(securityContext => Assert.Null(securityContext.SeLinuxOptions)); - parameters.SecurityContext.ForEach(securityContext => Assert.Null(securityContext.SupplementalGroups)); - } - - [Fact] - public void ParsesSomeSecurityContextExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ securityContext: { runAsGroup: 1001, runAsUser: 1000, runAsNonRoot: true } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.SecurityContext.HasValue); - parameters.SecurityContext.ForEach(securityContext => Assert.Equal(1001, securityContext.RunAsGroup)); - parameters.SecurityContext.ForEach(securityContext => Assert.Equal(1000, securityContext.RunAsUser)); - parameters.SecurityContext.ForEach(securityContext => Assert.Equal(true, securityContext.RunAsNonRoot)); - parameters.SecurityContext.ForEach(securityContext => Assert.Null(securityContext.Sysctls)); - parameters.SecurityContext.ForEach(securityContext => Assert.Null(securityContext.FsGroup)); - parameters.SecurityContext.ForEach(securityContext => Assert.Null(securityContext.SeLinuxOptions)); - parameters.SecurityContext.ForEach(securityContext => Assert.Null(securityContext.SupplementalGroups)); - } - - [Fact] - public void ParsesEmptyDeploymentStrategyExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ strategy: { } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.DeploymentStrategy.HasValue); - parameters.DeploymentStrategy.ForEach(strategy => Assert.Null(strategy.Type)); - parameters.DeploymentStrategy.ForEach(strategy => Assert.Null(strategy.RollingUpdate)); - } - - [Fact] - public void ParsesSomeDeploymentStrategyExperimentalOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse(@"{ strategy: { type: ""RollingUpdate"", rollingUpdate: { maxUnavailable: 1 } } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.DeploymentStrategy.HasValue); - parameters.DeploymentStrategy.ForEach(deploymentStrategy => Assert.Equal("RollingUpdate", deploymentStrategy.Type)); - parameters.DeploymentStrategy.ForEach(deploymentStrategy => Assert.Equal("1", deploymentStrategy.RollingUpdate.MaxUnavailable)); - } - - [Fact] - public void ParsesEmptyServiceOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse("{ serviceOptions: { } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.ServiceOptions.HasValue); - parameters.ServiceOptions.ForEach(options => Assert.False(options.Type.HasValue)); - parameters.ServiceOptions.ForEach(options => Assert.False(options.LoadBalancerIP.HasValue)); - } - - [Fact] - public void ParsesServiceOptions() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse(@"{ serviceOptions: { type: ""nodeport"", loadbalancerip: ""100.1.2.3"" } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.ServiceOptions.HasValue); - parameters.ServiceOptions.ForEach(options => - { - Assert.True(options.Type.HasValue); - options.Type.ForEach(t => Assert.Equal(PortMapServiceType.NodePort, t)); - }); - parameters.ServiceOptions.ForEach(options => - { - Assert.True(options.LoadBalancerIP.HasValue); - options.LoadBalancerIP.ForEach(l => Assert.Equal("100.1.2.3", l)); - }); - } - - [Fact] - public void ParsesServiceOptionsTypeOnly() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse(@"{ serviceOptions: { type: ""LoadBalancer"" } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.ServiceOptions.HasValue); - parameters.ServiceOptions.ForEach(options => - { - Assert.True(options.Type.HasValue); - options.Type.ForEach(t => Assert.Equal(PortMapServiceType.LoadBalancer, t)); - }); - parameters.ServiceOptions.ForEach(options => Assert.False(options.LoadBalancerIP.HasValue)); - } - - [Fact] - public void ParsesServiceOptionsLBIPOnly() - { - var experimental = new Dictionary - { - ["k8s-experimental"] = JToken.Parse(@"{ serviceOptions: { loadbalancerip: ""any old string"" } }") - }; - - var parameters = KubernetesExperimentalCreatePodParameters.Parse(experimental).OrDefault(); - - Assert.True(parameters.ServiceOptions.HasValue); - parameters.ServiceOptions.ForEach(options => Assert.False(options.Type.HasValue)); - parameters.ServiceOptions.ForEach(options => - { - Assert.True(options.LoadBalancerIP.HasValue); - options.LoadBalancerIP.ForEach(l => Assert.Equal("any old string", l)); - }); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesModuleIdentityLifecycleManagerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesModuleIdentityLifecycleManagerTest.cs deleted file mode 100644 index 890de3a5775..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesModuleIdentityLifecycleManagerTest.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Core.Test; - using Microsoft.Azure.Devices.Edge.Agent.Edgelet; - using Microsoft.Azure.Devices.Edge.Agent.Edgelet.Models; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Moq; - using Xunit; - - [Unit] - public class KubernetesModuleIdentityLifecycleManagerTest - { - const string IothubHostName = "test.azure-devices.net"; - const string DeviceId = "edgeDevice1"; - const string GatewayHostName = "edgedevicehost"; - static readonly Uri EdgeletUri = new Uri("http://localhost"); - static readonly ConfigurationInfo DefaultConfigurationInfo = new ConfigurationInfo("1"); - static readonly ModuleIdentityProviderServiceBuilder ModuleIdentityProviderServiceBuilder = new ModuleIdentityProviderServiceBuilder(IothubHostName, DeviceId, GatewayHostName); - - [Fact] - public async Task TestGetModulesIdentityIIdentityManagerExceptionShouldReturnEmptyIdentities() - { - // Arrange - var identityManager = Mock.Of(); - Mock.Get(identityManager).Setup(m => m.GetIdentities()).ThrowsAsync(new InvalidOperationException()); - var moduleIdentityLifecycleManager = new KubernetesModuleIdentityLifecycleManager(identityManager, ModuleIdentityProviderServiceBuilder, EdgeletUri); - var envVar = new Dictionary(); - - var module1 = new TestModule("mod1", "v1", "test", ModuleStatus.Running, new TestConfig("image"), RestartPolicy.OnUnhealthy, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, envVar); - var module2 = new TestModule("mod2", "v1", "test", ModuleStatus.Running, new TestConfig("image"), RestartPolicy.OnUnhealthy, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, envVar); - var module3 = new TestModule("mod3", "v1", "test", ModuleStatus.Running, new TestConfig("image"), RestartPolicy.OnUnhealthy, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, envVar); - var module4 = new TestModule("$edgeHub", "v1", "test", ModuleStatus.Running, new TestConfig("image"), RestartPolicy.OnUnhealthy, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, envVar); - ModuleSet desired = ModuleSet.Create(module1, module4); - ModuleSet current = ModuleSet.Create(module2, module3, module4); - - // Act - IImmutableDictionary modulesIdentities = await moduleIdentityLifecycleManager.GetModuleIdentitiesAsync(desired, current); - - // Assert - Assert.False(modulesIdentities.Any()); - Mock.Get(identityManager).Verify(); - } - - [Fact] - [Unit] - public async Task TestGetModulesIdentityShouldReturnAll() - { - // Arrange - const string Module1 = "module1"; - var identity1 = new Identity(Module1, Guid.NewGuid().ToString(), Constants.ModuleIdentityEdgeManagedByValue); - - const string Module2 = "module2"; - var identity2 = new Identity(Module2, Guid.NewGuid().ToString(), Constants.ModuleIdentityEdgeManagedByValue); - - const string Module3 = "module3"; - var identity3 = new Identity(Module3, Guid.NewGuid().ToString(), Constants.ModuleIdentityEdgeManagedByValue); - - var identityManager = Mock.Of( - m => - m.GetIdentities() == Task.FromResult(new List { identity1, identity2, identity3 }.AsEnumerable())); - - var moduleIdentityLifecycleManager = new KubernetesModuleIdentityLifecycleManager(identityManager, ModuleIdentityProviderServiceBuilder, EdgeletUri); - var envVar = new Dictionary(); - var desiredModule1 = new TestModule(Module1, "v1", "test", ModuleStatus.Running, new TestConfig("image"), RestartPolicy.OnUnhealthy, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, envVar); - var desiredModule2 = new TestModule(Module2, "v1", "test", ModuleStatus.Running, new TestConfig("image"), RestartPolicy.OnUnhealthy, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, envVar); - var desiredModule3 = new TestModule(Module3, "v1", "test", ModuleStatus.Running, new TestConfig("image"), RestartPolicy.OnUnhealthy, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, envVar); - ModuleSet desired = ModuleSet.Create(desiredModule1, desiredModule2, desiredModule3); - ModuleSet current = ModuleSet.Create(desiredModule1, desiredModule2, desiredModule3); - - // Act - IImmutableDictionary moduleIdentities = await moduleIdentityLifecycleManager.GetModuleIdentitiesAsync(desired, current); - - // Assert - Assert.NotNull(moduleIdentities); - Assert.True(moduleIdentities.TryGetValue(Module1, out IModuleIdentity module1Identity)); - Assert.True(moduleIdentities.TryGetValue(Module2, out IModuleIdentity module2Identity)); - Assert.True(moduleIdentities.TryGetValue(Module3, out IModuleIdentity module3Identity)); - Assert.Equal(Module1, module1Identity.ModuleId); - Assert.Equal(Module2, module2Identity.ModuleId); - Assert.Equal(Module3, module3Identity.ModuleId); - foreach (var moduleIdentity in moduleIdentities) - { - Assert.IsType(moduleIdentity.Value.Credentials); - Assert.Equal(EdgeletUri.ToString(), ((IdentityProviderServiceCredentials)moduleIdentity.Value.Credentials).ProviderUri); - Assert.Equal(Option.None(), ((IdentityProviderServiceCredentials)moduleIdentity.Value.Credentials).Version); - } - - Mock.Get(identityManager).VerifyAll(); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesModuleTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesModuleTest.cs deleted file mode 100644 index b4d9111f4c4..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesModuleTest.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System.Collections.Generic; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - - [Unit] - public class KubernetesModuleTest - { - [Fact] - public void CompareModule() - { - Dictionary goodEnv = new Dictionary(); - Dictionary newEnv = new Dictionary { ["a"] = new EnvVal("B") }; - IReadOnlyList dockerEnv = new List { "c=d" }; - KubernetesConfig goodConfig = new KubernetesConfig("image:tag", CreatePodParameters.Create(), Option.None()); - KubernetesConfig imageDifferent = new KubernetesConfig("image:newtag", CreatePodParameters.Create(), Option.None()); - - var auth1 = new AuthConfig("secret1"); - KubernetesConfig auth1Config = new KubernetesConfig("image:tag", CreatePodParameters.Create(), Option.Some(auth1)); - - var auth2 = new AuthConfig("secret2"); - KubernetesConfig auth2Config = new KubernetesConfig("image:tag", CreatePodParameters.Create(), Option.Some(auth2)); - - var auth3 = new AuthConfig("secret3"); - KubernetesConfig auth3Config = new KubernetesConfig("image:tag", CreatePodParameters.Create(), Option.Some(auth3)); - - var auth4 = new AuthConfig("secret4"); - KubernetesConfig auth4Config = new KubernetesConfig("image:tag", CreatePodParameters.Create(), Option.Some(auth4)); - - KubernetesConfig createContainerConfigDifferent = new KubernetesConfig("image:tag", CreatePodParameters.Create(dockerEnv), Option.None()); - - ConfigurationInfo goodInfo = new ConfigurationInfo(string.Empty); - - KubernetesModuleOwner moduleOwner = new KubernetesModuleOwner("v1", "Deployment", "iotedged", "123"); - - var m1 = new KubernetesModule("name1", "v1", "docker", ModuleStatus.Running, RestartPolicy.Always, goodInfo, goodEnv, goodConfig, ImagePullPolicy.OnCreate, moduleOwner); - var m2 = new KubernetesModule("name2", "v1", "docker", ModuleStatus.Running, RestartPolicy.Always, goodInfo, goodEnv, goodConfig, ImagePullPolicy.OnCreate, moduleOwner); - - var m3 = new KubernetesModule("name1", "v1", "docker", ModuleStatus.Running, RestartPolicy.Always, goodInfo, goodEnv, goodConfig, ImagePullPolicy.OnCreate, moduleOwner); - var m4 = new KubernetesModule("name1", "v2", "docker", ModuleStatus.Running, RestartPolicy.Always, goodInfo, goodEnv, goodConfig, ImagePullPolicy.OnCreate, moduleOwner); - - var m5 = new KubernetesModule("name1", "v1", "docker", ModuleStatus.Running, RestartPolicy.Always, goodInfo, goodEnv, goodConfig, ImagePullPolicy.OnCreate, moduleOwner); - var m6 = new KubernetesModule("name1", "v1", "docker", ModuleStatus.Stopped, RestartPolicy.Always, goodInfo, goodEnv, goodConfig, ImagePullPolicy.OnCreate, moduleOwner); - - var m7 = new KubernetesModule("name1", "v1", "docker", ModuleStatus.Running, RestartPolicy.Always, goodInfo, goodEnv, goodConfig, ImagePullPolicy.OnCreate, moduleOwner); - var m8 = new KubernetesModule("name1", "v1", "docker", ModuleStatus.Running, RestartPolicy.Never, goodInfo, goodEnv, goodConfig, ImagePullPolicy.OnCreate, moduleOwner); - - var m9 = new KubernetesModule("name1", "v1", "docker", ModuleStatus.Running, RestartPolicy.Always, goodInfo, goodEnv, imageDifferent, ImagePullPolicy.OnCreate, moduleOwner); - var m10 = new KubernetesModule("name1", "v1", "docker", ModuleStatus.Running, RestartPolicy.Always, goodInfo, goodEnv, auth1Config, ImagePullPolicy.OnCreate, moduleOwner); - var m11 = new KubernetesModule("name1", "v1", "docker", ModuleStatus.Running, RestartPolicy.Always, goodInfo, goodEnv, auth2Config, ImagePullPolicy.OnCreate, moduleOwner); - var m12 = new KubernetesModule("name1", "v1", "docker", ModuleStatus.Running, RestartPolicy.Always, goodInfo, goodEnv, auth3Config, ImagePullPolicy.OnCreate, moduleOwner); - var m13 = new KubernetesModule("name1", "v1", "docker", ModuleStatus.Running, RestartPolicy.Always, goodInfo, goodEnv, auth4Config, ImagePullPolicy.OnCreate, moduleOwner); - var m14 = new KubernetesModule("name1", "v1", "docker", ModuleStatus.Running, RestartPolicy.Always, goodInfo, goodEnv, createContainerConfigDifferent, ImagePullPolicy.OnCreate, moduleOwner); - - var m15 = new KubernetesModule("name1", "v1", "docker", ModuleStatus.Running, RestartPolicy.Always, goodInfo, newEnv, goodConfig, ImagePullPolicy.OnCreate, moduleOwner); - - Assert.NotEqual(m1, m2); - Assert.NotEqual(m3, m4); - Assert.NotEqual(m5, m6); - Assert.NotEqual(m7, m8); - Assert.NotEqual(m1, m9); - Assert.NotEqual(m9, m1); - Assert.NotEqual(m10, m9); - Assert.NotEqual(m9, m10); - Assert.NotEqual(m10, m11); - Assert.NotEqual(m11, m10); - Assert.NotEqual(m10, m12); - Assert.NotEqual(m10, m13); - Assert.NotEqual(m11, m14); - Assert.NotEqual(m11, m15); - - Assert.True(m5.IsOnlyModuleStatusChanged(m6)); - - Assert.False(m1.IsOnlyModuleStatusChanged(m2)); - Assert.False(m1.IsOnlyModuleStatusChanged(m9)); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesRuntimeInfoProviderTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesRuntimeInfoProviderTest.cs deleted file mode 100644 index 3d72178254f..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesRuntimeInfoProviderTest.cs +++ /dev/null @@ -1,348 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using k8s; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Agent.Edgelet; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Microsoft.Rest; - using Moq; - using Newtonsoft.Json; - using Xunit; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - [Unit] - public class KubernetesRuntimeInfoProviderTest - { - const string Namespace = "msiot-dwr-hub-dwr-ha3"; - - public static IEnumerable SystemResponseData() - { - var nodeFilled = new V1Node(status: new V1NodeStatus(nodeInfo: new V1NodeSystemInfo("architecture", "bootID", "containerRuntimeVersion", "kernelVersion", "kubeProxyVersion", "kubeletVersion", "machineID", "operatingSystem", "osImage", "systemUUID"))); - var emptyNode = new V1Node(); - yield return new object[] { new V1NodeList(), new SystemInfo("Kubernetes", "Kubernetes", "Kubernetes") }; - yield return new object[] { new V1NodeList(new List { emptyNode }), new SystemInfo("Kubernetes", "Kubernetes", "Kubernetes") }; - yield return new object[] { new V1NodeList(new List { nodeFilled }), new SystemInfo("Kubernetes", "Kubernetes", "Kubernetes") }; - } - - public static V1Pod CreatePodWithPodParametersOnly(string podPhase, string podReason, string podMessage) - => new V1Pod - { - Metadata = new V1ObjectMeta - { - Name = "module-a-abc123", - Labels = new Dictionary - { - [KubernetesConstants.K8sEdgeModuleLabel] = "module-a" - }, - Annotations = new Dictionary - { - [KubernetesConstants.K8sEdgeOriginalModuleId] = "Module-A" - } - }, - Status = new V1PodStatus - { - Phase = podPhase, - Message = podMessage, - Reason = podReason, - } - }; - - public static V1Pod CreatePodInPhaseWithContainerStatus(string podPhase, V1ContainerState containerState) - => new V1Pod - { - Metadata = new V1ObjectMeta - { - Name = "module-a-abc123", - Labels = new Dictionary - { - [KubernetesConstants.K8sEdgeModuleLabel] = "module-a" - }, - Annotations = new Dictionary - { - [KubernetesConstants.K8sEdgeOriginalModuleId] = "Module-A" - } - }, - Status = new V1PodStatus - { - Phase = podPhase, - ContainerStatuses = new List() - { - new V1ContainerStatus - { - Name = "module-a", - State = containerState, - } - } - } - }; - - public static IEnumerable GetListOfPodsInRunningPhase() - { - return new[] - { - new object[] - { - CreatePodInPhaseWithContainerStatus("Running", new V1ContainerState(waiting: new V1ContainerStateWaiting("Waiting", "CrashBackLoopOff"))), - "Module in Back-off reason: CrashBackLoopOff", - ModuleStatus.Backoff - }, - new object[] - { - CreatePodInPhaseWithContainerStatus("Running", new V1ContainerState(terminated: new V1ContainerStateTerminated(0, reason: "Completed"))), - "Module Stopped reason: Completed", - ModuleStatus.Stopped - }, - new object[] - { - CreatePodInPhaseWithContainerStatus("Running", new V1ContainerState(terminated: new V1ContainerStateTerminated(139, reason: "Segmentation Fault"))), - "Module Failed reason: Segmentation Fault", - ModuleStatus.Failed - }, - new object[] - { - CreatePodInPhaseWithContainerStatus("Running", new V1ContainerState(running: new V1ContainerStateRunning(startedAt: DateTime.Parse("2019-06-12T16:11:22Z")))), - "Started at " + DateTime.Parse("2019-06-12T16:11:22Z"), - ModuleStatus.Running - } - }; - } - - public static IEnumerable GetListOfPodsInPendingPhase() - { - return new[] - { - new object[] - { - CreatePodInPhaseWithContainerStatus("Pending", new V1ContainerState(waiting: new V1ContainerStateWaiting("Waiting", "CrashBackLoopOff"))), - "Module in Back-off reason: CrashBackLoopOff", - ModuleStatus.Backoff - }, - new object[] - { - CreatePodInPhaseWithContainerStatus("Pending", new V1ContainerState(terminated: new V1ContainerStateTerminated(0, reason: "Completed"))), - "Module Stopped reason: Completed", - ModuleStatus.Stopped - }, - new object[] - { - CreatePodInPhaseWithContainerStatus("Pending", new V1ContainerState(terminated: new V1ContainerStateTerminated(139, reason: "Segmentation Fault"))), - "Module Failed reason: Segmentation Fault", - ModuleStatus.Failed - }, - new object[] - { - CreatePodInPhaseWithContainerStatus("Pending", new V1ContainerState(running: new V1ContainerStateRunning(startedAt: DateTime.Parse("2019-06-12T16:11:22Z")))), - "Started at " + DateTime.Parse("2019-06-12T16:11:22Z"), - ModuleStatus.Backoff - } - }; - } - - public static IEnumerable GetListOfPodsInAbnormalPhase() - { - return new[] - { - new object[] - { - CreatePodWithPodParametersOnly("Unknown", "Unknown", "Unknown"), - "Module status Unknown reason: Unknown", - ModuleStatus.Unknown - }, - new object[] - { - CreatePodWithPodParametersOnly("Failed", "Terminated", "Non-zero exit code"), - "Module Failed reason: Terminated with message: Non-zero exit code", - ModuleStatus.Failed - }, - new object[] - { - CreatePodWithPodParametersOnly("Succeeded", "Completed", "Zero exit code"), - "Module Stopped reason: Completed with message: Zero exit code", - ModuleStatus.Stopped - } - }; - } - - [Fact] - public void ConstructorChecksNull() - { - var client = new Mock(MockBehavior.Strict); - var moduleManager = new Mock(MockBehavior.Strict); - - Assert.Throws(() => new KubernetesRuntimeInfoProvider(null, client.Object, moduleManager.Object)); - Assert.Throws(() => new KubernetesRuntimeInfoProvider("namespace ", null, moduleManager.Object)); - Assert.Throws(() => new KubernetesRuntimeInfoProvider("namespace", client.Object, null)); - } - - [Fact] - public async Task ReturnsEmptyModulesWhenNoDataAvailable() - { - var client = new Mock(MockBehavior.Strict); - var moduleManager = new Mock(MockBehavior.Strict); - var runtimeInfo = new KubernetesRuntimeInfoProvider(Namespace, client.Object, moduleManager.Object); - - var modules = await runtimeInfo.GetModules(CancellationToken.None); - - Assert.Empty(modules); - } - - [Fact] - public async Task ReturnsModulesWhenModuleInfoAdded() - { - V1Pod edgeagent = BuildPodList()["edgeagent"]; - var client = new Mock(MockBehavior.Strict); - var moduleManager = new Mock(MockBehavior.Strict); - var runtimeInfo = new KubernetesRuntimeInfoProvider(Namespace, client.Object, moduleManager.Object); - runtimeInfo.CreateOrUpdateAddPodInfo(edgeagent); - - var modules = await runtimeInfo.GetModules(CancellationToken.None); - - var info = modules.Single(); - Assert.NotNull(info); - Assert.Equal("edgeAgent", info.Name); - } - - [Fact] - public async Task ReturnsRestModulesWhenSomeModulesInfoRemoved() - { - V1Pod edgeagent = BuildPodList()["edgeagent"]; - V1Pod edgehub = BuildPodList()["edgehub"]; - var client = new Mock(MockBehavior.Strict); - var moduleManager = new Mock(MockBehavior.Strict); - var runtimeInfo = new KubernetesRuntimeInfoProvider(Namespace, client.Object, moduleManager.Object); - runtimeInfo.CreateOrUpdateAddPodInfo(edgeagent); - runtimeInfo.CreateOrUpdateAddPodInfo(edgehub); - runtimeInfo.RemovePodInfo(edgeagent); - - var modules = await runtimeInfo.GetModules(CancellationToken.None); - - var info = modules.Single(); - Assert.NotNull(info); - Assert.Equal("edgeHub", info.Name); - } - - [Fact] - public async Task ReturnsModuleRuntimeInfoWhenPodsAreUpdated() - { - V1Pod edgeagent1 = BuildPodList()["edgeagent"]; - edgeagent1.Metadata.Name = "edgeagent_123"; - edgeagent1.Status.ContainerStatuses - .First(c => c.Name == "edgeagent").State.Running.StartedAt = new DateTime(2019, 10, 28); - V1Pod edgeagent2 = BuildPodList()["edgeagent"]; - edgeagent2.Metadata.Name = "edgeAgent_456"; - edgeagent2.Status.ContainerStatuses - .First(c => c.Name == "edgeagent").State.Running.StartedAt = new DateTime(2019, 10, 29); - var client = new Mock(MockBehavior.Strict); - var moduleManager = new Mock(MockBehavior.Strict); - var runtimeInfo = new KubernetesRuntimeInfoProvider(Namespace, client.Object, moduleManager.Object); - runtimeInfo.CreateOrUpdateAddPodInfo(edgeagent1); - runtimeInfo.CreateOrUpdateAddPodInfo(edgeagent2); - runtimeInfo.RemovePodInfo(edgeagent1); - - var modules = await runtimeInfo.GetModules(CancellationToken.None); - - var info = modules.Single(); - Assert.NotNull(info); - Assert.Equal(info.StartTime, Option.Some(new DateTime(2019, 10, 29))); - } - - [Fact] - public async Task ConvertsPodsToModules() - { - var client = new Mock(MockBehavior.Strict); - var moduleManager = new Mock(MockBehavior.Strict); - var runtimeInfo = new KubernetesRuntimeInfoProvider(Namespace, client.Object, moduleManager.Object); - foreach (V1Pod pod in BuildPodList().Values) - { - runtimeInfo.CreateOrUpdateAddPodInfo(pod); - } - - var modules = (await runtimeInfo.GetModules(CancellationToken.None)).ToList(); - - Assert.Equal(3, modules.Count); - foreach (var module in modules) - { - Assert.Contains("Started", module.Description); - Assert.Equal(ModuleStatus.Running, module.ModuleStatus); - Assert.Equal(new DateTime(2019, 6, 12), module.StartTime.GetOrElse(DateTime.MinValue).Date); - Assert.Equal("docker", module.Type); - if (module is ModuleRuntimeInfo config) - { - Assert.NotEqual("unknown:unknown", config.Config.Image); - } - } - } - - [Theory] - [MemberData(nameof(GetListOfPodsInRunningPhase))] - public async Task ReturnModuleStatusWhenPodIsRunning(V1Pod pod, string description, ModuleStatus status) - { - var client = new Mock(MockBehavior.Strict); - var moduleManager = new Mock(MockBehavior.Strict); - var runtimeInfo = new KubernetesRuntimeInfoProvider(Namespace, client.Object, moduleManager.Object); - runtimeInfo.CreateOrUpdateAddPodInfo(pod); - - ModuleRuntimeInfo info = (await runtimeInfo.GetModules(CancellationToken.None)).Single(); - - Assert.Equal(status, info.ModuleStatus); - Assert.Equal(description, info.Description); - } - - [Theory] - [MemberData(nameof(GetListOfPodsInPendingPhase))] - public async Task ReturnModuleStatusWhenPodIsPending(V1Pod pod, string description, ModuleStatus status) - { - var client = new Mock(MockBehavior.Strict); - var moduleManager = new Mock(MockBehavior.Strict); - var runtimeInfo = new KubernetesRuntimeInfoProvider(Namespace, client.Object, moduleManager.Object); - runtimeInfo.CreateOrUpdateAddPodInfo(pod); - - ModuleRuntimeInfo info = (await runtimeInfo.GetModules(CancellationToken.None)).Single(); - - Assert.Equal(status, info.ModuleStatus); - Assert.Equal(description, info.Description); - } - - [Theory] - [MemberData(nameof(GetListOfPodsInAbnormalPhase))] - public async Task ReturnModuleStatusWhenPodIsAbnormal(V1Pod pod, string description, ModuleStatus status) - { - var client = new Mock(MockBehavior.Strict); - var moduleManager = new Mock(MockBehavior.Strict); - var runtimeInfo = new KubernetesRuntimeInfoProvider(Namespace, client.Object, moduleManager.Object); - runtimeInfo.CreateOrUpdateAddPodInfo(pod); - - ModuleRuntimeInfo info = (await runtimeInfo.GetModules(CancellationToken.None)).Single(); - - Assert.Equal(status, info.ModuleStatus); - Assert.Equal(description, info.Description); - } - - static Dictionary BuildPodList() - { - string content = File.ReadAllText("podwatch.txt"); - var list = JsonConvert.DeserializeObject(content); - return list.Items - .Select( - pod => - { - string name = default(string); - pod.Metadata.Labels?.TryGetValue(KubernetesConstants.K8sEdgeModuleLabel, out name); - return new { name, pod }; - }) - .Where(item => !string.IsNullOrEmpty(item.name)) - .ToDictionary(item => item.name, item => item.pod); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesServiceAccountMapperTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesServiceAccountMapperTest.cs deleted file mode 100644 index c0d9bc190ef..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesServiceAccountMapperTest.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Agent.Docker.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.ServiceAccount; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Moq; - using Xunit; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - [Unit] - public class KubernetesServiceAccountMapperTest - { - static readonly ResourceName ResourceName = new ResourceName("hostname", "deviceId"); - - static readonly KubernetesModuleOwner EdgeletModuleOwner = new KubernetesModuleOwner("v1", "Deployment", "iotedged", "123"); - - [Fact] - public void RequiredMetadataExistsWhenCreated() - { - var identity = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "ModuleId", Mock.Of()); - var mapper = new KubernetesServiceAccountMapper(); - var labels = new Dictionary { ["device"] = "k8s-device" }; - var config = new KubernetesConfig("image", CreatePodParameters.Create(labels: labels), Option.None()); - var configurationInfo = new ConfigurationInfo("1"); - var envVarsDict = new Dictionary(); - - var dockerConfig = new DockerConfig("test-image:1"); - - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, dockerConfig, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, configurationInfo, envVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - - var serviceAccount = mapper.CreateServiceAccount(module, identity, labels); - - Assert.NotNull(serviceAccount); - Assert.Equal("moduleid", serviceAccount.Metadata.Name); - Assert.Equal(1, serviceAccount.Metadata.Annotations.Count); - Assert.Equal("ModuleId", serviceAccount.Metadata.Annotations[KubernetesConstants.K8sEdgeOriginalModuleId]); - Assert.Equal(1, serviceAccount.Metadata.Labels.Count); - Assert.Equal("k8s-device", serviceAccount.Metadata.Labels["device"]); - Assert.Equal(1, serviceAccount.Metadata.OwnerReferences.Count); - Assert.Equal(V1Deployment.KubeKind, serviceAccount.Metadata.OwnerReferences[0].Kind); - Assert.Equal(EdgeletModuleOwner.Name, serviceAccount.Metadata.OwnerReferences[0].Name); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesServiceMapperTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesServiceMapperTest.cs deleted file mode 100644 index 1b0f168b5f1..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/KubernetesServiceMapperTest.cs +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Moq; - using Xunit; - using DockerModels = global::Microsoft.Azure.Devices.Edge.Agent.Docker.Models; - using EmptyStruct = global::Docker.DotNet.Models.EmptyStruct; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - [Unit] - public class KubernetesServiceMapperTest - { - static readonly ConfigurationInfo DefaultConfigurationInfo = new ConfigurationInfo("1"); - static readonly IDictionary EnvVars = new Dictionary(); - static readonly DockerConfig Config1 = new DockerConfig("test-image:1"); - static readonly ResourceName ResourceName = new ResourceName("hostname", "deviceId"); - static readonly KubernetesModuleOwner EdgeletModuleOwner = new KubernetesModuleOwner("v1", "Deployment", "iotedged", "123"); - - [Fact] - public void EmptyIsNotAllowedAsServiceAnnotation() - { - // string.Empty is an invalid label name - var labels = new Dictionary { { string.Empty, "test" } }; - var createOptions = CreatePodParameters.Create(labels: labels); - var config = new KubernetesConfig("image", createOptions, Option.None()); - var moduleId = new ModuleIdentity("hub", "gateway", "deviceId", "moduleid", Mock.Of()); - var docker = new DockerModule(moduleId.ModuleId, "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var moduleLabels = new Dictionary(); - var mapper = new KubernetesServiceMapper(PortMapServiceType.ClusterIP); - - Assert.Throws(() => mapper.CreateService(moduleId, module, moduleLabels)); - } - - [Fact] - public void NoPortsExposedMeansNoServiceCreated() - { - var createOptions = CreatePodParameters.Create(); - var config = new KubernetesConfig("image", createOptions, Option.None()); - var moduleId = new ModuleIdentity("hub", "gateway", "deviceId", "moduleid", Mock.Of()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var moduleLabels = new Dictionary(); - - var mapper = new KubernetesServiceMapper(PortMapServiceType.ClusterIP); - - var service = mapper.CreateService(moduleId, module, moduleLabels); - Assert.False(service.HasValue); - } - - [Fact] - public void InvalidPortBindingDoesNotCreateAService() - { - // Add invalid port - var exposedPorts = new Dictionary { { "aa/TCP", default(EmptyStruct) } }; - var createOptions = CreatePodParameters.Create(exposedPorts: exposedPorts); - var config = new KubernetesConfig("image", createOptions, Option.None()); - var moduleId = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "moduleid", Mock.Of()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var moduleLabels = new Dictionary(); - var mapper = new KubernetesServiceMapper(PortMapServiceType.ClusterIP); - - var service = mapper.CreateService(moduleId, module, moduleLabels); - - Assert.False(service.HasValue); - } - - [Fact] - public void UnknownProtocolDoesNotCreateService() - { - // Add unknown protocol - var exposedPorts = new Dictionary { { "123/XXX", default(EmptyStruct) } }; - var createOptions = CreatePodParameters.Create(exposedPorts: exposedPorts); - var config = new KubernetesConfig("image", createOptions, Option.None()); - var moduleId = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "moduleid", Mock.Of()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var moduleLabels = new Dictionary(); - var mapper = new KubernetesServiceMapper(PortMapServiceType.ClusterIP); - - var service = mapper.CreateService(moduleId, module, moduleLabels); - - Assert.False(service.HasValue); - } - - [Fact] - public void SimpleServiceCreationHappyPath() - { - var exposedPorts = new Dictionary { ["10/TCP"] = default(EmptyStruct) }; - var createOptions = CreatePodParameters.Create(exposedPorts: exposedPorts); - var config = new KubernetesConfig("image", createOptions, Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var moduleLabels = new Dictionary(); - var mapper = new KubernetesServiceMapper(PortMapServiceType.ClusterIP); - var moduleId = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "moduleid", Mock.Of()); - - var service = mapper.CreateService(moduleId, module, moduleLabels).OrDefault(); - - Assert.NotNull(service); - Assert.Equal(1, service.Metadata.OwnerReferences.Count); - Assert.Equal(V1Deployment.KubeKind, service.Metadata.OwnerReferences[0].Kind); - Assert.Equal(EdgeletModuleOwner.Name, service.Metadata.OwnerReferences[0].Name); - Assert.Equal(1, service.Spec.Ports.Count); - Assert.Equal(0, service.Spec.Selector.Count); - } - - [Fact] - public void DockerLabelsConvertedAsAnnotations() - { - var exposedPorts = new Dictionary { ["10/TCP"] = default(EmptyStruct) }; - var labels = new Dictionary { ["GPU"] = "Enabled" }; - var createOptions = CreatePodParameters.Create(exposedPorts: exposedPorts, labels: labels); - var config = new KubernetesConfig("image", createOptions, Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var moduleLabels = new Dictionary(); - var mapper = new KubernetesServiceMapper(PortMapServiceType.ClusterIP); - var moduleId = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "moduleid", Mock.Of()); - - var service = mapper.CreateService(moduleId, module, moduleLabels).OrDefault(); - - Assert.Equal(1, service.Spec.Ports.Count); - Assert.Equal(2, service.Metadata.Annotations.Count); - Assert.NotNull(service.Metadata.Annotations[KubernetesConstants.CreationString]); - Assert.Equal("Enabled", service.Metadata.Annotations["GPU"]); - Assert.Equal(0, service.Metadata.Labels.Count); - Assert.Equal(0, service.Spec.Selector.Count); - } - - [Fact] - public void LabelsConvertedAsLabelsAndSelectors() - { - var exposedPorts = new Dictionary { ["10/TCP"] = default(EmptyStruct) }; - var createOptions = CreatePodParameters.Create(exposedPorts: exposedPorts); - var config = new KubernetesConfig("image", createOptions, Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var moduleLabels = new Dictionary { { "Label1", "VaLue1" } }; - var mapper = new KubernetesServiceMapper(PortMapServiceType.ClusterIP); - var moduleId = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "moduleid", Mock.Of()); - - var service = mapper.CreateService(moduleId, module, moduleLabels).OrDefault(); - - Assert.Equal(1, service.Spec.Ports.Count); - Assert.Equal(1, service.Metadata.Annotations.Count); - Assert.NotNull(service.Metadata.Annotations[KubernetesConstants.CreationString]); - Assert.Equal(1, service.Metadata.Labels.Count); - Assert.Equal("VaLue1", service.Metadata.Labels["Label1"]); - Assert.Equal("ClusterIP", service.Spec.Type); - Assert.Equal(1, service.Spec.Selector.Count); - Assert.Equal("VaLue1", service.Spec.Selector["Label1"]); - } - - [Fact] - public void PortBindingsCreatesAServiceWithPorts() - { - var hostConfig = new DockerModels.HostConfig - { - PortBindings = new Dictionary> - { - ["10/TCP"] = new List { new DockerModels.PortBinding { HostPort = "10" } } - } - }; - var createOptions = CreatePodParameters.Create(hostConfig: hostConfig); - var config = new KubernetesConfig("image", createOptions, Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var moduleLabels = new Dictionary(); - var mapper = new KubernetesServiceMapper(PortMapServiceType.ClusterIP); - var moduleId = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "moduleid", Mock.Of()); - - var service = mapper.CreateService(moduleId, module, moduleLabels).OrDefault(); - - Assert.Equal(1, service.Spec.Ports.Count); - AssertPort(new V1ServicePort(10, name: "hostport-10-tcp", protocol: "TCP", targetPort: 10), service.Spec.Ports.First()); - Assert.Equal("ClusterIP", service.Spec.Type); - } - - [Fact] - public void ExposingPortsCreatesAServiceWithPorts() - { - var exposedPorts = new Dictionary { ["10/TCP"] = default(EmptyStruct) }; - var createOptions = CreatePodParameters.Create(exposedPorts: exposedPorts); - var config = new KubernetesConfig("image", createOptions, Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var moduleLabels = new Dictionary(); - var mapper = new KubernetesServiceMapper(PortMapServiceType.ClusterIP); - var moduleId = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "moduleid", Mock.Of()); - - var service = mapper.CreateService(moduleId, module, moduleLabels).OrDefault(); - - Assert.Equal(1, service.Spec.Ports.Count); - AssertPort(new V1ServicePort(10, name: "exposedport-10-tcp", protocol: "TCP"), service.Spec.Ports.First()); - Assert.Equal("ClusterIP", service.Spec.Type); - } - - [Fact] - public void PortBindingOverrideExposedPort() - { - var exposedPorts = new Dictionary { ["10/TCP"] = default(EmptyStruct) }; - var hostConfig = new DockerModels.HostConfig - { - PortBindings = new Dictionary> - { - ["10/TCP"] = new List { new DockerModels.PortBinding { HostPort = "10" } } - } - }; - var createOptions = CreatePodParameters.Create(exposedPorts: exposedPorts, hostConfig: hostConfig); - var config = new KubernetesConfig("image", createOptions, Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var moduleLabels = new Dictionary(); - var mapper = new KubernetesServiceMapper(PortMapServiceType.ClusterIP); - var moduleId = new ModuleIdentity("hostname", "gatewayhost", "deviceid", "moduleid", Mock.Of()); - - var service = mapper.CreateService(moduleId, module, moduleLabels).OrDefault(); - - Assert.Equal(1, service.Spec.Ports.Count); - AssertPort(new V1ServicePort(10, name: "hostport-10-tcp", protocol: "TCP", targetPort: 10), service.Spec.Ports.First()); - Assert.Equal("ClusterIP", service.Spec.Type); - } - - static void AssertPort(V1ServicePort expected, V1ServicePort actual) - { - Assert.NotNull(actual); - Assert.Equal(expected.Name, actual.Name); - Assert.Equal(expected.Port, actual.Port); - Assert.Equal(expected.NodePort, actual.NodePort); - Assert.Equal(expected.TargetPort, actual.TargetPort); - Assert.Equal(expected.Protocol, actual.Protocol); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test.csproj b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test.csproj deleted file mode 100644 index 2a7935a6efd..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test.csproj +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - True - Debug;Release;CheckInBuild - true - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - - - - - - ..\..\..\stylecop.ruleset - - - - diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ObjectToStringConverterTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ObjectToStringConverterTest.cs deleted file mode 100644 index 591b9c11958..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ObjectToStringConverterTest.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Newtonsoft.Json; - using Xunit; - - [Unit] - public class ObjectToStringConverterTest - { - [Fact] - public void SerializesWholeObjectInsideWrapperAsString() - { - var wrapper = new Wrapper { Inner = new ObjectToSerialize { Name = "some name here" } }; - - string json = JsonConvert.SerializeObject(wrapper); - - Assert.Equal("{\"Inner\":\"{\\\"Name\\\":\\\"some name here\\\"}\"}", json); - } - - [Fact] - public void DeserializesWholeObjectInsideWrapperFromString() - { - string json = "{\"Inner\":\"{\\\"Name\\\":\\\"some name here\\\"}\"}"; - - Wrapper wrapper = JsonConvert.DeserializeObject(json); - - Assert.Equal("some name here", wrapper.Inner.Name); - } - - class ObjectToSerialize - { - public string Name { get; set; } - } - - class Wrapper - { - [JsonConverter(typeof(ObjectToStringConverter))] - public ObjectToSerialize Inner { get; set; } - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/PortAndProtocolTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/PortAndProtocolTest.cs deleted file mode 100644 index 55194f8c9e9..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/PortAndProtocolTest.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - - [Unit] - public class PortAndProtocolTest - { - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void UnableToParseEmptyString(string name) - { - var result = PortAndProtocol.Parse(name); - - Assert.Equal(Option.None(), result); - } - - [Fact] - public void UnableToParseUnsupportedProtocol() - { - var result = PortAndProtocol.Parse("123/HTTP"); - - Assert.Equal(Option.None(), result); - } - - [Fact] - public void UnableToParseInvalidPort() - { - var result = PortAndProtocol.Parse("1a23/TCP"); - - Assert.Equal(Option.None(), result); - } - - [Fact] - public void UnableToParseTooManyParts() - { - var result = PortAndProtocol.Parse("10/tcp/udp"); - - Assert.Equal(Option.None(), result); - } - - [Fact] - public void DefaultProtocolIsTCP() - { - var result = PortAndProtocol.Parse("3434").OrDefault(); - - Assert.Equal(3434, result.Port); - Assert.Equal("TCP", result.Protocol); - } - - [Theory] - [InlineData("123/TCP", 123, "TCP")] - [InlineData("456/UDP", 456, "UDP")] - [InlineData("10/SCTP", 10, "SCTP")] - public void ParsesPortAndProtocol(string name, int expectedPort, string expectedProtocol) - { - var result = PortAndProtocol.Parse(name).OrDefault(); - - Assert.Equal(expectedPort, result.Port); - Assert.Equal(expectedProtocol, result.Protocol); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ResourceNameTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ResourceNameTest.cs deleted file mode 100644 index 2ad72c41156..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ResourceNameTest.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - - [Unit] - public class ResourceNameTest - { - [Theory] - [InlineData(null, null)] - [InlineData("host", null)] - [InlineData(null, "device")] - [InlineData(" ", "device")] - [InlineData("host", " ")] - public void UnableToCreateInvalidResourceName(string hostname, string deviceId) - { - Assert.Throws(() => new ResourceName(hostname, deviceId)); - } - - [Fact] - public void CreateValidResourceName() - { - var name = new ResourceName("hostname", "device"); - Assert.Equal("hostname", name.Hostname); - Assert.Equal("device", name.DeviceId); - } - - [Fact] - public void RepresentsKubernetesResourceNameString() - { - var name = new ResourceName("hostname", "device"); - Assert.Equal("hostname-device", name.ToString()); - } - - [Fact] - public void EqualsToKubernetesResourceNameString() - { - var name = new ResourceName("hostname", "device"); - Assert.Equal("hostname-device", name); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ResourceSettingsTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ResourceSettingsTest.cs deleted file mode 100644 index 325203449f0..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/ResourceSettingsTest.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System; - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - - [Unit] - public class ResourceSettingsTest - { - static Dictionary resourceSet1 = new Dictionary - { - ["resource1"] = "100Mi", - ["resource2"] = "200M", - ["resource3"] = "3", - }; - - static Dictionary resourceSet2 = new Dictionary - { - ["resource4"] = "300Mi", - ["resource5"] = "100M", - ["resource6"] = "6", - }; - - static Dictionary resourceSet3 = new Dictionary(); - - static ResourceQuantity rq1 = new ResourceQuantity("100Mi"); - static ResourceQuantity rq2 = new ResourceQuantity("200M"); - static ResourceQuantity rq3 = new ResourceQuantity("3"); - static ResourceQuantity rq4 = new ResourceQuantity("300Mi"); - static ResourceQuantity rq5 = new ResourceQuantity("100M"); - static ResourceQuantity rq6 = new ResourceQuantity("6"); - - [Fact] - public void NullSettingsYieldNullResourceRequirements() - { - var nullResource = new ResourceSettings - { - Limits = null, - Requests = null, - }; - - V1ResourceRequirements resources = nullResource.ToResourceRequirements(); - - Assert.NotNull(resources); - Assert.Null(resources.Limits); - Assert.Null(resources.Requests); - } - - [Fact] - public void EmptySettingsYieldEmptyResourceRequirements() - { - var nullResource = new ResourceSettings - { - Limits = resourceSet3, - Requests = resourceSet3, - }; - - V1ResourceRequirements resources = nullResource.ToResourceRequirements(); - - Assert.NotNull(resources); - Assert.NotNull(resources.Limits); - Assert.NotNull(resources.Requests); - Assert.Equal(0, resources.Limits.Count); - Assert.Equal(0, resources.Requests.Count); - } - - [Fact] - public void AssignedSettingsYieldResourceRequirements() - { - var nullResource = new ResourceSettings - { - Limits = resourceSet1, - Requests = resourceSet2, - }; - - V1ResourceRequirements resources = nullResource.ToResourceRequirements(); - - Assert.NotNull(resources); - Assert.NotNull(resources.Limits); - Assert.NotNull(resources.Requests); - Assert.Equal(rq1, resources.Limits["resource1"]); - Assert.Equal(rq2, resources.Limits["resource2"]); - Assert.Equal(rq3, resources.Limits["resource3"]); - Assert.Equal(rq4, resources.Requests["resource4"]); - Assert.Equal(rq5, resources.Requests["resource5"]); - Assert.Equal(rq6, resources.Requests["resource6"]); - } - } -} \ No newline at end of file diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/SetTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/SetTest.cs deleted file mode 100644 index 74f706dc30b..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/SetTest.cs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test -{ - using System.Collections.Generic; - using System.Linq; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Diff; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - - public class SetTest - { - [Theory] - [Unit] - [MemberData(nameof(DiffTestSet.TestDataForDefaultComparison), MemberType = typeof(DiffTestSet))] - public void ReturnsExpectedDiffResult(Set either, Set other, Diff expected) - { - Diff actual = other.Diff(either); - Assert.Equal(expected, actual); - } - - [Theory] - [Unit] - [MemberData(nameof(DiffTestSet.TestDataForByNameComparison), MemberType = typeof(DiffTestSet))] - public void ReturnsExpectedDiffResultWithComparer(Set either, Set other, Diff expected) - { - IEqualityComparer comparer = new ByNameComparer(); - Diff actual = other.Diff(either, comparer); - Assert.Equal(expected, actual); - } - - public class Module - { - public Module(string name) - { - this.Name = name; - } - - public string Name { get; } - } - - sealed class ByNameComparer : IEqualityComparer - { - public bool Equals(Module x, Module y) - { - if (ReferenceEquals(x, y)) - { - return true; - } - - if (ReferenceEquals(x, null)) - { - return false; - } - - if (ReferenceEquals(null, y)) - { - return false; - } - - return x.Name == y.Name; - } - - public int GetHashCode(Module obj) => obj.Name.GetHashCode(); - } - - static readonly Module Module1 = new Module("mod1"); - static readonly Module Module2 = new Module("mod2"); - static readonly Module Module3 = new Module("mod3"); - static readonly Module Module4 = new Module("mod4"); - static readonly Module Module5 = new Module("mod5"); - static readonly Module Module2Duplicate = new Module("mod2"); - - static class DiffTestSet - { - static Set Create(params Module[] modules) - => new Set(modules.ToDictionary(module => module.Name)); - - public static IEnumerable TestDataForDefaultComparison => new List - { - // adding modules - new object[] - { - Set.Empty, - Create(Module1, Module2), - new Diff.Builder() - .WithAdded(Module1, Module2) - .Build() - }, - - // removing modules - new object[] - { - Create(Module1, Module2), - Set.Empty, - new Diff.Builder() - .WithRemoved("mod1", "mod2") - .Build() - }, - - // change a module - new object[] - { - Create(Module2, Module1), - Create(Module4, Module2), - new Diff.Builder() - .WithAdded(Module4) - .WithRemoved("mod1") - .Build() - }, - - // no changes - new object[] - { - Create(Module5, Module3, Module2), - Create(Module2, Module3, Module5), - Diff.Empty - }, - - // changes because of different objects - new object[] - { - Create(Module1, Module2), - Create(Module2Duplicate, Module1), - new Diff.Builder() - .WithUpdated(new Update(Module2, Module2Duplicate)) - .Build() - } - }; - - public static IEnumerable TestDataForByNameComparison => new List - { - // adding modules - new object[] - { - Set.Empty, - Create(Module1, Module2), - new Diff.Builder() - .WithAdded(Module1, Module2) - .Build() - }, - - // removing modules - new object[] - { - Create(Module1, Module2), - Set.Empty, - new Diff.Builder() - .WithRemoved("mod1", "mod2") - .Build() - }, - - // change a module - new object[] - { - Create(Module2, Module1), - Create(Module4, Module2), - new Diff.Builder() - .WithAdded(Module4) - .WithRemoved("mod1") - .Build() - }, - - // no changes - new object[] - { - Create(Module5, Module3, Module2), - Create(Module2, Module3, Module5), - Diff.Empty - }, - - // no change because of identical names - new object[] - { - Create(Module1, Module2), - Create(Module2Duplicate, Module1), - Diff.Empty - } - }; - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/EdgeDeploymentControllerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/EdgeDeploymentControllerTest.cs deleted file mode 100644 index 068de8c3d80..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/EdgeDeploymentControllerTest.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test.EdgeDeployment -{ - using System.Collections.Immutable; - using System.Threading.Tasks; - using k8s; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Moq; - using Xunit; - - [Unit] - public class EdgeDeploymentControllerTest - { - const string DeviceSelector = "selector"; - const string DeviceNamespace = "a-namespace"; - static readonly ResourceName ResourceName = new ResourceName("hostname", "deviceId"); - - [Fact] - public async Task ReturnFailureWhenUnableToObtainModuleIdentities() - { - var client = new Mock(); - var lifecycle = new Mock(); - lifecycle.Setup(l => l.GetModuleIdentitiesAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(ImmutableDictionary.Create()); - var serviceMapper = new Mock(); - var deploymentMapper = new Mock(); - var pvcMapper = new Mock(); - var serviceAccountMapper = new Mock(); - var controller = new EdgeDeploymentController( - ResourceName, - DeviceSelector, - DeviceNamespace, - client.Object, - lifecycle.Object, - serviceMapper.Object, - deploymentMapper.Object, - pvcMapper.Object, - serviceAccountMapper.Object); - var module = new Mock(); - module.SetupGet(m => m.Name).Returns("module1"); - var desiredModules = ModuleSet.Create(module.Object); - var currentModules = ModuleSet.Empty; - - var status = await controller.DeployModulesAsync(desiredModules, currentModules); - - Assert.Equal(EdgeDeploymentStatus.Failure("Unable to obtain identities for desired modules"), status); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/EdgeDeploymentOperatorTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/EdgeDeploymentOperatorTest.cs deleted file mode 100644 index 1eae6791996..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/EdgeDeploymentOperatorTest.cs +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test.EdgeDeployment -{ - using System; - using System.Collections.Generic; - using System.Net; - using System.Net.Http; - using System.Threading; - using k8s; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Core.ConfigSources; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Microsoft.Rest; - using Moq; - using Newtonsoft.Json.Linq; - using Xunit; - using Constants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - public class EdgeDeploymentOperatorTest - { - const string DeviceNamespace = "a-namespace"; - const string ExceptionMessage = "ExceptionMessage"; - static readonly ResourceName ResourceName = new ResourceName("hostname", "deviceId"); - - [Unit] - [Fact] - public async void WhatDeployModulesReturnsIsWhatIsReported() - { - var returnedStatus = EdgeDeploymentStatus.Success("Successfully deployed"); - Option reportedStatus = Option.None(); - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List(), null); - var response = new HttpOperationResponse() - { - Body = edgeDefinition, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - // DeployModules returns a status, confirm this is what is reported. - var controller = Mock.Of(); - Mock.Get(controller).Setup(c => c.DeployModulesAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(returnedStatus); - - var client = Mock.Of(); - Mock.Get(client).Setup(c => c.ReplaceNamespacedCustomObjectStatusWithHttpMessagesAsync(It.IsAny(), Constants.EdgeDeployment.Group, Constants.EdgeDeployment.Version, DeviceNamespace, Constants.EdgeDeployment.Plural, It.IsAny(), It.IsAny(), It.IsAny(), null, It.IsAny())) - .Callback((object o, string group, string version, string _namespace, string plural, string name, string dryRun, string fieldmanager, Dictionary> headers, CancellationToken token) => - { - Assert.True(o is JObject); - EdgeDeploymentDefinition e = ((JObject)o).ToObject(); - reportedStatus = e.Status; - }) - .ReturnsAsync(response); - - var edgeOperator = new EdgeDeploymentOperator( - ResourceName, - DeviceNamespace, - client, - controller); - - await edgeOperator.EdgeDeploymentOnEventHandlerAsync(WatchEventType.Added, edgeDefinition); - - Assert.True(reportedStatus.HasValue); - Assert.Equal(returnedStatus, reportedStatus.OrDefault()); - - Mock.Get(controller).VerifyAll(); - Mock.Get(client).VerifyAll(); - } - - [Unit] - [Fact] - public async void GettingSameStatusTwiceReportsOnce() - { - var returnedStatus = EdgeDeploymentStatus.Success("Successfully deployed"); - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List(), null); - var response = new HttpOperationResponse() - { - Body = edgeDefinition, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - // DeployModules returns a status, confirm this is what is reported. - var controller = Mock.Of(); - Mock.Get(controller).Setup(c => c.DeployModulesAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(returnedStatus); - - var client = Mock.Of(); - Mock.Get(client).Setup(c => c.ReplaceNamespacedCustomObjectStatusWithHttpMessagesAsync(It.IsAny(), Constants.EdgeDeployment.Group, Constants.EdgeDeployment.Version, DeviceNamespace, Constants.EdgeDeployment.Plural, It.IsAny(), It.IsAny(), It.IsAny(), null, It.IsAny())) - .ReturnsAsync(response); - - var edgeOperator = new EdgeDeploymentOperator( - ResourceName, - DeviceNamespace, - client, - controller); - - await edgeOperator.EdgeDeploymentOnEventHandlerAsync(WatchEventType.Added, edgeDefinition); - await edgeOperator.EdgeDeploymentOnEventHandlerAsync(WatchEventType.Modified, edgeDefinition); - - Mock.Get(controller).Verify(c => c.DeployModulesAsync(It.IsAny(), It.IsAny()), Times.Exactly(2)); - Mock.Get(client).Verify(c => c.ReplaceNamespacedCustomObjectStatusWithHttpMessagesAsync(It.IsAny(), Constants.EdgeDeployment.Group, Constants.EdgeDeployment.Version, DeviceNamespace, Constants.EdgeDeployment.Plural, It.IsAny(), It.IsAny(), It.IsAny(), null, It.IsAny()), Times.Once); - } - - [Unit] - [Fact] - public async void NoProcessingDeploymentOnError() - { - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List(), null); - - var client = Mock.Of(); - var controller = Mock.Of(); - - var edgeOperator = new EdgeDeploymentOperator( - ResourceName, - DeviceNamespace, - client, - controller); - - await edgeOperator.EdgeDeploymentOnEventHandlerAsync(WatchEventType.Error, edgeDefinition); - - Mock.Get(controller).VerifyAll(); - Mock.Get(client).VerifyAll(); - } - - [Unit] - [Fact] - public async void NoProcessingDeploymentIfEdgeDeploymentNameMismatch() - { - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: "not-the-resource-name"), new List(), null); - - var client = Mock.Of(); - var controller = Mock.Of(); - - var edgeOperator = new EdgeDeploymentOperator( - ResourceName, - DeviceNamespace, - client, - controller); - - await edgeOperator.EdgeDeploymentOnEventHandlerAsync(WatchEventType.Added, edgeDefinition); - Mock.Get(controller).VerifyAll(); - Mock.Get(client).VerifyAll(); - } - - [Unit] - [Fact] - public async void StatusIsFailedWhenDeploymentControllerThrowsUnexpectedException() - { - Exception controllerException = new Exception(ExceptionMessage); - EdgeDeploymentStatus expectedStatus = EdgeDeploymentStatus.Failure(controllerException); - Option reportedStatus = Option.None(); - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List(), null); - var response = new HttpOperationResponse() - { - Body = edgeDefinition, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - var controller = Mock.Of(); - Mock.Get(controller).Setup(c => c.DeployModulesAsync(It.IsAny(), It.IsAny())) - .ThrowsAsync(controllerException); - - var client = Mock.Of(); - Mock.Get(client).Setup(c => c.ReplaceNamespacedCustomObjectStatusWithHttpMessagesAsync(It.IsAny(), Constants.EdgeDeployment.Group, Constants.EdgeDeployment.Version, DeviceNamespace, Constants.EdgeDeployment.Plural, It.IsAny(), It.IsAny(), It.IsAny(), null, It.IsAny())) - .Callback((object o, string group, string version, string _namespace, string plural, string name, string dryRun, string fieldmanager, Dictionary> headers, CancellationToken token) => - { - Assert.True(o is JObject); - EdgeDeploymentDefinition e = ((JObject)o).ToObject(); - reportedStatus = e.Status; - }) - .ReturnsAsync(response); - - var edgeOperator = new EdgeDeploymentOperator( - ResourceName, - DeviceNamespace, - client, - controller); - - await edgeOperator.EdgeDeploymentOnEventHandlerAsync(WatchEventType.Added, edgeDefinition); - Assert.True(reportedStatus.HasValue); - Assert.Equal(expectedStatus, reportedStatus.OrDefault()); - Mock.Get(controller).VerifyAll(); - Mock.Get(client).VerifyAll(); - } - - [Unit] - [Fact] - public async void StatusIsFailedWhenDeploymentControllerThrowsHttpExceptionIsThrown() - { - HttpOperationException controllerException = new HttpOperationException(ExceptionMessage) - { - Request = new HttpRequestMessageWrapper(new HttpRequestMessage(HttpMethod.Put, new Uri("http://valid-uri")), "content") - }; - Option reportedStatus = Option.None(); - EdgeDeploymentStatus expectedStatus = EdgeDeploymentStatus.Failure($"{controllerException.Request.Method} [{controllerException.Request.RequestUri}]({controllerException.Message})"); - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List(), null); - var response = new HttpOperationResponse() - { - Body = edgeDefinition, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - var controller = Mock.Of(); - Mock.Get(controller).Setup(c => c.DeployModulesAsync(It.IsAny(), It.IsAny())) - .ThrowsAsync(controllerException); - - var client = Mock.Of(); - Mock.Get(client).Setup(c => c.ReplaceNamespacedCustomObjectStatusWithHttpMessagesAsync(It.IsAny(), Constants.EdgeDeployment.Group, Constants.EdgeDeployment.Version, DeviceNamespace, Constants.EdgeDeployment.Plural, It.IsAny(), It.IsAny(), It.IsAny(), null, It.IsAny())) - .Callback((object o, string group, string version, string _namespace, string plural, string name, string dryRun, string fieldmanager, Dictionary> headers, CancellationToken token) => - { - Assert.True(o is JObject); - EdgeDeploymentDefinition e = ((JObject)o).ToObject(); - reportedStatus = e.Status; - }) - .ReturnsAsync(response); - - var edgeOperator = new EdgeDeploymentOperator( - ResourceName, - DeviceNamespace, - client, - controller); - - await edgeOperator.EdgeDeploymentOnEventHandlerAsync(WatchEventType.Added, edgeDefinition); - Assert.True(reportedStatus.HasValue); - Assert.Equal(expectedStatus, reportedStatus.OrDefault()); - Mock.Get(controller).VerifyAll(); - Mock.Get(client).VerifyAll(); - } - - [Integration] - [Fact] - public async void CommandFailsWhenStatusIsFailedonUnexpectedExceptionThrown() - { - Exception controllerException = new Exception(ExceptionMessage); - Option reportedStatus = Option.None(); - - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List(), null); - var response = new HttpOperationResponse() - { - Body = edgeDefinition, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - var controller = Mock.Of(); - Mock.Get(controller).Setup(c => c.DeployModulesAsync(It.IsAny(), It.IsAny())) - .ThrowsAsync(controllerException); - - var client = Mock.Of(); - Mock.Get(client).Setup(c => c.ReplaceNamespacedCustomObjectStatusWithHttpMessagesAsync(It.IsAny(), Constants.EdgeDeployment.Group, Constants.EdgeDeployment.Version, DeviceNamespace, Constants.EdgeDeployment.Plural, It.IsAny(), It.IsAny(), It.IsAny(), null, It.IsAny())) - .Callback((object o, string group, string version, string _namespace, string plural, string name, string dryRun, string fieldmanager, Dictionary> headers, CancellationToken token) => - { - Assert.True(o is JObject); - EdgeDeploymentDefinition e = ((JObject)o).ToObject(); - reportedStatus = e.Status; - }) - .ReturnsAsync(response); - - var edgeOperator = new EdgeDeploymentOperator( - ResourceName, - DeviceNamespace, - client, - controller); - - await edgeOperator.EdgeDeploymentOnEventHandlerAsync(WatchEventType.Added, edgeDefinition); - EdgeDeploymentStatusCommand commandResult = new EdgeDeploymentStatusCommand(reportedStatus); - ConfigOperationFailureException ex = await Assert.ThrowsAsync( - () => commandResult.ExecuteAsync(CancellationToken.None)); - Assert.Equal(ExceptionMessage, ex.Message); - } - - [Integration] - [Fact] - public async void CommandFailsWhenStatusIsFailedWithSpecialMessageWhenHttpExceptionThrown() - { - HttpOperationException controllerException = new HttpOperationException(ExceptionMessage) - { - Request = new HttpRequestMessageWrapper(new HttpRequestMessage(HttpMethod.Put, new Uri("http://valid-uri")), "content") - }; - Option reportedStatus = Option.None(); - string expectedMessage = $"{controllerException.Request.Method} [{controllerException.Request.RequestUri}]({controllerException.Message})"; - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List(), null); - var response = new HttpOperationResponse() - { - Body = edgeDefinition, - Response = new HttpResponseMessage(HttpStatusCode.OK) - }; - - var controller = Mock.Of(); - Mock.Get(controller).Setup(c => c.DeployModulesAsync(It.IsAny(), It.IsAny())) - .ThrowsAsync(controllerException); - - var client = Mock.Of(); - Mock.Get(client).Setup(c => c.ReplaceNamespacedCustomObjectStatusWithHttpMessagesAsync(It.IsAny(), Constants.EdgeDeployment.Group, Constants.EdgeDeployment.Version, DeviceNamespace, Constants.EdgeDeployment.Plural, It.IsAny(), It.IsAny(), It.IsAny(), null, It.IsAny())) - .Callback((object o, string group, string version, string _namespace, string plural, string name, string dryRun, string fieldmanager, Dictionary> headers, CancellationToken token) => - { - Assert.True(o is JObject); - EdgeDeploymentDefinition e = ((JObject)o).ToObject(); - reportedStatus = e.Status; - }) - .ReturnsAsync(response); - - var edgeOperator = new EdgeDeploymentOperator( - ResourceName, - DeviceNamespace, - client, - controller); - - await edgeOperator.EdgeDeploymentOnEventHandlerAsync(WatchEventType.Added, edgeDefinition); - EdgeDeploymentStatusCommand commandResult = new EdgeDeploymentStatusCommand(reportedStatus); - ConfigOperationFailureException ex = await Assert.ThrowsAsync( - () => commandResult.ExecuteAsync(CancellationToken.None)); - Assert.Equal(expectedMessage, ex.Message); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/EdgeDeploymentStatusTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/EdgeDeploymentStatusTest.cs deleted file mode 100644 index d307aec0cc2..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/EdgeDeploymentStatusTest.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test.EdgeDeployment -{ - using System; - using System.Collections.Generic; - using System.Net.Http; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Microsoft.Rest; - using Xunit; - - [Unit] - public class EdgeDeploymentStatusTest - { - static readonly EdgeDeploymentStatus Status1 = EdgeDeploymentStatus.Failure("message"); - static readonly EdgeDeploymentStatus Status2 = EdgeDeploymentStatus.Success("message"); - static readonly EdgeDeploymentStatus Status3 = EdgeDeploymentStatus.Failure("message1"); - static readonly EdgeDeploymentStatus Status4 = EdgeDeploymentStatus.Failure("message2"); - static readonly EdgeDeploymentStatus Status5 = EdgeDeploymentStatus.Success(null); - static readonly EdgeDeploymentStatus Status6 = EdgeDeploymentStatus.Success(null); - static readonly EdgeDeploymentStatus Status7 = EdgeDeploymentStatus.Failure("message"); - static readonly EdgeDeploymentStatus Status8 = EdgeDeploymentStatus.Failure("message"); - - [Fact] - public void EdgeDeploymentStatusSuccessFactoryIsSuccessStatus() - { - var status = EdgeDeploymentStatus.Success("deployed"); - Assert.Equal(EdgeDeploymentStatusType.Success, status.State); - Assert.Equal("deployed", status.Message); - } - - [Fact] - public void EdgeDeploymentStatusExceptionFailureFactoryIsFailureStatus() - { - Exception e = new Exception("deployment failed"); - var status = EdgeDeploymentStatus.Failure(e); - Assert.Equal(EdgeDeploymentStatusType.Failure, status.State); - Assert.Equal("deployment failed", status.Message); - - var uri = new Uri("https://my-uri"); - HttpOperationException httpEx = new HttpOperationException("HTTP Exception") - { - Request = new HttpRequestMessageWrapper(new HttpRequestMessage(HttpMethod.Post, uri), string.Empty) - }; - var httpStatus = EdgeDeploymentStatus.Failure(httpEx); - Assert.Equal(EdgeDeploymentStatusType.Failure, httpStatus.State); - Assert.Equal($"POST [{uri}](HTTP Exception)", httpStatus.Message); - } - - public static IEnumerable GetDifferentStatus() => new List - { - new object[] { Status1, Status2 }, - new object[] { Status3, Status4 }, - }; - - [Theory] - [MemberData(nameof(GetDifferentStatus))] - public void EdgeDeploymentStatusAreDifferentWithDifferentParametersTest(EdgeDeploymentStatus x, EdgeDeploymentStatus y) - { - Assert.NotEqual(x, y); - } - - public static IEnumerable GetSameStatus() => new List - { - new object[] { Status5, Status6 }, - new object[] { Status7, Status8 }, - }; - - [Theory] - [MemberData(nameof(GetSameStatus))] - public void EdgeDeploymentStatusAreSameWithSameParametersTest(EdgeDeploymentStatus x, EdgeDeploymentStatus y) - { - Assert.Equal(x, y); - } - - [Fact] - public void EdgeDeploymentStatusReferenceChecks() - { - Assert.Equal(Status1, Status1); - Assert.False(Status2.Equals(null)); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/KubernetesImagePullSecretBySecretDataEqualityComparerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/KubernetesImagePullSecretBySecretDataEqualityComparerTest.cs deleted file mode 100644 index 65cbfcde53a..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/KubernetesImagePullSecretBySecretDataEqualityComparerTest.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test.EdgeDeployment -{ - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - [Unit] - public class KubernetesImagePullSecretBySecretDataEqualityComparerTest - { - static readonly KubernetesImagePullSecretBySecretDataEqualityComparer Comparer = new KubernetesImagePullSecretBySecretDataEqualityComparer(); - - [Fact] - public void ReferenceComparisonTest() - { - var secret1 = new V1Secret(); - var secret2 = secret1; - - Assert.True(Comparer.Equals(secret1, secret2)); - - Assert.False(Comparer.Equals(null, secret2)); - Assert.False(Comparer.Equals(secret1, null)); - } - - [Fact] - public void ReturnsFalseIfFieldsAreDifferent() - { - var x = new V1Secret - { - Metadata = new V1ObjectMeta - { - Annotations = new Dictionary - { - [KubernetesConstants.K8sEdgeOriginalModuleId] = "Object1" - }, - Labels = new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = "device1", - }, - Name = "object1" - }, - Data = new Dictionary - { - [KubernetesConstants.K8sPullSecretData] = new byte[] { 1 } - } - }; - var y = new V1Secret - { - Metadata = new V1ObjectMeta - { - Annotations = new Dictionary - { - [KubernetesConstants.K8sEdgeOriginalModuleId] = "Object1" - }, - Labels = new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = "device1", - }, - Name = "object1" - }, - Data = new Dictionary - { - [KubernetesConstants.K8sPullSecretData] = new byte[] { 1 } - } - }; - Assert.True(Comparer.Equals(x, y)); - - y.Metadata.Name = "object2"; - Assert.False(Comparer.Equals(x, y)); - - y.Metadata.Name = "object1"; - Assert.True(Comparer.Equals(x, y)); - - y.Metadata.Labels = new Dictionary - { - ["newkey2"] = "Object1" - }; - Assert.False(Comparer.Equals(x, y)); - } - - [Fact] - public void IgnoreOtherMetadataTest() - { - var saWithOwnerRef = new V1Secret - { - Metadata = new V1ObjectMeta - { - Annotations = new Dictionary - { - [KubernetesConstants.K8sEdgeOriginalModuleId] = "Object1" - }, - Labels = new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = "device1", - }, - Name = "object1", - OwnerReferences = new List - { - new V1OwnerReference("v1", name: "iotedged", kind: "Deployment", uid: "123") - } - } - }; - var saWithoutOwnerRef = new V1Secret - { - Metadata = new V1ObjectMeta - { - Annotations = new Dictionary - { - [KubernetesConstants.K8sEdgeOriginalModuleId] = "Object1" - }, - Labels = new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = "device1", - }, - Name = "object1" - } - }; - Assert.True(Comparer.Equals(saWithOwnerRef, saWithoutOwnerRef)); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/pvc/KubernetesPvcByValueComparerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/pvc/KubernetesPvcByValueComparerTest.cs deleted file mode 100644 index 048c447bfd8..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/pvc/KubernetesPvcByValueComparerTest.cs +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test.EdgeDeployment.Pvc -{ - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Pvc; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - [Unit] - public class KubernetesPvcByValueComparerTest - { - static readonly KubernetesPvcByValueEqualityComparer Comparer = new KubernetesPvcByValueEqualityComparer(); - - [Fact] - public void ReferenceComparisonTest() - { - var pvc1 = new V1PersistentVolumeClaim(); - var pvc2 = pvc1; - Assert.True(Comparer.Equals(pvc1, pvc2)); - Assert.False(Comparer.Equals(null, pvc2)); - Assert.False(Comparer.Equals(pvc1, null)); - } - - [Fact] - public void NoAccessModeTest() - { - var x = new V1PersistentVolumeClaim - { - Metadata = new V1ObjectMeta( - name: "pvc1", - labels: new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = KubeUtils.SanitizeLabelValue("device1"), - }), - Spec = new V1PersistentVolumeClaimSpec - { - VolumeName = "steve", - Resources = new V1ResourceRequirements { Requests = new Dictionary { ["storage"] = new ResourceQuantity("10M") } } - } - }; - var y = new V1PersistentVolumeClaim - { - Metadata = new V1ObjectMeta( - name: "pvc1", - labels: new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = KubeUtils.SanitizeLabelValue("device1"), - }), - Spec = new V1PersistentVolumeClaimSpec - { - VolumeName = "steve", - Resources = new V1ResourceRequirements { Requests = new Dictionary { ["storage"] = new ResourceQuantity("10M") } } - } - }; - Assert.False(Comparer.Equals(x, y)); - } - - [Fact] - public void FieldComparisonTests() - { - var x = new V1PersistentVolumeClaim - { - Metadata = new V1ObjectMeta( - name: "pvc1", - labels: new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = "device1", - }), - Spec = new V1PersistentVolumeClaimSpec - { - VolumeName = "steve", - StorageClassName = "angela", - AccessModes = new List { "ReadOnce" }, - Resources = new V1ResourceRequirements { Requests = new Dictionary { ["storage"] = new ResourceQuantity("10M") } } - } - }; - var y = new V1PersistentVolumeClaim - { - Metadata = new V1ObjectMeta( - name: "pvc1", - labels: new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = "device1", - }), - Spec = new V1PersistentVolumeClaimSpec - { - VolumeName = "steve", - StorageClassName = "angela", - AccessModes = new List { "ReadOnce" }, - Resources = new V1ResourceRequirements { Requests = new Dictionary { ["storage"] = new ResourceQuantity("10M") } } - } - }; - x.Metadata.Name = "pvc2"; - Assert.False(Comparer.Equals(x, y)); - x.Metadata.Name = "pvc1"; - Assert.True(Comparer.Equals(x, y)); - - x.Metadata.Labels[KubernetesConstants.K8sEdgeDeviceLabel] = "device2"; - Assert.False(Comparer.Equals(x, y)); - x.Metadata.Labels[KubernetesConstants.K8sEdgeDeviceLabel] = "device1"; - Assert.True(Comparer.Equals(x, y)); - - // Match Either VolumeName or StorageClassName - x.Spec.VolumeName = "artemus"; - Assert.True(Comparer.Equals(x, y)); - x.Spec.VolumeName = "steve"; - x.Spec.StorageClassName = "dakota"; - Assert.True(Comparer.Equals(x, y)); - - // Both VolumeName and StorageClassName differ - x.Spec.VolumeName = "artemus"; - x.Spec.StorageClassName = "dakota"; - Assert.False(Comparer.Equals(x, y)); - x.Spec.VolumeName = "steve"; - x.Spec.StorageClassName = "angela"; - Assert.True(Comparer.Equals(x, y)); - - x.Spec.Resources.Requests = null; - Assert.False(Comparer.Equals(x, y)); - x.Spec.Resources.Requests = new Dictionary - { - ["storage"] = new ResourceQuantity("10M") - }; - Assert.True(Comparer.Equals(x, y)); - - x.Spec.AccessModes = new List { "ReadOnlyMany" }; - Assert.False(Comparer.Equals(x, y)); - x.Spec.AccessModes = new List { "ReadOnce" }; - Assert.True(Comparer.Equals(x, y)); - } - - [Fact] - public void SuccessComparisonTest() - { - var (x, y) = MakeIdenticalPVCs(); - Assert.True(Comparer.Equals(x, y)); - } - - [Fact] - public void DistictionTest() - { - var (x, y) = MakeIdenticalPVCs(); - var claims = new List { x, y }; - var distinct = claims.Distinct(Comparer); - - Assert.Equal(1, distinct.LongCount()); - } - - [Fact] - public void IgnoreOtherMetadataTest() - { - var pvcWithOwnerRefMetadata = new V1PersistentVolumeClaim - { - Metadata = new V1ObjectMeta( - name: "pvc1", - labels: new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = KubeUtils.SanitizeLabelValue("device1"), - }, - ownerReferences: new List - { - new V1OwnerReference("v1", name: "iotedged", kind: "Deployment", uid: "123") - }), - Spec = new V1PersistentVolumeClaimSpec - { - VolumeName = "steve", - Resources = new V1ResourceRequirements { Requests = new Dictionary { ["storage"] = new ResourceQuantity("10M") } } - } - }; - var pvcWithoutOwnerRefMetadata = new V1PersistentVolumeClaim - { - Metadata = new V1ObjectMeta( - name: "pvc1", - labels: new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = KubeUtils.SanitizeLabelValue("device1"), - }), - Spec = new V1PersistentVolumeClaimSpec - { - VolumeName = "steve", - Resources = new V1ResourceRequirements { Requests = new Dictionary { ["storage"] = new ResourceQuantity("10M") } } - } - }; - Assert.False(Comparer.Equals(pvcWithOwnerRefMetadata, pvcWithoutOwnerRefMetadata)); - } - - static (V1PersistentVolumeClaim, V1PersistentVolumeClaim) MakeIdenticalPVCs() - { - var x = new V1PersistentVolumeClaim - { - Metadata = new V1ObjectMeta( - name: "pvc1", - labels: new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = "device1", - }), - Spec = new V1PersistentVolumeClaimSpec - { - VolumeName = "steve", - AccessModes = new List { "ReadOnce" }, - Resources = new V1ResourceRequirements { Requests = new Dictionary { ["storage"] = new ResourceQuantity("10M") } } - } - }; - var y = new V1PersistentVolumeClaim - { - Metadata = new V1ObjectMeta( - name: "pvc1", - labels: new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = "device1", - }), - Spec = new V1PersistentVolumeClaimSpec - { - VolumeName = "steve", - AccessModes = new List { "ReadOnce" }, - Resources = new V1ResourceRequirements { Requests = new Dictionary { ["storage"] = new ResourceQuantity("10M") } } - } - }; - return (x, y); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/pvc/KubernetesPvcMapperTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/pvc/KubernetesPvcMapperTest.cs deleted file mode 100644 index e20cd8aa72f..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/pvc/KubernetesPvcMapperTest.cs +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test.EdgeDeployment.Pvc -{ - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Agent.Docker.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Pvc; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - [Unit] - public class KubernetesPvcMapperTest - { - static readonly ConfigurationInfo DefaultConfigurationInfo = new ConfigurationInfo("1"); - - static readonly IDictionary EnvVarsDict = new Dictionary(); - - static readonly DockerConfig Config1 = new DockerConfig("test-image:1"); - - static readonly HostConfig VolumeMountHostConfig = new HostConfig - { - Mounts = new List - { - new Mount - { - Type = "volume", - ReadOnly = true, - Source = "a-volume", - Target = "/tmp/volumea" - }, - new Mount - { - Type = "volume", - ReadOnly = false, - Source = "b-volume", - Target = "/tmp/volumeb" - } - } - }; - - static readonly HostConfig VolumeMountHostConfig1 = new HostConfig - { - Mounts = new List - { - new Mount - { - Type = "volume", - ReadOnly = true, - Source = "a-volume", - Target = "/tmp/volumea" - } - } - }; - - static readonly HostConfig VolumeNullMount = new HostConfig - { - Mounts = null - }; - - static readonly Dictionary DefaultLabels = new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = KubeUtils.SanitizeLabelValue("device1"), - }; - - static readonly ResourceName ResourceName = new ResourceName("hostname", "deviceId"); - - static readonly KubernetesModuleOwner EdgeletModuleOwner = new KubernetesModuleOwner("v1", "Deployment", "iotedged", "123"); - - [Fact] - public void NullMountsNoClaims() - { - var config = new KubernetesConfig("image", CreatePodParameters.Create(hostConfig: VolumeNullMount), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var mapper = new KubernetesPvcMapper(false, "storage", 1); - - var pvcs = mapper.CreatePersistentVolumeClaims(module, DefaultLabels); - - Assert.False(pvcs.HasValue); - } - - [Fact] - public void NoMountsNoClaims() - { - var config = new KubernetesConfig("image", CreatePodParameters.Create(), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var mapper = new KubernetesPvcMapper(false, "storage", 1); - - var pvcs = mapper.CreatePersistentVolumeClaims(module, DefaultLabels); - - Assert.False(pvcs.HasValue); - } - - [Fact] - public void EmptyDirMappingForVolume() - { - var config = new KubernetesConfig("image", CreatePodParameters.Create(hostConfig: VolumeMountHostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var mapper = new KubernetesPvcMapper(false, null, 0); - - var pvcs = mapper.CreatePersistentVolumeClaims(module, DefaultLabels); - - Assert.False(pvcs.HasValue); - } - - [Fact] - public void EmptyDirMappingForVolume2() - { - var config = new KubernetesConfig("image", CreatePodParameters.Create(hostConfig: VolumeMountHostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var mapper = new KubernetesPvcMapper(false, null, 0); - - var pvcs = mapper.CreatePersistentVolumeClaims(module, DefaultLabels); - - Assert.False(pvcs.HasValue); - } - - [Fact] - public void DefaultStorageClassMappingForVolume() - { - var config = new KubernetesConfig("image", CreatePodParameters.Create(hostConfig: VolumeMountHostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var mapper = new KubernetesPvcMapper(false, string.Empty, 10); - var resourceQuantity = new ResourceQuantity("10Mi"); - - var pvcs = mapper.CreatePersistentVolumeClaims(module, DefaultLabels); - - Assert.True(pvcs.HasValue); - var pvcList = pvcs.OrDefault(); - Assert.True(pvcList.Any()); - Assert.Equal(2, pvcList.Count); - - var aVolumeClaim = pvcList.Single(pvc => pvc.Metadata.Name == "a-volume"); - Assert.True(aVolumeClaim.Metadata.Labels.SequenceEqual(DefaultLabels)); - Assert.Equal("ReadOnlyMany", aVolumeClaim.Spec.AccessModes[0]); - Assert.Null(aVolumeClaim.Spec.VolumeName); - Assert.Equal(string.Empty, aVolumeClaim.Spec.StorageClassName); - Assert.Equal(resourceQuantity, aVolumeClaim.Spec.Resources.Requests["storage"]); - Assert.Equal(1, aVolumeClaim.Metadata.OwnerReferences.Count); - Assert.Equal(V1Deployment.KubeKind, aVolumeClaim.Metadata.OwnerReferences[0].Kind); - Assert.Equal(EdgeletModuleOwner.Name, aVolumeClaim.Metadata.OwnerReferences[0].Name); - - var bVolumeClaim = pvcList.Single(pvc => pvc.Metadata.Name == "b-volume"); - Assert.True(bVolumeClaim.Metadata.Labels.SequenceEqual(DefaultLabels)); - Assert.Equal("ReadWriteMany", bVolumeClaim.Spec.AccessModes[0]); - Assert.Null(bVolumeClaim.Spec.VolumeName); - Assert.Equal(string.Empty, bVolumeClaim.Spec.StorageClassName); - Assert.Equal(resourceQuantity, bVolumeClaim.Spec.Resources.Requests["storage"]); - Assert.Equal(1, bVolumeClaim.Metadata.OwnerReferences.Count); - Assert.Equal(V1Deployment.KubeKind, bVolumeClaim.Metadata.OwnerReferences[0].Kind); - Assert.Equal(EdgeletModuleOwner.Name, bVolumeClaim.Metadata.OwnerReferences[0].Name); - } - - [Fact] - public void StorageClassMappingForVolume() - { - var config = new KubernetesConfig("image", CreatePodParameters.Create(hostConfig: VolumeMountHostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var mapper = new KubernetesPvcMapper(false, "default", 10); - var resourceQuantity = new ResourceQuantity("10Mi"); - - var pvcs = mapper.CreatePersistentVolumeClaims(module, DefaultLabels); - - Assert.True(pvcs.HasValue); - var pvcList = pvcs.OrDefault(); - Assert.True(pvcList.Any()); - Assert.Equal(2, pvcList.Count); - - var aVolumeClaim = pvcList.Single(pvc => pvc.Metadata.Name == "a-volume"); - Assert.True(aVolumeClaim.Metadata.Labels.SequenceEqual(DefaultLabels)); - Assert.Equal("ReadOnlyMany", aVolumeClaim.Spec.AccessModes[0]); - Assert.Null(aVolumeClaim.Spec.VolumeName); - Assert.Equal("default", aVolumeClaim.Spec.StorageClassName); - Assert.Equal(resourceQuantity, aVolumeClaim.Spec.Resources.Requests["storage"]); - Assert.Equal(1, aVolumeClaim.Metadata.OwnerReferences.Count); - Assert.Equal(V1Deployment.KubeKind, aVolumeClaim.Metadata.OwnerReferences[0].Kind); - Assert.Equal(EdgeletModuleOwner.Name, aVolumeClaim.Metadata.OwnerReferences[0].Name); - - var bVolumeClaim = pvcList.Single(pvc => pvc.Metadata.Name == "b-volume"); - Assert.True(bVolumeClaim.Metadata.Labels.SequenceEqual(DefaultLabels)); - Assert.Equal("ReadWriteMany", bVolumeClaim.Spec.AccessModes[0]); - Assert.Null(bVolumeClaim.Spec.VolumeName); - Assert.Equal("default", bVolumeClaim.Spec.StorageClassName); - Assert.Equal(resourceQuantity, bVolumeClaim.Spec.Resources.Requests["storage"]); - Assert.Equal(1, bVolumeClaim.Metadata.OwnerReferences.Count); - Assert.Equal(V1Deployment.KubeKind, bVolumeClaim.Metadata.OwnerReferences[0].Kind); - Assert.Equal(EdgeletModuleOwner.Name, bVolumeClaim.Metadata.OwnerReferences[0].Name); - } - - [Fact] - public void VolumeNameMappingForVolume() - { - var config = new KubernetesConfig("image", CreatePodParameters.Create(hostConfig: VolumeMountHostConfig), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var mapper = new KubernetesPvcMapper(true, "storage-class", 37); - var resourceQuantity = new ResourceQuantity("37Mi"); - - var pvcs = mapper.CreatePersistentVolumeClaims(module, DefaultLabels); - - Assert.True(pvcs.HasValue); - var pvcList = pvcs.OrDefault(); - Assert.True(pvcList.Any()); - Assert.Equal(2, pvcList.Count); - - var aVolumeClaim = pvcList.Single(pvc => pvc.Metadata.Name == "a-volume"); - Assert.True(aVolumeClaim.Metadata.Labels.SequenceEqual(DefaultLabels)); - Assert.Equal("ReadOnlyMany", aVolumeClaim.Spec.AccessModes[0]); - Assert.Equal("storage-class", aVolumeClaim.Spec.StorageClassName); - Assert.Equal("a-volume", aVolumeClaim.Spec.VolumeName); - Assert.Equal(resourceQuantity, aVolumeClaim.Spec.Resources.Requests["storage"]); - Assert.Equal(1, aVolumeClaim.Metadata.OwnerReferences.Count); - Assert.Equal(V1Deployment.KubeKind, aVolumeClaim.Metadata.OwnerReferences[0].Kind); - Assert.Equal(EdgeletModuleOwner.Name, aVolumeClaim.Metadata.OwnerReferences[0].Name); - - var bVolumeClaim = pvcList.Single(pvc => pvc.Metadata.Name == "b-volume"); - Assert.True(bVolumeClaim.Metadata.Labels.SequenceEqual(DefaultLabels)); - Assert.Equal("ReadWriteMany", bVolumeClaim.Spec.AccessModes[0]); - Assert.Equal("storage-class", bVolumeClaim.Spec.StorageClassName); - Assert.Equal("b-volume", bVolumeClaim.Spec.VolumeName); - Assert.Equal(resourceQuantity, bVolumeClaim.Spec.Resources.Requests["storage"]); - Assert.Equal(1, bVolumeClaim.Metadata.OwnerReferences.Count); - Assert.Equal(V1Deployment.KubeKind, bVolumeClaim.Metadata.OwnerReferences[0].Kind); - Assert.Equal(EdgeletModuleOwner.Name, bVolumeClaim.Metadata.OwnerReferences[0].Name); - } - - [Fact] - public void PreferVolumeNameMappingForVolume() - { - var config = new KubernetesConfig("image", CreatePodParameters.Create(hostConfig: VolumeMountHostConfig1), Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var module = new KubernetesModule(docker, config, EdgeletModuleOwner); - var mapper = new KubernetesPvcMapper(true, "storageclass", 1); - var resourceQuantity = new ResourceQuantity("1Mi"); - - var pvcs = mapper.CreatePersistentVolumeClaims(module, DefaultLabels); - - Assert.True(pvcs.HasValue); - var pvcList = pvcs.OrDefault(); - Assert.True(pvcList.Any()); - Assert.Single(pvcList); - - var aVolumeClaim = pvcList.Single(pvc => pvc.Metadata.Name == "a-volume"); - Assert.True(aVolumeClaim.Metadata.Labels.SequenceEqual(DefaultLabels)); - Assert.Equal("ReadOnlyMany", aVolumeClaim.Spec.AccessModes[0]); - Assert.Equal("storageclass", aVolumeClaim.Spec.StorageClassName); - Assert.Equal("a-volume", aVolumeClaim.Spec.VolumeName); - Assert.Equal(resourceQuantity, aVolumeClaim.Spec.Resources.Requests["storage"]); - Assert.Equal(1, aVolumeClaim.Metadata.OwnerReferences.Count); - Assert.Equal(V1Deployment.KubeKind, aVolumeClaim.Metadata.OwnerReferences[0].Kind); - Assert.Equal(EdgeletModuleOwner.Name, aVolumeClaim.Metadata.OwnerReferences[0].Name); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/service/KubernetesServiceMapperTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/service/KubernetesServiceMapperTest.cs deleted file mode 100644 index 8eeecd5c1bc..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/service/KubernetesServiceMapperTest.cs +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test.EdgeDeployment.Service -{ - using System.Collections.Generic; - using System.Linq; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Agent.Docker.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.Service; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - using DockerEmptyStruct = global::Docker.DotNet.Models.EmptyStruct; - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - [Unit] - public class KubernetesServiceMapperTest - { - static readonly ConfigurationInfo DefaultConfigurationInfo = new ConfigurationInfo("1"); - - static readonly IDictionary EnvVarsDict = new Dictionary(); - - static readonly DockerConfig Config1 = new DockerConfig("test-image:1"); - - static readonly Dictionary ExposedPorts = new Dictionary - { - ["80/tcp"] = default(DockerEmptyStruct), - ["5000/udp"] = default(DockerEmptyStruct) - }; - - static readonly HostConfig HostPorts = new HostConfig - { - PortBindings = new Dictionary> - { - ["80/tcp"] = new List - { - new PortBinding { HostPort = "8080" } - }, - ["5000/udp"] = new List - { - new PortBinding { HostPort = "5050" } - } - } - }; - - static readonly Dictionary DefaultLabels = new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = KubeUtils.SanitizeLabelValue("device1"), - }; - - static readonly ModuleIdentity CreateIdentity = new ModuleIdentity("hostname", "gateway", "device1", "Module1", new ConnectionStringCredentials("connection string")); - - static KubernetesModule CreateKubernetesModule(CreatePodParameters podParameters) - { - var config = new KubernetesConfig("image", podParameters, Option.None()); - var docker = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVarsDict); - var owner = new KubernetesModuleOwner("v1", "Owner", "an-owner", "a-uid"); - return new KubernetesModule(docker, config, owner); - } - - [Fact] - public void NoPortInOptionsCreatesNoService() - { - var module = CreateKubernetesModule(CreatePodParameters.Create()); - var mapper = new KubernetesServiceMapper(PortMapServiceType.LoadBalancer); - Option result = mapper.CreateService(CreateIdentity, module, DefaultLabels); - Assert.False(result.HasValue); - } - - [Fact] - public void CreateServiceExposedPortsOnlyCreatesClusterIP() - { - var module = CreateKubernetesModule(CreatePodParameters.Create(exposedPorts: ExposedPorts)); - var mapper = new KubernetesServiceMapper(PortMapServiceType.LoadBalancer); - Option result = mapper.CreateService(CreateIdentity, module, DefaultLabels); - Assert.True(result.HasValue); - var service = result.OrDefault(); - Assert.Equal(PortMapServiceType.ClusterIP.ToString(), service.Spec.Type); - } - - [Fact] - public void CreateServiceSetsServiceOptions() - { - var serviceOptions = new KubernetesServiceOptions("loadBalancerIP", "nodeport"); - var module = CreateKubernetesModule(CreatePodParameters.Create(exposedPorts: ExposedPorts, serviceOptions: serviceOptions)); - var mapper = new KubernetesServiceMapper(PortMapServiceType.ClusterIP); - - Option result = mapper.CreateService(CreateIdentity, module, DefaultLabels); - - Assert.True(result.HasValue); - var service = result.OrDefault(); - Assert.Equal(PortMapServiceType.NodePort.ToString(), service.Spec.Type); - Assert.Equal("loadBalancerIP", service.Spec.LoadBalancerIP); - } - - [Fact] - public void CreateServiceSetsServiceOptionsOverridesSetDefault() - { - var serviceOptions = new KubernetesServiceOptions("loadBalancerIP", "clusterIP"); - var module = CreateKubernetesModule(CreatePodParameters.Create(hostConfig: HostPorts, serviceOptions: serviceOptions)); - var mapper = new KubernetesServiceMapper(PortMapServiceType.LoadBalancer); - - Option result = mapper.CreateService(CreateIdentity, module, DefaultLabels); - - Assert.True(result.HasValue); - var service = result.OrDefault(); - Assert.Equal(PortMapServiceType.ClusterIP.ToString(), service.Spec.Type); - Assert.Equal("loadBalancerIP", service.Spec.LoadBalancerIP); - } - - [Fact] - public void CreateServiceSetsServiceOptionsNoIPOnNullLoadBalancerIP() - { - var serviceOptions = new KubernetesServiceOptions(null, "loadBalancer"); - var module = CreateKubernetesModule(CreatePodParameters.Create(exposedPorts: ExposedPorts, serviceOptions: serviceOptions)); - var mapper = new KubernetesServiceMapper(PortMapServiceType.ClusterIP); - - Option result = mapper.CreateService(CreateIdentity, module, DefaultLabels); - - Assert.True(result.HasValue); - var service = result.OrDefault(); - Assert.Equal(PortMapServiceType.LoadBalancer.ToString(), service.Spec.Type); - Assert.Null(service.Spec.LoadBalancerIP); - } - - [Fact] - public void CreateServiceSetsServiceOptionsSetDefaultOnNullType() - { - var serviceOptions = new KubernetesServiceOptions("loadBalancerIP", null); - var module = CreateKubernetesModule(CreatePodParameters.Create(hostConfig: HostPorts, serviceOptions: serviceOptions)); - var mapper = new KubernetesServiceMapper(PortMapServiceType.LoadBalancer); - - Option result = mapper.CreateService(CreateIdentity, module, DefaultLabels); - - Assert.True(result.HasValue); - var service = result.OrDefault(); - Assert.Equal(PortMapServiceType.LoadBalancer.ToString(), service.Spec.Type); - Assert.Equal("loadBalancerIP", service.Spec.LoadBalancerIP); - } - - [Fact] - public void CreateServiceExposedPortsOnlyCreatesExposedPortService() - { - var module = CreateKubernetesModule(CreatePodParameters.Create(exposedPorts: ExposedPorts)); - var mapper = new KubernetesServiceMapper(PortMapServiceType.LoadBalancer); - Option result = mapper.CreateService(CreateIdentity, module, DefaultLabels); - Assert.True(result.HasValue); - var service = result.OrDefault(); - V1ServicePort port80 = service.Spec.Ports.Single(p => p.Port == 80); - Assert.Equal("exposedport-80-tcp", port80.Name); - Assert.Equal("TCP", port80.Protocol); - V1ServicePort port5000 = service.Spec.Ports.Single(p => p.Port == 5000); - Assert.Equal("exposedport-5000-udp", port5000.Name); - Assert.Equal("UDP", port5000.Protocol); - } - - [Fact] - public void CreateServiceHostPortsCreatesDefaultServiceType() - { - var module = CreateKubernetesModule(CreatePodParameters.Create(hostConfig: HostPorts)); - var mapper = new KubernetesServiceMapper(PortMapServiceType.LoadBalancer); - Option result = mapper.CreateService(CreateIdentity, module, DefaultLabels); - Assert.True(result.HasValue); - var service = result.OrDefault(); - Assert.Equal(PortMapServiceType.LoadBalancer.ToString(), service.Spec.Type); - } - - [Fact] - public void CreateServiceHostPortsCreatesHostportService() - { - var module = CreateKubernetesModule(CreatePodParameters.Create(hostConfig: HostPorts)); - var mapper = new KubernetesServiceMapper(PortMapServiceType.LoadBalancer); - Option result = mapper.CreateService(CreateIdentity, module, DefaultLabels); - Assert.True(result.HasValue); - var service = result.OrDefault(); - V1ServicePort port80 = service.Spec.Ports.Single(p => p.Port == 8080); - Assert.Equal("hostport-80-tcp", port80.Name); - Assert.Equal("TCP", port80.Protocol); - Assert.Equal("80", port80.TargetPort.Value); - V1ServicePort port5000 = service.Spec.Ports.Single(p => p.Port == 5050); - Assert.Equal("hostport-5000-udp", port5000.Name); - Assert.Equal("UDP", port5000.Protocol); - Assert.Equal("5000", port5000.TargetPort.Value); - } - - [Fact] - public void CreateServiceExposedAndHostPortsCreatesHostportService() - { - var module = CreateKubernetesModule(CreatePodParameters.Create(exposedPorts: ExposedPorts, hostConfig: HostPorts)); - var mapper = new KubernetesServiceMapper(PortMapServiceType.LoadBalancer); - Option result = mapper.CreateService(CreateIdentity, module, DefaultLabels); - Assert.True(result.HasValue); - var service = result.OrDefault(); - Assert.Equal(PortMapServiceType.LoadBalancer.ToString(), service.Spec.Type); - V1ServicePort port80 = service.Spec.Ports.Single(p => p.Port == 8080); - Assert.Equal("hostport-80-tcp", port80.Name); - Assert.Equal("TCP", port80.Protocol); - Assert.Equal("80", port80.TargetPort.Value); - V1ServicePort port5000 = service.Spec.Ports.Single(p => p.Port == 5050); - Assert.Equal("hostport-5000-udp", port5000.Name); - Assert.Equal("UDP", port5000.Protocol); - Assert.Equal("5000", port5000.TargetPort.Value); - } - - [Fact] - public void ServiceNameIsModuleName() - { - var module = CreateKubernetesModule(CreatePodParameters.Create(exposedPorts: ExposedPorts, hostConfig: HostPorts)); - var mapper = new KubernetesServiceMapper(PortMapServiceType.LoadBalancer); - Option result = mapper.CreateService(CreateIdentity, module, DefaultLabels); - - Assert.True(result.HasValue); - var service = result.OrDefault(); - Assert.Equal("module1", service.Metadata.Name); - } - - [Fact] - public void ServiceAnnotationsAreLabels() - { - var dockerLabels = new Dictionary - { - ["Complicated Value that doesn't fit in k8s label name"] = "Complicated Value that doesn't fit in k8s label value", - ["Label2"] = "Value2" - }; - var module = CreateKubernetesModule(CreatePodParameters.Create(exposedPorts: ExposedPorts, hostConfig: HostPorts, labels: dockerLabels)); - var mapper = new KubernetesServiceMapper(PortMapServiceType.LoadBalancer); - Option result = mapper.CreateService(CreateIdentity, module, DefaultLabels); - - Assert.True(result.HasValue); - var service = result.OrDefault(); - Assert.Equal("Complicated Value that doesn't fit in k8s label value", service.Metadata.Annotations["ComplicatedValuethatdoesntfitink8slabelname"]); - Assert.Equal("Value2", service.Metadata.Annotations["Label2"]); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/serviceaccount/KubernetesServiceAccountByValueComparerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/serviceaccount/KubernetesServiceAccountByValueComparerTest.cs deleted file mode 100644 index f981b0cea6f..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/edgedeployment/serviceaccount/KubernetesServiceAccountByValueComparerTest.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test.EdgeDeployment.ServiceAccount -{ - using System.Collections.Generic; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment.ServiceAccount; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; - - using KubernetesConstants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - [Unit] - public class KubernetesServiceAccountByValueComparerTest - { - static readonly KubernetesServiceAccountByValueEqualityComparer Comparer = new KubernetesServiceAccountByValueEqualityComparer(); - - [Fact] - public void ReferenceComparisonTest() - { - var pvc1 = new V1ServiceAccount(); - var pvc2 = pvc1; - Assert.True(Comparer.Equals(pvc1, pvc2)); - Assert.False(Comparer.Equals(null, pvc2)); - Assert.False(Comparer.Equals(pvc1, null)); - } - - [Fact] - public void ReturnsFalseIfFieldsAreDifferent() - { - var x = new V1ServiceAccount - { - Metadata = new V1ObjectMeta - { - Annotations = new Dictionary - { - [KubernetesConstants.K8sEdgeOriginalModuleId] = "Object1" - }, - Labels = new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = "device1", - }, - Name = "object1" - } - }; - var y = new V1ServiceAccount - { - Metadata = new V1ObjectMeta - { - Annotations = new Dictionary - { - [KubernetesConstants.K8sEdgeOriginalModuleId] = "Object1" - }, - Labels = new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = "device1", - }, - Name = "object1" - } - }; - Assert.True(Comparer.Equals(x, y)); - - y.Metadata.Name = "object2"; - Assert.False(Comparer.Equals(x, y)); - - y.Metadata.Name = "object1"; - Assert.True(Comparer.Equals(x, y)); - - y.Metadata.Annotations = new Dictionary - { - ["newkey"] = "Object1" - }; - Assert.False(Comparer.Equals(x, y)); - - y.Metadata.Annotations = new Dictionary - { - [KubernetesConstants.K8sEdgeOriginalModuleId] = "Object1" - }; - Assert.True(Comparer.Equals(x, y)); - - y.Metadata.Labels = new Dictionary - { - ["newkey2"] = "Object1" - }; - Assert.False(Comparer.Equals(x, y)); - } - - [Fact] - public void IgnoreOtherMetadataTest() - { - var saWithOwnerRef = new V1ServiceAccount - { - Metadata = new V1ObjectMeta - { - Annotations = new Dictionary - { - [KubernetesConstants.K8sEdgeOriginalModuleId] = "Object1" - }, - Labels = new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = "device1", - }, - Name = "object1", - OwnerReferences = new List - { - new V1OwnerReference("v1", name: "iotedged", kind: "Deployment", uid: "123") - } - } - }; - var saWithoutOwnerRef = new V1ServiceAccount - { - Metadata = new V1ObjectMeta - { - Annotations = new Dictionary - { - [KubernetesConstants.K8sEdgeOriginalModuleId] = "Object1" - }, - Labels = new Dictionary - { - [KubernetesConstants.K8sEdgeDeviceLabel] = "device1", - }, - Name = "object1" - } - }; - Assert.True(Comparer.Equals(saWithOwnerRef, saWithoutOwnerRef)); - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/planners/KubernetesPlannerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/planners/KubernetesPlannerTest.cs deleted file mode 100644 index 6655838ffe5..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/planners/KubernetesPlannerTest.cs +++ /dev/null @@ -1,380 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test.Planners -{ - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - using System.Threading.Tasks; - using k8s; - using k8s.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.EdgeDeployment; - using Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Planners; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Moq; - using Newtonsoft.Json; - using Xunit; - using Constants = Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Constants; - - [Unit] - public class KubernetesPlannerTest - { - const string Namespace = "namespace"; - const string Selector = "selector"; - static readonly ResourceName ResourceName = new ResourceName("hostname", "deviceId"); - static readonly IDictionary EnvVars = new Dictionary(); - static readonly DockerConfig Config1 = new DockerConfig("image1"); - static readonly DockerConfig Config2 = new DockerConfig("image2"); - static readonly ConfigurationInfo DefaultConfigurationInfo = new ConfigurationInfo("1"); - static readonly KubernetesModuleOwner EdgeletModuleOwner = new KubernetesModuleOwner("v1", "Deployment", "iotedged", "123"); - static readonly IRuntimeInfo RuntimeInfo = Mock.Of(); - static readonly IKubernetes DefaultClient = Mock.Of(); - static readonly ICommandFactory DefaultCommandFactory = new KubernetesCommandFactory(); - static readonly ICombinedConfigProvider ConfigProvider = Mock.Of>(); - - [Fact] - [Unit] - public void ConstructorThrowsOnInvalidParams() - { - Assert.Throws(() => new KubernetesPlanner(null, Selector, ResourceName, DefaultClient, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner)); - Assert.Throws(() => new KubernetesPlanner(ResourceName, null, ResourceName, DefaultClient, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner)); - Assert.Throws(() => new KubernetesPlanner(ResourceName, string.Empty, Namespace, DefaultClient, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner)); - Assert.Throws(() => new KubernetesPlanner(ResourceName, Selector, null, DefaultClient, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner)); - Assert.Throws(() => new KubernetesPlanner(ResourceName, Selector, string.Empty, DefaultClient, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner)); - Assert.Throws(() => new KubernetesPlanner(ResourceName, Selector, Namespace, null, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner)); - Assert.Throws(() => new KubernetesPlanner(ResourceName, Selector, Namespace, DefaultClient, null, ConfigProvider, EdgeletModuleOwner)); - Assert.Throws(() => new KubernetesPlanner(ResourceName, Selector, Namespace, DefaultClient, DefaultCommandFactory, null, EdgeletModuleOwner)); - Assert.Throws(() => new KubernetesPlanner(ResourceName, Selector, Namespace, DefaultClient, DefaultCommandFactory, ConfigProvider, null)); - } - - [Fact] - [Unit] - public async void KubernetesPlannerNoModulesNoPlan() - { - Option reportedStatus = Option.None(); - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List(), null); - bool getCrdCalled = false; - - using (var server = new KubernetesApiServer( - resp: string.Empty, - shouldNext: async httpContext => - { - string pathStr = httpContext.Request.Path.Value; - string method = httpContext.Request.Method; - if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase)) - { - if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}/{ResourceName}")) - { - getCrdCalled = true; - await httpContext.Response.Body.WriteAsync(JsonConvert.SerializeObject(edgeDefinition).ToBody()); - } - } - - return false; - })) - { - var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri }); - var planner = new KubernetesPlanner(ResourceName, Selector, Namespace, client, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner); - - Plan addPlan = await planner.PlanAsync(ModuleSet.Empty, ModuleSet.Empty, RuntimeInfo, ImmutableDictionary.Empty); - Assert.True(getCrdCalled); - Assert.Equal(Plan.Empty, addPlan); - Assert.False(reportedStatus.HasValue); - } - } - - [Fact] - [Unit] - public async void KubernetesPlannerPlanFailsWithNonDistinctModules() - { - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List(), null); - bool getCrdCalled = false; - - using (var server = new KubernetesApiServer( - resp: string.Empty, - shouldNext: async httpContext => - { - string pathStr = httpContext.Request.Path.Value; - string method = httpContext.Request.Method; - if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase)) - { - if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}/{ResourceName}")) - { - getCrdCalled = true; - await httpContext.Response.Body.WriteAsync(JsonConvert.SerializeObject(edgeDefinition).ToBody()); - } - } - - return false; - })) - { - IModule m1 = new DockerModule("module1", "v1", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - IModule m2 = new DockerModule("Module1", "v1", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - ModuleSet addRunning = ModuleSet.Create(m1, m2); - - var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri }); - var planner = new KubernetesPlanner(ResourceName, Selector, Namespace, client, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner); - await Assert.ThrowsAsync( - () => planner.PlanAsync(addRunning, ModuleSet.Empty, RuntimeInfo, ImmutableDictionary.Empty)); - Assert.True(getCrdCalled); - } - } - - [Fact] - [Unit] - public async void KubernetesPlannerPlanFailsWithNonDockerModules() - { - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List(), null); - bool getCrdCalled = false; - - using (var server = new KubernetesApiServer( - resp: string.Empty, - shouldNext: async httpContext => - { - string pathStr = httpContext.Request.Path.Value; - string method = httpContext.Request.Method; - if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase)) - { - if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}/{ResourceName}")) - { - getCrdCalled = true; - await httpContext.Response.Body.WriteAsync(JsonConvert.SerializeObject(edgeDefinition).ToBody()); - } - } - - return false; - })) - { - IModule m1 = new NonDockerModule("module1", "v1", "unknown", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars, string.Empty); - ModuleSet addRunning = ModuleSet.Create(m1); - - var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri }); - var planner = new KubernetesPlanner(ResourceName, Selector, Namespace, client, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner); - await Assert.ThrowsAsync(() => planner.PlanAsync(addRunning, ModuleSet.Empty, RuntimeInfo, ImmutableDictionary.Empty)); - Assert.True(getCrdCalled); - } - } - - [Fact] - [Unit] - public async void KubernetesPlannerStatusCommandExistsWhenEdgeDeploymentHasStatus() - { - IModule m1 = new DockerModule("module1", "v1", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - IModule m2 = new DockerModule("module2", "v1", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - KubernetesConfig kc1 = new KubernetesConfig("image1", CreatePodParameters.Create(), Option.None()); - KubernetesConfig kc2 = new KubernetesConfig("image2", CreatePodParameters.Create(), Option.None()); - EdgeDeploymentStatus status = EdgeDeploymentStatus.Success("This went very well."); - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List() { new KubernetesModule(m1, kc1, EdgeletModuleOwner), new KubernetesModule(m2, kc2, EdgeletModuleOwner) }, status); - bool getCrdCalled = false; - - using (var server = new KubernetesApiServer( - resp: string.Empty, - shouldNext: async httpContext => - { - string pathStr = httpContext.Request.Path.Value; - string method = httpContext.Request.Method; - if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase)) - { - if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}/{ResourceName}")) - { - getCrdCalled = true; - await httpContext.Response.Body.WriteAsync(JsonConvert.SerializeObject(edgeDefinition, EdgeDeploymentSerialization.SerializerSettings).ToBody()); - } - } - - return false; - })) - { - ModuleSet desired = ModuleSet.Create(m1, m2); - ModuleSet current = ModuleSet.Create(m1, m2); - - var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri }); - var planner = new KubernetesPlanner(ResourceName, Selector, Namespace, client, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner); - var plan = await planner.PlanAsync(desired, current, RuntimeInfo, ImmutableDictionary.Empty); - Assert.True(getCrdCalled); - Assert.Equal(2, plan.Commands.Count); - Assert.True(plan.Commands.First() is EdgeDeploymentCommand); - Assert.True(plan.Commands.Last() is EdgeDeploymentStatusCommand); - } - } - - [Fact] - [Unit] - public async void KubernetesPlannerPlanExistsWhenNoChanges() - { - IModule m1 = new DockerModule("module1", "v1", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - IModule m2 = new DockerModule("module2", "v1", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - KubernetesConfig kc1 = new KubernetesConfig("image1", CreatePodParameters.Create(), Option.None()); - KubernetesConfig kc2 = new KubernetesConfig("image2", CreatePodParameters.Create(), Option.None()); - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List() { new KubernetesModule(m1, kc1, EdgeletModuleOwner), new KubernetesModule(m2, kc2, EdgeletModuleOwner) }, null); - bool getCrdCalled = false; - - using (var server = new KubernetesApiServer( - resp: string.Empty, - shouldNext: async httpContext => - { - string pathStr = httpContext.Request.Path.Value; - string method = httpContext.Request.Method; - if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase)) - { - if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}/{ResourceName}")) - { - getCrdCalled = true; - await httpContext.Response.Body.WriteAsync(JsonConvert.SerializeObject(edgeDefinition, EdgeDeploymentSerialization.SerializerSettings).ToBody()); - } - } - - return false; - })) - { - ModuleSet desired = ModuleSet.Create(m1, m2); - ModuleSet current = ModuleSet.Create(m1, m2); - - var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri }); - var planner = new KubernetesPlanner(ResourceName, Selector, Namespace, client, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner); - var plan = await planner.PlanAsync(desired, current, RuntimeInfo, ImmutableDictionary.Empty); - Assert.True(getCrdCalled); - Assert.Equal(2, plan.Commands.Count); - Assert.True(plan.Commands.First() is EdgeDeploymentCommand); - Assert.True(plan.Commands.Last() is EdgeDeploymentStatusCommand); - } - } - - [Fact] - [Unit] - public async void KubernetesPlannerPlanExistsWhenChangesMade() - { - var edgeDefinition = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List(), null); - bool getCrdCalled = false; - - using (var server = new KubernetesApiServer( - resp: string.Empty, - shouldNext: async httpContext => - { - string pathStr = httpContext.Request.Path.Value; - string method = httpContext.Request.Method; - if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase)) - { - if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}/{ResourceName}")) - { - getCrdCalled = true; - await httpContext.Response.Body.WriteAsync(JsonConvert.SerializeObject(edgeDefinition).ToBody()); - } - } - - return false; - })) - { - IModule m1 = new DockerModule("module1", "v1", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - IModule m2 = new DockerModule("module2", "v1", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - ModuleSet desired = ModuleSet.Create(m1); - ModuleSet current = ModuleSet.Create(m2); - - var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri }); - var planner = new KubernetesPlanner(ResourceName, Selector, Namespace, client, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner); - var plan = await planner.PlanAsync(desired, current, RuntimeInfo, ImmutableDictionary.Empty); - Assert.True(getCrdCalled); - Assert.Equal(2, plan.Commands.Count); - Assert.True(plan.Commands.First() is EdgeDeploymentCommand); - Assert.True(plan.Commands.Last() is EdgeDeploymentStatusCommand); - } - } - - [Fact] - [Unit] - public async void KubernetesPlannerPlanExistsWhenDeploymentsQueryFails() - { - bool getCrdCalled = false; - - using (var server = new KubernetesApiServer( - resp: string.Empty, - shouldNext: httpContext => - { - string pathStr = httpContext.Request.Path.Value; - string method = httpContext.Request.Method; - if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase)) - { - if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}/{ResourceName}")) - { - getCrdCalled = true; - httpContext.Response.StatusCode = 404; - } - } - - return Task.FromResult(false); - })) - { - IModule m1 = new DockerModule("module1", "v1", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - IModule m2 = new DockerModule("module2", "v1", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - ModuleSet desired = ModuleSet.Create(m1); - ModuleSet current = ModuleSet.Create(m2); - - var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri }); - var planner = new KubernetesPlanner(ResourceName, Selector, Namespace, client, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner); - var plan = await planner.PlanAsync(desired, current, RuntimeInfo, ImmutableDictionary.Empty); - Assert.True(getCrdCalled); - Assert.Single(plan.Commands); - Assert.True(plan.Commands.First() is EdgeDeploymentCommand); - } - } - - [Fact] - [Unit] - public async void KubernetesPlannerShutdownTest() - { - IModule m1 = new DockerModule("module1", "v1", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultStartupOrder, DefaultConfigurationInfo, EnvVars); - ModuleSet current = ModuleSet.Create(m1); - - var planner = new KubernetesPlanner(ResourceName, Selector, Namespace, DefaultClient, DefaultCommandFactory, ConfigProvider, EdgeletModuleOwner); - var plan = await planner.CreateShutdownPlanAsync(current); - Assert.Equal(Plan.Empty, plan); - } - - class NonDockerModule : IModule - { - public NonDockerModule(string name, string version, string type, ModuleStatus desiredStatus, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy restartPolicy, ImagePullPolicy imagePullPolicy, uint startupOrder, ConfigurationInfo configurationInfo, IDictionary env, string config) - { - this.Name = name; - this.Version = version; - this.Type = type; - this.DesiredStatus = desiredStatus; - this.RestartPolicy = restartPolicy; - this.ImagePullPolicy = imagePullPolicy; - this.StartupOrder = startupOrder; - this.ConfigurationInfo = configurationInfo; - this.Env = env; - this.Config = config; - } - - public bool Equals(IModule other) => throw new NotImplementedException(); - - public string Name { get; set; } - - public string Version { get; } - - public string Type { get; } - - public ModuleStatus DesiredStatus { get; } - - public global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy RestartPolicy { get; } - - public ImagePullPolicy ImagePullPolicy { get; } - - public uint StartupOrder { get; } - - public ConfigurationInfo ConfigurationInfo { get; } - - public IDictionary Env { get; } - - public bool IsOnlyModuleStatusChanged(IModule other) => throw new NotImplementedException(); - - public bool Equals(IModule other) => throw new NotImplementedException(); - - public string Config { get; } - } - } -} diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/podwatch.txt b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/podwatch.txt deleted file mode 100644 index 99cabfa3c17..00000000000 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Kubernetes.Test/podwatch.txt +++ /dev/null @@ -1,1010 +0,0 @@ -{ - "apiVersion": "v1", - "items": [ - { - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "annotations": { - "net.azure-devices.edge.original-moduleid": "edgeAgent" - }, - "creationTimestamp": "2019-06-12T16:10:59Z", - "generateName": "edgeagent-648b5d4cb-", - "labels": { - "net.azure-devices.edge.deviceid": "dwr-ha3", - "net.azure-devices.edge.hub": "dwr-hub.azure-devices.net", - "net.azure-devices.edge.module": "edgeagent", - "pod-template-hash": "648b5d4cb" - }, - "name": "edgeagent-648b5d4cb-qb48v", - "namespace": "msiot-dwr-hub-dwr-ha3", - "ownerReferences": [ - { - "apiVersion": "apps/v1", - "blockOwnerDeletion": true, - "controller": true, - "kind": "ReplicaSet", - "name": "edgeagent-648b5d4cb", - "uid": "aac5b82e-8d2c-11e9-b951-08002751284e" - } - ], - "resourceVersion": "619078", - "selfLink": "/api/v1/namespaces/msiot-dwr-hub-dwr-ha3/pods/edgeagent-648b5d4cb-qb48v", - "uid": "aac731a2-8d2c-11e9-b951-08002751284e" - }, - "spec": { - "containers": [ - { - "env": [ - { - "name": "IOTEDGE_MODULEGENERATIONID", - "valueFrom": { - "configMapKeyRef": { - "key": "generationId", - "name": "edgy3-ea-config" - } - } - }, - { - "name": "EDGEDEVICEHOSTNAME", - "value": "edge" - }, - { - "name": "NetworkId", - "value": "azure-iot-edge" - }, - { - "name": "IOTEDGE_AUTHSCHEME", - "value": "sasToken" - }, - { - "name": "IOTEDGE_WORKLOADURI", - "value": "http://localhost:35001" - }, - { - "name": "IOTEDGE_MANAGEMENTURI", - "value": "http://localhost:35000" - }, - { - "name": "IOTEDGE_MODULEID", - "value": "$edgeAgent" - }, - { - "name": "IOTEDGE_DEVICEID", - "valueFrom": { - "configMapKeyRef": { - "key": "deviceId", - "name": "edgy3-ea-config" - } - } - }, - { - "name": "IOTEDGE_IOTHUBHOSTNAME", - "valueFrom": { - "configMapKeyRef": { - "key": "iotHubHostName", - "name": "edgy3-ea-config" - } - } - }, - { - "name": "Mode", - "value": "kubernetes" - }, - { - "name": "IOTEDGE_APIVERSION", - "value": "2018-06-28" - }, - { - "name": "ProxyConfigVolume", - "value": "edgy3-edge-kubernetes-iotedged-proxy-config" - }, - { - "name": "ServiceAccountName", - "value": "edgy3-service-account" - }, - { - "name": "PortMappingServiceType", - "value": "ClusterIP" - }, - { - "name": "EnableK8sServiceCallTracing", - "value": "false" - }, - { - "name": "K8sNamespaceBaseName", - "value": "msiot" - }, - { - "name": "PersistentVolumeClaimDefaultSizeInMb", - "value": "2" - }, - { - "name": "UseMountSourceForVolumeName", - "value": "False" - }, - { - "name": "StorageClassName", - "value": "standard" - }, - { - "name": "RuntimeLogLevel", - "value": "warning" - } - ], - "image": "darobs/azureiotedge-agent:amd64_0.23", - "imagePullPolicy": "Always", - "name": "edgeagent", - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "edgy3-service-account-token-lr7nc", - "readOnly": true - } - ] - }, - { - "image": "traefik:v1.7-alpine", - "imagePullPolicy": "Always", - "name": "proxy", - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/etc/traefik", - "name": "config-volume" - }, - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "edgy3-service-account-token-lr7nc", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "enableServiceLinks": true, - "nodeName": "minikube", - "priority": 0, - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": {}, - "serviceAccount": "edgy3-service-account", - "serviceAccountName": "edgy3-service-account", - "terminationGracePeriodSeconds": 30, - "tolerations": [ - { - "effect": "NoExecute", - "key": "node.kubernetes.io/not-ready", - "operator": "Exists", - "tolerationSeconds": 300 - }, - { - "effect": "NoExecute", - "key": "node.kubernetes.io/unreachable", - "operator": "Exists", - "tolerationSeconds": 300 - } - ], - "volumes": [ - { - "configMap": { - "defaultMode": 420, - "name": "edgy3-edge-kubernetes-iotedged-proxy-config" - }, - "name": "config-volume" - }, - { - "name": "edgy3-service-account-token-lr7nc", - "secret": { - "defaultMode": 420, - "secretName": "edgy3-service-account-token-lr7nc" - } - } - ] - }, - "status": { - "conditions": [ - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:10:59Z", - "status": "True", - "type": "Initialized" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:11:08Z", - "status": "True", - "type": "Ready" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:11:08Z", - "status": "True", - "type": "ContainersReady" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:10:59Z", - "status": "True", - "type": "PodScheduled" - } - ], - "containerStatuses": [ - { - "containerID": "docker://b35f27346e316545aa6f3ef88d38bd267bf49835a2251ea730d5a323e13b55f0", - "image": "darobs/azureiotedge-agent:amd64_0.23", - "imageID": "docker-pullable://darobs/azureiotedge-agent@sha256:2e1d401959103e131bf363ab24c46162dec6940d3fb616a9a6bea0bf2b6f6adf", - "lastState": {}, - "name": "edgeagent", - "ready": true, - "restartCount": 0, - "state": { - "running": { - "startedAt": "2019-06-12T16:11:07Z" - } - } - }, - { - "containerID": "docker://732cdfe0827d2670ed71815b38351d602d3b7f21124ea5580f170cddbd3bd3f4", - "image": "traefik:v1.7-alpine", - "imageID": "docker-pullable://traefik@sha256:bb665343b4e0fc42c677a2b66c3f6febf0046b0c73d2d1f26d1178a2a83ff58b", - "lastState": {}, - "name": "proxy", - "ready": true, - "restartCount": 0, - "state": { - "running": { - "startedAt": "2019-06-12T16:11:05Z" - } - } - } - ], - "hostIP": "10.0.2.15", - "phase": "Running", - "podIP": "172.17.0.6", - "qosClass": "BestEffort", - "startTime": "2019-06-12T16:10:59Z" - } - }, - { - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "annotations": { - "net.azure-devices.edge.original-moduleid": "edgeHub" - }, - "creationTimestamp": "2019-06-12T16:11:21Z", - "generateName": "edgehub-5c457894c-", - "labels": { - "net.azure-devices.edge.deviceid": "dwr-ha3", - "net.azure-devices.edge.hub": "dwr-hub.azure-devices.net", - "net.azure-devices.edge.module": "edgehub", - "pod-template-hash": "5c457894c" - }, - "name": "edgehub-5c457894c-x76vq", - "namespace": "msiot-dwr-hub-dwr-ha3", - "ownerReferences": [ - { - "apiVersion": "apps/v1", - "blockOwnerDeletion": true, - "controller": true, - "kind": "ReplicaSet", - "name": "edgehub-5c457894c", - "uid": "b7b6f5ca-8d2c-11e9-b951-08002751284e" - } - ], - "resourceVersion": "619156", - "selfLink": "/api/v1/namespaces/msiot-dwr-hub-dwr-ha3/pods/edgehub-5c457894c-x76vq", - "uid": "b7b804b1-8d2c-11e9-b951-08002751284e" - }, - "spec": { - "containers": [ - { - "env": [ - { - "name": "IOTEDGE_IOTHUBHOSTNAME", - "value": "dwr-hub.azure-devices.net" - }, - { - "name": "IOTEDGE_AUTHSCHEME", - "value": "sasToken" - }, - { - "name": "RuntimeLogLevel", - "value": "Warning" - }, - { - "name": "IOTEDGE_WORKLOADURI", - "value": "http://localhost:35001/" - }, - { - "name": "IOTEDGE_MODULEGENERATIONID", - "value": "636922463249058629" - }, - { - "name": "IOTEDGE_DEVICEID", - "value": "DWR-ha3" - }, - { - "name": "IOTEDGE_MODULEID", - "value": "$edgeHub" - }, - { - "name": "IOTEDGE_APIVERSION", - "value": "2018-06-28" - }, - { - "name": "EdgeDeviceHostName", - "value": "edge" - } - ], - "image": "mcr.microsoft.com/azureiotedge-hub:1.0", - "imagePullPolicy": "IfNotPresent", - "name": "edgehub", - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/var/run/iotedge", - "name": "workload" - }, - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "default-token-h7spc", - "readOnly": true - } - ] - }, - { - "env": [ - { - "name": "IOTEDGE_IOTHUBHOSTNAME", - "value": "dwr-hub.azure-devices.net" - }, - { - "name": "IOTEDGE_AUTHSCHEME", - "value": "sasToken" - }, - { - "name": "RuntimeLogLevel", - "value": "Warning" - }, - { - "name": "IOTEDGE_WORKLOADURI", - "value": "http://localhost:35001/" - }, - { - "name": "IOTEDGE_MODULEGENERATIONID", - "value": "636922463249058629" - }, - { - "name": "IOTEDGE_DEVICEID", - "value": "DWR-ha3" - }, - { - "name": "IOTEDGE_MODULEID", - "value": "$edgeHub" - }, - { - "name": "IOTEDGE_APIVERSION", - "value": "2018-06-28" - }, - { - "name": "EdgeDeviceHostName", - "value": "edge" - } - ], - "image": "traefik:v1.7-alpine", - "imagePullPolicy": "IfNotPresent", - "name": "proxy", - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/var/run/iotedge", - "name": "workload" - }, - { - "mountPath": "/etc/traefik", - "name": "config-volume" - }, - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "default-token-h7spc", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "enableServiceLinks": true, - "nodeName": "minikube", - "priority": 0, - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": {}, - "serviceAccount": "default", - "serviceAccountName": "default", - "terminationGracePeriodSeconds": 30, - "tolerations": [ - { - "effect": "NoExecute", - "key": "node.kubernetes.io/not-ready", - "operator": "Exists", - "tolerationSeconds": 300 - }, - { - "effect": "NoExecute", - "key": "node.kubernetes.io/unreachable", - "operator": "Exists", - "tolerationSeconds": 300 - } - ], - "volumes": [ - { - "emptyDir": {}, - "name": "workload" - }, - { - "configMap": { - "defaultMode": 420, - "name": "edgy3-edge-kubernetes-iotedged-proxy-config" - }, - "name": "config-volume" - }, - { - "name": "default-token-h7spc", - "secret": { - "defaultMode": 420, - "secretName": "default-token-h7spc" - } - } - ] - }, - "status": { - "conditions": [ - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:11:21Z", - "status": "True", - "type": "Initialized" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:11:23Z", - "status": "True", - "type": "Ready" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:11:23Z", - "status": "True", - "type": "ContainersReady" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:11:21Z", - "status": "True", - "type": "PodScheduled" - } - ], - "containerStatuses": [ - { - "containerID": "docker://102409124f0b30fbe8f44ec353966919868107aa534396c8af88ec70e96d309c", - "image": "mcr.microsoft.com/azureiotedge-hub:1.0", - "imageID": "docker-pullable://mcr.microsoft.com/azureiotedge-hub@sha256:65ade6b60d1085efb7805538514cb96ca670e9484333736ab5b4b9ad2c5e9a59", - "lastState": {}, - "name": "edgehub", - "ready": true, - "restartCount": 0, - "state": { - "running": { - "startedAt": "2019-06-12T16:11:22Z" - } - } - }, - { - "containerID": "docker://79fba71e7e2a8b324f93f8570d91afd24cf7873129f02c68997aff78e8ca1609", - "image": "traefik:v1.7-alpine", - "imageID": "docker-pullable://traefik@sha256:bb665343b4e0fc42c677a2b66c3f6febf0046b0c73d2d1f26d1178a2a83ff58b", - "lastState": {}, - "name": "proxy", - "ready": true, - "restartCount": 0, - "state": { - "running": { - "startedAt": "2019-06-12T16:11:22Z" - } - } - } - ], - "hostIP": "10.0.2.15", - "phase": "Running", - "podIP": "172.17.0.7", - "qosClass": "BestEffort", - "startTime": "2019-06-12T16:11:21Z" - } - }, - { - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "creationTimestamp": "2019-06-12T16:10:59Z", - "generateName": "iotedged-6bbb599b8b-", - "labels": { - "app.kubernetes.io/instance": "edgy3", - "app.kubernetes.io/name": "edge-kubernetes-iotedged", - "pod-template-hash": "6bbb599b8b" - }, - "name": "iotedged-6bbb599b8b-qcj9z", - "namespace": "msiot-dwr-hub-dwr-ha3", - "ownerReferences": [ - { - "apiVersion": "apps/v1", - "blockOwnerDeletion": true, - "controller": true, - "kind": "ReplicaSet", - "name": "iotedged-6bbb599b8b", - "uid": "aac70fbb-8d2c-11e9-b951-08002751284e" - } - ], - "resourceVersion": "619085", - "selfLink": "/api/v1/namespaces/msiot-dwr-hub-dwr-ha3/pods/iotedged-6bbb599b8b-qcj9z", - "uid": "aac7e1ac-8d2c-11e9-b951-08002751284e" - }, - "spec": { - "containers": [ - { - "args": [ - "-c", - "/etc/iotedged/config.yaml" - ], - "command": [ - "/app/iotedged" - ], - "image": "azureiotedge/iotedged:0.1.0-alpha", - "imagePullPolicy": "Always", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/systeminfo?api-version=2018-06-28", - "port": 35000, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "name": "edge-kubernetes-iotedged", - "ports": [ - { - "containerPort": 35000, - "name": "management", - "protocol": "TCP" - }, - { - "containerPort": 35001, - "name": "workload", - "protocol": "TCP" - } - ], - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/systeminfo?api-version=2018-06-28", - "port": 35000, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/etc/iotedged", - "name": "config", - "readOnly": true - }, - { - "mountPath": "/var/lib/iotedge", - "name": "edge-home" - }, - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "default-token-h7spc", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "enableServiceLinks": true, - "nodeName": "minikube", - "priority": 0, - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": {}, - "serviceAccount": "default", - "serviceAccountName": "default", - "terminationGracePeriodSeconds": 30, - "tolerations": [ - { - "effect": "NoExecute", - "key": "node.kubernetes.io/not-ready", - "operator": "Exists", - "tolerationSeconds": 300 - }, - { - "effect": "NoExecute", - "key": "node.kubernetes.io/unreachable", - "operator": "Exists", - "tolerationSeconds": 300 - } - ], - "volumes": [ - { - "name": "config", - "secret": { - "defaultMode": 420, - "secretName": "edgy3-edge-kubernetes-iotedged-config" - } - }, - { - "emptyDir": {}, - "name": "edge-home" - }, - { - "name": "default-token-h7spc", - "secret": { - "defaultMode": 420, - "secretName": "default-token-h7spc" - } - } - ] - }, - "status": { - "conditions": [ - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:10:59Z", - "status": "True", - "type": "Initialized" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:11:12Z", - "status": "True", - "type": "Ready" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:11:12Z", - "status": "True", - "type": "ContainersReady" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:10:59Z", - "status": "True", - "type": "PodScheduled" - } - ], - "containerStatuses": [ - { - "containerID": "docker://c2c3c45bebfa8255f4d755e175e720ab163de8e7cf376f5617a43f87a4da0b31", - "image": "azureiotedge/iotedged:0.1.0-alpha", - "imageID": "docker-pullable://azureiotedge/iotedged@sha256:9fee12bc5287fd92fcc08dceed73ae67916038003dfd2a1e481262536b011f8b", - "lastState": {}, - "name": "edge-kubernetes-iotedged", - "ready": true, - "restartCount": 0, - "state": { - "running": { - "startedAt": "2019-06-12T16:11:01Z" - } - } - } - ], - "hostIP": "10.0.2.15", - "phase": "Running", - "podIP": "172.17.0.5", - "qosClass": "BestEffort", - "startTime": "2019-06-12T16:10:59Z" - } - }, - { - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "annotations": { - "net.azure-devices.edge.original-moduleid": "SimulatedTemperatureSensor" - }, - "creationTimestamp": "2019-06-12T16:11:21Z", - "generateName": "simulatedtemperaturesensor-598778b64-", - "labels": { - "net.azure-devices.edge.deviceid": "dwr-ha3", - "net.azure-devices.edge.hub": "dwr-hub.azure-devices.net", - "net.azure-devices.edge.module": "simulatedtemperaturesensor", - "pod-template-hash": "598778b64" - }, - "name": "simulatedtemperaturesensor-598778b64-k5524", - "namespace": "msiot-dwr-hub-dwr-ha3", - "ownerReferences": [ - { - "apiVersion": "apps/v1", - "blockOwnerDeletion": true, - "controller": true, - "kind": "ReplicaSet", - "name": "simulatedtemperaturesensor-598778b64", - "uid": "b7bc7369-8d2c-11e9-b951-08002751284e" - } - ], - "resourceVersion": "619172", - "selfLink": "/api/v1/namespaces/msiot-dwr-hub-dwr-ha3/pods/simulatedtemperaturesensor-598778b64-k5524", - "uid": "b7be7cd1-8d2c-11e9-b951-08002751284e" - }, - "spec": { - "containers": [ - { - "env": [ - { - "name": "IOTEDGE_IOTHUBHOSTNAME", - "value": "dwr-hub.azure-devices.net" - }, - { - "name": "IOTEDGE_AUTHSCHEME", - "value": "sasToken" - }, - { - "name": "RuntimeLogLevel", - "value": "Warning" - }, - { - "name": "IOTEDGE_WORKLOADURI", - "value": "http://localhost:35001/" - }, - { - "name": "IOTEDGE_MODULEGENERATIONID", - "value": "636922506999956868" - }, - { - "name": "IOTEDGE_DEVICEID", - "value": "DWR-ha3" - }, - { - "name": "IOTEDGE_MODULEID", - "value": "SimulatedTemperatureSensor" - }, - { - "name": "IOTEDGE_APIVERSION", - "value": "2018-06-28" - }, - { - "name": "IOTEDGE_GATEWAYHOSTNAME", - "value": "edgehub" - } - ], - "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0", - "imagePullPolicy": "IfNotPresent", - "name": "simulatedtemperaturesensor", - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/var/run/iotedge", - "name": "workload" - }, - { - "mountPath": "/usr/share/nginx/html", - "name": "content" - }, - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "default-token-h7spc", - "readOnly": true - } - ] - }, - { - "env": [ - { - "name": "IOTEDGE_IOTHUBHOSTNAME", - "value": "dwr-hub.azure-devices.net" - }, - { - "name": "IOTEDGE_AUTHSCHEME", - "value": "sasToken" - }, - { - "name": "RuntimeLogLevel", - "value": "Warning" - }, - { - "name": "IOTEDGE_WORKLOADURI", - "value": "http://localhost:35001/" - }, - { - "name": "IOTEDGE_MODULEGENERATIONID", - "value": "636922506999956868" - }, - { - "name": "IOTEDGE_DEVICEID", - "value": "DWR-ha3" - }, - { - "name": "IOTEDGE_MODULEID", - "value": "SimulatedTemperatureSensor" - }, - { - "name": "IOTEDGE_APIVERSION", - "value": "2018-06-28" - }, - { - "name": "IOTEDGE_GATEWAYHOSTNAME", - "value": "edgehub" - } - ], - "image": "traefik:v1.7-alpine", - "imagePullPolicy": "IfNotPresent", - "name": "proxy", - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/var/run/iotedge", - "name": "workload" - }, - { - "mountPath": "/etc/traefik", - "name": "config-volume" - }, - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "default-token-h7spc", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "enableServiceLinks": true, - "nodeName": "minikube", - "priority": 0, - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": {}, - "serviceAccount": "default", - "serviceAccountName": "default", - "terminationGracePeriodSeconds": 30, - "tolerations": [ - { - "effect": "NoExecute", - "key": "node.kubernetes.io/not-ready", - "operator": "Exists", - "tolerationSeconds": 300 - }, - { - "effect": "NoExecute", - "key": "node.kubernetes.io/unreachable", - "operator": "Exists", - "tolerationSeconds": 300 - } - ], - "volumes": [ - { - "emptyDir": {}, - "name": "workload" - }, - { - "configMap": { - "defaultMode": 420, - "name": "edgy3-edge-kubernetes-iotedged-proxy-config" - }, - "name": "config-volume" - }, - { - "name": "content", - "persistentVolumeClaim": { - "claimName": "content" - } - }, - { - "name": "default-token-h7spc", - "secret": { - "defaultMode": 420, - "secretName": "default-token-h7spc" - } - } - ] - }, - "status": { - "conditions": [ - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:11:21Z", - "status": "True", - "type": "Initialized" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:11:25Z", - "status": "True", - "type": "Ready" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:11:25Z", - "status": "True", - "type": "ContainersReady" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2019-06-12T16:11:21Z", - "status": "True", - "type": "PodScheduled" - } - ], - "containerStatuses": [ - { - "containerID": "docker://4766d80affaa619308c3728d747fb3a19b17f1d2c395a7e0a03383ecacfa35e4", - "image": "traefik:v1.7-alpine", - "imageID": "docker-pullable://traefik@sha256:bb665343b4e0fc42c677a2b66c3f6febf0046b0c73d2d1f26d1178a2a83ff58b", - "lastState": {}, - "name": "proxy", - "ready": true, - "restartCount": 0, - "state": { - "running": { - "startedAt": "2019-06-12T16:11:22Z" - } - } - }, - { - "containerID": "docker://ad526f4e80bc6f5652123dd8a8b9ef24baf8149795755014426be2ac15f64f6b", - "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0", - "imageID": "docker-pullable://mcr.microsoft.com/azureiotedge-simulated-temperature-sensor@sha256:5644aae2d980b93f1b4092991b0cae8e1d8421e669d8049f1fb13262b817d3a3", - "lastState": { - "terminated": { - "containerID": "docker://96575296f67824c4fc647128165e551518edd72ceab6dcbeb76797a437ce4fe7", - "exitCode": 139, - "finishedAt": "2019-06-12T16:11:23Z", - "reason": "Error", - "startedAt": "2019-06-12T16:11:22Z" - } - }, - "name": "simulatedtemperaturesensor", - "ready": true, - "restartCount": 1, - "state": { - "running": { - "startedAt": "2019-06-12T16:11:24Z" - } - } - } - ], - "hostIP": "10.0.2.15", - "phase": "Running", - "podIP": "172.17.0.8", - "qosClass": "BestEffort", - "startTime": "2019-06-12T16:11:21Z" - } - } - ], - "kind": "List", - "metadata": { - "resourceVersion": "", - "selfLink": "" - } -}