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 <ICombinedConfigProvider <CombinedDockerConfig> >(); dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.None <string>(), Option.Maybe(DockerAuth))); var configProvider = new Mock <ICombinedConfigProvider <CombinedKubernetesConfig> >(); 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 <KubernetesModule>()); var client = new Mock <IKubernetes>(); client.SetupListSecrets().ReturnsAsync(() => CreateResponse(new V1SecretList { Items = new List <V1Secret>() })); 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(); }
public async void Execute_CreatesOnlyOneImagePullSecret_When2ModulesWithSameSecret() { IModule dockerModule1 = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultPriority, DefaultConfigurationInfo, EnvVars); IModule dockerModule2 = new DockerModule("module2", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config2, ImagePullPolicy.OnCreate, Core.Constants.DefaultPriority, DefaultConfigurationInfo, EnvVars); var dockerConfigProvider = new Mock <ICombinedConfigProvider <CombinedDockerConfig> >(); dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(It.IsAny <DockerModule>(), Runtime)) .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.None <string>(), Option.Maybe(DockerAuth))); var configProvider = new Mock <ICombinedConfigProvider <CombinedKubernetesConfig> >(); configProvider.Setup(cp => cp.GetCombinedConfig(It.IsAny <DockerModule>(), Runtime)) .Returns(() => new CombinedKubernetesConfig("test-image:1", CreatePodParameters.Create(image: "test-image:1"), Option.Maybe(ImagePullSecret))); var client = new Mock <IKubernetes>(); client.SetupListSecrets().ReturnsAsync(() => CreateResponse(new V1SecretList { Items = new List <V1Secret>() })); 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 <EdgeDeploymentDefinition>(), Runtime, configProvider.Object, EdgeletModuleOwner); await cmd.ExecuteAsync(CancellationToken.None); client.VerifyAll(); client.VerifyCreateSecret(Times.Once()); }
public async void CrdCommandExecuteDeploysModulesWithEnvVars() { IDictionary <string, EnvVal> moduleEnvVars = new Dictionary <string, EnvVal> { { "ACamelCaseEnvVar", new EnvVal("ACamelCaseEnvVarValue") } }; IModule dockerModule = new DockerModule("module1", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, DefaultConfigurationInfo, moduleEnvVars); ModuleSet currentModules = ModuleSet.Create(dockerModule); var dockerConfigProvider = new Mock <ICombinedConfigProvider <CombinedDockerConfig> >(); dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.Maybe(DockerAuth))); var configProvider = new Mock <ICombinedConfigProvider <CombinedKubernetesConfig> >(); configProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedKubernetesConfig("test-image:1", CreatePodParameters.Create(image: "test-image:1"), Option.Maybe(ImagePullSecret))); EdgeDeploymentDefinition postedEdgeDeploymentDefinition = null; bool postCrdCalled = 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)) { httpContext.Response.StatusCode = 404; } else if (string.Equals(method, "POST", StringComparison.OrdinalIgnoreCase)) { httpContext.Response.StatusCode = 201; httpContext.Response.Body = httpContext.Request.Body; if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}")) { postCrdCalled = true; using (var reader = new StreamReader(httpContext.Response.Body)) { string crdBody = await reader.ReadToEndAsync(); postedEdgeDeploymentDefinition = JsonConvert.DeserializeObject <EdgeDeploymentDefinition>(crdBody); } } } return(false); })) { var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri }); var cmd = new EdgeDeploymentCommand(Namespace, ResourceName, client, new[] { dockerModule }, currentModules, Runtime, configProvider.Object); await cmd.ExecuteAsync(CancellationToken.None); Assert.True(postCrdCalled); Assert.Equal("module1", postedEdgeDeploymentDefinition.Spec[0].Name); Assert.Equal("test-image:1", postedEdgeDeploymentDefinition.Spec[0].Config.Image); Assert.True(postedEdgeDeploymentDefinition.Spec[0].Env.Contains(new KeyValuePair <string, EnvVal>("ACamelCaseEnvVar", new EnvVal("ACamelCaseEnvVarValue")))); } }
public async void CrdCommandExecuteWithAuthCreateNewObjects() { IModule dockerModule = new DockerModule("module1", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, DefaultConfigurationInfo, EnvVars); ModuleSet currentModules = ModuleSet.Create(dockerModule); var dockerConfigProvider = new Mock <ICombinedConfigProvider <CombinedDockerConfig> >(); dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.Maybe(DockerAuth))); var configProvider = new Mock <ICombinedConfigProvider <CombinedKubernetesConfig> >(); configProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedKubernetesConfig("test-image:1", CreatePodParameters.Create(image: "test-image:1"), Option.Maybe(ImagePullSecret))); bool getSecretCalled = false; bool postSecretCalled = false; bool postCrdCalled = 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)) { httpContext.Response.StatusCode = 404; if (pathStr.Contains($"api/v1/namespaces/{Namespace}/secrets")) { getSecretCalled = true; } } else if (string.Equals(method, "POST", StringComparison.OrdinalIgnoreCase)) { httpContext.Response.StatusCode = 201; httpContext.Response.Body = httpContext.Request.Body; if (pathStr.Contains($"api/v1/namespaces/{Namespace}/secrets")) { postSecretCalled = true; } else if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}")) { postCrdCalled = true; } } return(Task.FromResult(false)); })) { var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri }); var cmd = new EdgeDeploymentCommand(Namespace, ResourceName, client, new[] { dockerModule }, Option.None <EdgeDeploymentDefinition>(), Runtime, configProvider.Object, EdgeletModuleOwner); await cmd.ExecuteAsync(CancellationToken.None); Assert.True(getSecretCalled, nameof(getSecretCalled)); Assert.True(postSecretCalled, nameof(postSecretCalled)); Assert.True(postCrdCalled, nameof(postCrdCalled)); } }
public async void CrdCommandExecuteEdgeAgentGetsCurrentImage() { IModule dockerModule = new DockerModule("edgeAgent", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, DefaultConfigurationInfo, EnvVars); IRuntimeModule currentModule = new EdgeAgentDockerRuntimeModule(AgentConfig1, ModuleStatus.Running, 0, "description", DateTime.Today, DateTime.Today, ImagePullPolicy.OnCreate, DefaultConfigurationInfo, EnvVars); var dockerConfigProvider = new Mock <ICombinedConfigProvider <CombinedDockerConfig> >(); dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.Maybe(DockerAuth))); var configProvider = new Mock <ICombinedConfigProvider <CombinedKubernetesConfig> >(); configProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedKubernetesConfig("test-image:1", CreatePodParameters.Create(image: "test-image:1"), Option.Maybe(ImagePullSecret))); configProvider.Setup(cp => cp.GetCombinedConfig(currentModule, Runtime)) .Returns(() => new CombinedKubernetesConfig(AgentConfig1.Image, CreatePodParameters.Create(image: AgentConfig1.Image), Option.Maybe(ImagePullSecret))); var edgeDefinition = Option.None <EdgeDeploymentDefinition>(); KubernetesConfig kc = new KubernetesConfig(AgentConfig1.Image, CreatePodParameters.Create(), Option.None <AuthConfig>()); var edgeDefinitionCurrentModule = new EdgeDeploymentDefinition("v1", "EdgeDeployment", new V1ObjectMeta(name: ResourceName), new List <KubernetesModule>() { new KubernetesModule(currentModule, kc, EdgeletModuleOwner) }, null); 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)) { httpContext.Response.StatusCode = 404; } else if (string.Equals(method, "PUT", StringComparison.OrdinalIgnoreCase)) { httpContext.Response.Body = httpContext.Request.Body; if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}")) { StreamReader reader = new StreamReader(httpContext.Request.Body); string bodyText = reader.ReadToEnd(); var body = JsonConvert.DeserializeObject <EdgeDeploymentDefinition>(bodyText); edgeDefinition = Option.Maybe(body); } } return(Task.FromResult(false)); })) { var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri }); var cmd = new EdgeDeploymentCommand(Namespace, ResourceName, client, new[] { dockerModule }, Option.Maybe(edgeDefinitionCurrentModule), Runtime, configProvider.Object, EdgeletModuleOwner); await cmd.ExecuteAsync(CancellationToken.None); Assert.True(edgeDefinition.HasValue); var receivedEdgeDefinition = edgeDefinition.OrDefault(); var agentModule = receivedEdgeDefinition.Spec[0]; Assert.Equal(AgentConfig1.Image, agentModule.Config.Image); } }
public async Task <Plan> PlanAsync( ModuleSet desired, ModuleSet current, IRuntimeInfo runtimeInfo, IImmutableDictionary <string, IModuleIdentity> moduleIdentities) { Events.LogDesired(desired); Events.LogCurrent(current); Events.LogIdentities(moduleIdentities); // 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 <DockerConfig>)) { throw new InvalidModuleException($"Kubernetes deployment currently only handles type={typeof(DockerConfig).FullName}"); } // This is a workaround for K8s Public Preview Refresh // TODO: remove this workaround when merging to the main release desired = new ModuleSet(desired.Modules.Remove(Constants.EdgeAgentModuleName)); current = new ModuleSet(current.Modules.Remove(Constants.EdgeAgentModuleName)); Diff moduleDifference = desired.Diff(current); Plan plan; if (!moduleDifference.IsEmpty) { // The "Plan" here is very simple - if we have any change, publish all desired modules to a EdgeDeployment CRD. // The CRD allows us to give the customer a Kubernetes-centric way to see the deployment // and the status of that deployment through the "edgedeployments" API. var crdCommand = new EdgeDeploymentCommand(this.deviceNamespace, this.resourceName, this.client, desired.Modules.Values, runtimeInfo, this.configProvider); var planCommand = await this.commandFactory.WrapAsync(crdCommand); var planList = new List <ICommand> { planCommand }; Events.PlanCreated(planList); plan = new Plan(planList); } else { plan = Plan.Empty; } return(plan); }
public async void Execute_UpdatesSecretData_WhenImagePullSecretCreatedNotByAgent() { string secretName = "username-docker.io"; var existingSecret = new V1Secret { Data = new Dictionary <string, byte[]> { [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.DefaultPriority, DefaultConfigurationInfo, EnvVars); var dockerConfigProvider = new Mock <ICombinedConfigProvider <CombinedDockerConfig> >(); dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.None <string>(), Option.Maybe(DockerAuth))); var configProvider = new Mock <ICombinedConfigProvider <CombinedKubernetesConfig> >(); 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 <IKubernetes>(); client.SetupListSecrets().ReturnsAsync(() => CreateResponse(new V1SecretList { Items = new List <V1Secret>() })); 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 <string, List <string> > 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 <EdgeDeploymentDefinition>(), 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(); }
public async void Execute_UpdatesImagePullSecret_WhenExistsWithSameName() { string secretName = "username-docker.io"; var secretData = new Dictionary <string, byte[]> { [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.DefaultPriority, DefaultConfigurationInfo, EnvVars); var dockerConfigProvider = new Mock <ICombinedConfigProvider <CombinedDockerConfig> >(); dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.None <string>(), Option.Maybe(DockerAuth))); var configProvider = new Mock <ICombinedConfigProvider <CombinedKubernetesConfig> >(); 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 <IKubernetes>(); client.SetupListSecrets().ReturnsAsync(() => CreateResponse(new V1SecretList { Items = new List <V1Secret> { existingSecret } })); V1Secret updatedSecret = null; client.SetupUpdateSecret() .Callback( (V1Secret body, string name, string ns, string dryRun, string fieldManager, string pretty, Dictionary <string, List <string> > 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 <EdgeDeploymentDefinition>(), Runtime, configProvider.Object, EdgeletModuleOwner); await cmd.ExecuteAsync(CancellationToken.None); Assert.NotNull(updatedSecret); client.VerifyAll(); }
public async void Execute_CreatesNewImagePullSecret_WhenEmpty() { IModule dockerModule = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultPriority, DefaultConfigurationInfo, EnvVars); var dockerConfigProvider = new Mock <ICombinedConfigProvider <CombinedDockerConfig> >(); dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.None <string>(), Option.Maybe(DockerAuth))); var configProvider = new Mock <ICombinedConfigProvider <CombinedKubernetesConfig> >(); 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 <IKubernetes>(); client.SetupListSecrets() .ReturnsAsync( () => new HttpOperationResponse <V1SecretList> { Response = new HttpResponseMessage(), Body = new V1SecretList { Items = new List <V1Secret>() } }); V1Secret createdSecret = null; client.SetupCreateSecret() .Callback( (V1Secret body, string ns, string dryRun, string fieldManager, string pretty, Dictionary <string, List <string> > 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 <EdgeDeploymentDefinition>(), Runtime, configProvider.Object, EdgeletModuleOwner); await cmd.ExecuteAsync(CancellationToken.None); Assert.NotNull(createdSecret); client.VerifyAll(); }
public async void Execute_PreservesCaseOfEnvVars_WhenModuleDeployed() { IDictionary <string, EnvVal> moduleEnvVars = new Dictionary <string, EnvVal> { { "ACamelCaseEnvVar", new EnvVal("ACamelCaseEnvVarValue") } }; IModule dockerModule = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, Core.Constants.DefaultPriority, DefaultConfigurationInfo, moduleEnvVars); var dockerConfigProvider = new Mock <ICombinedConfigProvider <CombinedDockerConfig> >(); dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.None <string>(), Option.Maybe(DockerAuth))); var configProvider = new Mock <ICombinedConfigProvider <CombinedKubernetesConfig> >(); 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 <IKubernetes>(); client.SetupListSecrets().ReturnsAsync(() => CreateResponse(new V1SecretList { Items = new List <V1Secret>() })); 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 name, Dictionary <string, List <string> > headers, CancellationToken token) => { edgeDeploymentDefinition = ((JObject)body).ToObject <EdgeDeploymentDefinition>(); }) .ReturnsAsync(() => CreateResponse <object>(edgeDeploymentDefinition)); var cmd = new EdgeDeploymentCommand(ResourceName, Selector, Namespace, client.Object, new[] { dockerModule }, Option.None <EdgeDeploymentDefinition>(), 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 <string, EnvVal>("ACamelCaseEnvVar", new EnvVal("ACamelCaseEnvVarValue")))); }
public async Task <Plan> PlanAsync( ModuleSet desired, ModuleSet current, IRuntimeInfo runtimeInfo, IImmutableDictionary <string, IModuleIdentity> 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 <EdgeDeploymentDefinition> 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 <DockerConfig>)) { 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 <ICommand> { planCommand }; Events.PlanCreated(planList); plan = new Plan(planList); } else { plan = Plan.Empty; } return(plan); }
public async void CrdCommandExecuteTwoModulesWithSamePullSecret() { string secretName = "username-docker.io"; IModule dockerModule1 = new DockerModule("module1", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, DefaultConfigurationInfo, EnvVars); IModule dockerModule2 = new DockerModule("module2", "v1", ModuleStatus.Running, RestartPolicy.Always, Config2, ImagePullPolicy.OnCreate, DefaultConfigurationInfo, EnvVars); var dockerConfigProvider = new Mock <ICombinedConfigProvider <CombinedDockerConfig> >(); dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(It.IsAny <DockerModule>(), Runtime)) .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.Maybe(DockerAuth))); var configProvider = new Mock <ICombinedConfigProvider <CombinedKubernetesConfig> >(); configProvider.Setup(cp => cp.GetCombinedConfig(It.IsAny <DockerModule>(), Runtime)) .Returns(() => new CombinedKubernetesConfig("test-image:1", CreatePodParameters.Create(image: "test-image:1"), Option.Maybe(ImagePullSecret))); bool getSecretCalled = false; bool putSecretCalled = false; int postSecretCalled = 0; bool getCrdCalled = false; bool putCrdCalled = false; int postCrdCalled = 0; Stream secretBody = Stream.Null; 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($"api/v1/namespaces/{Namespace}/secrets/{secretName}")) { if (secretBody == Stream.Null) { // 1st pass, secret should not exist getSecretCalled = true; httpContext.Response.StatusCode = 404; } else { // 2nd pass, use secret from creation. httpContext.Response.Body = secretBody; } } else if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}/{ResourceName}")) { getCrdCalled = true; httpContext.Response.StatusCode = 404; } } else if (string.Equals(method, "POST", StringComparison.OrdinalIgnoreCase)) { httpContext.Response.StatusCode = 201; httpContext.Response.Body = httpContext.Request.Body; if (pathStr.Contains($"api/v1/namespaces/{Namespace}/secrets")) { postSecretCalled++; secretBody = httpContext.Request.Body; // save this for next query. } else if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}")) { postCrdCalled++; } } else if (string.Equals(method, "PUT", StringComparison.OrdinalIgnoreCase)) { httpContext.Response.Body = httpContext.Request.Body; if (pathStr.Contains($"api/v1/namespaces/{Namespace}/secrets")) { putSecretCalled = true; } else if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}")) { putCrdCalled = true; } } return(Task.FromResult(false)); })) { var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri }); var cmd = new EdgeDeploymentCommand(Namespace, ResourceName, client, new[] { dockerModule1, dockerModule2 }, Runtime, configProvider.Object); await cmd.ExecuteAsync(CancellationToken.None); Assert.True(getSecretCalled, nameof(getSecretCalled)); Assert.Equal(1, postSecretCalled); Assert.False(putSecretCalled, nameof(putSecretCalled)); Assert.True(getCrdCalled, nameof(getCrdCalled)); Assert.Equal(1, postCrdCalled); Assert.False(putCrdCalled, nameof(putCrdCalled)); } }
public async void CrdCommandExecuteWithAuthReplaceObjects() { string secretName = "username-docker.io"; var secretData = new Dictionary <string, byte[]> { [Constants.K8sPullSecretData] = Encoding.UTF8.GetBytes("Invalid Secret Data") }; var secretMeta = new V1ObjectMeta(name: secretName, namespaceProperty: Namespace); IModule dockerModule = new DockerModule("module1", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, DefaultConfigurationInfo, EnvVars); var dockerConfigProvider = new Mock <ICombinedConfigProvider <CombinedDockerConfig> >(); dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.Maybe(DockerAuth))); var configProvider = new Mock <ICombinedConfigProvider <CombinedKubernetesConfig> >(); configProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedKubernetesConfig("test-image:1", CreatePodParameters.Create(image: "test-image:1"), Option.Maybe(ImagePullSecret))); var existingSecret = new V1Secret("v1", secretData, type: Constants.K8sPullSecretType, kind: "Secret", metadata: secretMeta); var existingDeployment = new EdgeDeploymentDefinition(Constants.EdgeDeployment.ApiVersion, Constants.EdgeDeployment.Kind, new V1ObjectMeta(name: ResourceName), new List <KubernetesModule>()); bool getSecretCalled = false; bool putSecretCalled = false; bool getCrdCalled = false; bool putCrdCalled = 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($"api/v1/namespaces/{Namespace}/secrets/{secretName}")) { getSecretCalled = true; await httpContext.Response.Body.WriteAsync(JsonConvert.SerializeObject(existingSecret).ToBody()); } else if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}/{ResourceName}")) { getCrdCalled = true; await httpContext.Response.Body.WriteAsync(JsonConvert.SerializeObject(existingDeployment).ToBody()); } } else if (string.Equals(method, "PUT", StringComparison.OrdinalIgnoreCase)) { httpContext.Response.Body = httpContext.Request.Body; if (pathStr.Contains($"api/v1/namespaces/{Namespace}/secrets/{secretName}")) { putSecretCalled = true; } else if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}/{ResourceName}")) { putCrdCalled = true; } } return(false); })) { var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri }); var cmd = new EdgeDeploymentCommand(Namespace, ResourceName, client, new[] { dockerModule }, Runtime, configProvider.Object); await cmd.ExecuteAsync(CancellationToken.None); Assert.True(getSecretCalled, nameof(getSecretCalled)); Assert.True(putSecretCalled, nameof(putSecretCalled)); Assert.True(getCrdCalled, nameof(getCrdCalled)); Assert.True(putCrdCalled, nameof(putCrdCalled)); } }
public async void CrdCommandExecuteEdgeAgentDeploymentImageFallback() { IModule dockerModule = new DockerModule("edgeAgent", "v1", ModuleStatus.Running, RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, DefaultConfigurationInfo, EnvVars); var dockerConfigProvider = new Mock <ICombinedConfigProvider <CombinedDockerConfig> >(); dockerConfigProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.Maybe(DockerAuth))); var configProvider = new Mock <ICombinedConfigProvider <CombinedKubernetesConfig> >(); configProvider.Setup(cp => cp.GetCombinedConfig(dockerModule, Runtime)) .Returns(() => new CombinedKubernetesConfig("test-image:1", CreatePodParameters.Create(image: "test-image:1"), Option.Maybe(ImagePullSecret))); Option <EdgeDeploymentDefinition> edgeDefinition = Option.None <EdgeDeploymentDefinition>(); string agentDeploymentImage = "image:3"; bool postSecretCalled = false; bool postCrdCalled = 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}/deployment")) { httpContext.Response.StatusCode = 200; V1Deployment d = new V1Deployment { ApiVersion = "apps/v1", Kind = "Deployment", Metadata = new V1ObjectMeta { Name = "edgeagent", NamespaceProperty = Namespace }, Spec = new V1DeploymentSpec { Template = new V1PodTemplateSpec { Metadata = new V1ObjectMeta { Name = "edgeagent", }, Spec = new V1PodSpec { Containers = new List <V1Container> { new V1Container { Image = agentDeploymentImage, Name = "edgeagent" } } } } } }; await httpContext.Response.Body.WriteAsync(JsonConvert.SerializeObject(d).ToBody()); } else { httpContext.Response.StatusCode = 404; } } else if (string.Equals(method, "POST", StringComparison.OrdinalIgnoreCase)) { httpContext.Response.StatusCode = 201; httpContext.Response.Body = httpContext.Request.Body; if (pathStr.Contains($"api/v1/namespaces/{Namespace}/secrets")) { postSecretCalled = true; } else if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}")) { postCrdCalled = true; StreamReader reader = new StreamReader(httpContext.Request.Body); string bodyText = reader.ReadToEnd(); var body = JsonConvert.DeserializeObject <EdgeDeploymentDefinition>(bodyText); edgeDefinition = Option.Maybe(body); } } return(false); })) { var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri }); var cmd = new EdgeDeploymentCommand(Namespace, ResourceName, client, new[] { dockerModule }, Option.None <EdgeDeploymentDefinition>(), Runtime, configProvider.Object, EdgeletModuleOwner); await cmd.ExecuteAsync(CancellationToken.None); Assert.True(postSecretCalled, nameof(postSecretCalled)); Assert.True(postCrdCalled, nameof(postCrdCalled)); Assert.True(edgeDefinition.HasValue); var receivedEdgeDefinition = edgeDefinition.OrDefault(); var agentModule = receivedEdgeDefinition.Spec[0]; Assert.Equal(agentDeploymentImage, agentModule.Config.Image); } }
public async void CrdCommandExecuteWithAuthCreateNewObjects() { CombinedDockerConfig config = new CombinedDockerConfig("image", new Docker.Models.CreateContainerParameters(), Option.None <AuthConfig>()); IModule m1 = new DockerModule("module1", "v1", ModuleStatus.Running, Core.RestartPolicy.Always, Config1, ImagePullPolicy.OnCreate, DefaultConfigurationInfo, EnvVars); var km1 = new KubernetesModule((IModule <DockerConfig>)m1, config); KubernetesModule[] modules = { km1 }; var token = default(CancellationToken); var auth = new AuthConfig() { Username = "******", Password = "******", ServerAddress = "docker.io" }; var configProvider = new Mock <ICombinedConfigProvider <CombinedDockerConfig> >(); configProvider.Setup(cp => cp.GetCombinedConfig(km1, Runtime)).Returns(() => new CombinedDockerConfig("test-image:1", Config1.CreateOptions, Option.Maybe(auth))); bool getSecretCalled = false; bool postSecretCalled = false; bool getCrdCalled = false; bool postCrdCalled = 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)) { httpContext.Response.StatusCode = 404; if (pathStr.Contains($"api/v1/namespaces/{Namespace}/secrets")) { getSecretCalled = true; } else if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}")) { getCrdCalled = true; } } else if (string.Equals(method, "POST", StringComparison.OrdinalIgnoreCase)) { httpContext.Response.StatusCode = 201; httpContext.Response.Body = httpContext.Request.Body; if (pathStr.Contains($"api/v1/namespaces/{Namespace}/secrets")) { postSecretCalled = true; } else if (pathStr.Contains($"namespaces/{Namespace}/{Constants.EdgeDeployment.Plural}")) { postCrdCalled = true; } } return(Task.FromResult(false)); })) { var client = new Kubernetes( new KubernetesClientConfiguration { Host = server.Uri }); var cmd = new EdgeDeploymentCommand(Namespace, ResourceName, client, modules, Runtime, configProvider.Object); await cmd.ExecuteAsync(token); Assert.True(getSecretCalled, nameof(getSecretCalled)); Assert.True(postSecretCalled, nameof(postSecretCalled)); Assert.True(getCrdCalled, nameof(getCrdCalled)); Assert.True(postCrdCalled, nameof(postCrdCalled)); } }