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());
        }
Esempio n. 3
0
        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"))));
            }
        }
Esempio n. 4
0
        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));
            }
        }
Esempio n. 5
0
        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);
            }
        }
Esempio n. 6
0
        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"))));
        }
Esempio n. 11
0
        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));
            }
        }
Esempio n. 14
0
        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);
            }
        }
Esempio n. 15
0
        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));
            }
        }