/// <summary>
        ///     Ensure that a Deployment resource exists for the specified database server.
        /// </summary>
        /// <returns>
        ///     The Deployment resource, as a <see cref="DeploymentV1Beta1"/>.
        /// </returns>
        public async Task <DeploymentV1Beta1> EnsureDeploymentPresent()
        {
            RequireCurrentState();

            DeploymentV1Beta1 existingDeployment = await FindDeployment();

            if (existingDeployment != null)
            {
                Log.LogInformation("Found existing deployment {DeploymentName} for server {ServerId}.",
                                   existingDeployment.Metadata.Name,
                                   State.Id
                                   );

                return(existingDeployment);
            }

            Log.LogInformation("Creating deployment for server {ServerId}...",
                               State.Id
                               );

            DeploymentV1Beta1 createdDeployment = await KubeClient.DeploymentsV1Beta1().Create(
                KubeResources.Deployment(State,
                                         kubeNamespace: KubeOptions.KubeNamespace
                                         )
                );

            Log.LogInformation("Successfully created deployment {DeploymentName} for server {ServerId}.",
                               createdDeployment.Metadata.Name,
                               State.Id
                               );

            return(createdDeployment);
        }
        /// <summary>
        ///     Request update (PATCH) of a <see cref="Deployment"/>.
        /// </summary>
        /// <param name="deployment">
        ///     A <see cref="DeploymentV1Beta1"/> representing the Deployment to update.
        /// </param>
        /// <param name="cancellationToken">
        ///     An optional <see cref="CancellationToken"/> that can be used to cancel the request.
        /// </param>
        /// <returns>
        ///     A <see cref="DeploymentV1Beta1"/> representing the current state for the newly-created Deployment.
        /// </returns>
        public async Task <DeploymentV1Beta1> Update(DeploymentV1Beta1 deployment, CancellationToken cancellationToken = default)
        {
            if (deployment == null)
            {
                throw new ArgumentNullException(nameof(deployment));
            }

            if (String.IsNullOrWhiteSpace(deployment.Metadata?.Name))
            {
                throw new ArgumentException("Cannot update Deployment without a value for its Metadata.Name property.", nameof(deployment));
            }

            if (String.IsNullOrWhiteSpace(deployment.Metadata?.Namespace))
            {
                throw new ArgumentException("Cannot update Deployment without a value for its Metadata.Namespace property.", nameof(deployment));
            }

            return(await Http
                   .PatchAsync(
                       Requests.ByName.WithTemplateParameters(new
            {
                Name = deployment.Metadata.Name,
                Namespace = deployment.Metadata.Namespace
            }),
                       patchBody : deployment,
                       mediaType : PatchMediaType,
                       cancellationToken : cancellationToken
                       )
                   .ReadContentAsAsync <DeploymentV1Beta1, StatusV1>());
        }
        private DeploymentV1Beta1 GetDeployment(string name, string image, double cpu, int memory, string port, string nameSpace, int min)
        {
            var deployment = new DeploymentV1Beta1();
            var metadata   = new ObjectMetaV1();

            metadata.Namespace = nameSpace;

            var labels = new System.Collections.Generic.Dictionary <string, string>();

            labels.Add("app", name);

            metadata.Labels = labels;

            deployment.Metadata      = metadata;
            deployment.Metadata.Name = name;

            deployment.Spec                         = new DeploymentSpecV1Beta1();
            deployment.Spec.Replicas                = min;
            deployment.Spec.Selector                = new LabelSelectorV1();
            deployment.Spec.Selector.MatchLabels    = labels;
            deployment.Spec.ProgressDeadlineSeconds = 60;

            deployment.Spec.Template                 = new PodTemplateSpecV1();
            deployment.Spec.Template.Metadata        = new ObjectMetaV1();
            deployment.Spec.Template.Metadata.Labels = labels;

            deployment.Spec.Template.Spec            = new PodSpecV1();
            deployment.Spec.Template.Spec.Containers = new List <ContainerV1>();

            deployment.Spec.Template.Spec.Containers.Add(new ContainerV1()
            {
                Name      = name,
                Image     = image,
                Resources = new ResourceRequirementsV1()
                {
                    Requests = new Dictionary <string, string>()
                    {
                        { "cpu", cpu.ToString() },
                        { "memory", $"{memory}Mi" }
                    }
                }
            });

            if (!string.IsNullOrEmpty(port))
            {
                deployment.Spec.Template.Spec.Containers[0].Ports = new List <ContainerPortV1>()
                {
                    new ContainerPortV1()
                    {
                        ContainerPort = int.Parse(port)
                    }
                };
            }

            return(deployment);
        }
