/// <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); }
/// <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) ); }