From 0cc297b86a9ffd30d987845afc2a04c6ef2ce984 Mon Sep 17 00:00:00 2001 From: Abhisek Dwivedi Date: Tue, 9 Jul 2024 15:58:58 +0530 Subject: [PATCH] Added Printer columns --- api/v1beta1/aerospikebackup_types.go | 5 + api/v1beta1/aerospikebackupservice_types.go | 18 ++- api/v1beta1/aerospikerestore_types.go | 3 + .../asdb.aerospike.com_aerospikebackups.yaml | 12 +- ...aerospike.com_aerospikebackupservices.yaml | 22 ++- .../asdb.aerospike.com_aerospikerestores.yaml | 9 ++ .../samples/asdb_v1beta1_aerospikebackup.yaml | 4 +- controllers/backup-service/reconciler.go | 125 ++++++++++++++++-- ...n_aerospikebackups.asdb.aerospike.com.yaml | 12 +- ...pikebackupservices.asdb.aerospike.com.yaml | 22 ++- ..._aerospikerestores.asdb.aerospike.com.yaml | 9 ++ pkg/utils/utils.go | 3 +- 12 files changed, 224 insertions(+), 20 deletions(-) diff --git a/api/v1beta1/aerospikebackup_types.go b/api/v1beta1/aerospikebackup_types.go index 474c934c6..7edd57cc3 100644 --- a/api/v1beta1/aerospikebackup_types.go +++ b/api/v1beta1/aerospikebackup_types.go @@ -61,10 +61,15 @@ type OnDemandSpec struct { // AerospikeBackupStatus defines the observed state of AerospikeBackup type AerospikeBackupStatus struct { OnDemand []OnDemandSpec `json:"onDemand,omitempty"` + + // TODO: finalize the status and phase } //+kubebuilder:object:root=true //+kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Backup Service Name",type=string,JSONPath=`.spec.backupService.name` +// +kubebuilder:printcolumn:name="Backup Service Namespace",type=string,JSONPath=`.spec.backupService.namespace` +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // AerospikeBackup is the Schema for the aerospikebackup API type AerospikeBackup struct { diff --git a/api/v1beta1/aerospikebackupservice_types.go b/api/v1beta1/aerospikebackupservice_types.go index 32012f393..a0b0efc72 100644 --- a/api/v1beta1/aerospikebackupservice_types.go +++ b/api/v1beta1/aerospikebackupservice_types.go @@ -23,8 +23,15 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. +// +kubebuilder:validation:Enum=InProgress;Completed;Failed +type AerospikeBackupServicePhase string + +// These are the valid phases of Aerospike Backup Service reconcile flow. +const ( + AerospikeBackupServiceInProgress AerospikeBackupServicePhase = "InProgress" + AerospikeBackupServiceCompleted AerospikeBackupServicePhase = "Completed" + AerospikeBackupServiceFailed AerospikeBackupServicePhase = "Failed" +) // AerospikeBackupServiceSpec defines the desired state of AerospikeBackupService // @@ -58,12 +65,19 @@ type AerospikeBackupServiceStatus struct { // Backup service config hash ConfigHash string `json:"configHash"` + // Backup service phase + Phase AerospikeBackupServicePhase `json:"phase,omitempty"` + // Backup service listening port Port int32 `json:"port"` } //+kubebuilder:object:root=true //+kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Image",type=string,JSONPath=`.spec.image` +// +kubebuilder:printcolumn:name="Service Type",type=string,JSONPath=`.spec.service.type` +//+kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase` +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // AerospikeBackupService is the Schema for the aerospikebackupservices API type AerospikeBackupService struct { diff --git a/api/v1beta1/aerospikerestore_types.go b/api/v1beta1/aerospikerestore_types.go index 6f852e8ef..7075284f7 100644 --- a/api/v1beta1/aerospikerestore_types.go +++ b/api/v1beta1/aerospikerestore_types.go @@ -75,7 +75,10 @@ type AerospikeRestoreStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Backup Service Name",type=string,JSONPath=`.spec.backupService.name` +// +kubebuilder:printcolumn:name="Backup Service Namespace",type=string,JSONPath=`.spec.backupService.namespace` //+kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.restoreResult.status` +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // AerospikeRestore is the Schema for the aerospikerestores API // diff --git a/config/crd/bases/asdb.aerospike.com_aerospikebackups.yaml b/config/crd/bases/asdb.aerospike.com_aerospikebackups.yaml index c1c77dc50..fd5bc6256 100644 --- a/config/crd/bases/asdb.aerospike.com_aerospikebackups.yaml +++ b/config/crd/bases/asdb.aerospike.com_aerospikebackups.yaml @@ -14,7 +14,17 @@ spec: singular: aerospikebackup scope: Namespaced versions: - - name: v1beta1 + - additionalPrinterColumns: + - jsonPath: .spec.backupService.name + name: Backup Service Name + type: string + - jsonPath: .spec.backupService.namespace + name: Backup Service Namespace + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 schema: openAPIV3Schema: description: AerospikeBackup is the Schema for the aerospikebackup API diff --git a/config/crd/bases/asdb.aerospike.com_aerospikebackupservices.yaml b/config/crd/bases/asdb.aerospike.com_aerospikebackupservices.yaml index 1b4e23c90..150bfda7c 100644 --- a/config/crd/bases/asdb.aerospike.com_aerospikebackupservices.yaml +++ b/config/crd/bases/asdb.aerospike.com_aerospikebackupservices.yaml @@ -14,7 +14,20 @@ spec: singular: aerospikebackupservice scope: Namespaced versions: - - name: v1beta1 + - additionalPrinterColumns: + - jsonPath: .spec.image + name: Image + type: string + - jsonPath: .spec.service.type + name: Service Type + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 schema: openAPIV3Schema: description: AerospikeBackupService is the Schema for the aerospikebackupservices @@ -163,6 +176,13 @@ spec: contextPath: description: Backup Service API context path type: string + phase: + description: Backup service phase + enum: + - InProgress + - Completed + - Failed + type: string port: description: Backup service listening port format: int32 diff --git a/config/crd/bases/asdb.aerospike.com_aerospikerestores.yaml b/config/crd/bases/asdb.aerospike.com_aerospikerestores.yaml index 2ab236b63..a5039ff08 100644 --- a/config/crd/bases/asdb.aerospike.com_aerospikerestores.yaml +++ b/config/crd/bases/asdb.aerospike.com_aerospikerestores.yaml @@ -15,9 +15,18 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: + - jsonPath: .spec.backupService.name + name: Backup Service Name + type: string + - jsonPath: .spec.backupService.namespace + name: Backup Service Namespace + type: string - jsonPath: .status.restoreResult.status name: Status type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date name: v1beta1 schema: openAPIV3Schema: diff --git a/config/samples/asdb_v1beta1_aerospikebackup.yaml b/config/samples/asdb_v1beta1_aerospikebackup.yaml index 858b6bd90..7a7df8676 100644 --- a/config/samples/asdb_v1beta1_aerospikebackup.yaml +++ b/config/samples/asdb_v1beta1_aerospikebackup.yaml @@ -13,8 +13,8 @@ spec: name: aerospikebackupservice-sample namespace: aerospike onDemand: - id: first-ad-hoc-backup - routineName: test-routine + - id: first-ad-hoc-backup + routineName: test-routine config: aerospike-cluster: test-cluster: diff --git a/controllers/backup-service/reconciler.go b/controllers/backup-service/reconciler.go index 987f37fe9..fcde6ff62 100644 --- a/controllers/backup-service/reconciler.go +++ b/controllers/backup-service/reconciler.go @@ -3,6 +3,7 @@ package backupservice import ( "context" "fmt" + "time" "github.com/go-logr/logr" app "k8s.io/api/apps/v1" @@ -12,11 +13,13 @@ import ( "k8s.io/apimachinery/pkg/labels" k8sRuntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/rest" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/yaml" asdbv1beta1 "github.com/aerospike/aerospike-kubernetes-operator/api/v1beta1" @@ -49,15 +52,35 @@ type SingleBackupServiceReconciler struct { } func (r *SingleBackupServiceReconciler) Reconcile() (result ctrl.Result, recErr error) { + // Set the status phase to Error if the recErr is not nil + // recErr is only set when reconcile failure should result in Error phase of the Backup service operation + defer func() { + if recErr != nil { + r.Log.Error(recErr, "Reconcile failed") + + if err := r.setStatusPhase(asdbv1beta1.AerospikeBackupServiceFailed); err != nil { + recErr = err + } + } + }() + + // Set the status to AerospikeClusterInProgress before starting any operations + if err := r.setStatusPhase(asdbv1beta1.AerospikeBackupServiceInProgress); err != nil { + return reconcile.Result{}, err + } + if err := r.reconcileConfigMap(); err != nil { + recErr = err return ctrl.Result{}, err } if err := r.reconcileDeployment(); err != nil { + recErr = err return ctrl.Result{}, err } if err := r.reconcileService(); err != nil { + recErr = err return ctrl.Result{}, err } @@ -202,7 +225,7 @@ func (r *SingleBackupServiceReconciler) reconcileDeployment() error { return fmt.Errorf("failed to deploy Backup service deployment: %v", err) } - return nil + return r.waitForDeploymentToBeReady() } r.Log.Info( @@ -225,7 +248,7 @@ func (r *SingleBackupServiceReconciler) reconcileDeployment() error { if oldResourceVersion != deploy.ResourceVersion { r.Log.Info("Deployment spec is updated, will result in rolling restart") - return nil + return r.waitForDeploymentToBeReady() } hash, err := utils.GetHash(string(r.aeroBackupService.Spec.Config.Raw)) @@ -237,14 +260,7 @@ func (r *SingleBackupServiceReconciler) reconcileDeployment() error { if r.aeroBackupService.Status.ConfigHash != "" && hash != r.aeroBackupService.Status.ConfigHash { r.Log.Info("Config hash is updated, will result in rolling restart") - var podList corev1.PodList - - labelSelector := labels.SelectorFromSet(utils.LabelsForAerospikeBackupService(r.aeroBackupService.Name)) - listOps := &client.ListOptions{ - Namespace: r.aeroBackupService.Namespace, LabelSelector: labelSelector, - } - - err = r.Client.List(context.TODO(), &podList, listOps) + podList, err := r.getBackupServicePodList() if err != nil { return err } @@ -258,7 +274,7 @@ func (r *SingleBackupServiceReconciler) reconcileDeployment() error { } } - return nil + return r.waitForDeploymentToBeReady() } return nil @@ -268,6 +284,21 @@ func getBackupServiceName(aeroBackupService *asdbv1beta1.AerospikeBackupService) return types.NamespacedName{Name: aeroBackupService.Name, Namespace: aeroBackupService.Namespace} } +func (r *SingleBackupServiceReconciler) getBackupServicePodList() (*corev1.PodList, error) { + var podList corev1.PodList + + labelSelector := labels.SelectorFromSet(utils.LabelsForAerospikeBackupService(r.aeroBackupService.Name)) + listOps := &client.ListOptions{ + Namespace: r.aeroBackupService.Namespace, LabelSelector: labelSelector, + } + + if err := r.Client.List(context.TODO(), &podList, listOps); err != nil { + return nil, err + } + + return &podList, nil +} + func (r *SingleBackupServiceReconciler) getDeploymentObject() (*app.Deployment, error) { svcLabels := utils.LabelsForAerospikeBackupService(r.aeroBackupService.Name) volumeMounts, volumes := r.getVolumeAndMounts() @@ -534,6 +565,77 @@ func (r *SingleBackupServiceReconciler) getBackupServiceConfig() (*serviceConfig return &svcConfig, nil } +func (r *SingleBackupServiceReconciler) waitForDeploymentToBeReady() error { + const ( + podStatusTimeout = 2 * time.Minute + podStatusRetryInterval = time.Second * 5 + ) + + r.Log.Info( + "Waiting for deployment to be ready", "WaitTimePerPod", podStatusTimeout, + ) + + if err := wait.PollUntilContextTimeout(context.TODO(), + podStatusRetryInterval, podStatusTimeout, true, func(ctx context.Context) (done bool, err error) { + podList, err := r.getBackupServicePodList() + if err != nil { + return false, err + } + + if len(podList.Items) == 0 { + return false, fmt.Errorf("no pod found for deployment") + } + + for idx := range podList.Items { + pod := &podList.Items[idx] + + if err := utils.CheckPodFailed(pod); err != nil { + return false, fmt.Errorf("pod %s failed: %v", pod.Name, err) + } + + if !utils.IsPodRunningAndReady(pod) { + r.Log.Info("Pod is not ready", "pod", pod.Name) + return false, nil + } + } + + var deploy app.Deployment + if err := r.Client.Get( + ctx, + types.NamespacedName{Name: r.aeroBackupService.Name, Namespace: r.aeroBackupService.Namespace}, + &deploy, + ); err != nil { + return false, err + } + + if deploy.Status.Replicas != *deploy.Spec.Replicas { + return false, fmt.Errorf("deployment status is not updated") + } + + return true, nil + }, + ); err != nil { + return err + } + + r.Log.Info("Deployment is ready") + + return nil +} + +func (r *SingleBackupServiceReconciler) setStatusPhase(phase asdbv1beta1.AerospikeBackupServicePhase) error { + if r.aeroBackupService.Status.Phase != phase { + r.aeroBackupService.Status.Phase = phase + + if err := r.Client.Status().Update(context.Background(), r.aeroBackupService); err != nil { + r.Log.Error(err, fmt.Sprintf("Failed to set restore status to %s", phase)) + return err + } + } + + return nil +} + func (r *SingleBackupServiceReconciler) updateStatus() error { svcConfig, err := r.getBackupServiceConfig() if err != nil { @@ -548,6 +650,7 @@ func (r *SingleBackupServiceReconciler) updateStatus() error { r.aeroBackupService.Status.ContextPath = svcConfig.contextPath r.aeroBackupService.Status.Port = svcConfig.portInfo[common.HTTPKey] r.aeroBackupService.Status.ConfigHash = hash + r.aeroBackupService.Status.Phase = asdbv1beta1.AerospikeBackupServiceCompleted r.Log.Info(fmt.Sprintf("Updating status: %+v", r.aeroBackupService.Status)) diff --git a/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackups.asdb.aerospike.com.yaml b/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackups.asdb.aerospike.com.yaml index c1c77dc50..fd5bc6256 100644 --- a/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackups.asdb.aerospike.com.yaml +++ b/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackups.asdb.aerospike.com.yaml @@ -14,7 +14,17 @@ spec: singular: aerospikebackup scope: Namespaced versions: - - name: v1beta1 + - additionalPrinterColumns: + - jsonPath: .spec.backupService.name + name: Backup Service Name + type: string + - jsonPath: .spec.backupService.namespace + name: Backup Service Namespace + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 schema: openAPIV3Schema: description: AerospikeBackup is the Schema for the aerospikebackup API diff --git a/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackupservices.asdb.aerospike.com.yaml b/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackupservices.asdb.aerospike.com.yaml index 1b4e23c90..150bfda7c 100644 --- a/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackupservices.asdb.aerospike.com.yaml +++ b/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikebackupservices.asdb.aerospike.com.yaml @@ -14,7 +14,20 @@ spec: singular: aerospikebackupservice scope: Namespaced versions: - - name: v1beta1 + - additionalPrinterColumns: + - jsonPath: .spec.image + name: Image + type: string + - jsonPath: .spec.service.type + name: Service Type + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 schema: openAPIV3Schema: description: AerospikeBackupService is the Schema for the aerospikebackupservices @@ -163,6 +176,13 @@ spec: contextPath: description: Backup Service API context path type: string + phase: + description: Backup service phase + enum: + - InProgress + - Completed + - Failed + type: string port: description: Backup service listening port format: int32 diff --git a/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikerestores.asdb.aerospike.com.yaml b/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikerestores.asdb.aerospike.com.yaml index 2ab236b63..a5039ff08 100644 --- a/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikerestores.asdb.aerospike.com.yaml +++ b/helm-charts/aerospike-kubernetes-operator/crds/customresourcedefinition_aerospikerestores.asdb.aerospike.com.yaml @@ -15,9 +15,18 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: + - jsonPath: .spec.backupService.name + name: Backup Service Name + type: string + - jsonPath: .spec.backupService.namespace + name: Backup Service Namespace + type: string - jsonPath: .status.restoreResult.status name: Status type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date name: v1beta1 schema: openAPIV3Schema: diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 4bc34efe4..bb5d46bb5 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -13,6 +13,7 @@ import ( "k8s.io/apimachinery/pkg/types" asdbv1 "github.com/aerospike/aerospike-kubernetes-operator/api/v1" + "github.com/aerospike/aerospike-kubernetes-operator/controllers/common" ) const ( @@ -146,7 +147,7 @@ func LabelsForPodAntiAffinity(clName string) map[string]string { // belonging to the given AerospikeBackupService CR name. func LabelsForAerospikeBackupService(clName string) map[string]string { return map[string]string{ - asdbv1.AerospikeAppLabel: "aerospike-backup-service", + asdbv1.AerospikeAppLabel: common.AerospikeBackupService, asdbv1.AerospikeCustomResourceLabel: clName, } }