Skip to content

Commit

Permalink
improvement: apply sleep to etcd store (#66)
Browse files Browse the repository at this point in the history
* improvement: apply sleep to etcd store

* release: 1.4.1
  • Loading branch information
waveywaves authored Oct 25, 2023
1 parent e66b443 commit 6603aef
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 32 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
4 changes: 2 additions & 2 deletions chart/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
55 changes: 44 additions & 11 deletions controllers/uffizzicluster/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand Down
11 changes: 5 additions & 6 deletions controllers/uffizzicluster/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
39 changes: 29 additions & 10 deletions controllers/uffizzicluster/uffizzicluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -128,6 +129,7 @@ func (r *UffizziClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque
intialConditions = []metav1.Condition{
Initializing(),
InitializingAPI(),
InitializingDataStore(),
DefaultSleepState(),
}
helmReleaseRef = ""
Expand Down Expand Up @@ -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
Expand All @@ -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
}
}
Expand All @@ -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
Expand All @@ -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{
Expand Down
2 changes: 1 addition & 1 deletion controllers/uffizzicluster/vcluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 6603aef

Please sign in to comment.