Skip to content

Commit

Permalink
feat: status for sleep (#51)
Browse files Browse the repository at this point in the history
* feat: status for sleep

* messages for sleep conditions

* feat: release 1.3
  • Loading branch information
waveywaves authored Sep 9, 2023
1 parent 6db9272 commit eaeb1d9
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 31 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.3.0
VERSION ?= 1.3.1

# CHANNELS define the bundle channels used in the bundle.
# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")
Expand Down
1 change: 1 addition & 0 deletions api/v1alpha1/uffizzicluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ type UffizziClusterStatus struct {
Host *string `json:"host,omitempty"`
LastAppliedConfiguration *string `json:"lastAppliedConfiguration,omitempty"`
LastAppliedHelmReleaseSpec *string `json:"lastAppliedHelmReleaseSpec,omitempty"`
LastAwakeTime *string `json:"lastAwakeTime,omitempty"`
}

// VClusterKubeConfig is the KubeConfig SecretReference of the related VCluster
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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.3.0
version: 1.3.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.3.0"
appVersion: "v1.3.1"
dependencies:
- name: common
repository: https://charts.bitnami.com/bitnami
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ spec:
type: string
lastAppliedHelmReleaseSpec:
type: string
lastAwakeTime:
type: string
type: object
type: object
served: true
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.3.0
tag: v1.3.1

# `flux` dependency values
flux:
Expand Down
2 changes: 2 additions & 0 deletions config/crd/bases/uffizzi.com_uffizziclusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ spec:
type: string
lastAppliedHelmReleaseSpec:
type: string
lastAwakeTime:
type: string
type: object
type: object
served: true
Expand Down
91 changes: 88 additions & 3 deletions controllers/conditions.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,100 @@
package controllers

import (
uclusteruffizzicomv1alpha1 "github.com/UffizziCloud/uffizzi-cluster-operator/api/v1alpha1"
fluxhelmv2beta1 "github.com/fluxcd/helm-controller/api/v2beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func buildInitializingCondition() metav1.Condition {
// Condition types.
const (
// TypeReady resources are believed to be ready to handle work.
TypeReady = "Ready"
TypeSleep = "Sleep"
)

// Reasons a resource is or is not ready.
const (
ReasonInitializing = "Initializing"
ReasonSleeping = "Sleeping"
ReasonAwoken = "Awoken"
)

func Initializing() metav1.Condition {
return metav1.Condition{
Type: "Ready",
Type: TypeReady,
Status: metav1.ConditionUnknown,
Reason: "Initializing",
Reason: ReasonInitializing,
LastTransitionTime: metav1.Now(),
Message: "UffizziCluster is being initialized",
}
}

func Sleeping(time metav1.Time) metav1.Condition {
return metav1.Condition{
Type: TypeSleep,
Status: metav1.ConditionTrue,
Reason: ReasonSleeping,
LastTransitionTime: time,
Message: "UffizziCluster put to sleep manually",
}
}

func Awoken(time metav1.Time) metav1.Condition {
return metav1.Condition{
Type: TypeSleep,
Status: metav1.ConditionFalse,
Reason: ReasonAwoken,
LastTransitionTime: time,
Message: "UffizziCluster awoken manually",
}
}

func mirrorHelmReleaseConditions(helmRelease *fluxhelmv2beta1.HelmRelease, uCluster *uclusteruffizzicomv1alpha1.UffizziCluster) {
uClusterConditions := []metav1.Condition{}
for _, c := range helmRelease.Status.Conditions {
helmMessage := "[HelmRelease] " + c.Message
uClusterCondition := c
uClusterCondition.Message = helmMessage
uClusterConditions = append(uClusterConditions, uClusterCondition)
}
setConditions(uCluster, uClusterConditions...)
}

// setConditions sets the supplied conditions, replacing any existing conditions
// of the same type. This is a no-op if all supplied conditions are identical,
// ignoring the last transition time, to those already set.
func setConditions(uCluster *uclusteruffizzicomv1alpha1.UffizziCluster, c ...metav1.Condition) {
for _, new := range c {
exists := false
for i, existing := range uCluster.Status.Conditions {
if existing.Type != new.Type {
continue
}
if conditionsEqual(existing, new) {
exists = true
continue
}
uCluster.Status.Conditions[i] = new
exists = true
}
if !exists {
uCluster.Status.Conditions = append(uCluster.Status.Conditions, new)
}
}
}

func setCondition(uCluster *uclusteruffizzicomv1alpha1.UffizziCluster, c metav1.Condition) {
setConditions(uCluster, c)
}

// Equal returns true if the condition is identical to the supplied condition,
// ignoring the LastTransitionTime.
//
//nolint:gocritic // just a few bytes too heavy
func conditionsEqual(c, other metav1.Condition) bool {
return c.Type == other.Type &&
c.Status == other.Status &&
c.Reason == other.Reason &&
c.Message == other.Message
}
2 changes: 1 addition & 1 deletion controllers/conditions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
)

func TestBuildInitializingCondition(t *testing.T) {
initCondition := buildInitializingCondition()
initCondition := Initializing()
if initCondition.Type != "Ready" {
t.Errorf("Expected Ready, got %s", initCondition.Type)
}
Expand Down
57 changes: 34 additions & 23 deletions controllers/uffizzicluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,19 +145,21 @@ func (r *UffizziClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque
if len(uCluster.Status.Conditions) == 0 {
var (
intialConditions = []metav1.Condition{
buildInitializingCondition(),
Initializing(),
}
helmReleaseRef = ""
host = ""
kubeConfig = uclusteruffizzicomv1alpha1.VClusterKubeConfig{
SecretRef: &meta.SecretKeyReference{},
}
lastAwakeTime = metav1.Now().String()
)
uCluster.Status = uclusteruffizzicomv1alpha1.UffizziClusterStatus{
Conditions: intialConditions,
HelmReleaseRef: &helmReleaseRef,
Host: &host,
KubeConfig: kubeConfig,
LastAwakeTime: &lastAwakeTime,
}
if err := r.Status().Update(ctx, uCluster); err != nil {
logger.Error(err, "Failed to update the default UffizziCluster status")
Expand Down Expand Up @@ -228,14 +230,7 @@ func (r *UffizziClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque
} else {
lifecycleOpType = LIFECYCLE_OP_TYPE_UPDATE
// if helm release already exists then replicate the status conditions onto the uffizzicluster object
uClusterConditions := []metav1.Condition{}
for _, c := range helmRelease.Status.Conditions {
helmMessage := "[HelmRelease] " + c.Message
uClusterCondition := c
uClusterCondition.Message = helmMessage
uClusterConditions = append(uClusterConditions, uClusterCondition)
}
uCluster.Status.Conditions = uClusterConditions
mirrorHelmReleaseConditions(helmRelease, uCluster)
if err := r.Status().Update(ctx, uCluster); err != nil {
//logger.Error(err, "Failed to update UffizziCluster status")
return ctrl.Result{RequeueAfter: time.Second * 5}, err
Expand Down Expand Up @@ -286,39 +281,55 @@ func (r *UffizziClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque
// ----------------------
// UCLUSTER SLEEP
// ----------------------
if err := r.reconcileSleepState(ctx, uCluster); err != nil {
if k8serrors.IsNotFound(err) {
logger.Info("vcluster statefulset not found, requeueing")
return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil
}
logger.Error(err, "Failed to reconcile sleep state")
return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil
}

// Requeue the request to check the helm release status
return ctrl.Result{Requeue: true}, nil
}

func (r *UffizziClusterReconciler) reconcileSleepState(ctx context.Context, uCluster *uclusteruffizzicomv1alpha1.UffizziCluster) error {
// get the stateful set created by the helm chart
ucStatefulSet := &appsv1.StatefulSet{}
if err := r.Get(ctx, types.NamespacedName{
Name: BuildVClusterHelmReleaseName(uCluster),
Namespace: req.NamespacedName.Namespace}, ucStatefulSet); err != nil {
logger.Error(err, "Failed to get UffizziCluster StatefulSet")
return ctrl.Result{}, err
Namespace: uCluster.Namespace}, ucStatefulSet); err != nil {
return err
}
// get the current replicas
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 {
logger.Error(err, "Failed to scale down UffizziCluster StatefulSet")
return ctrl.Result{}, err
return err
}
logger.Info("UffizziCluster StatefulSet scaled down to 0")
err := r.deleteWorkloads(ctx, uCluster)
if err != nil {
logger.Error(err, "Failed to delete vcluster workloads")
return ctrl.Result{}, err
return err
}
sleepingTime := metav1.Now()
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 {
logger.Error(err, "Failed to scale up UffizziCluster StatefulSet")
return ctrl.Result{}, err
return err
}
logger.Info("UffizziCluster StatefulSet scaled up to 1")
// set status for vcluster waking up
lastAwakeTime := metav1.Now()
lastAwakeTimeString := lastAwakeTime.String()
uCluster.Status.LastAwakeTime = &lastAwakeTimeString
setCondition(uCluster, Awoken(lastAwakeTime))
}

// Requeue the request to check the status
return ctrl.Result{Requeue: true}, nil
if err := r.Status().Update(ctx, uCluster); err != nil {
return err
}
return nil
}

// scaleStatefulSet scales the stateful set to the given scale
Expand Down

0 comments on commit eaeb1d9

Please sign in to comment.