diff --git a/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go b/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go index 913ac1890..809303371 100644 --- a/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go +++ b/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go @@ -222,9 +222,9 @@ type RolloutStatus struct { // Conditions represents the latest available observations of a CloneSet's current state. runtimev1alpha1.ConditionedStatus `json:",inline"` - // RolloutTargetSize is the size of the target resources. This is determined once the initial spec verification + // RolloutTargetTotalSize is the size of the target resources. This is determined once the initial spec verification // and does not change until the rollout is restarted - RolloutTargetSize int32 `json:"rolloutTargetSize,omitempty"` + RolloutTargetTotalSize int32 `json:"rolloutTargetSize,omitempty"` // NewPodTemplateIdentifier is a string that uniquely represent the new pod template // each workload type could use different ways to identify that so we cannot compare between resources diff --git a/apis/standard.oam.dev/v1alpha1/rollout_state.go b/apis/standard.oam.dev/v1alpha1/rollout_state.go index fd3c13f52..414d7402a 100644 --- a/apis/standard.oam.dev/v1alpha1/rollout_state.go +++ b/apis/standard.oam.dev/v1alpha1/rollout_state.go @@ -180,7 +180,7 @@ func (r *RolloutStatus) RolloutFailing(reason string) { // ResetStatus resets the status of the rollout to start from beginning func (r *RolloutStatus) ResetStatus() { r.NewPodTemplateIdentifier = "" - r.RolloutTargetSize = -1 + r.RolloutTargetTotalSize = -1 r.LastAppliedPodTemplateIdentifier = "" r.RollingState = VerifyingSpecState r.BatchRollingState = BatchInitializingState diff --git a/charts/vela-core/crds/core.oam.dev_applications.yaml b/charts/vela-core/crds/core.oam.dev_applications.yaml index 4476c8917..ed22a8911 100644 --- a/charts/vela-core/crds/core.oam.dev_applications.yaml +++ b/charts/vela-core/crds/core.oam.dev_applications.yaml @@ -373,7 +373,7 @@ spec: description: RollingState is the Rollout State type: string rolloutTargetSize: - description: RolloutTargetSize is the size of the target resources. This is determined once the initial spec verification and does not change until the rollout is restarted + description: RolloutTargetTotalSize is the size of the target resources. This is determined once the initial spec verification and does not change until the rollout is restarted format: int32 type: integer services: diff --git a/charts/vela-core/crds/core.oam.dev_approllouts.yaml b/charts/vela-core/crds/core.oam.dev_approllouts.yaml index e57ee4bf8..d1b429976 100644 --- a/charts/vela-core/crds/core.oam.dev_approllouts.yaml +++ b/charts/vela-core/crds/core.oam.dev_approllouts.yaml @@ -321,7 +321,7 @@ spec: description: RollingState is the Rollout State type: string rolloutTargetSize: - description: RolloutTargetSize is the size of the target resources. This is determined once the initial spec verification and does not change until the rollout is restarted + description: RolloutTargetTotalSize is the size of the target resources. This is determined once the initial spec verification and does not change until the rollout is restarted format: int32 type: integer targetGeneration: diff --git a/charts/vela-core/crds/standard.oam.dev_rollouttraits.yaml b/charts/vela-core/crds/standard.oam.dev_rollouttraits.yaml index 923308faa..098731274 100644 --- a/charts/vela-core/crds/standard.oam.dev_rollouttraits.yaml +++ b/charts/vela-core/crds/standard.oam.dev_rollouttraits.yaml @@ -342,7 +342,7 @@ spec: description: RollingState is the Rollout State type: string rolloutTargetSize: - description: RolloutTargetSize is the size of the target resources. This is determined once the initial spec verification and does not change until the rollout is restarted + description: RolloutTargetTotalSize is the size of the target resources. This is determined once the initial spec verification and does not change until the rollout is restarted format: int32 type: integer targetGeneration: diff --git a/docs/examples/deployment-rollout/README.md b/docs/examples/deployment-rollout/README.md new file mode 100644 index 000000000..2904c600d --- /dev/null +++ b/docs/examples/deployment-rollout/README.md @@ -0,0 +1,46 @@ +# Rollout Example +Here is an example of how to rollout an application with a component of type deployment. + + +## Rollout steps +1. Install deployment based workloadDefinition +```shell +kubectl apply -f docs/examples/deployment-rollout/webservice-definition.yaml +``` + +2. Apply an application +```shell +kubectl apply -f docs/examples/deployment-rollout/app-source.yaml +``` +Wait for the application's status to be "running" + +3. Prepare the application for rolling out +```shell +kubectl apply -f docs/examples/deployment-rollout/app-source-prep.yaml +``` +Wait for the applicationConfiguration "test-rolling-v1" `Rolling Status` to be "RollingTemplated" + +4. Modify the application image and apply +```shell +kubectl apply -f docs/examples/deployment-rollout/app-target.yaml +``` +Wait for the applicationConfiguration "test-rolling-v2" `Rolling Status` to be "RollingTemplated" + +5. Mark the application as normal +```shell +kubectl apply -f docs/examples/deployment-rollout/app-target-done.yaml +``` + +6. Apply the application deployment with pause +```shell +kubectl apply -f docs/examples/deployment-rollout/app-deploy-pause.yaml +``` +Check the status of the ApplicationDeployment and see the step by step rolling out. +This rollout will pause after the second batch. + +7. Apply the application deployment that completes the rollout +```shell +kubectl apply -f docs/examples/deployment-rollout/app-deploy-finish.yaml +``` +Check the status of the ApplicationDeployment and see the rollout completes, and the +applicationDeployment's "Rolling State" becomes `rolloutSucceed` \ No newline at end of file diff --git a/docs/examples/deployment-rollout/app-deploy-finish.yaml b/docs/examples/deployment-rollout/app-deploy-finish.yaml new file mode 100644 index 000000000..b6f402d18 --- /dev/null +++ b/docs/examples/deployment-rollout/app-deploy-finish.yaml @@ -0,0 +1,17 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: AppRollout +metadata: + name: rolling-test +spec: + # application (revision) reference + targetAppRevisionName: test-rolling-v2 + sourceAppRevisionName: test-rolling-v1 + # HPA reference (optional) + componentList: + - metrics-provider + rolloutPlan: + rolloutStrategy: "IncreaseFirst" + rolloutBatches: + - replicas: 10% + - replicas: 2 + - replicas: 2 \ No newline at end of file diff --git a/docs/examples/deployment-rollout/app-deploy-pause.yaml b/docs/examples/deployment-rollout/app-deploy-pause.yaml new file mode 100644 index 000000000..436691cd6 --- /dev/null +++ b/docs/examples/deployment-rollout/app-deploy-pause.yaml @@ -0,0 +1,18 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: AppRollout +metadata: + name: rolling-test +spec: + # application (revision) reference + targetAppRevisionName: test-rolling-v2 + sourceAppRevisionName: test-rolling-v1 + # HPA reference (optional) + componentList: + - metrics-provider + rolloutPlan: + rolloutStrategy: "IncreaseFirst" + rolloutBatches: + - replicas: 10% + - replicas: 2 + - replicas: 2 + batchPartition: 1 diff --git a/docs/examples/deployment-rollout/app-deploy-revert.yaml b/docs/examples/deployment-rollout/app-deploy-revert.yaml new file mode 100644 index 000000000..14ff86916 --- /dev/null +++ b/docs/examples/deployment-rollout/app-deploy-revert.yaml @@ -0,0 +1,17 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: AppRollout +metadata: + name: rolling-test +spec: + # application (revision) reference + targetAppRevisionName: test-rolling-v3 + sourceAppRevisionName: test-rolling-v2 + # HPA reference (optional) + componentList: + - metrics-provider + rolloutPlan: + rolloutStrategy: "IncreaseFirst" + rolloutBatches: + - replicas: 20% + - replicas: 30% + - replicas: 50% \ No newline at end of file diff --git a/docs/examples/deployment-rollout/app-source-prep.yaml b/docs/examples/deployment-rollout/app-source-prep.yaml new file mode 100644 index 000000000..5a8f011bd --- /dev/null +++ b/docs/examples/deployment-rollout/app-source-prep.yaml @@ -0,0 +1,18 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: test-rolling + annotations: + "app.oam.dev/rolling-components": "metrics-provider" + "app.oam.dev/rollout-template": "true" +spec: + components: + - name: metrics-provider + type: webservice + settings: + cmd: + - ./podinfo + - stress-cpu=1 + image: stefanprodan/podinfo:4.0.6 + port: 8080 + updateStrategyType: InPlaceIfPossible \ No newline at end of file diff --git a/docs/examples/deployment-rollout/app-source.yaml b/docs/examples/deployment-rollout/app-source.yaml new file mode 100644 index 000000000..5d39a2173 --- /dev/null +++ b/docs/examples/deployment-rollout/app-source.yaml @@ -0,0 +1,15 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: test-rolling +spec: + components: + - name: metrics-provider + type: webservice + settings: + cmd: + - ./podinfo + - stress-cpu=1 + image: stefanprodan/podinfo:4.0.6 + port: 8080 + updateStrategyType: InPlaceIfPossible \ No newline at end of file diff --git a/docs/examples/deployment-rollout/app-target-done.yaml b/docs/examples/deployment-rollout/app-target-done.yaml new file mode 100644 index 000000000..01fc96f01 --- /dev/null +++ b/docs/examples/deployment-rollout/app-target-done.yaml @@ -0,0 +1,15 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: test-rolling +spec: + components: + - name: metrics-provider + type: webservice + settings: + cmd: + - ./podinfo + - stress-cpu=1 + image: stefanprodan/podinfo:5.0.2 + port: 8080 + updateStrategyType: InPlaceIfPossible \ No newline at end of file diff --git a/docs/examples/deployment-rollout/app-target.yaml b/docs/examples/deployment-rollout/app-target.yaml new file mode 100644 index 000000000..70d82d57f --- /dev/null +++ b/docs/examples/deployment-rollout/app-target.yaml @@ -0,0 +1,18 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: test-rolling + annotations: + "app.oam.dev/rolling-components": "metrics-provider" + "app.oam.dev/rollout-template": "true" +spec: + components: + - name: metrics-provider + type: webservice + settings: + cmd: + - ./podinfo + - stress-cpu=1 + image: stefanprodan/podinfo:5.0.2 + port: 8080 + updateStrategyType: InPlaceIfPossible \ No newline at end of file diff --git a/docs/examples/deployment-rollout/webservice-definition.yaml b/docs/examples/deployment-rollout/webservice-definition.yaml new file mode 100644 index 000000000..2d50714b3 --- /dev/null +++ b/docs/examples/deployment-rollout/webservice-definition.yaml @@ -0,0 +1,94 @@ +# Code generated by KubeVela templates. DO NOT EDIT. +apiVersion: core.oam.dev/v1alpha2 +kind: WorkloadDefinition +metadata: + name: webservice + annotations: + definition.oam.dev/description: "Describes long-running, scalable, containerized services that have a stable network endpoint to receive external network traffic from customers." +spec: + definitionRef: + name: deployments.apps + schematic: + cue: + template: | + output: { + apiVersion: "apps/v1" + kind: "Deployment" + spec: { + selector: matchLabels: { + "app.oam.dev/component": context.name + } + replicas: parameter.replicas + template: { + metadata: labels: { + "app.oam.dev/component": context.name + } + + spec: { + containers: [{ + name: context.name + image: parameter.image + + if parameter["cmd"] != _|_ { + command: parameter.cmd + } + + if parameter["env"] != _|_ { + env: parameter.env + } + + if context["config"] != _|_ { + env: context.config + } + + ports: [{ + containerPort: parameter.port + }] + + if parameter["cpu"] != _|_ { + resources: { + limits: + cpu: parameter.cpu + requests: + cpu: parameter.cpu + } + } + }] + } + } + } + } + parameter: { + // +usage=Which image would you like to use for your service + // +short=i + image: string + + // +usage=Commands to run in the container + cmd?: [...string] + + // +usage=Which port do you want customer traffic sent to + // +short=p + port: *80 | int + // +usage=Define arguments by using environment variables + env?: [...{ + // +usage=Environment variable name + name: string + // +usage=The value of the environment variable + value?: string + // +usage=Specifies a source the value of this var should come from + valueFrom?: { + // +usage=Selects a key of a secret in the pod's namespace + secretKeyRef: { + // +usage=The name of the secret in the pod's namespace to select from + name: string + // +usage=The key of the secret to select from. Must be a valid secret key + key: string + } + } + }] + // +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core) + cpu?: string + // +usage=Number of pods in the deployment + replicas: *5 | int + } + diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applications.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applications.yaml index b411f0087..f633a0b8a 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applications.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applications.yaml @@ -373,7 +373,7 @@ spec: description: RollingState is the Rollout State type: string rolloutTargetSize: - description: RolloutTargetSize is the size of the target resources. This is determined once the initial spec verification and does not change until the rollout is restarted + description: RolloutTargetTotalSize is the size of the target resources. This is determined once the initial spec verification and does not change until the rollout is restarted format: int32 type: integer services: diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_approllouts.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_approllouts.yaml index 401b4ca02..ce6c3f270 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_approllouts.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_approllouts.yaml @@ -321,7 +321,7 @@ spec: description: RollingState is the Rollout State type: string rolloutTargetSize: - description: RolloutTargetSize is the size of the target resources. This is determined once the initial spec verification and does not change until the rollout is restarted + description: RolloutTargetTotalSize is the size of the target resources. This is determined once the initial spec verification and does not change until the rollout is restarted format: int32 type: integer targetGeneration: diff --git a/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouttraits.yaml b/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouttraits.yaml index ae7761241..8e3778f37 100644 --- a/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouttraits.yaml +++ b/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouttraits.yaml @@ -342,7 +342,7 @@ spec: description: RollingState is the Rollout State type: string rolloutTargetSize: - description: RolloutTargetSize is the size of the target resources. This is determined once the initial spec verification and does not change until the rollout is restarted + description: RolloutTargetTotalSize is the size of the target resources. This is determined once the initial spec verification and does not change until the rollout is restarted format: int32 type: integer targetGeneration: diff --git a/pkg/controller/common/rollout/rollout_plan_controller.go b/pkg/controller/common/rollout/rollout_plan_controller.go index f083abaa3..46676f48c 100644 --- a/pkg/controller/common/rollout/rollout_plan_controller.go +++ b/pkg/controller/common/rollout/rollout_plan_controller.go @@ -75,7 +75,7 @@ func (r *Controller) Reconcile(ctx context.Context) (res reconcile.Result, statu defer func() { klog.InfoS("Finished one round of reconciling rollout plan", "rollout state", status.RollingState, "batch rolling state", status.BatchRollingState, "current batch", status.CurrentBatch, - "upgraded Replicas", status.UpgradedReplicas, "ready Replicas", r.rolloutStatus.UpgradedReadyReplicas, + "upgraded Replicas", status.UpgradedReplicas, "ready Replicas", status.UpgradedReadyReplicas, "reconcile result ", res) }() status = r.rolloutStatus @@ -128,7 +128,7 @@ func (r *Controller) Reconcile(ctx context.Context) (res reconcile.Result, statu } case v1alpha1.FinalisingState: - if succeed := workloadController.Finalize(ctx, false); succeed { + if succeed := workloadController.Finalize(ctx, true); succeed { r.finalizeRollout(ctx) } @@ -154,7 +154,7 @@ func (r *Controller) reconcileBatchInRolling(ctx context.Context, workloadContro } // makes sure that the current batch and replica count in the status are validate - err := r.validateRollingBatchStatus(int(r.rolloutStatus.RolloutTargetSize)) + err := r.validateRollingBatchStatus(int(r.rolloutStatus.RolloutTargetTotalSize)) if err != nil { r.rolloutStatus.RolloutFailing(err.Error()) return @@ -374,8 +374,8 @@ func (r *Controller) GetWorkloadController() (workloads.WorkloadController, erro } var source types.NamespacedName if r.sourceWorkload != nil { - source.Namespace = r.targetWorkload.GetNamespace() - source.Name = r.targetWorkload.GetName() + source.Namespace = r.sourceWorkload.GetNamespace() + source.Name = r.sourceWorkload.GetName() } if r.targetWorkload.GroupVersionKind().Group == kruisev1.GroupVersion.Group { diff --git a/pkg/controller/common/rollout/workloads/cloneset_controller.go b/pkg/controller/common/rollout/workloads/cloneset_controller.go index 22be21af4..079e06513 100644 --- a/pkg/controller/common/rollout/workloads/cloneset_controller.go +++ b/pkg/controller/common/rollout/workloads/cloneset_controller.go @@ -63,7 +63,7 @@ func (c *CloneSetController) VerifySpec(ctx context.Context) (bool, error) { } // record the size klog.InfoS("record the target size", "total replicas", totalReplicas) - c.rolloutStatus.RolloutTargetSize = totalReplicas + c.rolloutStatus.RolloutTargetTotalSize = totalReplicas // make sure that the updateRevision is different from what we have already done targetHash := c.cloneSet.Status.UpdateRevision @@ -103,6 +103,13 @@ func (c *CloneSetController) Initialize(ctx context.Context) (bool, error) { c.rolloutStatus.RolloutRetry(err.Error()) return false, nil } + + if controller := metav1.GetControllerOf(c.cloneSet); controller != nil { + if controller.Kind == v1alpha2.AppRolloutKind && controller.APIVersion == v1alpha2.SchemeGroupVersion.String() { + // it's already there + return true, nil + } + } // add the parent controller to the owner of the cloneset // before kicking start the update and start from every pod in the old version clonePatch := client.MergeFrom(c.cloneSet.DeepCopyObject()) diff --git a/pkg/controller/common/rollout/workloads/deployment_controller.go b/pkg/controller/common/rollout/workloads/deployment_controller.go index 76dc546dd..a7ee2c4c8 100644 --- a/pkg/controller/common/rollout/workloads/deployment_controller.go +++ b/pkg/controller/common/rollout/workloads/deployment_controller.go @@ -21,7 +21,7 @@ import ( "github.com/oam-dev/kubevela/pkg/oam" ) -// DeploymentController is responsible for handle rollout deployment type of workloads +// DeploymentController is responsible for handling rollout deployment type of workloads type DeploymentController struct { client client.Client recorder event.Recorder @@ -35,7 +35,7 @@ type DeploymentController struct { targetDeploy apps.Deployment } -// NewDeploymentController creates a new Deployment rollout controller +// NewDeploymentController creates a new deployment rollout controller func NewDeploymentController(client client.Client, recorder event.Recorder, parentController oam.Object, rolloutSpec *v1alpha1.RolloutPlan, rolloutStatus *v1alpha1.RolloutStatus, sourceNamespacedName, targetNamespacedName types.NamespacedName) *DeploymentController { @@ -62,7 +62,7 @@ func (c *DeploymentController) VerifySpec(ctx context.Context) (bool, error) { }() // check if the rollout spec is compatible with the current state - totalReplicas, verifyErr := c.calculateTargetSize(ctx) + targetTotalReplicas, verifyErr := c.calculateTargetTotalSize(ctx) if verifyErr != nil { // do not fail the rollout just because we can't get the resource c.rolloutStatus.RolloutRetry(verifyErr.Error()) @@ -70,7 +70,7 @@ func (c *DeploymentController) VerifySpec(ctx context.Context) (bool, error) { } // record the size and we will use this value to drive the rest of the batches // we do not handle scale case in this controller - c.rolloutStatus.RolloutTargetSize = totalReplicas + c.rolloutStatus.RolloutTargetTotalSize = targetTotalReplicas // make sure that the updateRevision is different from what we have already done targetHash, verifyErr := utils.ComputeSpecHash(c.targetDeploy.Spec) @@ -87,12 +87,17 @@ func (c *DeploymentController) VerifySpec(ctx context.Context) (bool, error) { // check if the rollout batch replicas added up to the Deployment replicas // we don't handle scale case in this controller - if verifyErr = c.verifyRolloutBatchReplicaValue(totalReplicas); verifyErr != nil { + if verifyErr = c.verifyRolloutBatchReplicaValue(targetTotalReplicas); verifyErr != nil { return false, verifyErr } + if !c.sourceDeploy.Spec.Paused { + return false, fmt.Errorf("the source eployment %s is still being reconciled, need to be paused", + c.sourceDeploy.GetName()) + } + if !c.targetDeploy.Spec.Paused && c.targetDeploy.Spec.Replicas != pointer.Int32Ptr(0) { - return false, fmt.Errorf("the Deployment %s is in the middle of updating, need to be paused or empty", + return false, fmt.Errorf("the target deployment %s is not empty, need to be paused or empty", c.sourceDeploy.GetName()) } @@ -121,12 +126,12 @@ func (c *DeploymentController) Initialize(ctx context.Context) (bool, error) { c.rolloutStatus.RolloutRetry(err.Error()) return false, nil } - err = c.claimDeployment(ctx, &c.sourceDeploy) + err = c.claimDeployment(ctx, &c.sourceDeploy, false) if err != nil { c.rolloutStatus.RolloutRetry(err.Error()) return false, nil } - err = c.claimDeployment(ctx, &c.targetDeploy) + err = c.claimDeployment(ctx, &c.targetDeploy, c.calculateInitialTargetSize(ctx)) if err != nil { c.rolloutStatus.RolloutRetry(err.Error()) return false, nil @@ -151,13 +156,13 @@ func (c *DeploymentController) RolloutOneBatchPods(ctx context.Context) (bool, e rolloutStrategy = c.rolloutSpec.RolloutStrategy } // Determine if we are the first or the second part of the current batch rollout - if currentSizeSetting == c.rolloutStatus.RolloutTargetSize { + if currentSizeSetting == c.rolloutStatus.RolloutTargetTotalSize { // we need to finish the first part of the rollout, // will always return false to not move to the next phase return false, c.rolloutBatchFirstHalf(ctx, rolloutStrategy) } // we are at the second half - targetSize := c.calculateCurrentTarget(c.rolloutStatus.RolloutTargetSize) + targetSize := c.calculateCurrentTarget(c.rolloutStatus.RolloutTargetTotalSize) if !c.rolloutBatchSecondHalf(ctx, rolloutStrategy, targetSize) { return false, nil } @@ -165,7 +170,7 @@ func (c *DeploymentController) RolloutOneBatchPods(ctx context.Context) (bool, e klog.InfoS("upgraded one batch", "current batch", c.rolloutStatus.CurrentBatch, "target deployment size", targetSize) c.recorder.Event(c.parentController, event.Normal("Batch Rollout", - fmt.Sprintf("Submitted upgrade quest for batch %d", c.rolloutStatus.CurrentBatch))) + fmt.Sprintf("Submitted upgrade quests for batch %d", c.rolloutStatus.CurrentBatch))) c.rolloutStatus.UpgradedReplicas = targetSize return true, nil } @@ -181,8 +186,8 @@ func (c *DeploymentController) CheckOneBatchPods(ctx context.Context) (bool, err readyTargetPodCount := c.targetDeploy.Status.ReadyReplicas sourcePodCount := c.sourceDeploy.Status.Replicas currentBatch := c.rolloutSpec.RolloutBatches[c.rolloutStatus.CurrentBatch] - targetGoal := c.calculateCurrentTarget(c.rolloutStatus.RolloutTargetSize) - sourceGoal := c.calculateCurrentSource(c.rolloutStatus.RolloutTargetSize) + targetGoal := c.calculateCurrentTarget(c.rolloutStatus.RolloutTargetTotalSize) + sourceGoal := c.calculateCurrentSource(c.rolloutStatus.RolloutTargetTotalSize) maxUnavail := 0 if currentBatch.MaxUnavailable != nil { maxUnavail, _ = intstr.GetValueFromIntOrPercent(currentBatch.MaxUnavailable, int(targetGoal), true) @@ -194,7 +199,7 @@ func (c *DeploymentController) CheckOneBatchPods(ctx context.Context) (bool, err // make sure that the source deployment has the correct pods before moving the target // and the total we could overshoot in revert cases if sourcePodCount != sourceGoal || - int32(maxUnavail)+readyTargetPodCount+sourcePodCount < c.rolloutStatus.RolloutTargetSize { + int32(maxUnavail)+readyTargetPodCount+sourcePodCount < c.rolloutStatus.RolloutTargetTotalSize { // continue to verify klog.InfoS("the batch is not ready yet", "current batch", c.rolloutStatus.CurrentBatch) c.rolloutStatus.RolloutRetry(fmt.Sprintf( @@ -214,12 +219,17 @@ func (c *DeploymentController) CheckOneBatchPods(ctx context.Context) (bool, err // FinalizeOneBatch makes sure that the rollout status are updated correctly func (c *DeploymentController) FinalizeOneBatch(ctx context.Context) (bool, error) { // nothing to do for Deployment for now - return false, nil + return true, nil } // Finalize makes sure the Deployment is all upgraded func (c *DeploymentController) Finalize(ctx context.Context, succeed bool) bool { - err := c.releaseDeployment(ctx, &c.sourceDeploy) + err := c.fetchDeployments(ctx) + if err != nil { + // don't fail the rollout just because of we can't get the resource + return false + } + err = c.releaseDeployment(ctx, &c.sourceDeploy) if err != nil { c.rolloutStatus.RolloutRetry(err.Error()) return false @@ -239,31 +249,36 @@ func (c *DeploymentController) Finalize(ctx context.Context, succeed bool) bool The functions below are helper functions --------------------- */ -// calculateTargetSize fetches the Deployment and returns the replicas (not the actual number of pods) -func (c *DeploymentController) calculateTargetSize(ctx context.Context) (int32, error) { +// calculateTargetTotalSize fetches the Deployment and returns the replicas (not the actual number of pods) +func (c *DeploymentController) calculateTargetTotalSize(ctx context.Context) (int32, error) { if err := c.fetchDeployments(ctx); err != nil { return -1, err } - + // the spec target size is the truth if it's set + if c.rolloutSpec.TargetSize != nil { + return *c.rolloutSpec.TargetSize, nil + } + // otherwise, we assume that the source is the total // source default is 1 var sourceSize int32 = 1 - var targetSize int32 = 0 if c.sourceDeploy.Spec.Replicas != nil { sourceSize = *c.sourceDeploy.Spec.Replicas } - if c.targetDeploy.Spec.Replicas != nil { - targetSize = *c.targetDeploy.Spec.Replicas + return sourceSize, nil +} + +// calculateInitialTargetSize calculates what the initial replica should be set to the target deploy +func (c *DeploymentController) calculateInitialTargetSize(ctx context.Context) bool { + total, _ := c.calculateTargetTotalSize(ctx) + var sourceSize int32 = 1 + if c.sourceDeploy.Spec.Replicas != nil { + sourceSize = *c.sourceDeploy.Spec.Replicas } - return sourceSize + targetSize, nil + return total == sourceSize } // check if the replicas in all the rollout batches add up to the right number func (c *DeploymentController) verifyRolloutBatchReplicaValue(totalReplicas int32) error { - // the target size has to be the same as the Deployment size - if c.rolloutSpec.TargetSize != nil && *c.rolloutSpec.TargetSize != totalReplicas { - return fmt.Errorf("the rollout plan is attempting to scale the Deployment, target = %d, Deployment size = %d", - *c.rolloutSpec.TargetSize, totalReplicas) - } // use a common function to check if the sum of all the batches can match the Deployment size err := VerifySumOfBatchSizes(c.rolloutSpec, totalReplicas) if err != nil { @@ -294,13 +309,18 @@ func (c *DeploymentController) fetchDeployments(ctx context.Context) error { return nil } -// add the parent controller to the owner of the Deployment +// add the parent controller to the owner of the deployment, unpause it and initialize the size // before kicking start the update and start from every pod in the old version -func (c *DeploymentController) claimDeployment(ctx context.Context, deploy *apps.Deployment) error { +func (c *DeploymentController) claimDeployment(ctx context.Context, deploy *apps.Deployment, initSize bool) error { deployPatch := client.MergeFrom(deploy.DeepCopyObject()) - ref := metav1.NewControllerRef(c.parentController, v1alpha2.AppRolloutKindVersionKind) - deploy.SetOwnerReferences(append(deploy.GetOwnerReferences(), *ref)) + if controller := metav1.GetControllerOf(deploy); controller == nil { + ref := metav1.NewControllerRef(c.parentController, v1alpha2.AppRolloutKindVersionKind) + deploy.SetOwnerReferences(append(deploy.GetOwnerReferences(), *ref)) + } deploy.Spec.Paused = false + if initSize { + deploy.Spec.Replicas = pointer.Int32Ptr(0) + } // patch the Deployment if err := c.client.Patch(ctx, deploy, deployPatch, client.FieldOwner(c.parentController.GetUID())); err != nil { c.recorder.Event(c.parentController, event.Warning("Failed to the start the Deployment update", err)) @@ -313,7 +333,7 @@ func (c *DeploymentController) rolloutBatchFirstHalf(ctx context.Context, rollou var err error if rolloutStrategy == v1alpha1.IncreaseFirstRolloutStrategyType { // set the target replica first which should increase its size - if err = c.patchDeployment(ctx, c.calculateCurrentTarget(c.rolloutStatus.RolloutTargetSize), + if err = c.patchDeployment(ctx, c.calculateCurrentTarget(c.rolloutStatus.RolloutTargetTotalSize), &c.targetDeploy); err != nil { c.rolloutStatus.RolloutRetry(err.Error()) } @@ -321,7 +341,7 @@ func (c *DeploymentController) rolloutBatchFirstHalf(ctx context.Context, rollou } if rolloutStrategy == v1alpha1.DecreaseFirstRolloutStrategyType { // set the source replicas first which should shrink its size - if err = c.patchDeployment(ctx, c.calculateCurrentSource(c.rolloutStatus.RolloutTargetSize), + if err = c.patchDeployment(ctx, c.calculateCurrentSource(c.rolloutStatus.RolloutTargetTotalSize), &c.sourceDeploy); err != nil { c.rolloutStatus.RolloutRetry(err.Error()) } @@ -343,7 +363,7 @@ func (c *DeploymentController) rolloutBatchSecondHalf(ctx context.Context, // make sure that the target deployment has enough ready pods before reducing the source if c.targetDeploy.Status.ReadyReplicas+int32(maxUnavail) >= targetSize { // set the source replicas now which should shrink its size - if err = c.patchDeployment(ctx, c.calculateCurrentSource(c.rolloutStatus.RolloutTargetSize), + if err = c.patchDeployment(ctx, c.calculateCurrentSource(c.rolloutStatus.RolloutTargetTotalSize), &c.sourceDeploy); err != nil { c.rolloutStatus.RolloutRetry(err.Error()) return false @@ -358,7 +378,7 @@ func (c *DeploymentController) rolloutBatchSecondHalf(ctx context.Context, } } else if rolloutStrategy == v1alpha1.DecreaseFirstRolloutStrategyType { // make sure that the source deployment has the correct pods before moving the target - sourceSize := c.calculateCurrentSource(c.rolloutStatus.RolloutTargetSize) + sourceSize := c.calculateCurrentSource(c.rolloutStatus.RolloutTargetTotalSize) if c.sourceDeploy.Status.Replicas == sourceSize { // we can increase the target deployment as soon as the source deployment's replica is correct // no need to wait for them to be ready @@ -402,18 +422,10 @@ func (c *DeploymentController) calculateCurrentTarget(totalSize int32) int32 { return targetSize } -// the target deploy size for the current batch +// the source deploy size for the current batch func (c *DeploymentController) calculateCurrentSource(totalSize int32) int32 { currentBatch := int(c.rolloutStatus.CurrentBatch) - var sourceSize int32 - for i, r := range c.rolloutSpec.RolloutBatches { - batchSize, _ := intstr.GetValueFromIntOrPercent(&r.Replicas, int(totalSize), true) - if i < currentBatch { - sourceSize += int32(batchSize) - } else { - break - } - } + sourceSize := totalSize - c.calculateCurrentTarget(totalSize) klog.InfoS("Calculated the number of pods in the source deployment after current batch", "current batch", currentBatch, "source deploy size", sourceSize) return sourceSize @@ -429,9 +441,8 @@ func (c *DeploymentController) patchDeployment(ctx context.Context, target int32 "Failed to update the deployment %s to the correct target %d", deploy.GetName(), target)), err)) return err } - c.recorder.Event(c.parentController, event.Normal("Batch Rollout", - fmt.Sprintf("Submitted upgrade quest for deployment %s to have %d pods for batch %d", - deploy.GetName(), target, c.rolloutStatus.CurrentBatch))) + klog.InfoS("Submitted upgrade quest for deployment", "deployment", + deploy.GetName(), "target replica size", target, "batch", c.rolloutStatus.CurrentBatch) return nil } diff --git a/pkg/utils/env/env.go b/pkg/utils/env/env.go index 4cfaef403..dd21ecea4 100644 --- a/pkg/utils/env/env.go +++ b/pkg/utils/env/env.go @@ -15,6 +15,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8stypes "k8s.io/apimachinery/pkg/types" "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" @@ -75,9 +76,15 @@ func CreateOrUpdateEnv(ctx context.Context, c client.Client, envName string, env } var message = "" - // Create Namespace - if err := c.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: envArgs.Namespace}}); err != nil && !apierrors.IsAlreadyExists(err) { - return message, err + // Check If Namespace Exists + if err := c.Get(ctx, k8stypes.NamespacedName{Name: envArgs.Namespace}, &corev1.Namespace{}); err != nil { + if !apierrors.IsNotFound(err) { + return message, err + } + // Create Namespace if not found + if err := c.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: envArgs.Namespace}}); err != nil { + return message, err + } } // Create Issuer For SSL if both email and domain are all set. diff --git a/test/e2e-test/rollout_plan_test.go b/test/e2e-test/rollout_plan_test.go index 5228264d3..d936a4e32 100644 --- a/test/e2e-test/rollout_plan_test.go +++ b/test/e2e-test/rollout_plan_test.go @@ -199,8 +199,8 @@ var _ = Describe("Cloneset based rollout tests", func() { return appRollout.Status.RollingState }, time.Second*240, time.Second).Should(Equal(oamstd.RolloutSucceedState)) - Expect(appRollout.Status.UpgradedReadyReplicas).Should(BeEquivalentTo(appRollout.Status.RolloutTargetSize)) - Expect(appRollout.Status.UpgradedReplicas).Should(BeEquivalentTo(appRollout.Status.RolloutTargetSize)) + Expect(appRollout.Status.UpgradedReadyReplicas).Should(BeEquivalentTo(appRollout.Status.RolloutTargetTotalSize)) + Expect(appRollout.Status.UpgradedReplicas).Should(BeEquivalentTo(appRollout.Status.RolloutTargetTotalSize)) } VerifyAppConfigRollingCompleted := func(appConfigName string) {