Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions api/v1alpha1/clusteraddon_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package v1alpha1

import (
"github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusteraddon"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
Expand All @@ -27,17 +28,33 @@ const (
ClusterAddonFinalizer = "clusteraddon.clusterstack.x-k8s.io"
)

// HelmChartStatusConditions defines the status of helm chart in the cluster addon.
type HelmChartStatusConditions string
// StagePhase defines the status of helm chart in the cluster addon.
type StagePhase string

var (
None = HelmChartStatusConditions("")
WaitingForPreCondition = HelmChartStatusConditions("waitingForPreCondition")
ApplyingOrDeleting = HelmChartStatusConditions("applyingOrDeleting")
WaitingForPostCondition = HelmChartStatusConditions("waitingForPostCondition")
Done = HelmChartStatusConditions("done")
None = StagePhase("")
Pending = StagePhase("Pending")
WaitingForPreCondition = StagePhase("waitingForPreCondition")
ApplyingOrDeleting = StagePhase("applyingOrDeleting")
WaitingForPostCondition = StagePhase("waitingForPostCondition")
Done = StagePhase("done")
)

// StageStatus represents the helm charts of the hook and it's phases.
type StageStatus struct {
// Name represent name of the helm chart
// +optional
Name string `json:"helmChartName"`

// Action is the action of the helm chart. e.g. - apply and delete.
// +optional
Action clusteraddon.Action `json:"action,omitempty"`

// Phase is the current phase of the helm chart.
// +optional
Phase StagePhase `json:"phase"`
}

// ClusterAddonSpec defines the desired state of a ClusterAddon object.
type ClusterAddonSpec struct {
// ClusterStack is the full string <provider>-<name>-<Kubernetes version>-<version> that will be filled with the cluster stack that
Expand All @@ -64,9 +81,9 @@ type ClusterAddonStatus struct {
// +optional
Resources []*Resource `json:"resources,omitempty"`

// HelmChartStatus defines the status of helm chart in the cluster addon.
// Stages shows the state of all stages in the current running hook.
// +optional
HelmChartStatus map[string]HelmChartStatusConditions `json:"helmChartStatus,omitempty"`
Stages []StageStatus `json:"stages,omitempty"`

// +optional
// +kubebuilder:default:=false
Expand Down Expand Up @@ -95,6 +112,27 @@ type ClusterAddon struct {
Status ClusterAddonStatus `json:"status,omitempty"`
}

// GetStagePhase returns helm chart status for the helm chart.
func (r *ClusterAddon) GetStagePhase(helmChartName string, action clusteraddon.Action) StagePhase {
for _, stage := range r.Status.Stages {
if stage.Name == helmChartName && stage.Action == action {
return stage.Phase
}
}

// This cannot occur as we populate phase value with "pending".
return None
}

// SetStagePhase sets the helm chart status phase.
func (r *ClusterAddon) SetStagePhase(helmChartName string, action clusteraddon.Action, phase StagePhase) {
for i, _ := range r.Status.Stages {
if r.Status.Stages[i].Name == helmChartName && r.Status.Stages[i].Action == action {
r.Status.Stages[i].Phase = phase
}
}
}

// GetConditions returns the observations of the operational state of the ClusterAddon resource.
func (r *ClusterAddon) GetConditions() clusterv1.Conditions {
return r.Status.Conditions
Expand Down
25 changes: 19 additions & 6 deletions api/v1alpha1/zz_generated.deepcopy.go

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

26 changes: 18 additions & 8 deletions config/crd/bases/clusterstack.x-k8s.io_clusteraddons.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,6 @@ spec:
- type
type: object
type: array
helmChartStatus:
additionalProperties:
description: HelmChartStatusConditions defines the status of helm
chart in the cluster addon.
type: string
description: HelmChartStatus defines the status of helm chart in the
cluster addon.
type: object
ready:
default: false
type: boolean
Expand Down Expand Up @@ -201,6 +193,24 @@ spec:
type: string
type: object
type: array
stages:
description: Stages shows the state of all stages in the current running
hook.
items:
description: StageStatus represents the helm charts of the hook
and it's phases.
properties:
action:
description: Action is the action of the helm chart. e.g. -
apply and delete.
helmChartName:
description: Name represent name of the helm chart
type: string
phase:
description: Phase is the current phase of the helm chart.
type: string
type: object
type: array
type: object
type: object
served: true
Expand Down
52 changes: 31 additions & 21 deletions internal/controller/clusteraddon_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,12 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re
return reconcile.Result{}, fmt.Errorf("failed to get addon stages input: %w", err)
}

// clusteraddon.yaml in the release.
clusterAddonConfig, err := clusteraddon.ParseClusterAddonConfig(in.clusterAddonConfigPath)
if err != nil {
return reconcile.Result{}, fmt.Errorf("failed to parse clusteraddon.yaml config: %w", err)
}

var (
oldRelease release.Release
requeue bool
Expand Down Expand Up @@ -307,11 +313,13 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re
// if a hook is set, it is expected that HelmChartAppliedCondition is removed
if clusterAddon.Spec.Hook != "" {
// if the clusterAddon was ready before, it means this hook is fresh and we have to reset the status
if clusterAddon.Status.Ready {
clusterAddon.Status.HelmChartStatus = make(map[string]csov1alpha1.HelmChartStatusConditions)
}
if clusterAddon.Status.HelmChartStatus == nil {
clusterAddon.Status.HelmChartStatus = make(map[string]csov1alpha1.HelmChartStatusConditions)
if clusterAddon.Status.Ready || len(clusterAddon.Status.Stages) == 0 {
clusterAddon.Status.Stages = make([]csov1alpha1.StageStatus, len(clusterAddonConfig.AddonStages[clusterAddon.Spec.Hook]))
for i, stage := range clusterAddonConfig.AddonStages[clusterAddon.Spec.Hook] {
clusterAddon.Status.Stages[i].Name = stage.HelmChartName
clusterAddon.Status.Stages[i].Action = stage.Action
clusterAddon.Status.Stages[i].Phase = csov1alpha1.Pending
}
}
clusterAddon.Status.Ready = false
}
Expand All @@ -320,7 +328,14 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re
// Therefore, we have to check whether the ClusterStack is upgraded and if that is the case, the ClusterAddons have to be upgraded as well.
if clusterAddon.Spec.ClusterStack != cluster.Spec.Topology.Class && oldRelease.Meta.Versions.Kubernetes == releaseAsset.Meta.Versions.Kubernetes {
if clusterAddon.Spec.Version != releaseAsset.Meta.Versions.Components.ClusterAddon {
clusterAddon.Status.HelmChartStatus = make(map[string]csov1alpha1.HelmChartStatusConditions)
if clusterAddon.Status.Ready || len(clusterAddon.Status.Stages) == 0 {
clusterAddon.Status.Stages = make([]csov1alpha1.StageStatus, len(clusterAddonConfig.AddonStages["BeforeClusterUpgrade"]))
for i, stage := range clusterAddonConfig.AddonStages["BeforeClusterUpgrade"] {
clusterAddon.Status.Stages[i].Name = stage.HelmChartName
clusterAddon.Status.Stages[i].Action = stage.Action
clusterAddon.Status.Stages[i].Phase = csov1alpha1.Pending
}
}
clusterAddon.Status.Ready = false
conditions.Delete(clusterAddon, csov1alpha1.HelmChartAppliedCondition)
} else {
Expand All @@ -336,12 +351,6 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re
return reconcile.Result{}, nil
}

// clusteraddon.yaml in the release.
clusterAddonConfig, err := clusteraddon.ParseClusterAddonConfig(in.clusterAddonConfigPath)
if err != nil {
return reconcile.Result{}, fmt.Errorf("failed to parse clusteraddon.yaml config: %w", err)
}

// In case the Kubernetes version stayed the same during an upgrade, the hook server does not trigger and
// we just take the Helm charts that are supposed to be installed in the BeforeClusterUpgrade hook and apply them.
if oldRelease.Meta.Versions.Kubernetes == releaseAsset.Meta.Versions.Kubernetes {
Expand All @@ -363,7 +372,7 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re
clusterAddon.Status.Resources = make([]*csov1alpha1.Resource, 0)

// remove the helm chart status from the status.
clusterAddon.Status.HelmChartStatus = make(map[string]csov1alpha1.HelmChartStatusConditions)
clusterAddon.Status.Stages = make([]csov1alpha1.StageStatus, 0)

// update the latest cluster class
clusterAddon.Spec.ClusterStack = cluster.Spec.Topology.Class
Expand Down Expand Up @@ -397,7 +406,7 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re
conditions.MarkTrue(clusterAddon, csov1alpha1.HelmChartAppliedCondition)

// remove the helm chart status from the status.
clusterAddon.Status.HelmChartStatus = make(map[string]csov1alpha1.HelmChartStatusConditions)
clusterAddon.Status.Stages = make([]csov1alpha1.StageStatus, 0)

// remove the status resource if hook is finished
clusterAddon.Status.Resources = make([]*csov1alpha1.Resource, 0)
Expand Down Expand Up @@ -534,8 +543,8 @@ func (r *ClusterAddonReconciler) executeStage(ctx context.Context, stage cluster
}

check:
switch in.clusterAddon.Status.HelmChartStatus[stage.HelmChartName] {
case csov1alpha1.None, csov1alpha1.WaitingForPreCondition:
switch in.clusterAddon.GetStagePhase(stage.HelmChartName, stage.Action) {
case csov1alpha1.Pending, csov1alpha1.WaitingForPreCondition:
// If WaitForPreCondition is mentioned.
if !reflect.DeepEqual(stage.WaitForPreCondition, clusteraddon.WaitForCondition{}) {
// Evaluate the condition.
Expand All @@ -550,15 +559,16 @@ check:
"failed to successfully evaluate pre condition: %q: %s", stage.HelmChartName, err.Error(),
)

in.clusterAddon.Status.HelmChartStatus[stage.HelmChartName] = csov1alpha1.WaitingForPreCondition
in.clusterAddon.SetStagePhase(stage.HelmChartName, stage.Action, csov1alpha1.WaitingForPreCondition)

return true, nil
}
return false, fmt.Errorf("failed to get dynamic resource and evaluate cel expression for pre condition: %w", err)
}
logger.V(1).Info("finished evaluating pre condition", "clusterStack", in.clusterAddon.Spec.ClusterStack, "helm chart", stage.HelmChartName, "hook", in.clusterAddon.Spec.Hook)
}
in.clusterAddon.Status.HelmChartStatus[stage.HelmChartName] = csov1alpha1.ApplyingOrDeleting

in.clusterAddon.SetStagePhase(stage.HelmChartName, stage.Action, csov1alpha1.ApplyingOrDeleting)
goto check

case csov1alpha1.ApplyingOrDeleting:
Expand All @@ -584,7 +594,7 @@ check:
// remove status resource if applied successfully
in.clusterAddon.Status.Resources = make([]*csov1alpha1.Resource, 0)

in.clusterAddon.Status.HelmChartStatus[stage.HelmChartName] = csov1alpha1.WaitingForPostCondition
in.clusterAddon.SetStagePhase(stage.HelmChartName, stage.Action, csov1alpha1.WaitingForPostCondition)
goto check

} else {
Expand All @@ -610,7 +620,7 @@ check:
// remove status resource if deleted successfully
in.clusterAddon.Status.Resources = make([]*csov1alpha1.Resource, 0)

in.clusterAddon.Status.HelmChartStatus[stage.HelmChartName] = csov1alpha1.WaitingForPostCondition
in.clusterAddon.SetStagePhase(stage.HelmChartName, stage.Action, csov1alpha1.WaitingForPostCondition)
goto check
}

Expand All @@ -636,7 +646,7 @@ check:
logger.V(1).Info("finished evaluating post condition", "clusterStack", in.clusterAddon.Spec.ClusterStack, "helm chart", stage.HelmChartName, "hook", in.clusterAddon.Spec.Hook)
}

in.clusterAddon.Status.HelmChartStatus[stage.HelmChartName] = csov1alpha1.Done
in.clusterAddon.SetStagePhase(stage.HelmChartName, stage.Action, csov1alpha1.Done)
}

return false, nil
Expand Down
2 changes: 2 additions & 0 deletions test/cluster-stacks/2/docker/ferrol/1-27/clusteraddon.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ addonStages:
BeforeClusterUpgrade:
- helmChartName: metrics-server
action: apply
- helmChartName: metrics-server
action: delete
- helmChartName: ngnix
action: apply
waitForPostCondition:
Expand Down
2 changes: 1 addition & 1 deletion test/cluster-stacks/2/docker/ferrol/1-27/csmctl.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
apiVersion: csmctl.clusterstack.x-k8s.io/v1alpha1
config:
kubernetesVersion: v1.27.7
kubernetesVersion: v1.27.6
clusterStackName: ferrol
provider:
type: docker
Expand Down