Exemple #4
0
        /// <summary>
        ///     Request creation of a <see cref="DeploymentV1Beta1"/>.
        /// </summary>
        /// <param name="newDeployment">
        ///     A <see cref="DeploymentV1Beta1"/> representing the Deployment to create.
        /// </param>
        /// <param name="cancellationToken">
        ///     An optional <see cref="CancellationToken"/> that can be used to cancel the request.
        /// </param>
        /// <returns>
        ///     A <see cref="DeploymentV1Beta1"/> representing the current state for the newly-created Deployment.
        /// </returns>
        public async Task <DeploymentV1Beta1> Create(DeploymentV1Beta1 newDeployment, CancellationToken cancellationToken = default)
        {
            if (newDeployment == null)
            {
                throw new ArgumentNullException(nameof(newDeployment));
            }

            return(await Http
                   .PostAsJsonAsync(
                       Requests.Collection.WithTemplateParameters(new
            {
                Namespace = newDeployment?.Metadata?.Namespace ?? KubeClient.DefaultNamespace
            }),
                       postBody : newDeployment,
                       cancellationToken : cancellationToken
                       )
                   .ReadContentAsAsync <DeploymentV1Beta1, StatusV1>());
        }
        /// <summary>
        ///     Ensure that a Deployment resource does not exist for the specified database server.
        /// </summary>
        /// <returns>
        ///     <c>true</c>, if the controller is now absent; otherwise, <c>false</c>.
        /// </returns>
        public async Task <bool> EnsureDeploymentAbsent()
        {
            RequireCurrentState();

            DeploymentV1Beta1 controller = await FindDeployment();

            if (controller == null)
            {
                return(true);
            }

            Log.LogInformation("Deleting deployment {DeploymentName} for server {ServerId}...",
                               controller.Metadata.Name,
                               State.Id
                               );

            try
            {
                await KubeClient.DeploymentsV1Beta1().Delete(
                    name: controller.Metadata.Name,
                    kubeNamespace: KubeOptions.KubeNamespace,
                    propagationPolicy: DeletePropagationPolicy.Background
                    );
            }
            catch (HttpRequestException <StatusV1> deleteFailed)
            {
                Log.LogError("Failed to delete deployment {DeploymentName} for server {ServerId} (Message:{FailureMessage}, Reason:{FailureReason}).",
                             controller.Metadata.Name,
                             State.Id,
                             deleteFailed.Response.Message,
                             deleteFailed.Response.Reason
                             );

                return(false);
            }

            Log.LogInformation("Deleted deployment {DeploymentName} for server {ServerId}.",
                               controller.Metadata.Name,
                               State.Id
                               );

            return(true);
        }
        /// <summary>
        ///     Called when the actor is waiting for the server's Deployment to indicate that all replicas are Available.
        /// </summary>
        void WaitForServerAvailable()
        {
            Log.Info("Waiting for server {ServerId}'s Deployment to become Available...", ServerId);

            StartPolling(Signal.PollDeployment);

            ReceiveAsync <Signal>(async signal =>
            {
                string actionDescription;
                switch (Provisioner.State.Action)
                {
                case ProvisioningAction.Provision:
                    {
                        actionDescription = "Provisioning";

                        break;
                    }

                case ProvisioningAction.Reconfigure:
                    {
                        actionDescription = "Reconfiguration";

                        break;
                    }

                case ProvisioningAction.Deprovision:
                    {
                        actionDescription = "De-provisioning";

                        break;
                    }

                default:
                    {
                        return;
                    }
                }

                switch (signal)
                {
                case Signal.PollDeployment:
                    {
                        if (Provisioner.State.Status == ProvisioningStatus.Ready)
                        {
                            Become(Ready);

                            break;
                        }

                        DeploymentV1Beta1 deployment = await Provisioner.FindDeployment();
                        if (deployment == null)
                        {
                            Log.Warning("{Action} failed - cannot find Deployment for server {ServerId}.", actionDescription, ServerId);

                            FailCurrentAction(
                                reason: $"Cannot find server's associated Deployment in Kubernetes."
                                );

                            Become(Ready);
                        }
                        else if (deployment.Status.AvailableReplicas == deployment.Status.Replicas)
                        {
                            Log.Info("Server {ServerID} is now available ({AvailableReplicaCount} of {ReplicaCount} replicas are marked as Available).",
                                     ServerId,
                                     deployment.Status.AvailableReplicas,
                                     deployment.Status.Replicas
                                     );

                            // We're done with the Deployment now that it's marked as Available, so we're ready to initialise the server configuration.
                            if (Provisioner.State.Action == ProvisioningAction.Provision)
                            {
                                StartProvisioningPhase(ServerProvisioningPhase.Configuration);
                            }
                            else if (Provisioner.State.Action == ProvisioningAction.Reconfigure)
                            {
                                StartReconfigurationPhase(ServerProvisioningPhase.Configuration);
                            }
                            else
                            {
                                Log.Error("WaitForServerAvailable: Unexpected provisioning action '{Action}' on server {ServerId}.",
                                          Provisioner.State.Action,
                                          Provisioner.State.Id
                                          );

                                FailCurrentAction(
                                    reason: $"Server has unexpected provisioning action ({Provisioner.State.Action})."
                                    );

                                return;
                            }

                            Become(Ready);
                        }
                        else
                        {
                            Log.Debug("Server {ServerID} is not available yet ({AvailableReplicaCount} of {ReplicaCount} replicas are marked as Available).",
                                      ServerId,
                                      deployment.Status.AvailableReplicas,
                                      deployment.Status.Replicas
                                      );
                        }

                        break;
                    }

                case Signal.Timeout:
                    {
                        Log.Warning("{Action} failed - timed out waiting server {ServerId}'s Deployment to become ready.", actionDescription, ServerId);

                        FailCurrentAction(
                            reason: "Timed out waiting for server's associated Deployment in Kubernetes to become available."
                            );

                        Become(Ready);

                        break;
                    }

                default:
                    {
                        Unhandled(signal);

                        break;
                    }
                }
            });
            Receive <DatabaseServer>(_ =>
            {
                Log.Debug("Ignoring DatabaseServer state message (waiting for server's Deployment to become Available).'");
            });
            Receive <Terminated>(
                terminated => HandleTermination(terminated)
                );
        }