diff --git a/Makefile b/Makefile index 91150740..5440b6bf 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ #include tests/e2e/Makefile -VERSION ?= 1.4.0 +VERSION ?= 1.4.1 # check if we are using MacOS or LINUX and use that to determine the sed command UNAME_S := $(shell uname -s) diff --git a/chart/Chart.yaml b/chart/Chart.yaml index b9f44c3d..f7a93d87 100644 --- a/chart/Chart.yaml +++ b/chart/Chart.yaml @@ -5,12 +5,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.4.0 +version: 1.4.1 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "v1.4.0" +appVersion: "v1.4.1" dependencies: - name: common repository: https://charts.bitnami.com/bitnami diff --git a/chart/values.yaml b/chart/values.yaml index 359984bb..cf73a368 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -4,7 +4,7 @@ image: repository: docker.io/uffizzi/uffizzi-cluster-operator - tag: v1.4.0 + tag: v1.4.1 # `flux` dependency values flux: helmController: diff --git a/controllers/uffizzicluster/conditions.go b/controllers/uffizzicluster/conditions.go index 613560a7..f03c3908 100644 --- a/controllers/uffizzicluster/conditions.go +++ b/controllers/uffizzicluster/conditions.go @@ -10,19 +10,22 @@ import ( // Condition types. const ( // TypeReady resources are believed to be ready to handle work. - TypeReady = "Ready" - TypeAPIReady = "APIReady" - TypeSleep = "Sleep" + TypeReady = "Ready" + TypeAPIReady = "APIReady" + TypeDataStoreReady = "DataStoreReady" + TypeSleep = "Sleep" ) // Reasons a resource is or is not ready. const ( - ReasonDefault = "Default" - ReasonInitializing = "Initializing" - ReasonReady = "APIReady" - ReasonNotReady = "APINotReady" - ReasonSleeping = "Sleeping" - ReasonAwoken = "Awoken" + ReasonDefault = "Default" + ReasonInitializing = "Initializing" + ReasonAPIReady = "APIReady" + ReasonAPINotReady = "APINotReady" + ReasonDataStoreReady = "DataStoreReady" + ReasonDataStoreNotReady = "DataStoreNotReady" + ReasonSleeping = "Sleeping" + ReasonAwoken = "Awoken" ) func Initializing() metav1.Condition { @@ -45,26 +48,56 @@ func InitializingAPI() metav1.Condition { } } +func InitializingDataStore() metav1.Condition { + return metav1.Condition{ + Type: TypeDataStoreReady, + Status: metav1.ConditionUnknown, + Reason: ReasonInitializing, + LastTransitionTime: metav1.Now(), + Message: "UffizziCluster is being initialized", + } +} + func APIReady() metav1.Condition { return metav1.Condition{ Type: TypeAPIReady, Status: metav1.ConditionTrue, - Reason: ReasonReady, + Reason: ReasonAPIReady, LastTransitionTime: metav1.Now(), Message: "UffizziCluster API is ready", } } +func DataStoreReady() metav1.Condition { + return metav1.Condition{ + Type: TypeDataStoreReady, + Status: metav1.ConditionTrue, + Reason: ReasonDataStoreReady, + LastTransitionTime: metav1.Now(), + Message: "UffizziCluster Datastore is ready", + } +} + func APINotReady() metav1.Condition { return metav1.Condition{ Type: TypeAPIReady, Status: metav1.ConditionFalse, - Reason: ReasonNotReady, + Reason: ReasonAPINotReady, LastTransitionTime: metav1.Now(), Message: "UffizziCluster API is not ready", } } +func DataStoreNotReady() metav1.Condition { + return metav1.Condition{ + Type: TypeDataStoreReady, + Status: metav1.ConditionFalse, + Reason: ReasonDataStoreNotReady, + LastTransitionTime: metav1.Now(), + Message: "UffizziCluster Datastore is not ready", + } +} + func DefaultSleepState() metav1.Condition { return metav1.Condition{ Type: TypeSleep, diff --git a/controllers/uffizzicluster/helm.go b/controllers/uffizzicluster/helm.go index 9b3126e5..4ba7bde8 100644 --- a/controllers/uffizzicluster/helm.go +++ b/controllers/uffizzicluster/helm.go @@ -341,12 +341,11 @@ func (r *UffizziClusterReconciler) upsertVClusterK3sHelmRelease(update bool, ctx if err := r.Create(ctx, newHelmRelease); err != nil { return nil, errors.Wrap(err, "failed to create HelmRelease") } - patch := client.MergeFrom(uCluster.DeepCopy()) uCluster.Status.LastAppliedHelmReleaseSpec = &newHelmReleaseSpec + patch := client.MergeFrom(uCluster.DeepCopy()) if err := r.Status().Patch(ctx, uCluster, patch); err != nil { return nil, errors.Wrap(err, "Failed to update the default UffizziCluster lastAppliedHelmReleaseSpec") } - } else if uCluster.Status.LastAppliedHelmReleaseSpec != nil { // create helm release if there is no existing helm release to update if update && *uCluster.Status.LastAppliedHelmReleaseSpec != newHelmReleaseSpec { @@ -542,12 +541,12 @@ func (r *UffizziClusterReconciler) updateHelmRelease(newHelmRelease *fluxhelmv2b } } } - + helmReleasePatch := client.MergeFrom(existingHelmRelease.DeepCopy()) newHelmRelease.Spec.Upgrade = &fluxhelmv2beta1.Upgrade{ Force: true, } existingHelmRelease.Spec = newHelmRelease.Spec - if err := r.Update(ctx, existingHelmRelease); err != nil { + if err := r.Client.Patch(ctx, existingHelmRelease, helmReleasePatch); err != nil { return errors.Wrap(err, "error while updating helm release") } // update the lastAppliedConfig @@ -561,10 +560,10 @@ func (r *UffizziClusterReconciler) updateHelmRelease(newHelmRelease *fluxhelmv2b } updatedSpec := string(updatedSpecBytes) updatedHelmReleaseSpec := string(updatedHelmReleaseSpecBytes) - patch := client.MergeFrom(uCluster.DeepCopy()) + uClusterPatch := client.MergeFrom(uCluster.DeepCopy()) uCluster.Status.LastAppliedConfiguration = &updatedSpec uCluster.Status.LastAppliedHelmReleaseSpec = &updatedHelmReleaseSpec - if err := r.Status().Patch(ctx, uCluster, patch); err != nil { + if err := r.Status().Patch(ctx, uCluster, uClusterPatch); err != nil { return errors.Wrap(err, "Failed to update the default UffizziCluster lastAppliedConfig") } return nil diff --git a/controllers/uffizzicluster/uffizzicluster_controller.go b/controllers/uffizzicluster/uffizzicluster_controller.go index 2d65675b..f2b6ed4e 100644 --- a/controllers/uffizzicluster/uffizzicluster_controller.go +++ b/controllers/uffizzicluster/uffizzicluster_controller.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "github.com/UffizziCloud/uffizzi-cluster-operator/controllers/constants" + uffizzicluster "github.com/UffizziCloud/uffizzi-cluster-operator/controllers/etcd" "github.com/fluxcd/pkg/apis/meta" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" @@ -128,6 +129,7 @@ func (r *UffizziClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque intialConditions = []metav1.Condition{ Initializing(), InitializingAPI(), + InitializingDataStore(), DefaultSleepState(), } helmReleaseRef = "" @@ -288,17 +290,27 @@ func (r *UffizziClusterReconciler) reconcileSleepState(ctx context.Context, uClu Namespace: uCluster.Namespace}, ucStatefulSet); err != nil { return err } + // get the etcd stateful set created by the helm chart + etcdStatefulSet := &appsv1.StatefulSet{} + if err := r.Get(ctx, types.NamespacedName{ + Name: uffizzicluster.BuildEtcdHelmReleaseName(uCluster), + Namespace: uCluster.Namespace}, etcdStatefulSet); err != nil { + return err + } // get the current replicas patch := client.MergeFrom(uCluster.DeepCopy()) currentReplicas := ucStatefulSet.Spec.Replicas // scale the vcluster instance to 0 if the sleep flag is true if uCluster.Spec.Sleep && *currentReplicas > 0 { - if err := r.scaleStatefulSet(ctx, ucStatefulSet, 0); err != nil { + if err := r.scaleStatefulSet(ctx, 0, ucStatefulSet, etcdStatefulSet); err != nil { return err } - if err := r.waitForStatefulSetReady(ctx, ucStatefulSet, 0); err == nil { + if err := r.waitForStatefulSetToScale(ctx, 0, ucStatefulSet); err == nil { setCondition(uCluster, APINotReady()) } + if err := r.waitForStatefulSetToScale(ctx, 0, etcdStatefulSet); err == nil { + setCondition(uCluster, DataStoreNotReady()) + } err := r.deleteWorkloads(ctx, uCluster) if err != nil { return err @@ -307,7 +319,7 @@ func (r *UffizziClusterReconciler) reconcileSleepState(ctx context.Context, uClu setCondition(uCluster, Sleeping(sleepingTime)) // if the current replicas is 0, then do nothing } else if !uCluster.Spec.Sleep && *currentReplicas == 0 { - if err := r.scaleStatefulSet(ctx, ucStatefulSet, 1); err != nil { + if err := r.scaleStatefulSet(ctx, 1, etcdStatefulSet, ucStatefulSet); err != nil { return err } } @@ -318,9 +330,12 @@ func (r *UffizziClusterReconciler) reconcileSleepState(ctx context.Context, uClu uCluster.Status.LastAwakeTime = lastAwakeTime // if the above runs successfully, then set the status to awake setCondition(uCluster, Awoken(lastAwakeTime)) - if err := r.waitForStatefulSetReady(ctx, ucStatefulSet, 1); err == nil { + if err := r.waitForStatefulSetToScale(ctx, 1, etcdStatefulSet); err == nil { setCondition(uCluster, APIReady()) } + if err := r.waitForStatefulSetToScale(ctx, 1, ucStatefulSet); err == nil { + setCondition(uCluster, DataStoreReady()) + } } if err := r.Status().Patch(ctx, uCluster, patch); err != nil { return err @@ -329,16 +344,20 @@ func (r *UffizziClusterReconciler) reconcileSleepState(ctx context.Context, uClu } // scaleStatefulSet scales the stateful set to the given scale -func (r *UffizziClusterReconciler) scaleStatefulSet(ctx context.Context, ucStatefulSet *appsv1.StatefulSet, scale int) error { +func (r *UffizziClusterReconciler) scaleStatefulSet(ctx context.Context, scale int, statefulSets ...*appsv1.StatefulSet) error { // if the current replicas is greater than 0, then scale down to 0 replicas := int32(scale) - // scale down to 0 - ucStatefulSet.Spec.Replicas = &replicas - return r.Update(ctx, ucStatefulSet) + for _, ss := range statefulSets { + ss.Spec.Replicas = &replicas + if err := r.Update(ctx, ss); err != nil { + return err + } + } + return nil } -// waitForStatefulSetReady is a goroutine which waits for the stateful set to be ready -func (r *UffizziClusterReconciler) waitForStatefulSetReady(ctx context.Context, ucStatefulSet *appsv1.StatefulSet, scale int) error { +// waitForStatefulSetToScale is a goroutine which waits for the stateful set to be ready +func (r *UffizziClusterReconciler) waitForStatefulSetToScale(ctx context.Context, scale int, ucStatefulSet *appsv1.StatefulSet) error { // wait for the StatefulSet to be ready return wait.PollImmediate(time.Second*5, time.Minute*1, func() (bool, error) { if err := r.Get(ctx, types.NamespacedName{ diff --git a/controllers/uffizzicluster/vcluster.go b/controllers/uffizzicluster/vcluster.go index d58c18af..95b78561 100644 --- a/controllers/uffizzicluster/vcluster.go +++ b/controllers/uffizzicluster/vcluster.go @@ -223,7 +223,7 @@ type VClusterToleration struct { } type VClusterStorage struct { - Persistence bool `json:"persistence,omitempty"` + Persistence bool `json:"persistence"` } func BuildVClusterHelmReleaseName(uCluster *v1alpha1.UffizziCluster) string {