From 9c55209a91438fa495639fbe1e254191106cf9d0 Mon Sep 17 00:00:00 2001 From: "liheng.zms" Date: Tue, 7 Nov 2023 14:52:51 +0800 Subject: [PATCH] new v1beta1 apis --- api/v1alpha1/conversion.go | 444 ++++++++++++++++++ api/v1alpha1/deployment_types.go | 16 + api/v1beta1/batchrelease_plan_types.go | 4 + api/v1beta1/batchrelease_types.go | 5 +- api/v1beta1/rollout_types.go | 76 +-- api/v1beta1/trafficrouting.go | 50 ++ api/v1beta1/zz_generated.deepcopy.go | 106 ++++- .../rollouts.kruise.io_batchreleases.yaml | 47 +- .../bases/rollouts.kruise.io_rollouts.yaml | 90 ++-- config/crd/kustomization.yaml | 4 +- .../crd/patches/webhook_in_batchreleases.yaml | 2 +- config/crd/patches/webhook_in_rollouts.yaml | 2 +- docs/proposals/20231107-v1beta1-apis.md | 224 +++++++++ .../convert_test_case_to_lua_object.go | 12 +- .../batchrelease/batchrelease_controller.go | 6 +- .../batchrelease_controller_test.go | 23 +- .../batchrelease_event_handler.go | 8 +- .../batchrelease/batchrelease_executor.go | 10 +- .../canarystyle/deployment/control_test.go | 10 +- .../partitionstyle/cloneset/control_test.go | 10 +- .../partitionstyle/daemonset/control_test.go | 10 +- .../partitionstyle/deployment/control_test.go | 10 +- .../statefulset/control_test.go | 10 +- pkg/controller/rollout/rollout_canary.go | 39 +- pkg/controller/rollout/rollout_canary_test.go | 7 +- pkg/controller/rollout/rollout_controller.go | 6 +- .../rollout/rollout_controller_test.go | 49 +- .../rollout/rollout_event_handler.go | 2 +- pkg/controller/rollout/rollout_progressing.go | 6 +- .../rollout/rollout_progressing_test.go | 60 +-- pkg/controller/rollout/rollout_status_test.go | 15 +- .../trafficrouting_controller.go | 9 +- pkg/trafficrouting/manager.go | 10 +- pkg/trafficrouting/manager_test.go | 38 +- .../custom_network_provider.go | 21 +- .../custom_network_provider_test.go | 38 +- pkg/trafficrouting/network/gateway/gateway.go | 12 +- .../network/gateway/gateway_test.go | 12 +- pkg/trafficrouting/network/ingress/ingress.go | 12 +- .../network/ingress/ingress_test.go | 45 +- pkg/trafficrouting/network/interface.go | 4 +- pkg/util/controller_finder.go | 40 +- pkg/util/rollout_utils.go | 4 +- .../rollout_create_update_handler.go | 68 ++- .../rollout_create_update_handler_test.go | 139 +++--- .../validating/validate_v1alphal_rollout.go | 26 +- pkg/webhook/server.go | 4 +- .../util/controller/webhook_controller.go | 5 + pkg/webhook/util/crd/crd.go | 87 ++++ .../mutating/workload_update_handler.go | 4 +- .../mutating/workload_update_handler_test.go | 22 +- 51 files changed, 1423 insertions(+), 540 deletions(-) create mode 100644 api/v1alpha1/conversion.go create mode 100644 api/v1beta1/trafficrouting.go create mode 100644 docs/proposals/20231107-v1beta1-apis.md create mode 100644 pkg/webhook/util/crd/crd.go diff --git a/api/v1alpha1/conversion.go b/api/v1alpha1/conversion.go new file mode 100644 index 00000000..b1a677dd --- /dev/null +++ b/api/v1alpha1/conversion.go @@ -0,0 +1,444 @@ +/* +Copyright 2023 The Kruise Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "fmt" + + "github.com/openkruise/rollouts/api/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/conversion" +) + +func (src *Rollout) ConvertTo(dst conversion.Hub) error { + switch t := dst.(type) { + case *v1beta1.Rollout: + obj := dst.(*v1beta1.Rollout) + obj.ObjectMeta = src.ObjectMeta + obj.Spec = v1beta1.RolloutSpec{} + srcSpec := src.Spec + obj.Spec.WorkloadRef = v1beta1.ObjectRef{ + APIVersion: srcSpec.ObjectRef.WorkloadRef.APIVersion, + Kind: srcSpec.ObjectRef.WorkloadRef.Kind, + Name: srcSpec.ObjectRef.WorkloadRef.Name, + } + obj.Spec.Disabled = srcSpec.Disabled + obj.Spec.Strategy = v1beta1.RolloutStrategy{ + Paused: srcSpec.Strategy.Paused, + Canary: &v1beta1.CanaryStrategy{ + FailureThreshold: srcSpec.Strategy.Canary.FailureThreshold, + }, + } + for _, step := range srcSpec.Strategy.Canary.Steps { + o := v1beta1.CanaryStep{ + TrafficRoutingStrategy: ConversionToV1beta1TrafficRoutingStrategy(step.TrafficRoutingStrategy), + DesiredReplicas: step.Replicas, + Pause: v1beta1.RolloutPause{Duration: step.Pause.Duration}, + } + obj.Spec.Strategy.Canary.Steps = append(obj.Spec.Strategy.Canary.Steps, o) + } + for _, ref := range srcSpec.Strategy.Canary.TrafficRoutings { + o := ConversionToV1beta1TrafficRoutingRef(ref) + obj.Spec.Strategy.Canary.TrafficRoutings = append(obj.Spec.Strategy.Canary.TrafficRoutings, o) + } + if srcSpec.Strategy.Canary.PatchPodTemplateMetadata != nil { + obj.Spec.Strategy.Canary.PatchPodTemplateMetadata = &v1beta1.PatchPodTemplateMetadata{ + Annotations: map[string]string{}, + Labels: map[string]string{}, + } + for k, v := range srcSpec.Strategy.Canary.PatchPodTemplateMetadata.Annotations { + obj.Spec.Strategy.Canary.PatchPodTemplateMetadata.Annotations[k] = v + } + for k, v := range srcSpec.Strategy.Canary.PatchPodTemplateMetadata.Labels { + obj.Spec.Strategy.Canary.PatchPodTemplateMetadata.Labels[k] = v + } + } + if src.Annotations[RolloutStyleAnnotation] != string(PartitionRollingStyle) { + obj.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true + } + if src.Annotations[TrafficRoutingAnnotation] != "" { + obj.Spec.Strategy.Canary.TrafficRoutingRef = src.Annotations[TrafficRoutingAnnotation] + } + + // status + obj.Status = v1beta1.RolloutStatus{ + ObservedGeneration: src.Status.ObservedGeneration, + Phase: v1beta1.RolloutPhase(src.Status.Phase), + Message: src.Status.Message, + } + for _, cond := range src.Status.Conditions { + o := v1beta1.RolloutCondition{ + Type: v1beta1.RolloutConditionType(cond.Type), + Status: cond.Status, + LastUpdateTime: cond.LastUpdateTime, + LastTransitionTime: cond.LastTransitionTime, + Reason: cond.Reason, + Message: cond.Message, + } + obj.Status.Conditions = append(obj.Status.Conditions, o) + } + if src.Status.CanaryStatus == nil { + return nil + } + obj.Status.CanaryStatus = &v1beta1.CanaryStatus{ + ObservedWorkloadGeneration: src.Status.CanaryStatus.ObservedWorkloadGeneration, + ObservedRolloutID: src.Status.CanaryStatus.ObservedRolloutID, + RolloutHash: src.Status.CanaryStatus.RolloutHash, + StableRevision: src.Status.CanaryStatus.StableRevision, + CanaryRevision: src.Status.CanaryStatus.CanaryRevision, + PodTemplateHash: src.Status.CanaryStatus.PodTemplateHash, + CanaryReplicas: src.Status.CanaryStatus.CanaryReplicas, + CanaryReadyReplicas: src.Status.CanaryStatus.CanaryReadyReplicas, + CurrentStepIndex: src.Status.CanaryStatus.CurrentStepIndex, + CurrentStepState: v1beta1.CanaryStepState(src.Status.CanaryStatus.CurrentStepState), + Message: src.Status.CanaryStatus.Message, + LastUpdateTime: src.Status.CanaryStatus.LastUpdateTime, + } + return nil + default: + return fmt.Errorf("unsupported type %v", t) + } +} + +func ConversionToV1beta1TrafficRoutingRef(src TrafficRoutingRef) (dst v1beta1.TrafficRoutingRef) { + dst.Service = src.Service + dst.GracePeriodSeconds = src.GracePeriodSeconds + if src.Ingress != nil { + dst.Ingress = &v1beta1.IngressTrafficRouting{ + ClassType: src.Ingress.ClassType, + Name: src.Ingress.Name, + } + } + if src.Gateway != nil { + dst.Gateway = &v1beta1.GatewayTrafficRouting{ + HTTPRouteName: src.Gateway.HTTPRouteName, + } + } + for _, ref := range src.CustomNetworkRefs { + obj := v1beta1.ObjectRef{ + APIVersion: ref.APIVersion, + Kind: ref.Kind, + Name: ref.Name, + } + dst.CustomNetworkRefs = append(dst.CustomNetworkRefs, obj) + } + return dst +} + +func ConversionToV1beta1TrafficRoutingStrategy(src TrafficRoutingStrategy) (dst v1beta1.TrafficRoutingStrategy) { + dst.TrafficWeight = src.Weight + dst.RequestHeaderModifier = src.RequestHeaderModifier + for _, match := range src.Matches { + obj := v1beta1.HttpRouteMatch{ + Headers: match.Headers, + } + dst.Matches = append(dst.Matches, obj) + } + return dst +} + +func (dst *Rollout) ConvertFrom(src conversion.Hub) error { + switch t := src.(type) { + case *v1beta1.Rollout: + srcV1beta1 := src.(*v1beta1.Rollout) + dst.ObjectMeta = srcV1beta1.ObjectMeta + + // spec + dst.Spec = RolloutSpec{ + ObjectRef: ObjectRef{ + WorkloadRef: &WorkloadRef{ + APIVersion: srcV1beta1.Spec.WorkloadRef.APIVersion, + Kind: srcV1beta1.Spec.WorkloadRef.Kind, + Name: srcV1beta1.Spec.WorkloadRef.Name, + }, + }, + Strategy: RolloutStrategy{ + Paused: srcV1beta1.Spec.Strategy.Paused, + Canary: &CanaryStrategy{ + FailureThreshold: srcV1beta1.Spec.Strategy.Canary.FailureThreshold, + }, + }, + Disabled: srcV1beta1.Spec.Disabled, + } + for _, step := range srcV1beta1.Spec.Strategy.Canary.Steps { + obj := CanaryStep{ + TrafficRoutingStrategy: ConversionToV1alpha1TrafficRoutingStrategy(step.TrafficRoutingStrategy), + Replicas: step.DesiredReplicas, + Pause: RolloutPause{Duration: step.Pause.Duration}, + } + dst.Spec.Strategy.Canary.Steps = append(dst.Spec.Strategy.Canary.Steps, obj) + } + for _, ref := range srcV1beta1.Spec.Strategy.Canary.TrafficRoutings { + obj := ConversionToV1alpha1TrafficRoutingRef(ref) + dst.Spec.Strategy.Canary.TrafficRoutings = append(dst.Spec.Strategy.Canary.TrafficRoutings, obj) + } + if srcV1beta1.Spec.Strategy.Canary.PatchPodTemplateMetadata != nil { + dst.Spec.Strategy.Canary.PatchPodTemplateMetadata = &PatchPodTemplateMetadata{ + Annotations: map[string]string{}, + Labels: map[string]string{}, + } + for k, v := range srcV1beta1.Spec.Strategy.Canary.PatchPodTemplateMetadata.Annotations { + dst.Spec.Strategy.Canary.PatchPodTemplateMetadata.Annotations[k] = v + } + for k, v := range srcV1beta1.Spec.Strategy.Canary.PatchPodTemplateMetadata.Labels { + dst.Spec.Strategy.Canary.PatchPodTemplateMetadata.Labels[k] = v + } + } + if dst.Annotations == nil { + dst.Annotations = map[string]string{} + } + if srcV1beta1.Spec.Strategy.Canary.EnableExtraWorkloadForCanary { + dst.Annotations[RolloutStyleAnnotation] = string(CanaryRollingStyle) + } else { + dst.Annotations[RolloutStyleAnnotation] = string(PartitionRollingStyle) + } + if srcV1beta1.Spec.Strategy.Canary.TrafficRoutingRef != "" { + dst.Annotations[TrafficRoutingAnnotation] = srcV1beta1.Spec.Strategy.Canary.TrafficRoutingRef + } + + // status + dst.Status = RolloutStatus{ + ObservedGeneration: srcV1beta1.Status.ObservedGeneration, + Phase: RolloutPhase(srcV1beta1.Status.Phase), + Message: srcV1beta1.Status.Message, + } + for _, cond := range srcV1beta1.Status.Conditions { + obj := RolloutCondition{ + Type: RolloutConditionType(cond.Type), + Status: cond.Status, + LastUpdateTime: cond.LastUpdateTime, + LastTransitionTime: cond.LastTransitionTime, + Reason: cond.Reason, + Message: cond.Message, + } + dst.Status.Conditions = append(dst.Status.Conditions, obj) + } + if srcV1beta1.Status.CanaryStatus == nil { + return nil + } + dst.Status.CanaryStatus = &CanaryStatus{ + ObservedWorkloadGeneration: srcV1beta1.Status.CanaryStatus.ObservedWorkloadGeneration, + ObservedRolloutID: srcV1beta1.Status.CanaryStatus.ObservedRolloutID, + RolloutHash: srcV1beta1.Status.CanaryStatus.RolloutHash, + StableRevision: srcV1beta1.Status.CanaryStatus.StableRevision, + CanaryRevision: srcV1beta1.Status.CanaryStatus.CanaryRevision, + PodTemplateHash: srcV1beta1.Status.CanaryStatus.PodTemplateHash, + CanaryReplicas: srcV1beta1.Status.CanaryStatus.CanaryReplicas, + CanaryReadyReplicas: srcV1beta1.Status.CanaryStatus.CanaryReadyReplicas, + CurrentStepIndex: srcV1beta1.Status.CanaryStatus.CurrentStepIndex, + CurrentStepState: CanaryStepState(srcV1beta1.Status.CanaryStatus.CurrentStepState), + Message: srcV1beta1.Status.CanaryStatus.Message, + LastUpdateTime: srcV1beta1.Status.CanaryStatus.LastUpdateTime, + } + return nil + default: + return fmt.Errorf("unsupported type %v", t) + } +} + +func ConversionToV1alpha1TrafficRoutingStrategy(src v1beta1.TrafficRoutingStrategy) (dst TrafficRoutingStrategy) { + dst.Weight = src.TrafficWeight + dst.RequestHeaderModifier = src.RequestHeaderModifier + for _, match := range src.Matches { + obj := HttpRouteMatch{ + Headers: match.Headers, + } + dst.Matches = append(dst.Matches, obj) + } + return dst +} + +func ConversionToV1alpha1TrafficRoutingRef(src v1beta1.TrafficRoutingRef) (dst TrafficRoutingRef) { + dst.Service = src.Service + dst.GracePeriodSeconds = src.GracePeriodSeconds + if src.Ingress != nil { + dst.Ingress = &IngressTrafficRouting{ + ClassType: src.Ingress.ClassType, + Name: src.Ingress.Name, + } + } + if src.Gateway != nil { + dst.Gateway = &GatewayTrafficRouting{ + HTTPRouteName: src.Gateway.HTTPRouteName, + } + } + for _, ref := range src.CustomNetworkRefs { + obj := CustomNetworkRef{ + APIVersion: ref.APIVersion, + Kind: ref.Kind, + Name: ref.Name, + } + dst.CustomNetworkRefs = append(dst.CustomNetworkRefs, obj) + } + return dst +} + +func (src *BatchRelease) ConvertTo(dst conversion.Hub) error { + switch t := dst.(type) { + case *v1beta1.BatchRelease: + obj := dst.(*v1beta1.BatchRelease) + obj.ObjectMeta = src.ObjectMeta + obj.Spec = v1beta1.BatchReleaseSpec{} + srcSpec := src.Spec + obj.Spec.WorkloadRef = v1beta1.ObjectRef{ + APIVersion: srcSpec.TargetRef.WorkloadRef.APIVersion, + Kind: srcSpec.TargetRef.WorkloadRef.Kind, + Name: srcSpec.TargetRef.WorkloadRef.Name, + } + obj.Spec.ReleasePlan = v1beta1.ReleasePlan{ + BatchPartition: srcSpec.ReleasePlan.BatchPartition, + RolloutID: srcSpec.ReleasePlan.RolloutID, + FailureThreshold: srcSpec.ReleasePlan.FailureThreshold, + FinalizingPolicy: v1beta1.FinalizingPolicyType(srcSpec.ReleasePlan.FinalizingPolicy), + } + for _, batch := range srcSpec.ReleasePlan.Batches { + o := v1beta1.ReleaseBatch{ + CanaryReplicas: batch.CanaryReplicas, + } + obj.Spec.ReleasePlan.Batches = append(obj.Spec.ReleasePlan.Batches, o) + } + if srcSpec.ReleasePlan.PatchPodTemplateMetadata != nil { + obj.Spec.ReleasePlan.PatchPodTemplateMetadata = &v1beta1.PatchPodTemplateMetadata{ + Annotations: map[string]string{}, + Labels: map[string]string{}, + } + for k, v := range srcSpec.ReleasePlan.PatchPodTemplateMetadata.Annotations { + obj.Spec.ReleasePlan.PatchPodTemplateMetadata.Annotations[k] = v + } + for k, v := range srcSpec.ReleasePlan.PatchPodTemplateMetadata.Labels { + obj.Spec.ReleasePlan.PatchPodTemplateMetadata.Labels[k] = v + } + } + if src.Annotations[RolloutStyleAnnotation] != string(PartitionRollingStyle) { + obj.Spec.ReleasePlan.EnableExtraWorkloadForCanary = true + } + + // status + obj.Status = v1beta1.BatchReleaseStatus{ + StableRevision: src.Status.StableRevision, + UpdateRevision: src.Status.UpdateRevision, + ObservedGeneration: src.Status.ObservedGeneration, + ObservedRolloutID: src.Status.ObservedRolloutID, + ObservedWorkloadReplicas: src.Status.ObservedWorkloadReplicas, + ObservedReleasePlanHash: src.Status.ObservedReleasePlanHash, + CollisionCount: src.Status.CollisionCount, + Phase: v1beta1.RolloutPhase(src.Status.Phase), + } + for _, cond := range src.Status.Conditions { + o := v1beta1.RolloutCondition{ + Type: v1beta1.RolloutConditionType(cond.Type), + Status: cond.Status, + LastUpdateTime: cond.LastUpdateTime, + LastTransitionTime: cond.LastTransitionTime, + Reason: cond.Reason, + Message: cond.Message, + } + obj.Status.Conditions = append(obj.Status.Conditions, o) + } + obj.Status.CanaryStatus = v1beta1.BatchReleaseCanaryStatus{ + CurrentBatchState: v1beta1.BatchReleaseBatchStateType(src.Status.CanaryStatus.CurrentBatchState), + CurrentBatch: src.Status.CanaryStatus.CurrentBatch, + BatchReadyTime: src.Status.CanaryStatus.BatchReadyTime, + UpdatedReplicas: src.Status.CanaryStatus.UpdatedReplicas, + UpdatedReadyReplicas: src.Status.CanaryStatus.UpdatedReadyReplicas, + NoNeedUpdateReplicas: src.Status.CanaryStatus.NoNeedUpdateReplicas, + } + return nil + default: + return fmt.Errorf("unsupported type %v", t) + } +} + +func (dst *BatchRelease) ConvertFrom(src conversion.Hub) error { + switch t := src.(type) { + case *v1beta1.BatchRelease: + srcV1beta1 := src.(*v1beta1.BatchRelease) + dst.ObjectMeta = srcV1beta1.ObjectMeta + dst.Spec = BatchReleaseSpec{} + srcSpec := srcV1beta1.Spec + dst.Spec.TargetRef.WorkloadRef = &WorkloadRef{ + APIVersion: srcSpec.WorkloadRef.APIVersion, + Kind: srcSpec.WorkloadRef.Kind, + Name: srcSpec.WorkloadRef.Name, + } + dst.Spec.ReleasePlan = ReleasePlan{ + BatchPartition: srcSpec.ReleasePlan.BatchPartition, + RolloutID: srcSpec.ReleasePlan.RolloutID, + FailureThreshold: srcSpec.ReleasePlan.FailureThreshold, + FinalizingPolicy: FinalizingPolicyType(srcSpec.ReleasePlan.FinalizingPolicy), + } + for _, batch := range srcSpec.ReleasePlan.Batches { + obj := ReleaseBatch{ + CanaryReplicas: batch.CanaryReplicas, + } + dst.Spec.ReleasePlan.Batches = append(dst.Spec.ReleasePlan.Batches, obj) + } + if srcSpec.ReleasePlan.PatchPodTemplateMetadata != nil { + dst.Spec.ReleasePlan.PatchPodTemplateMetadata = &PatchPodTemplateMetadata{ + Annotations: map[string]string{}, + Labels: map[string]string{}, + } + for k, v := range srcSpec.ReleasePlan.PatchPodTemplateMetadata.Annotations { + dst.Spec.ReleasePlan.PatchPodTemplateMetadata.Annotations[k] = v + } + for k, v := range srcSpec.ReleasePlan.PatchPodTemplateMetadata.Labels { + dst.Spec.ReleasePlan.PatchPodTemplateMetadata.Labels[k] = v + } + } + if dst.Annotations == nil { + dst.Annotations = map[string]string{} + } + if srcV1beta1.Spec.ReleasePlan.EnableExtraWorkloadForCanary { + dst.Annotations[RolloutStyleAnnotation] = string(CanaryRollingStyle) + } else { + dst.Annotations[RolloutStyleAnnotation] = string(PartitionRollingStyle) + } + + // status + dst.Status = BatchReleaseStatus{ + StableRevision: srcV1beta1.Status.StableRevision, + UpdateRevision: srcV1beta1.Status.UpdateRevision, + ObservedGeneration: srcV1beta1.Status.ObservedGeneration, + ObservedRolloutID: srcV1beta1.Status.ObservedRolloutID, + ObservedWorkloadReplicas: srcV1beta1.Status.ObservedWorkloadReplicas, + ObservedReleasePlanHash: srcV1beta1.Status.ObservedReleasePlanHash, + CollisionCount: srcV1beta1.Status.CollisionCount, + Phase: RolloutPhase(srcV1beta1.Status.Phase), + } + for _, cond := range srcV1beta1.Status.Conditions { + obj := RolloutCondition{ + Type: RolloutConditionType(cond.Type), + Status: cond.Status, + LastUpdateTime: cond.LastUpdateTime, + LastTransitionTime: cond.LastTransitionTime, + Reason: cond.Reason, + Message: cond.Message, + } + dst.Status.Conditions = append(dst.Status.Conditions, obj) + } + dst.Status.CanaryStatus = BatchReleaseCanaryStatus{ + CurrentBatchState: BatchReleaseBatchStateType(srcV1beta1.Status.CanaryStatus.CurrentBatchState), + CurrentBatch: srcV1beta1.Status.CanaryStatus.CurrentBatch, + BatchReadyTime: srcV1beta1.Status.CanaryStatus.BatchReadyTime, + UpdatedReplicas: srcV1beta1.Status.CanaryStatus.UpdatedReplicas, + UpdatedReadyReplicas: srcV1beta1.Status.CanaryStatus.UpdatedReadyReplicas, + NoNeedUpdateReplicas: srcV1beta1.Status.CanaryStatus.NoNeedUpdateReplicas, + } + return nil + default: + return fmt.Errorf("unsupported type %v", t) + } +} diff --git a/api/v1alpha1/deployment_types.go b/api/v1alpha1/deployment_types.go index 940dd341..35202e5b 100644 --- a/api/v1alpha1/deployment_types.go +++ b/api/v1alpha1/deployment_types.go @@ -1,3 +1,19 @@ +/* +Copyright 2023 The Kruise Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package v1alpha1 import ( diff --git a/api/v1beta1/batchrelease_plan_types.go b/api/v1beta1/batchrelease_plan_types.go index 65b4ddb7..8b3c21a2 100644 --- a/api/v1beta1/batchrelease_plan_types.go +++ b/api/v1beta1/batchrelease_plan_types.go @@ -54,6 +54,10 @@ type ReleasePlan struct { // only support for canary deployment // +optional PatchPodTemplateMetadata *PatchPodTemplateMetadata `json:"patchPodTemplateMetadata,omitempty"` + // If true, then it will create new deployment for canary, such as: workload-demo-canary. + // When user verifies that the canary version is ready, we will remove the canary deployment and release the deployment workload-demo in full. + // Current only support k8s native deployment + EnableExtraWorkloadForCanary bool `json:"enableExtraWorkloadForCanary"` } type FinalizingPolicyType string diff --git a/api/v1beta1/batchrelease_types.go b/api/v1beta1/batchrelease_types.go index ffb80409..25e5befe 100644 --- a/api/v1beta1/batchrelease_types.go +++ b/api/v1beta1/batchrelease_types.go @@ -41,8 +41,9 @@ type BatchRelease struct { // BatchReleaseSpec defines how to describe an update between different compRevision type BatchReleaseSpec struct { - // TargetRef contains the GVK and name of the workload that we need to upgrade to. - TargetRef ObjectRef `json:"targetReference"` + // WorkloadRef contains enough information to let you identify a workload for Rollout + // Batch release of the bypass + WorkloadRef ObjectRef `json:"workloadRef,omitempty"` // ReleasePlan is the details on how to rollout the resources ReleasePlan ReleasePlan `json:"releasePlan"` } diff --git a/api/v1beta1/rollout_types.go b/api/v1beta1/rollout_types.go index 5e43eb48..0d4ac391 100644 --- a/api/v1beta1/rollout_types.go +++ b/api/v1beta1/rollout_types.go @@ -17,7 +17,6 @@ limitations under the License. package v1beta1 import ( - "github.com/openkruise/rollouts/api/v1alpha1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -42,51 +41,25 @@ const ( // RollbackInBatchAnnotation is set to rollout annotations. // RollbackInBatchAnnotation allow use disable quick rollback, and will roll back in batch style. RollbackInBatchAnnotation = "rollouts.kruise.io/rollback-in-batch" - - // RolloutStyleAnnotation define the rolling behavior for Deployment. - // must be "partition" or "canary": - // * "partition" means rolling in batches just like CloneSet, and will NOT create any extra Workload; - // * "canary" means rolling in canary way, and will create a canary Workload. - // Currently, only Deployment support both "partition" and "canary" rolling styles. - // For other workload types, they only support "partition" styles. - // Defaults to "canary" to Deployment. - // Defaults to "partition" to the others. - RolloutStyleAnnotation = "rollouts.kruise.io/rolling-style" - - // TrafficRoutingAnnotation is the TrafficRouting Name, and it is the Rollout's TrafficRouting. - // The Rollout release will trigger the TrafficRouting release. For example: - // A microservice consists of three applications, and the invocation relationship is as follows: a -> b -> c, - // and application(a, b, c)'s gateway is trafficRouting. Any application(a, b or b) release will trigger TrafficRouting release. - TrafficRoutingAnnotation = "rollouts.kruise.io/trafficrouting" ) // RolloutSpec defines the desired state of Rollout type RolloutSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - // ObjectRef indicates workload - ObjectRef ObjectRef `json:"objectRef"` + // WorkloadRef contains enough information to let you identify a workload for Rollout + // Batch release of the bypass + WorkloadRef ObjectRef `json:"workloadRef"` // rollout strategy Strategy RolloutStrategy `json:"strategy"` - // DeprecatedRolloutID is the deprecated field. - // It is recommended that configure RolloutId in workload.annotations[rollouts.kruise.io/rollout-id]. - // RolloutID should be changed before each workload revision publication. - // It is to distinguish consecutive multiple workload publications and rollout progress. - DeprecatedRolloutID string `json:"rolloutID,omitempty"` // if a rollout disabled, then the rollout would not watch changes of workload //+kubebuilder:validation:Optional //+kubebuilder:default=false Disabled bool `json:"disabled"` } +// ObjectRef holds a references to the Kubernetes object type ObjectRef struct { - // WorkloadRef contains enough information to let you identify a workload for Rollout - // Batch release of the bypass - WorkloadRef *WorkloadRef `json:"workloadRef,omitempty"` -} - -// WorkloadRef holds a references to the Kubernetes object -type WorkloadRef struct { // API Version of the referent APIVersion string `json:"apiVersion"` // Kind of the referent @@ -111,7 +84,7 @@ type CanaryStrategy struct { Steps []CanaryStep `json:"steps,omitempty"` // TrafficRoutings hosts all the supported service meshes supported to enable more fine-grained traffic routing // and current only support one TrafficRouting - TrafficRoutings []v1alpha1.TrafficRoutingRef `json:"trafficRoutings,omitempty"` + TrafficRoutings []TrafficRoutingRef `json:"trafficRoutings,omitempty"` // FailureThreshold indicates how many failed pods can be tolerated in all upgraded pods. // Only when FailureThreshold are satisfied, Rollout can enter ready state. // If FailureThreshold is nil, Rollout will use the MaxUnavailable of workload as its @@ -122,6 +95,12 @@ type CanaryStrategy struct { // only support for canary deployment // +optional PatchPodTemplateMetadata *PatchPodTemplateMetadata `json:"patchPodTemplateMetadata,omitempty"` + // If true, then it will create new deployment for canary, such as: workload-demo-canary. + // When user verifies that the canary version is ready, we will remove the canary deployment and release the deployment workload-demo in full. + // Current only support k8s native deployment + EnableExtraWorkloadForCanary bool `json:"enableExtraWorkloadForCanary,omitempty"` + // TrafficRoutingRef is TrafficRouting's Name + TrafficRoutingRef string `json:"trafficRoutingRef,omitempty"` } type PatchPodTemplateMetadata struct { @@ -133,15 +112,44 @@ type PatchPodTemplateMetadata struct { // CanaryStep defines a step of a canary workload. type CanaryStep struct { - v1alpha1.TrafficRoutingStrategy `json:",inline"` + TrafficRoutingStrategy `json:",inline"` // Replicas is the number of expected canary pods in this batch // it can be an absolute number (ex: 5) or a percentage of total pods. - Replicas *intstr.IntOrString `json:"replicas,omitempty"` + DesiredReplicas *intstr.IntOrString `json:"desiredReplicas,omitempty"` // Pause defines a pause stage for a rollout, manual or auto // +optional Pause RolloutPause `json:"pause,omitempty"` } +type TrafficRoutingStrategy struct { + // TrafficWeight indicate how many percentage of traffic the canary pods should receive + // +optional + TrafficWeight *int32 `json:"trafficWeight,omitempty"` + // Set overwrites the request with the given header (name, value) + // before the action. + // + // Input: + // GET /foo HTTP/1.1 + // my-header: foo + // + // requestHeaderModifier: + // set: + // - name: "my-header" + // value: "bar" + // + // Output: + // GET /foo HTTP/1.1 + // my-header: bar + // + // +optional + RequestHeaderModifier *gatewayv1alpha2.HTTPRequestHeaderFilter `json:"requestHeaderModifier,omitempty"` + // Matches define conditions used for matching the incoming HTTP requests to canary service. + // Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. + // If Gateway API, current only support one match. + // And cannot support both weight and matches, if both are configured, then matches takes precedence. + Matches []HttpRouteMatch `json:"matches,omitempty"` +} + type HttpRouteMatch struct { // Headers specifies HTTP request header matchers. Multiple match values are // ANDed together, meaning, a request must match all the specified headers diff --git a/api/v1beta1/trafficrouting.go b/api/v1beta1/trafficrouting.go new file mode 100644 index 00000000..3cd2c94b --- /dev/null +++ b/api/v1beta1/trafficrouting.go @@ -0,0 +1,50 @@ +/* +Copyright 2023 The Kruise Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +// TrafficRoutingRef hosts all the different configuration for supported service meshes to enable more fine-grained traffic routing +type TrafficRoutingRef struct { + // Service holds the name of a service which selects pods with stable version and don't select any pods with canary version. + Service string `json:"service"` + // Optional duration in seconds the traffic provider(e.g. nginx ingress controller) consumes the service, ingress configuration changes gracefully. + GracePeriodSeconds int32 `json:"gracePeriodSeconds,omitempty"` + // Ingress holds Ingress specific configuration to route traffic, e.g. Nginx, Alb. + Ingress *IngressTrafficRouting `json:"ingress,omitempty"` + // Gateway holds Gateway specific configuration to route traffic + // Gateway configuration only supports >= v0.4.0 (v1alpha2). + Gateway *GatewayTrafficRouting `json:"gateway,omitempty"` + // CustomNetworkRefs hold a list of custom providers to route traffic + CustomNetworkRefs []ObjectRef `json:"customNetworkRefs,omitempty"` +} + +// IngressTrafficRouting configuration for ingress controller to control traffic routing +type IngressTrafficRouting struct { + // ClassType refers to the type of `Ingress`. + // current support nginx, aliyun-alb. default is nginx. + // +optional + ClassType string `json:"classType,omitempty"` + // Name refers to the name of an `Ingress` resource in the same namespace as the `Rollout` + Name string `json:"name"` +} + +// GatewayTrafficRouting configuration for gateway api +type GatewayTrafficRouting struct { + // HTTPRouteName refers to the name of an `HTTPRoute` resource in the same namespace as the `Rollout` + HTTPRouteName *string `json:"httpRouteName,omitempty"` + // TCPRouteName *string `json:"tcpRouteName,omitempty"` + // UDPRouteName *string `json:"udpRouteName,omitempty"` +} diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 6e753450..c141e6ee 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -22,7 +22,6 @@ limitations under the License. package v1beta1 import ( - "github.com/openkruise/rollouts/api/v1alpha1" "k8s.io/api/apps/v1" runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" @@ -115,7 +114,7 @@ func (in *BatchReleaseList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BatchReleaseSpec) DeepCopyInto(out *BatchReleaseSpec) { *out = *in - in.TargetRef.DeepCopyInto(&out.TargetRef) + out.WorkloadRef = in.WorkloadRef in.ReleasePlan.DeepCopyInto(&out.ReleasePlan) } @@ -180,8 +179,8 @@ func (in *CanaryStatus) DeepCopy() *CanaryStatus { func (in *CanaryStep) DeepCopyInto(out *CanaryStep) { *out = *in in.TrafficRoutingStrategy.DeepCopyInto(&out.TrafficRoutingStrategy) - if in.Replicas != nil { - in, out := &in.Replicas, &out.Replicas + if in.DesiredReplicas != nil { + in, out := &in.DesiredReplicas, &out.DesiredReplicas *out = new(intstr.IntOrString) **out = **in } @@ -210,7 +209,7 @@ func (in *CanaryStrategy) DeepCopyInto(out *CanaryStrategy) { } if in.TrafficRoutings != nil { in, out := &in.TrafficRoutings, &out.TrafficRoutings - *out = make([]v1alpha1.TrafficRoutingRef, len(*in)) + *out = make([]TrafficRoutingRef, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -273,6 +272,26 @@ func (in *DeploymentStrategy) DeepCopy() *DeploymentStrategy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayTrafficRouting) DeepCopyInto(out *GatewayTrafficRouting) { + *out = *in + if in.HTTPRouteName != nil { + in, out := &in.HTTPRouteName, &out.HTTPRouteName + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayTrafficRouting. +func (in *GatewayTrafficRouting) DeepCopy() *GatewayTrafficRouting { + if in == nil { + return nil + } + out := new(GatewayTrafficRouting) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HttpRouteMatch) DeepCopyInto(out *HttpRouteMatch) { *out = *in @@ -296,13 +315,23 @@ func (in *HttpRouteMatch) DeepCopy() *HttpRouteMatch { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ObjectRef) DeepCopyInto(out *ObjectRef) { +func (in *IngressTrafficRouting) DeepCopyInto(out *IngressTrafficRouting) { *out = *in - if in.WorkloadRef != nil { - in, out := &in.WorkloadRef, &out.WorkloadRef - *out = new(WorkloadRef) - **out = **in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressTrafficRouting. +func (in *IngressTrafficRouting) DeepCopy() *IngressTrafficRouting { + if in == nil { + return nil } + out := new(IngressTrafficRouting) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectRef) DeepCopyInto(out *ObjectRef) { + *out = *in } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectRef. @@ -494,7 +523,7 @@ func (in *RolloutPause) DeepCopy() *RolloutPause { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RolloutSpec) DeepCopyInto(out *RolloutSpec) { *out = *in - in.ObjectRef.DeepCopyInto(&out.ObjectRef) + out.WorkloadRef = in.WorkloadRef in.Strategy.DeepCopyInto(&out.Strategy) } @@ -556,16 +585,63 @@ func (in *RolloutStrategy) DeepCopy() *RolloutStrategy { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WorkloadRef) DeepCopyInto(out *WorkloadRef) { +func (in *TrafficRoutingRef) DeepCopyInto(out *TrafficRoutingRef) { *out = *in + if in.Ingress != nil { + in, out := &in.Ingress, &out.Ingress + *out = new(IngressTrafficRouting) + **out = **in + } + if in.Gateway != nil { + in, out := &in.Gateway, &out.Gateway + *out = new(GatewayTrafficRouting) + (*in).DeepCopyInto(*out) + } + if in.CustomNetworkRefs != nil { + in, out := &in.CustomNetworkRefs, &out.CustomNetworkRefs + *out = make([]ObjectRef, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRoutingRef. +func (in *TrafficRoutingRef) DeepCopy() *TrafficRoutingRef { + if in == nil { + return nil + } + out := new(TrafficRoutingRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrafficRoutingStrategy) DeepCopyInto(out *TrafficRoutingStrategy) { + *out = *in + if in.TrafficWeight != nil { + in, out := &in.TrafficWeight, &out.TrafficWeight + *out = new(int32) + **out = **in + } + if in.RequestHeaderModifier != nil { + in, out := &in.RequestHeaderModifier, &out.RequestHeaderModifier + *out = new(v1alpha2.HTTPRequestHeaderFilter) + (*in).DeepCopyInto(*out) + } + if in.Matches != nil { + in, out := &in.Matches, &out.Matches + *out = make([]HttpRouteMatch, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadRef. -func (in *WorkloadRef) DeepCopy() *WorkloadRef { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRoutingStrategy. +func (in *TrafficRoutingStrategy) DeepCopy() *TrafficRoutingStrategy { if in == nil { return nil } - out := new(WorkloadRef) + out := new(TrafficRoutingStrategy) in.DeepCopyInto(out) return out } diff --git a/config/crd/bases/rollouts.kruise.io_batchreleases.yaml b/config/crd/bases/rollouts.kruise.io_batchreleases.yaml index 89cd6311..7eb29e17 100644 --- a/config/crd/bases/rollouts.kruise.io_batchreleases.yaml +++ b/config/crd/bases/rollouts.kruise.io_batchreleases.yaml @@ -345,6 +345,13 @@ spec: - canaryReplicas type: object type: array + enableExtraWorkloadForCanary: + description: 'If true, then it will create new deployment for + canary, such as: workload-demo-canary. When user verifies that + the canary version is ready, we will remove the canary deployment + and release the deployment workload-demo in full. Current only + support k8s native deployment' + type: boolean failureThreshold: anyOf: - type: integer @@ -378,33 +385,29 @@ spec: rolloutID: description: RolloutID indicates an id for each rollout progress type: string + required: + - enableExtraWorkloadForCanary type: object - targetReference: - description: TargetRef contains the GVK and name of the workload that - we need to upgrade to. + workloadRef: + description: WorkloadRef contains enough information to let you identify + a workload for Rollout Batch release of the bypass properties: - workloadRef: - description: WorkloadRef contains enough information to let you - identify a workload for Rollout Batch release of the bypass - properties: - apiVersion: - description: API Version of the referent - type: string - kind: - description: Kind of the referent - type: string - name: - description: Name of the referent - type: string - required: - - apiVersion - - kind - - name - type: object + apiVersion: + description: API Version of the referent + type: string + kind: + description: Kind of the referent + type: string + name: + description: Name of the referent + type: string + required: + - apiVersion + - kind + - name type: object required: - releasePlan - - targetReference type: object status: description: BatchReleaseStatus defines the observed state of a release diff --git a/config/crd/bases/rollouts.kruise.io_rollouts.yaml b/config/crd/bases/rollouts.kruise.io_rollouts.yaml index f99434da..1819e26b 100644 --- a/config/crd/bases/rollouts.kruise.io_rollouts.yaml +++ b/config/crd/bases/rollouts.kruise.io_rollouts.yaml @@ -567,37 +567,6 @@ spec: description: if a rollout disabled, then the rollout would not watch changes of workload type: boolean - objectRef: - description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - Important: Run "make" to regenerate code after modifying this file - ObjectRef indicates workload' - properties: - workloadRef: - description: WorkloadRef contains enough information to let you - identify a workload for Rollout Batch release of the bypass - properties: - apiVersion: - description: API Version of the referent - type: string - kind: - description: Kind of the referent - type: string - name: - description: Name of the referent - type: string - required: - - apiVersion - - kind - - name - type: object - type: object - rolloutID: - description: DeprecatedRolloutID is the deprecated field. It is recommended - that configure RolloutId in workload.annotations[rollouts.kruise.io/rollout-id]. - RolloutID should be changed before each workload revision publication. - It is to distinguish consecutive multiple workload publications - and rollout progress. - type: string strategy: description: rollout strategy properties: @@ -605,6 +574,13 @@ spec: description: CanaryStrategy defines parameters for a Replica Based Canary properties: + enableExtraWorkloadForCanary: + description: 'If true, then it will create new deployment + for canary, such as: workload-demo-canary. When user verifies + that the canary version is ready, we will remove the canary + deployment and release the deployment workload-demo in full. + Current only support k8s native deployment' + type: boolean failureThreshold: anyOf: - type: integer @@ -637,6 +613,14 @@ spec: items: description: CanaryStep defines a step of a canary workload. properties: + desiredReplicas: + anyOf: + - type: integer + - type: string + description: 'Replicas is the number of expected canary + pods in this batch it can be an absolute number (ex: + 5) or a percentage of total pods.' + x-kubernetes-int-or-string: true matches: description: Matches define conditions used for matching the incoming HTTP requests to canary service. Each @@ -718,14 +702,6 @@ spec: format: int32 type: integer type: object - replicas: - anyOf: - - type: integer - - type: string - description: 'Replicas is the number of expected canary - pods in this batch it can be an absolute number (ex: - 5) or a percentage of total pods.' - x-kubernetes-int-or-string: true requestHeaderModifier: description: "Set overwrites the request with the given header (name, value) before the action. \n Input: @@ -830,13 +806,16 @@ spec: - name x-kubernetes-list-type: map type: object - weight: - description: Weight indicate how many percentage of - traffic the canary pods should receive + trafficWeight: + description: TrafficWeight indicate how many percentage + of traffic the canary pods should receive format: int32 type: integer type: object type: array + trafficRoutingRef: + description: TrafficRoutingRef is TrafficRouting's Name + type: string trafficRoutings: description: TrafficRoutings hosts all the supported service meshes supported to enable more fine-grained traffic routing @@ -850,12 +829,17 @@ spec: description: CustomNetworkRefs hold a list of custom providers to route traffic items: + description: ObjectRef holds a references to the Kubernetes + object properties: apiVersion: + description: API Version of the referent type: string kind: + description: Kind of the referent type: string name: + description: Name of the referent type: string required: - apiVersion @@ -911,9 +895,29 @@ spec: value is false type: boolean type: object + workloadRef: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "make" to regenerate code after modifying this file + WorkloadRef contains enough information to let you identify a workload + for Rollout Batch release of the bypass' + properties: + apiVersion: + description: API Version of the referent + type: string + kind: + description: Kind of the referent + type: string + name: + description: Name of the referent + type: string + required: + - apiVersion + - kind + - name + type: object required: - - objectRef - strategy + - workloadRef type: object status: description: RolloutStatus defines the observed state of Rollout diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 205d70d7..9869075a 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -11,8 +11,8 @@ resources: patchesStrategicMerge: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD -#- patches/webhook_in_rollouts.yaml -#- patches/webhook_in_batchreleases.yaml +- patches/webhook_in_rollouts.yaml +- patches/webhook_in_batchreleases.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. diff --git a/config/crd/patches/webhook_in_batchreleases.yaml b/config/crd/patches/webhook_in_batchreleases.yaml index b8a39e88..335185de 100644 --- a/config/crd/patches/webhook_in_batchreleases.yaml +++ b/config/crd/patches/webhook_in_batchreleases.yaml @@ -13,4 +13,4 @@ spec: name: webhook-service path: /convert conversionReviewVersions: - - v1 + - v1beta1 diff --git a/config/crd/patches/webhook_in_rollouts.yaml b/config/crd/patches/webhook_in_rollouts.yaml index e7e2093a..01c5f29c 100644 --- a/config/crd/patches/webhook_in_rollouts.yaml +++ b/config/crd/patches/webhook_in_rollouts.yaml @@ -13,4 +13,4 @@ spec: name: webhook-service path: /convert conversionReviewVersions: - - v1 + - v1beta1 diff --git a/docs/proposals/20231107-v1beta1-apis.md b/docs/proposals/20231107-v1beta1-apis.md new file mode 100644 index 00000000..551115a9 --- /dev/null +++ b/docs/proposals/20231107-v1beta1-apis.md @@ -0,0 +1,224 @@ +--- +title: v1beta1-apis-proposal + +authors: +- "@zmberg" + +creation-date: 2023-11-07 + +--- + +## Motivation + +The Kruise Rollout project has been stable for a year, recently we plan to upgrade the apis from v1alpha1 to v1beta1 and optimize some of the fields in response to past questions and community feedback, +this proposal will organize the v1beta1 apis and discuss it with the community. + +## Proposal +To make it easier to understand, I'm going to introduce the v1beta1 field from 6 scenario. + +### Canary Release +``` +apiVersion: rollouts.kruise.io/v1beta1 +kind: Rollout +metadata: + name: rollouts-demo +spec: + workloadRef: + apiVersion: apps/v1 + kind: Deployment + name: workload-demo + strategy: + canary: + # If true, then it will create new deployment for canary, such as: workload-demo-canary. + # When user verifies that the canary version is OK, we will remove the canary deployment and release the deployment workload-demo in full. + # Current only support k8s native deployment + enableExtraWorkloadForCanary: true + steps: + - trafficWeight: 20% + desiredReplicas: 2 + trafficRoutings: + - service: service-demo + ingress: + classType: nginx + name: ingress-demo +``` + +### A/B Testing Release +``` +apiVersion: rollouts.kruise.io/v1beta1 +kind: Rollout +metadata: + name: rollouts-demo +spec: + workloadRef: + apiVersion: apps/v1 + kind: Deployment + name: workload-demo + strategy: + canary: + enableExtraWorkloadForCanary: true + steps: + - desiredReplicas: 2 + trafficMatches: + - headers: + - name: user-agent + type: Exact + value: pc + trafficRoutings: + - service: service-demo + ingress: + classType: nginx + name: ingress-demo +``` + +### Only Batch Release +``` +apiVersion: rollouts.kruise.io/v1beta1 +kind: Rollout +metadata: + name: rollouts-demo +spec: + workloadRef: + apiVersion: apps/v1 + kind: Deployment + name: workload-demo + strategy: + canary: + steps: + - desiredReplicas: 1 + - desiredReplicas: 10% + # After desiredReplicas Pods are ready, sleep 60 and continue to release later batches. + # If you don't configure it, manual confirmation is required by default. + pause: {duration: 60} + - desiredReplicas: 30% + pause: {duration: 60} + - desiredReplicas: 60% + pause: {duration: 60} + - desiredReplicas: 100% + pause: {duration: 60} +``` + +### Batch Release + Traffic Weight +``` +apiVersion: rollouts.kruise.io/v1beta1 +kind: Rollout +metadata: + name: rollouts-demo +spec: + workloadRef: + apiVersion: apps/v1 + kind: Deployment + name: workload-demo + strategy: + canary: + steps: + - trafficWeight: 5% + desiredReplicas: 2 + - desiredReplicas: 30% + - desiredReplicas: 60% + - desiredReplicas: 100% + trafficRoutings: + - service: service-demo + ingress: + classType: nginx + name: ingress-demo +``` +### Batch Release + Traffic A/B Testing +``` +apiVersion: rollouts.kruise.io/v1beta1 +kind: Rollout +metadata: + name: rollouts-demo +spec: + workloadRef: + apiVersion: apps/v1 + kind: Deployment + name: workload-demo + strategy: + canary: + steps: + - trafficMatches: + - headers: + - name: user-agent + type: Exact + value: pc + desiredReplicas: 2 + - desiredReplicas: 30% + - desiredReplicas: 60% + - desiredReplicas: 100% + trafficRoutings: + - service: service-demo + ingress: + classType: nginx + name: ingress-demo +``` +### End-to-End progressive delivery for microservice application +``` +apiVersion: rollouts.kruise.io/v1alpha1 +kind: TrafficRouting +metadata: + name: mse-traffic +spec: + objectRef: + - service: spring-cloud-a + ingress: + classType: mse + name: spring-cloud-a + strategy: + matches: + - headers: + - type: Exact + name: User-Agent + value: xiaoming + # http request via ingress, and add header[x-mse-tag]=gray + # for mse or istio routing the gray traffic to gray application + requestHeaderModifier: + set: + - name: x-mse-tag + value: gray +--- +apiVersion: rollouts.kruise.io/v1alpha1 +kind: Rollout +metadata: + name: rollout-a +spec: + workloadRef: + apiVersion: apps/v1 + kind: Deployment + name: spring-cloud-a + strategy: + canary: + enableExtraWorkloadForCanary: true + # Type TrafficRouting's name + trafficRoutingRef: mse-traffic + steps: + - desiredReplicas: 1 + # patch pod template metadata to canary workload + # current only support deployment, and when enableExtraWorkloadForCanary=true + patchPodTemplateMetadata: + labels: + alicloud.service.tag: gray + opensergo.io/canary-gray: gray +--- +apiVersion: rollouts.kruise.io/v1alpha1 +kind: Rollout +metadata: + name: rollout-a +spec: + workloadRef: + apiVersion: apps/v1 + kind: Deployment + name: spring-cloud-a + strategy: + canary: + enableExtraWorkloadForCanary: true + # Type TrafficRouting's name + trafficRoutingRef: mse-traffic + steps: + - desiredReplicas: 1 + # patch pod template metadata to canary workload + patchPodTemplateMetadata: + labels: + alicloud.service.tag: gray + opensergo.io/canary-gray: gray +``` diff --git a/lua_configuration/convert_test_case_to_lua_object.go b/lua_configuration/convert_test_case_to_lua_object.go index dadf3ba9..a6cb072b 100644 --- a/lua_configuration/convert_test_case_to_lua_object.go +++ b/lua_configuration/convert_test_case_to_lua_object.go @@ -82,8 +82,8 @@ func objectToTable(path string) error { if rollout != nil { steps := rollout.Spec.Strategy.Canary.Steps for i, step := range steps { - weight := step.TrafficRoutingStrategy.Weight - if step.TrafficRoutingStrategy.Weight == nil { + weight := step.TrafficRoutingStrategy.TrafficWeight + if step.TrafficRoutingStrategy.TrafficWeight == nil { weight = utilpointer.Int32(-1) } var canaryService string @@ -111,13 +111,19 @@ func objectToTable(path string) error { var canaryService string stableService := trafficRouting.Spec.ObjectRef[0].Service canaryService = stableService + matches := make([]v1beta1.HttpRouteMatch, 0) + for _, match := range trafficRouting.Spec.Strategy.Matches { + obj := v1beta1.HttpRouteMatch{} + obj.Headers = match.Headers + matches = append(matches, obj) + } data := &custom.LuaData{ Data: custom.Data{ Labels: testCase.Original.GetLabels(), Annotations: testCase.Original.GetAnnotations(), Spec: testCase.Original.Object["spec"], }, - Matches: trafficRouting.Spec.Strategy.Matches, + Matches: matches, CanaryWeight: *weight, StableWeight: 100 - *weight, CanaryService: canaryService, diff --git a/pkg/controller/batchrelease/batchrelease_controller.go b/pkg/controller/batchrelease/batchrelease_controller.go index a2f5caee..2eaf2faa 100644 --- a/pkg/controller/batchrelease/batchrelease_controller.go +++ b/pkg/controller/batchrelease/batchrelease_controller.go @@ -167,10 +167,10 @@ func (r *BatchReleaseReconciler) Reconcile(ctx context.Context, req ctrl.Request klog.Infof("Begin to reconcile BatchRelease(%v/%v), release-phase: %v", release.Namespace, release.Name, release.Status.Phase) // If workload watcher does not exist, then add the watcher dynamically - workloadRef := release.Spec.TargetRef.WorkloadRef - workloadGVK := util.GetGVKFrom(workloadRef) + workloadRef := release.Spec.WorkloadRef + workloadGVK := util.GetGVKFrom(&workloadRef) _, exists := watchedWorkload.Load(workloadGVK.String()) - if workloadRef != nil && !exists { + if !exists { succeeded, err := util.AddWatcherDynamically(runtimeController, workloadHandler, workloadGVK) if err != nil { return ctrl.Result{}, err diff --git a/pkg/controller/batchrelease/batchrelease_controller_test.go b/pkg/controller/batchrelease/batchrelease_controller_test.go index 7d434d06..c339b74b 100644 --- a/pkg/controller/batchrelease/batchrelease_controller_test.go +++ b/pkg/controller/batchrelease/batchrelease_controller_test.go @@ -61,15 +61,14 @@ var ( UID: types.UID("87076677"), }, Spec: v1beta1.BatchReleaseSpec{ - TargetRef: v1beta1.ObjectRef{ - WorkloadRef: &v1beta1.WorkloadRef{ - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "sample", - }, + WorkloadRef: v1beta1.ObjectRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "sample", }, ReleasePlan: v1beta1.ReleasePlan{ - BatchPartition: pointer.Int32(0), + EnableExtraWorkloadForCanary: true, + BatchPartition: pointer.Int32(0), Batches: []v1beta1.ReleaseBatch{ { CanaryReplicas: intstr.FromString("10%"), @@ -141,12 +140,10 @@ var ( UID: types.UID("87076677"), }, Spec: v1beta1.BatchReleaseSpec{ - TargetRef: v1beta1.ObjectRef{ - WorkloadRef: &v1beta1.WorkloadRef{ - APIVersion: "apps.kruise.io/v1alpha1", - Kind: "CloneSet", - Name: "sample", - }, + WorkloadRef: v1beta1.ObjectRef{ + APIVersion: "apps.kruise.io/v1alpha1", + Kind: "CloneSet", + Name: "sample", }, ReleasePlan: v1beta1.ReleasePlan{ BatchPartition: pointer.Int32Ptr(0), diff --git a/pkg/controller/batchrelease/batchrelease_event_handler.go b/pkg/controller/batchrelease/batchrelease_event_handler.go index a7a6d102..2fbdcd08 100644 --- a/pkg/controller/batchrelease/batchrelease_event_handler.go +++ b/pkg/controller/batchrelease/batchrelease_event_handler.go @@ -240,14 +240,14 @@ func getBatchRelease(c client.Reader, workloadNamespaceName types.NamespacedName for i := range brList.Items { br := &brList.Items[i] - targetRef := br.Spec.TargetRef - targetGV, err := schema.ParseGroupVersion(targetRef.WorkloadRef.APIVersion) + targetRef := br.Spec.WorkloadRef + targetGV, err := schema.ParseGroupVersion(targetRef.APIVersion) if err != nil { - klog.Errorf("Failed to parse targetRef's group version: %s for BatchRelease(%v)", targetRef.WorkloadRef.APIVersion, client.ObjectKeyFromObject(br)) + klog.Errorf("Failed to parse targetRef's group version: %s for BatchRelease(%v)", targetRef.APIVersion, client.ObjectKeyFromObject(br)) continue } - if targetRef.WorkloadRef.Kind == gvk.Kind && targetGV.Group == gvk.Group && targetRef.WorkloadRef.Name == workloadNamespaceName.Name { + if targetRef.Kind == gvk.Kind && targetGV.Group == gvk.Group && targetRef.Name == workloadNamespaceName.Name { nsn = client.ObjectKeyFromObject(br) } } diff --git a/pkg/controller/batchrelease/batchrelease_executor.go b/pkg/controller/batchrelease/batchrelease_executor.go index 23c9a1ed..e1aa563e 100644 --- a/pkg/controller/batchrelease/batchrelease_executor.go +++ b/pkg/controller/batchrelease/batchrelease_executor.go @@ -19,11 +19,9 @@ package batchrelease import ( "fmt" "reflect" - "strings" "time" appsv1alpha1 "github.com/openkruise/kruise-api/apps/v1alpha1" - "github.com/openkruise/rollouts/api/v1alpha1" "github.com/openkruise/rollouts/api/v1beta1" "github.com/openkruise/rollouts/pkg/controller/batchrelease/control" "github.com/openkruise/rollouts/pkg/controller/batchrelease/control/canarystyle" @@ -187,11 +185,7 @@ func (r *Executor) progressBatches(release *v1beta1.BatchRelease, newStatus *v1b // GetWorkloadController pick the right workload controller to work on the workload func (r *Executor) getReleaseController(release *v1beta1.BatchRelease, newStatus *v1beta1.BatchReleaseStatus) (control.Interface, error) { - targetRef := release.Spec.TargetRef.WorkloadRef - if targetRef == nil { - return nil, nil - } - + targetRef := release.Spec.WorkloadRef gvk := schema.FromAPIVersionAndKind(targetRef.APIVersion, targetRef.Kind) if !util.IsSupportedWorkload(gvk) { message := fmt.Sprintf("the workload type '%v' is not supported", gvk) @@ -217,7 +211,7 @@ func (r *Executor) getReleaseController(release *v1beta1.BatchRelease, newStatus case apps.SchemeGroupVersion.String(): if targetRef.Kind == reflect.TypeOf(apps.Deployment{}).Name() { - if strings.EqualFold(release.Annotations[v1beta1.RolloutStyleAnnotation], string(v1alpha1.PartitionRollingStyle)) { + if !release.Spec.ReleasePlan.EnableExtraWorkloadForCanary { klog.InfoS("Using Deployment partition-style release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace) return partitionstyle.NewControlPlane(partitiondeployment.NewController, r.client, r.recorder, release, newStatus, targetKey, gvk), nil } else { diff --git a/pkg/controller/batchrelease/control/canarystyle/deployment/control_test.go b/pkg/controller/batchrelease/control/canarystyle/deployment/control_test.go index 29418012..fec464eb 100644 --- a/pkg/controller/batchrelease/control/canarystyle/deployment/control_test.go +++ b/pkg/controller/batchrelease/control/canarystyle/deployment/control_test.go @@ -131,12 +131,10 @@ var ( }, }, }, - TargetRef: v1beta1.ObjectRef{ - WorkloadRef: &v1beta1.WorkloadRef{ - APIVersion: deploymentDemo.APIVersion, - Kind: deploymentDemo.Kind, - Name: deploymentDemo.Name, - }, + WorkloadRef: v1beta1.ObjectRef{ + APIVersion: deploymentDemo.APIVersion, + Kind: deploymentDemo.Kind, + Name: deploymentDemo.Name, }, }, Status: v1beta1.BatchReleaseStatus{ diff --git a/pkg/controller/batchrelease/control/partitionstyle/cloneset/control_test.go b/pkg/controller/batchrelease/control/partitionstyle/cloneset/control_test.go index d55816ae..2ad0b70c 100644 --- a/pkg/controller/batchrelease/control/partitionstyle/cloneset/control_test.go +++ b/pkg/controller/batchrelease/control/partitionstyle/cloneset/control_test.go @@ -129,12 +129,10 @@ var ( }, }, }, - TargetRef: v1beta1.ObjectRef{ - WorkloadRef: &v1beta1.WorkloadRef{ - APIVersion: cloneDemo.APIVersion, - Kind: cloneDemo.Kind, - Name: cloneDemo.Name, - }, + WorkloadRef: v1beta1.ObjectRef{ + APIVersion: cloneDemo.APIVersion, + Kind: cloneDemo.Kind, + Name: cloneDemo.Name, }, }, Status: v1beta1.BatchReleaseStatus{ diff --git a/pkg/controller/batchrelease/control/partitionstyle/daemonset/control_test.go b/pkg/controller/batchrelease/control/partitionstyle/daemonset/control_test.go index a5633300..2cd34dab 100644 --- a/pkg/controller/batchrelease/control/partitionstyle/daemonset/control_test.go +++ b/pkg/controller/batchrelease/control/partitionstyle/daemonset/control_test.go @@ -117,12 +117,10 @@ var ( }, }, }, - TargetRef: v1beta1.ObjectRef{ - WorkloadRef: &v1beta1.WorkloadRef{ - APIVersion: daemonDemo.APIVersion, - Kind: daemonDemo.Kind, - Name: daemonDemo.Name, - }, + WorkloadRef: v1beta1.ObjectRef{ + APIVersion: daemonDemo.APIVersion, + Kind: daemonDemo.Kind, + Name: daemonDemo.Name, }, }, Status: v1beta1.BatchReleaseStatus{ diff --git a/pkg/controller/batchrelease/control/partitionstyle/deployment/control_test.go b/pkg/controller/batchrelease/control/partitionstyle/deployment/control_test.go index 3f03f8f8..f15b066c 100644 --- a/pkg/controller/batchrelease/control/partitionstyle/deployment/control_test.go +++ b/pkg/controller/batchrelease/control/partitionstyle/deployment/control_test.go @@ -131,12 +131,10 @@ var ( }, }, }, - TargetRef: v1beta1.ObjectRef{ - WorkloadRef: &v1beta1.WorkloadRef{ - APIVersion: deploymentDemo.APIVersion, - Kind: deploymentDemo.Kind, - Name: deploymentDemo.Name, - }, + WorkloadRef: v1beta1.ObjectRef{ + APIVersion: deploymentDemo.APIVersion, + Kind: deploymentDemo.Kind, + Name: deploymentDemo.Name, }, }, Status: v1beta1.BatchReleaseStatus{ diff --git a/pkg/controller/batchrelease/control/partitionstyle/statefulset/control_test.go b/pkg/controller/batchrelease/control/partitionstyle/statefulset/control_test.go index b8490ed8..391bad73 100644 --- a/pkg/controller/batchrelease/control/partitionstyle/statefulset/control_test.go +++ b/pkg/controller/batchrelease/control/partitionstyle/statefulset/control_test.go @@ -136,12 +136,10 @@ var ( }, }, }, - TargetRef: v1beta1.ObjectRef{ - WorkloadRef: &v1beta1.WorkloadRef{ - APIVersion: stsDemo.APIVersion, - Kind: stsDemo.Kind, - Name: stsDemo.Name, - }, + WorkloadRef: v1beta1.ObjectRef{ + APIVersion: stsDemo.APIVersion, + Kind: stsDemo.Kind, + Name: stsDemo.Name, }, }, Status: v1beta1.BatchReleaseStatus{ diff --git a/pkg/controller/rollout/rollout_canary.go b/pkg/controller/rollout/rollout_canary.go index 05c0514c..d6d18983 100644 --- a/pkg/controller/rollout/rollout_canary.go +++ b/pkg/controller/rollout/rollout_canary.go @@ -70,7 +70,7 @@ func (m *canaryReleaseManager) runCanary(c *RolloutContext) error { // When the first batch is trafficRouting rolling and the next steps are rolling release, // We need to clean up the canary-related resources first and then rollout the rest of the batch. currentStep := c.Rollout.Spec.Strategy.Canary.Steps[canaryStatus.CurrentStepIndex-1] - if currentStep.Weight == nil && len(currentStep.Matches) == 0 { + if currentStep.TrafficWeight == nil && len(currentStep.Matches) == 0 { tr := newTrafficRoutingContext(c) done, err := m.trafficRoutingManager.FinalisingTrafficRouting(tr, false) c.NewStatus.CanaryStatus.LastUpdateTime = tr.LastUpdateTime @@ -201,8 +201,8 @@ func (m *canaryReleaseManager) doCanaryPaused(c *RolloutContext) (bool, error) { steps := len(c.Rollout.Spec.Strategy.Canary.Steps) // If it is the last step, and 100% of pods, then return true if int32(steps) == canaryStatus.CurrentStepIndex { - if currentStep.Weight != nil && *currentStep.Weight == 100 || - currentStep.Replicas != nil && currentStep.Replicas.StrVal == "100%" { + if currentStep.TrafficWeight != nil && *currentStep.TrafficWeight == 100 || + currentStep.DesiredReplicas != nil && currentStep.DesiredReplicas.StrVal == "100%" { return true, nil } } @@ -276,7 +276,7 @@ func (m *canaryReleaseManager) removeRolloutProgressingAnnotation(c *RolloutCont if _, ok := c.Workload.Annotations[util.InRolloutProgressingAnnotation]; !ok { return nil } - workloadRef := c.Rollout.Spec.ObjectRef.WorkloadRef + workloadRef := c.Rollout.Spec.WorkloadRef workloadGVK := schema.FromAPIVersionAndKind(workloadRef.APIVersion, workloadRef.Kind) obj := util.GetEmptyWorkloadObject(workloadGVK) obj.SetNamespace(c.Workload.Namespace) @@ -313,7 +313,6 @@ func (m *canaryReleaseManager) runBatchRelease(rollout *v1beta1.Rollout, rollout klog.Infof("rollout(%s/%s) do batchRelease batch(%d) success", rollout.Namespace, rollout.Name, batch+1) return true, br, nil } - // update batchRelease to the latest version if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error { if err = m.Get(context.TODO(), client.ObjectKey{Namespace: newBr.Namespace, Name: newBr.Name}, br); err != nil { @@ -341,10 +340,10 @@ func (m *canaryReleaseManager) fetchBatchRelease(ns, name string) (*v1beta1.Batc func createBatchRelease(rollout *v1beta1.Rollout, rolloutID string, batch int32, isRollback bool) *v1beta1.BatchRelease { var batches []v1beta1.ReleaseBatch for _, step := range rollout.Spec.Strategy.Canary.Steps { - if step.Replicas == nil { - batches = append(batches, v1beta1.ReleaseBatch{CanaryReplicas: intstr.FromString(strconv.Itoa(int(*step.Weight)) + "%")}) + if step.DesiredReplicas == nil { + batches = append(batches, v1beta1.ReleaseBatch{CanaryReplicas: intstr.FromString(strconv.Itoa(int(*step.TrafficWeight)) + "%")}) } else { - batches = append(batches, v1beta1.ReleaseBatch{CanaryReplicas: *step.Replicas}) + batches = append(batches, v1beta1.ReleaseBatch{CanaryReplicas: *step.DesiredReplicas}) } } br := &v1beta1.BatchRelease{ @@ -354,19 +353,18 @@ func createBatchRelease(rollout *v1beta1.Rollout, rolloutID string, batch int32, OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(rollout, rolloutControllerKind)}, }, Spec: v1beta1.BatchReleaseSpec{ - TargetRef: v1beta1.ObjectRef{ - WorkloadRef: &v1beta1.WorkloadRef{ - APIVersion: rollout.Spec.ObjectRef.WorkloadRef.APIVersion, - Kind: rollout.Spec.ObjectRef.WorkloadRef.Kind, - Name: rollout.Spec.ObjectRef.WorkloadRef.Name, - }, + WorkloadRef: v1beta1.ObjectRef{ + APIVersion: rollout.Spec.WorkloadRef.APIVersion, + Kind: rollout.Spec.WorkloadRef.Kind, + Name: rollout.Spec.WorkloadRef.Name, }, ReleasePlan: v1beta1.ReleasePlan{ - Batches: batches, - RolloutID: rolloutID, - BatchPartition: utilpointer.Int32Ptr(batch), - FailureThreshold: rollout.Spec.Strategy.Canary.FailureThreshold, - PatchPodTemplateMetadata: rollout.Spec.Strategy.Canary.PatchPodTemplateMetadata, + Batches: batches, + RolloutID: rolloutID, + BatchPartition: utilpointer.Int32Ptr(batch), + FailureThreshold: rollout.Spec.Strategy.Canary.FailureThreshold, + PatchPodTemplateMetadata: rollout.Spec.Strategy.Canary.PatchPodTemplateMetadata, + EnableExtraWorkloadForCanary: rollout.Spec.Strategy.Canary.EnableExtraWorkloadForCanary, }, }, } @@ -374,9 +372,6 @@ func createBatchRelease(rollout *v1beta1.Rollout, rolloutID string, batch int32, if isRollback { annotations[v1alpha1.RollbackInBatchAnnotation] = rollout.Annotations[v1alpha1.RollbackInBatchAnnotation] } - if style, ok := rollout.Annotations[v1beta1.RolloutStyleAnnotation]; ok { - annotations[v1beta1.RolloutStyleAnnotation] = style - } if len(annotations) > 0 { br.Annotations = annotations } diff --git a/pkg/controller/rollout/rollout_canary_test.go b/pkg/controller/rollout/rollout_canary_test.go index cf77fa0e..e24b0b3d 100644 --- a/pkg/controller/rollout/rollout_canary_test.go +++ b/pkg/controller/rollout/rollout_canary_test.go @@ -57,6 +57,7 @@ func TestRunCanary(t *testing.T) { }, getRollout: func() (*v1beta1.Rollout, *v1beta1.BatchRelease) { obj := rolloutDemo.DeepCopy() + obj.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true obj.Status.CanaryStatus.ObservedWorkloadGeneration = 2 obj.Status.CanaryStatus.RolloutHash = "f55bvd874d5f2fzvw46bv966x4bwbdv4wx6bd9f7b46ww788954b8z8w29b7wxfd" obj.Status.CanaryStatus.StableRevision = "pod-template-hash-v1" @@ -98,6 +99,7 @@ func TestRunCanary(t *testing.T) { }, } br.Spec.ReleasePlan.BatchPartition = utilpointer.Int32(0) + br.Spec.ReleasePlan.EnableExtraWorkloadForCanary = true return br }, }, @@ -130,6 +132,7 @@ func TestRunCanary(t *testing.T) { }, getRollout: func() (*v1beta1.Rollout, *v1beta1.BatchRelease) { obj := rolloutDemo.DeepCopy() + obj.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true obj.Status.CanaryStatus.ObservedWorkloadGeneration = 2 obj.Status.CanaryStatus.RolloutHash = "f55bvd874d5f2fzvw46bv966x4bwbdv4wx6bd9f7b46ww788954b8z8w29b7wxfd" obj.Status.CanaryStatus.StableRevision = "pod-template-hash-v1" @@ -155,9 +158,10 @@ func TestRunCanary(t *testing.T) { }, } br.Spec.ReleasePlan.BatchPartition = utilpointer.Int32(0) + br.Spec.ReleasePlan.EnableExtraWorkloadForCanary = true br.Status = v1beta1.BatchReleaseStatus{ ObservedGeneration: 1, - ObservedReleasePlanHash: "6d6a40791161e88ec0483688e951b589a4cbd0bf351974827706b79f99378fd5", + ObservedReleasePlanHash: "d444a1007776da957d7d8549e3375c96179621b85670ad1e2bb0fc5fea16446a", CanaryStatus: v1beta1.BatchReleaseCanaryStatus{ CurrentBatchState: v1beta1.ReadyBatchState, CurrentBatch: 0, @@ -200,6 +204,7 @@ func TestRunCanary(t *testing.T) { }, } br.Spec.ReleasePlan.BatchPartition = utilpointer.Int32(0) + br.Spec.ReleasePlan.EnableExtraWorkloadForCanary = true return br }, }, diff --git a/pkg/controller/rollout/rollout_controller.go b/pkg/controller/rollout/rollout_controller.go index 194ddd06..a1fe5606 100755 --- a/pkg/controller/rollout/rollout_controller.go +++ b/pkg/controller/rollout/rollout_controller.go @@ -111,10 +111,10 @@ func (r *RolloutReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct klog.Infof("Begin to reconcile Rollout %v", klog.KObj(rollout)) // If workload watcher does not exist, then add the watcher dynamically - workloadRef := rollout.Spec.ObjectRef.WorkloadRef - workloadGVK := util.GetGVKFrom(workloadRef) + workloadRef := rollout.Spec.WorkloadRef + workloadGVK := util.GetGVKFrom(&workloadRef) _, exists := watchedWorkload.Load(workloadGVK.String()) - if workloadRef != nil && !exists { + if !exists { succeeded, err := util.AddWatcherDynamically(runtimeController, workloadHandler, workloadGVK) if err != nil { return ctrl.Result{}, err diff --git a/pkg/controller/rollout/rollout_controller_test.go b/pkg/controller/rollout/rollout_controller_test.go index 339ae38f..37d4649e 100644 --- a/pkg/controller/rollout/rollout_controller_test.go +++ b/pkg/controller/rollout/rollout_controller_test.go @@ -48,45 +48,44 @@ var ( }, }, Spec: v1beta1.RolloutSpec{ - ObjectRef: v1beta1.ObjectRef{ - WorkloadRef: &v1beta1.WorkloadRef{ - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "echoserver", - }, + WorkloadRef: v1beta1.ObjectRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "echoserver", }, Strategy: v1beta1.RolloutStrategy{ Canary: &v1beta1.CanaryStrategy{ + EnableExtraWorkloadForCanary: true, Steps: []v1beta1.CanaryStep{ { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(5), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(5), }, - Replicas: &intstr.IntOrString{IntVal: 1}, + DesiredReplicas: &intstr.IntOrString{IntVal: 1}, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(20), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(20), }, - Replicas: &intstr.IntOrString{IntVal: 2}, + DesiredReplicas: &intstr.IntOrString{IntVal: 2}, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(60), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(60), }, - Replicas: &intstr.IntOrString{IntVal: 6}, + DesiredReplicas: &intstr.IntOrString{IntVal: 6}, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(100), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(100), }, - Replicas: &intstr.IntOrString{IntVal: 10}, + DesiredReplicas: &intstr.IntOrString{IntVal: 10}, }, }, - TrafficRoutings: []v1alpha1.TrafficRoutingRef{ + TrafficRoutings: []v1beta1.TrafficRoutingRef{ { Service: "echoserver", - Ingress: &v1alpha1.IngressTrafficRouting{ + Ingress: &v1beta1.IngressTrafficRouting{ Name: "echoserver", }, }, @@ -207,12 +206,10 @@ var ( Generation: 1, }, Spec: v1beta1.BatchReleaseSpec{ - TargetRef: v1beta1.ObjectRef{ - WorkloadRef: &v1beta1.WorkloadRef{ - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "echoserver", - }, + WorkloadRef: v1beta1.ObjectRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "echoserver", }, }, Status: v1beta1.BatchReleaseStatus{}, diff --git a/pkg/controller/rollout/rollout_event_handler.go b/pkg/controller/rollout/rollout_event_handler.go index 321bcda6..355d7d67 100644 --- a/pkg/controller/rollout/rollout_event_handler.go +++ b/pkg/controller/rollout/rollout_event_handler.go @@ -85,7 +85,7 @@ func (w *enqueueRequestForWorkload) getRolloutForWorkload(key types.NamespacedNa } for _, rollout := range rList.Items { - targetRef := rollout.Spec.ObjectRef.WorkloadRef + targetRef := rollout.Spec.WorkloadRef targetGV, err := schema.ParseGroupVersion(targetRef.APIVersion) if err != nil { klog.Errorf("failed to parse rollout(%s/%s) targetRef's group version: %s", rollout.Namespace, rollout.Name, targetRef.APIVersion) diff --git a/pkg/controller/rollout/rollout_progressing.go b/pkg/controller/rollout/rollout_progressing.go index a224735e..9dba1aa3 100644 --- a/pkg/controller/rollout/rollout_progressing.go +++ b/pkg/controller/rollout/rollout_progressing.go @@ -391,10 +391,10 @@ func (r *RolloutReconciler) recalculateCanaryStep(c *RolloutContext) (int32, err for i := range c.Rollout.Spec.Strategy.Canary.Steps { step := c.Rollout.Spec.Strategy.Canary.Steps[i] var desiredReplicas int - if step.Replicas != nil { - desiredReplicas, _ = intstr.GetScaledValueFromIntOrPercent(step.Replicas, int(c.Workload.Replicas), true) + if step.DesiredReplicas != nil { + desiredReplicas, _ = intstr.GetScaledValueFromIntOrPercent(step.DesiredReplicas, int(c.Workload.Replicas), true) } else { - replicas := intstr.FromString(strconv.Itoa(int(*step.Weight)) + "%") + replicas := intstr.FromString(strconv.Itoa(int(*step.TrafficWeight)) + "%") desiredReplicas, _ = intstr.GetScaledValueFromIntOrPercent(&replicas, int(c.Workload.Replicas), true) } stepIndex = int32(i + 1) diff --git a/pkg/controller/rollout/rollout_progressing_test.go b/pkg/controller/rollout/rollout_progressing_test.go index 3e316f73..e7138cf5 100644 --- a/pkg/controller/rollout/rollout_progressing_test.go +++ b/pkg/controller/rollout/rollout_progressing_test.go @@ -660,18 +660,18 @@ func TestReCalculateCanaryStepIndex(t *testing.T) { obj := rolloutDemo.DeepCopy() obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{ { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(20), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(20), }, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(50), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(50), }, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(100), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(100), }, }, } @@ -705,18 +705,18 @@ func TestReCalculateCanaryStepIndex(t *testing.T) { obj := rolloutDemo.DeepCopy() obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{ { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(20), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(20), }, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(40), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(40), }, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(100), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(100), }, }, } @@ -750,18 +750,18 @@ func TestReCalculateCanaryStepIndex(t *testing.T) { obj := rolloutDemo.DeepCopy() obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{ { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(40), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(40), }, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(60), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(60), }, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(100), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(100), }, }, } @@ -795,18 +795,18 @@ func TestReCalculateCanaryStepIndex(t *testing.T) { obj := rolloutDemo.DeepCopy() obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{ { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(10), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(10), }, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(30), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(30), }, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(100), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(100), }, }, } @@ -840,19 +840,19 @@ func TestReCalculateCanaryStepIndex(t *testing.T) { obj := rolloutDemo.DeepCopy() obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{ { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(2), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(2), }, - Replicas: &intstr.IntOrString{ + DesiredReplicas: &intstr.IntOrString{ Type: intstr.String, StrVal: "10%", }, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(3), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(3), }, - Replicas: &intstr.IntOrString{ + DesiredReplicas: &intstr.IntOrString{ Type: intstr.String, StrVal: "10%", }, diff --git a/pkg/controller/rollout/rollout_status_test.go b/pkg/controller/rollout/rollout_status_test.go index 96e27290..0976693c 100755 --- a/pkg/controller/rollout/rollout_status_test.go +++ b/pkg/controller/rollout/rollout_status_test.go @@ -20,7 +20,6 @@ import ( "context" "testing" - "github.com/openkruise/rollouts/api/v1alpha1" "github.com/openkruise/rollouts/api/v1beta1" "github.com/openkruise/rollouts/pkg/trafficrouting" "github.com/openkruise/rollouts/pkg/util" @@ -43,7 +42,7 @@ func TestCalculateRolloutHash(t *testing.T) { return obj }, expectHash: func() string { - return "626fd556c5d5v2d9b4f7c2xvbc9dxddxzd48xvb9w9wfcdvdz6v959fbzd84b57x" + return "z2v4vc445bb6bcv9bv5c4x86bvxx26vwvvxzvc4dfwxxc47bcfwb4fcxfv4f999v" }, }, { @@ -56,7 +55,7 @@ func TestCalculateRolloutHash(t *testing.T) { return obj }, expectHash: func() string { - return "626fd556c5d5v2d9b4f7c2xvbc9dxddxzd48xvb9w9wfcdvdz6v959fbzd84b57x" + return "z2v4vc445bb6bcv9bv5c4x86bvxx26vwvvxzvc4dfwxxc47bcfwb4fcxfv4f999v" }, }, { @@ -65,20 +64,20 @@ func TestCalculateRolloutHash(t *testing.T) { obj := rolloutDemo.DeepCopy() obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{ { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(50), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(50), }, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(100), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(100), }, }, } return obj }, expectHash: func() string { - return "8c449wxc46x8dd764x4v4wzvc7454f48478vd9db27fv8v9dw5cwbcb6b42b75dc" + return "4xdcb4zw4z57fw645wwf484z444x8xfcx8v9v9747822c599bbdw4vwx6c6x7zdc" }, }, } diff --git a/pkg/controller/trafficrouting/trafficrouting_controller.go b/pkg/controller/trafficrouting/trafficrouting_controller.go index 88ad5123..e3e44df1 100644 --- a/pkg/controller/trafficrouting/trafficrouting_controller.go +++ b/pkg/controller/trafficrouting/trafficrouting_controller.go @@ -215,12 +215,15 @@ func (r *TrafficRoutingReconciler) SetupWithManager(mgr ctrl.Manager) error { } func newTrafficRoutingContext(tr *v1alpha1.TrafficRouting) *trafficrouting.TrafficRoutingContext { - return &trafficrouting.TrafficRoutingContext{ + c := &trafficrouting.TrafficRoutingContext{ Key: fmt.Sprintf("TrafficRouting(%s/%s)", tr.Namespace, tr.Name), Namespace: tr.Namespace, - ObjectRef: tr.Spec.ObjectRef, - Strategy: tr.Spec.Strategy, + Strategy: v1alpha1.ConversionToV1beta1TrafficRoutingStrategy(tr.Spec.Strategy), OwnerRef: *metav1.NewControllerRef(tr, trControllerKind), OnlyTrafficRouting: true, } + for _, ref := range tr.Spec.ObjectRef { + c.ObjectRef = append(c.ObjectRef, v1alpha1.ConversionToV1beta1TrafficRoutingRef(ref)) + } + return c } diff --git a/pkg/trafficrouting/manager.go b/pkg/trafficrouting/manager.go index 7fc6251c..87348b86 100644 --- a/pkg/trafficrouting/manager.go +++ b/pkg/trafficrouting/manager.go @@ -21,7 +21,7 @@ import ( "fmt" "time" - "github.com/openkruise/rollouts/api/v1alpha1" + "github.com/openkruise/rollouts/api/v1beta1" "github.com/openkruise/rollouts/pkg/trafficrouting/network" custom "github.com/openkruise/rollouts/pkg/trafficrouting/network/customNetworkProvider" "github.com/openkruise/rollouts/pkg/trafficrouting/network/gateway" @@ -44,8 +44,8 @@ type TrafficRoutingContext struct { // only for log info Key string Namespace string - ObjectRef []v1alpha1.TrafficRoutingRef - Strategy v1alpha1.TrafficRoutingStrategy + ObjectRef []v1beta1.TrafficRoutingRef + Strategy v1beta1.TrafficRoutingStrategy // OnlyTrafficRouting OnlyTrafficRouting bool OwnerRef metav1.OwnerReference @@ -100,7 +100,7 @@ func (m *Manager) DoTrafficRouting(c *TrafficRoutingContext) (bool, error) { if trafficRouting.GracePeriodSeconds <= 0 { trafficRouting.GracePeriodSeconds = defaultGracePeriodSeconds } - if c.Strategy.Weight == nil && len(c.Strategy.Matches) == 0 { + if c.Strategy.TrafficWeight == nil && len(c.Strategy.Matches) == 0 { return true, nil } @@ -228,7 +228,7 @@ func (m *Manager) FinalisingTrafficRouting(c *TrafficRoutingContext, onlyRestore } // First route 100% traffic to stable service - c.Strategy.Weight = utilpointer.Int32(0) + c.Strategy.TrafficWeight = utilpointer.Int32(0) verify, err = trController.EnsureRoutes(context.TODO(), &c.Strategy) if err != nil { return false, err diff --git a/pkg/trafficrouting/manager_test.go b/pkg/trafficrouting/manager_test.go index b18f5658..41caeb73 100644 --- a/pkg/trafficrouting/manager_test.go +++ b/pkg/trafficrouting/manager_test.go @@ -113,45 +113,43 @@ var ( }, }, Spec: v1beta1.RolloutSpec{ - ObjectRef: v1beta1.ObjectRef{ - WorkloadRef: &v1beta1.WorkloadRef{ - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "echoserver", - }, + WorkloadRef: v1beta1.ObjectRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "echoserver", }, Strategy: v1beta1.RolloutStrategy{ Canary: &v1beta1.CanaryStrategy{ Steps: []v1beta1.CanaryStep{ { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(5), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(5), }, - Replicas: &intstr.IntOrString{IntVal: 1}, + DesiredReplicas: &intstr.IntOrString{IntVal: 1}, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(20), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(20), }, - Replicas: &intstr.IntOrString{IntVal: 2}, + DesiredReplicas: &intstr.IntOrString{IntVal: 2}, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(60), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(60), }, - Replicas: &intstr.IntOrString{IntVal: 6}, + DesiredReplicas: &intstr.IntOrString{IntVal: 6}, }, { - TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(100), + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(100), }, - Replicas: &intstr.IntOrString{IntVal: 10}, + DesiredReplicas: &intstr.IntOrString{IntVal: 10}, }, }, - TrafficRoutings: []v1alpha1.TrafficRoutingRef{ + TrafficRoutings: []v1beta1.TrafficRoutingRef{ { Service: "echoserver", - Ingress: &v1alpha1.IngressTrafficRouting{ + Ingress: &v1beta1.IngressTrafficRouting{ Name: "echoserver", }, }, diff --git a/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider.go b/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider.go index 07be66e6..be3fd66f 100644 --- a/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider.go +++ b/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider.go @@ -23,20 +23,19 @@ import ( "reflect" "strings" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/klog/v2" - - rolloutv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1" + "github.com/openkruise/rollouts/api/v1beta1" "github.com/openkruise/rollouts/pkg/trafficrouting/network" "github.com/openkruise/rollouts/pkg/util" "github.com/openkruise/rollouts/pkg/util/configuration" "github.com/openkruise/rollouts/pkg/util/luamanager" lua "github.com/yuin/gopher-lua" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/klog/v2" utilpointer "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -50,7 +49,7 @@ type LuaData struct { Data Data CanaryWeight int32 StableWeight int32 - Matches []rolloutv1alpha1.HttpRouteMatch + Matches []v1beta1.HttpRouteMatch CanaryService string StableService string } @@ -72,7 +71,7 @@ type Config struct { CanaryService string StableService string // network providers need to be created - TrafficConf []rolloutv1alpha1.CustomNetworkRef + TrafficConf []v1beta1.ObjectRef OwnerRef metav1.OwnerReference } @@ -107,11 +106,11 @@ func (r *customController) Initialize(ctx context.Context) error { } // when ensuring routes, first execute lua for all custom providers, then update -func (r *customController) EnsureRoutes(ctx context.Context, strategy *rolloutv1alpha1.TrafficRoutingStrategy) (bool, error) { +func (r *customController) EnsureRoutes(ctx context.Context, strategy *v1beta1.TrafficRoutingStrategy) (bool, error) { done := true // *strategy.Weight == 0 indicates traffic routing is doing finalising and tries to route whole traffic to stable service // then directly do finalising - if strategy.Weight != nil && *strategy.Weight == 0 { + if strategy.TrafficWeight != nil && *strategy.TrafficWeight == 0 { return true, nil } var err error @@ -254,8 +253,8 @@ func (r *customController) restoreObject(obj *unstructured.Unstructured) error { return nil } -func (r *customController) executeLuaForCanary(spec Data, strategy *rolloutv1alpha1.TrafficRoutingStrategy, luaScript string) (Data, error) { - weight := strategy.Weight +func (r *customController) executeLuaForCanary(spec Data, strategy *v1beta1.TrafficRoutingStrategy, luaScript string) (Data, error) { + weight := strategy.TrafficWeight matches := strategy.Matches if weight == nil { // the lua script does not have a pointer type, @@ -296,7 +295,7 @@ func (r *customController) executeLuaForCanary(spec Data, strategy *rolloutv1alp return Data{}, fmt.Errorf("expect table output from Lua script, not %s", returnValue.Type().String()) } -func (r *customController) getLuaScript(ctx context.Context, ref rolloutv1alpha1.CustomNetworkRef) (string, error) { +func (r *customController) getLuaScript(ctx context.Context, ref v1beta1.ObjectRef) (string, error) { // get local lua script // luaScript.Provider: CRDGroupt/Kind group := strings.Split(ref.APIVersion, "/")[0] diff --git a/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider_test.go b/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider_test.go index c4835355..42e694ef 100644 --- a/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider_test.go +++ b/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider_test.go @@ -28,7 +28,7 @@ import ( rolloutapi "github.com/openkruise/rollouts/api" rolloutsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1" - rolloutsv1beta1 "github.com/openkruise/rollouts/api/v1beta1" + "github.com/openkruise/rollouts/api/v1beta1" "github.com/openkruise/rollouts/pkg/util" "github.com/openkruise/rollouts/pkg/util/configuration" "github.com/openkruise/rollouts/pkg/util/luamanager" @@ -141,7 +141,7 @@ func TestInitialize(t *testing.T) { return Config{ StableService: "echoserver", CanaryService: "echoserver-canary", - TrafficConf: []rolloutsv1alpha1.CustomNetworkRef{ + TrafficConf: []v1beta1.ObjectRef{ { APIVersion: "networking.istio.io/v1alpha3", Kind: "VirtualService", @@ -174,7 +174,7 @@ func TestInitialize(t *testing.T) { return Config{ StableService: "echoserver", CanaryService: "echoserver-canary", - TrafficConf: []rolloutsv1alpha1.CustomNetworkRef{ + TrafficConf: []v1beta1.ObjectRef{ { APIVersion: "networking.test.io/v1alpha3", Kind: "VirtualService", @@ -237,7 +237,7 @@ func TestEnsureRoutes(t *testing.T) { cases := []struct { name string getLua func() map[string]string - getRoutes func() *rolloutsv1alpha1.TrafficRoutingStrategy + getRoutes func() *v1beta1.TrafficRoutingStrategy getUnstructureds func() []*unstructured.Unstructured getConfig func() Config expectState func() (bool, bool) @@ -245,9 +245,9 @@ func TestEnsureRoutes(t *testing.T) { }{ { name: "test1, do traffic routing for VirtualService and DestinationRule", - getRoutes: func() *rolloutsv1alpha1.TrafficRoutingStrategy { - return &rolloutsv1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(5), + getRoutes: func() *v1beta1.TrafficRoutingStrategy { + return &v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(5), } }, getUnstructureds: func() []*unstructured.Unstructured { @@ -268,7 +268,7 @@ func TestEnsureRoutes(t *testing.T) { Key: "rollout-demo", StableService: "echoserver", CanaryService: "echoserver-canary", - TrafficConf: []rolloutsv1alpha1.CustomNetworkRef{ + TrafficConf: []v1beta1.ObjectRef{ { APIVersion: "networking.istio.io/v1alpha3", Kind: "VirtualService", @@ -317,9 +317,9 @@ func TestEnsureRoutes(t *testing.T) { }, { name: "test2, do traffic routing but failed to execute lua", - getRoutes: func() *rolloutsv1alpha1.TrafficRoutingStrategy { - return &rolloutsv1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(5), + getRoutes: func() *v1beta1.TrafficRoutingStrategy { + return &v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(5), } }, getUnstructureds: func() []*unstructured.Unstructured { @@ -340,7 +340,7 @@ func TestEnsureRoutes(t *testing.T) { Key: "rollout-demo", StableService: "echoserver", CanaryService: "echoserver-canary", - TrafficConf: []rolloutsv1alpha1.CustomNetworkRef{ + TrafficConf: []v1beta1.ObjectRef{ { APIVersion: "networking.istio.io/v1alpha3", Kind: "VirtualService", @@ -446,7 +446,7 @@ func TestFinalise(t *testing.T) { return Config{ StableService: "echoserver", CanaryService: "echoserver-canary", - TrafficConf: []rolloutsv1alpha1.CustomNetworkRef{ + TrafficConf: []v1beta1.ObjectRef{ { APIVersion: "networking.istio.io/v1alpha3", Kind: "VirtualService", @@ -482,7 +482,7 @@ func TestFinalise(t *testing.T) { } type TestCase struct { - Rollout *rolloutsv1beta1.Rollout `json:"rollout,omitempty"` + Rollout *v1beta1.Rollout `json:"rollout,omitempty"` TrafficRouting *rolloutsv1alpha1.TrafficRouting `json:"trafficRouting,omitempty"` Original *unstructured.Unstructured `json:"original,omitempty"` Expected []*unstructured.Unstructured `json:"expected,omitempty"` @@ -522,7 +522,7 @@ func TestLuaScript(t *testing.T) { if rollout != nil { steps := rollout.Spec.Strategy.Canary.Steps for i, step := range steps { - weight := step.TrafficRoutingStrategy.Weight + weight := step.TrafficRoutingStrategy.TrafficWeight if weight == nil { weight = utilpointer.Int32(-1) } @@ -563,13 +563,19 @@ func TestLuaScript(t *testing.T) { var canaryService string stableService := trafficRouting.Spec.ObjectRef[0].Service canaryService = stableService + matches := make([]v1beta1.HttpRouteMatch, 0) + for _, match := range trafficRouting.Spec.Strategy.Matches { + obj := v1beta1.HttpRouteMatch{} + obj.Headers = match.Headers + matches = append(matches, obj) + } data := &LuaData{ Data: Data{ Labels: testCase.Original.GetLabels(), Annotations: testCase.Original.GetAnnotations(), Spec: testCase.Original.Object["spec"], }, - Matches: trafficRouting.Spec.Strategy.Matches, + Matches: matches, CanaryWeight: *weight, StableWeight: 100 - *weight, CanaryService: canaryService, diff --git a/pkg/trafficrouting/network/gateway/gateway.go b/pkg/trafficrouting/network/gateway/gateway.go index 572e138a..97f1ebda 100644 --- a/pkg/trafficrouting/network/gateway/gateway.go +++ b/pkg/trafficrouting/network/gateway/gateway.go @@ -17,7 +17,7 @@ import ( "context" "reflect" - rolloutv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1" + "github.com/openkruise/rollouts/api/v1beta1" "github.com/openkruise/rollouts/pkg/trafficrouting/network" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" @@ -34,7 +34,7 @@ type Config struct { Namespace string CanaryService string StableService string - TrafficConf *rolloutv1alpha1.GatewayTrafficRouting + TrafficConf *v1beta1.GatewayTrafficRouting } type gatewayController struct { @@ -56,8 +56,8 @@ func (r *gatewayController) Initialize(ctx context.Context) error { return r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: *r.conf.TrafficConf.HTTPRouteName}, route) } -func (r *gatewayController) EnsureRoutes(ctx context.Context, strategy *rolloutv1alpha1.TrafficRoutingStrategy) (bool, error) { - weight := strategy.Weight +func (r *gatewayController) EnsureRoutes(ctx context.Context, strategy *v1beta1.TrafficRoutingStrategy) (bool, error) { + weight := strategy.TrafficWeight matches := strategy.Matches // headerModifier := strategy.RequestHeaderModifier var httpRoute gatewayv1alpha2.HTTPRoute @@ -118,7 +118,7 @@ func (r *gatewayController) Finalise(ctx context.Context) error { return nil } -func (r *gatewayController) buildDesiredHTTPRoute(rules []gatewayv1alpha2.HTTPRouteRule, weight *int32, matches []rolloutv1alpha1.HttpRouteMatch, +func (r *gatewayController) buildDesiredHTTPRoute(rules []gatewayv1alpha2.HTTPRouteRule, weight *int32, matches []v1beta1.HttpRouteMatch, rh *gatewayv1alpha2.HTTPRequestHeaderFilter) []gatewayv1alpha2.HTTPRouteRule { var desired []gatewayv1alpha2.HTTPRouteRule // Only when finalize method parameter weight=-1, @@ -146,7 +146,7 @@ func (r *gatewayController) buildDesiredHTTPRoute(rules []gatewayv1alpha2.HTTPRo return r.buildCanaryWeightHttpRoutes(rules, weight) } -func (r *gatewayController) buildCanaryHeaderHttpRoutes(rules []gatewayv1alpha2.HTTPRouteRule, matchs []rolloutv1alpha1.HttpRouteMatch) []gatewayv1alpha2.HTTPRouteRule { +func (r *gatewayController) buildCanaryHeaderHttpRoutes(rules []gatewayv1alpha2.HTTPRouteRule, matchs []v1beta1.HttpRouteMatch) []gatewayv1alpha2.HTTPRouteRule { var desired []gatewayv1alpha2.HTTPRouteRule var canarys []gatewayv1alpha2.HTTPRouteRule for i := range rules { diff --git a/pkg/trafficrouting/network/gateway/gateway_test.go b/pkg/trafficrouting/network/gateway/gateway_test.go index 8edfbed6..3a4a844b 100644 --- a/pkg/trafficrouting/network/gateway/gateway_test.go +++ b/pkg/trafficrouting/network/gateway/gateway_test.go @@ -17,7 +17,7 @@ import ( "reflect" "testing" - rolloutsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1" + "github.com/openkruise/rollouts/api/v1beta1" "github.com/openkruise/rollouts/pkg/util" utilpointer "k8s.io/utils/pointer" gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -130,7 +130,7 @@ func TestBuildDesiredHTTPRoute(t *testing.T) { cases := []struct { name string getRouteRules func() []gatewayv1alpha2.HTTPRouteRule - getRoutes func() (*int32, []rolloutsv1alpha1.HttpRouteMatch) + getRoutes func() (*int32, []v1beta1.HttpRouteMatch) desiredRules func() []gatewayv1alpha2.HTTPRouteRule }{ { @@ -139,9 +139,9 @@ func TestBuildDesiredHTTPRoute(t *testing.T) { rules := routeDemo.DeepCopy().Spec.Rules return rules }, - getRoutes: func() (*int32, []rolloutsv1alpha1.HttpRouteMatch) { + getRoutes: func() (*int32, []v1beta1.HttpRouteMatch) { iType := gatewayv1alpha2.HeaderMatchRegularExpression - return nil, []rolloutsv1alpha1.HttpRouteMatch{ + return nil, []v1beta1.HttpRouteMatch{ // header { Headers: []gatewayv1alpha2.HTTPHeaderMatch{ @@ -360,7 +360,7 @@ func TestBuildDesiredHTTPRoute(t *testing.T) { } return rules }, - getRoutes: func() (*int32, []rolloutsv1alpha1.HttpRouteMatch) { + getRoutes: func() (*int32, []v1beta1.HttpRouteMatch) { return utilpointer.Int32(20), nil }, desiredRules: func() []gatewayv1alpha2.HTTPRouteRule { @@ -494,7 +494,7 @@ func TestBuildDesiredHTTPRoute(t *testing.T) { }) return rules }, - getRoutes: func() (*int32, []rolloutsv1alpha1.HttpRouteMatch) { + getRoutes: func() (*int32, []v1beta1.HttpRouteMatch) { return utilpointer.Int32(-1), nil }, desiredRules: func() []gatewayv1alpha2.HTTPRouteRule { diff --git a/pkg/trafficrouting/network/ingress/ingress.go b/pkg/trafficrouting/network/ingress/ingress.go index 9531a765..07920434 100644 --- a/pkg/trafficrouting/network/ingress/ingress.go +++ b/pkg/trafficrouting/network/ingress/ingress.go @@ -23,7 +23,7 @@ import ( "reflect" jsonpatch "github.com/evanphx/json-patch" - rolloutv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1" + "github.com/openkruise/rollouts/api/v1beta1" "github.com/openkruise/rollouts/pkg/trafficrouting/network" "github.com/openkruise/rollouts/pkg/util" "github.com/openkruise/rollouts/pkg/util/configuration" @@ -56,7 +56,7 @@ type Config struct { Namespace string CanaryService string StableService string - TrafficConf *rolloutv1alpha1.IngressTrafficRouting + TrafficConf *v1beta1.IngressTrafficRouting OwnerRef metav1.OwnerReference } @@ -82,8 +82,8 @@ func (r *ingressController) Initialize(ctx context.Context) error { return r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: r.conf.TrafficConf.Name}, ingress) } -func (r *ingressController) EnsureRoutes(ctx context.Context, strategy *rolloutv1alpha1.TrafficRoutingStrategy) (bool, error) { - weight := strategy.Weight +func (r *ingressController) EnsureRoutes(ctx context.Context, strategy *v1beta1.TrafficRoutingStrategy) (bool, error) { + weight := strategy.TrafficWeight matches := strategy.Matches headerModifier := strategy.RequestHeaderModifier @@ -217,7 +217,7 @@ func defaultCanaryIngressName(name string) string { return fmt.Sprintf("%s-canary", name) } -func (r *ingressController) executeLuaForCanary(annotations map[string]string, weight *int32, matches []rolloutv1alpha1.HttpRouteMatch, +func (r *ingressController) executeLuaForCanary(annotations map[string]string, weight *int32, matches []v1beta1.HttpRouteMatch, headerModifier *gatewayv1alpha2.HTTPRequestHeaderFilter) (map[string]string, error) { if weight == nil { @@ -228,7 +228,7 @@ func (r *ingressController) executeLuaForCanary(annotations map[string]string, w type LuaData struct { Annotations map[string]string Weight string - Matches []rolloutv1alpha1.HttpRouteMatch + Matches []v1beta1.HttpRouteMatch CanaryService string RequestHeaderModifier *gatewayv1alpha2.HTTPRequestHeaderFilter } diff --git a/pkg/trafficrouting/network/ingress/ingress_test.go b/pkg/trafficrouting/network/ingress/ingress_test.go index aebf00ed..8ae15ab5 100644 --- a/pkg/trafficrouting/network/ingress/ingress_test.go +++ b/pkg/trafficrouting/network/ingress/ingress_test.go @@ -20,8 +20,7 @@ import ( "testing" rolloutsapi "github.com/openkruise/rollouts/api" - rolloutsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1" - rolloutsv1beta1 "github.com/openkruise/rollouts/api/v1beta1" + "github.com/openkruise/rollouts/api/v1beta1" "github.com/openkruise/rollouts/pkg/util" "github.com/openkruise/rollouts/pkg/util/configuration" corev1 "k8s.io/api/core/v1" @@ -279,7 +278,7 @@ func TestInitialize(t *testing.T) { Key: "rollout-demo", StableService: "echoserver", CanaryService: "echoserver-canary", - TrafficConf: &rolloutsv1alpha1.IngressTrafficRouting{ + TrafficConf: &v1beta1.IngressTrafficRouting{ Name: "echoserver", }, } @@ -309,7 +308,7 @@ func TestEnsureRoutes(t *testing.T) { name string getConfigmap func() *corev1.ConfigMap getIngress func() []*netv1.Ingress - getRoutes func() *rolloutsv1beta1.CanaryStep + getRoutes func() *v1beta1.CanaryStep expectIngress func() *netv1.Ingress ingressType string }{ @@ -329,11 +328,11 @@ func TestEnsureRoutes(t *testing.T) { canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary" return []*netv1.Ingress{demoIngress.DeepCopy(), canary} }, - getRoutes: func() *rolloutsv1beta1.CanaryStep { - return &rolloutsv1beta1.CanaryStep{ - TrafficRoutingStrategy: rolloutsv1alpha1.TrafficRoutingStrategy{ - Weight: nil, - Matches: []rolloutsv1alpha1.HttpRouteMatch{ + getRoutes: func() *v1beta1.CanaryStep { + return &v1beta1.CanaryStep{ + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: nil, + Matches: []v1beta1.HttpRouteMatch{ // header { Headers: []gatewayv1alpha2.HTTPHeaderMatch{ @@ -400,10 +399,10 @@ func TestEnsureRoutes(t *testing.T) { canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary" return []*netv1.Ingress{demoIngress.DeepCopy(), canary} }, - getRoutes: func() *rolloutsv1beta1.CanaryStep { - return &rolloutsv1beta1.CanaryStep{ - TrafficRoutingStrategy: rolloutsv1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(40), + getRoutes: func() *v1beta1.CanaryStep { + return &v1beta1.CanaryStep{ + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(40), }, } }, @@ -435,11 +434,11 @@ func TestEnsureRoutes(t *testing.T) { canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary" return []*netv1.Ingress{demoIngress.DeepCopy(), canary} }, - getRoutes: func() *rolloutsv1beta1.CanaryStep { + getRoutes: func() *v1beta1.CanaryStep { iType := gatewayv1alpha2.HeaderMatchRegularExpression - return &rolloutsv1beta1.CanaryStep{ - TrafficRoutingStrategy: rolloutsv1alpha1.TrafficRoutingStrategy{ - Matches: []rolloutsv1alpha1.HttpRouteMatch{ + return &v1beta1.CanaryStep{ + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + Matches: []v1beta1.HttpRouteMatch{ // header { Headers: []gatewayv1alpha2.HTTPHeaderMatch{ @@ -483,10 +482,10 @@ func TestEnsureRoutes(t *testing.T) { canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary" return []*netv1.Ingress{demoIngress.DeepCopy(), canary} }, - getRoutes: func() *rolloutsv1beta1.CanaryStep { - return &rolloutsv1beta1.CanaryStep{ - TrafficRoutingStrategy: rolloutsv1alpha1.TrafficRoutingStrategy{ - Matches: []rolloutsv1alpha1.HttpRouteMatch{ + getRoutes: func() *v1beta1.CanaryStep { + return &v1beta1.CanaryStep{ + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + Matches: []v1beta1.HttpRouteMatch{ // header { Headers: []gatewayv1alpha2.HTTPHeaderMatch{ @@ -526,7 +525,7 @@ func TestEnsureRoutes(t *testing.T) { Key: "rollout-demo", StableService: "echoserver", CanaryService: "echoserver-canary", - TrafficConf: &rolloutsv1alpha1.IngressTrafficRouting{ + TrafficConf: &v1beta1.IngressTrafficRouting{ Name: "echoserver", }, } @@ -596,7 +595,7 @@ func TestFinalise(t *testing.T) { Key: "rollout-demo", StableService: "echoserver", CanaryService: "echoserver-canary", - TrafficConf: &rolloutsv1alpha1.IngressTrafficRouting{ + TrafficConf: &v1beta1.IngressTrafficRouting{ Name: "echoserver", }, } diff --git a/pkg/trafficrouting/network/interface.go b/pkg/trafficrouting/network/interface.go index 8cf0faf5..3f4b10fe 100644 --- a/pkg/trafficrouting/network/interface.go +++ b/pkg/trafficrouting/network/interface.go @@ -19,7 +19,7 @@ package network import ( "context" - rolloutv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1" + "github.com/openkruise/rollouts/api/v1beta1" ) // NetworkProvider common function across all TrafficRouting implementation @@ -33,7 +33,7 @@ type NetworkProvider interface { // 1. check if canary has been set desired weight. // 2. If not, set canary desired weight // When the first set weight is returned false, mainly to give the provider some time to process, only when again ensure, will return true - EnsureRoutes(ctx context.Context, strategy *rolloutv1alpha1.TrafficRoutingStrategy) (bool, error) + EnsureRoutes(ctx context.Context, strategy *v1beta1.TrafficRoutingStrategy) (bool, error) // Finalise will do some cleanup work after the canary rollout complete, such as delete canary ingress. // Finalise is called with a 3-second delay after completing the canary. Finalise(ctx context.Context) error diff --git a/pkg/util/controller_finder.go b/pkg/util/controller_finder.go index 07f05b93..ee8914d9 100644 --- a/pkg/util/controller_finder.go +++ b/pkg/util/controller_finder.go @@ -66,7 +66,7 @@ type Workload struct { // ControllerFinderFunc is a function type that maps a pod to a list of // controllers and their scale. -type ControllerFinderFunc func(namespace string, ref *rolloutv1beta1.WorkloadRef) (*Workload, error) +type ControllerFinderFunc func(namespace string, ref *rolloutv1beta1.ObjectRef) (*Workload, error) type ControllerFinder struct { client.Client @@ -86,29 +86,17 @@ func NewControllerFinder(c client.Client) *ControllerFinder { // +kubebuilder:rbac:groups=apps,resources=replicasets/status,verbs=get;update;patch func (r *ControllerFinder) GetWorkloadForRef(rollout *rolloutv1beta1.Rollout) (*Workload, error) { - workloadRef := rollout.Spec.ObjectRef.WorkloadRef - if workloadRef == nil { - return nil, nil - } - - switch strings.ToLower(rollout.Annotations[rolloutv1beta1.RolloutStyleAnnotation]) { - case strings.ToLower(string(rolloutv1alpha1.PartitionRollingStyle)): - for _, finder := range r.partitionStyleFinders() { - workload, err := finder(rollout.Namespace, workloadRef) - if workload != nil || err != nil { - return workload, err - } - } - case strings.ToLower(string(rolloutv1alpha1.CanaryRollingStyle)): - for _, finder := range r.canaryStyleFinders() { - workload, err := finder(rollout.Namespace, workloadRef) + workloadRef := rollout.Spec.WorkloadRef + if rollout.Spec.Strategy.Canary.EnableExtraWorkloadForCanary { + for _, finder := range append(r.canaryStyleFinders(), r.partitionStyleFinders()...) { + workload, err := finder(rollout.Namespace, &workloadRef) if workload != nil || err != nil { return workload, err } } - default: - for _, finder := range append(r.canaryStyleFinders(), r.partitionStyleFinders()...) { - workload, err := finder(rollout.Namespace, workloadRef) + } else { + for _, finder := range r.partitionStyleFinders() { + workload, err := finder(rollout.Namespace, &workloadRef) if workload != nil || err != nil { return workload, err } @@ -138,7 +126,7 @@ var ( ) // getKruiseCloneSet returns the kruise cloneSet referenced by the provided controllerRef. -func (r *ControllerFinder) getKruiseCloneSet(namespace string, ref *rolloutv1beta1.WorkloadRef) (*Workload, error) { +func (r *ControllerFinder) getKruiseCloneSet(namespace string, ref *rolloutv1beta1.ObjectRef) (*Workload, error) { // This error is irreversible, so there is no need to return error ok, _ := verifyGroupKind(ref, ControllerKruiseKindCS.Kind, []string{ControllerKruiseKindCS.Group}) if !ok { @@ -180,7 +168,7 @@ func (r *ControllerFinder) getKruiseCloneSet(namespace string, ref *rolloutv1bet return workload, nil } -func (r *ControllerFinder) getKruiseDaemonSet(namespace string, ref *rolloutv1beta1.WorkloadRef) (*Workload, error) { +func (r *ControllerFinder) getKruiseDaemonSet(namespace string, ref *rolloutv1beta1.ObjectRef) (*Workload, error) { // This error is irreversible, so there is no need to return error ok, _ := verifyGroupKind(ref, ControllerKruiseKindDS.Kind, []string{ControllerKruiseKindDS.Group}) if !ok { @@ -224,7 +212,7 @@ func (r *ControllerFinder) getKruiseDaemonSet(namespace string, ref *rolloutv1be } // getPartitionStyleDeployment returns the Advanced Deployment referenced by the provided controllerRef. -func (r *ControllerFinder) getAdvancedDeployment(namespace string, ref *rolloutv1beta1.WorkloadRef) (*Workload, error) { +func (r *ControllerFinder) getAdvancedDeployment(namespace string, ref *rolloutv1beta1.ObjectRef) (*Workload, error) { // This error is irreversible, so there is no need to return error ok, _ := verifyGroupKind(ref, ControllerKindDep.Kind, []string{ControllerKindDep.Group}) if !ok { @@ -278,7 +266,7 @@ func (r *ControllerFinder) getAdvancedDeployment(namespace string, ref *rolloutv } // getDeployment returns the k8s native deployment referenced by the provided controllerRef. -func (r *ControllerFinder) getDeployment(namespace string, ref *rolloutv1beta1.WorkloadRef) (*Workload, error) { +func (r *ControllerFinder) getDeployment(namespace string, ref *rolloutv1beta1.ObjectRef) (*Workload, error) { // This error is irreversible, so there is no need to return error ok, _ := verifyGroupKind(ref, ControllerKindDep.Kind, []string{ControllerKindDep.Group}) if !ok { @@ -335,7 +323,7 @@ func (r *ControllerFinder) getDeployment(namespace string, ref *rolloutv1beta1.W return workload, err } -func (r *ControllerFinder) getStatefulSetLikeWorkload(namespace string, ref *rolloutv1beta1.WorkloadRef) (*Workload, error) { +func (r *ControllerFinder) getStatefulSetLikeWorkload(namespace string, ref *rolloutv1beta1.ObjectRef) (*Workload, error) { if ref == nil { return nil, nil } @@ -446,7 +434,7 @@ func (r *ControllerFinder) getDeploymentStableRs(obj *apps.Deployment) (*apps.Re return rss[0], nil } -func verifyGroupKind(ref *rolloutv1beta1.WorkloadRef, expectedKind string, expectedGroups []string) (bool, error) { +func verifyGroupKind(ref *rolloutv1beta1.ObjectRef, expectedKind string, expectedGroups []string) (bool, error) { gv, err := schema.ParseGroupVersion(ref.APIVersion) if err != nil { return false, err diff --git a/pkg/util/rollout_utils.go b/pkg/util/rollout_utils.go index bc13f39f..f41d7bf7 100644 --- a/pkg/util/rollout_utils.go +++ b/pkg/util/rollout_utils.go @@ -50,7 +50,7 @@ func IsRollbackInBatchPolicy(rollout *rolloutv1beta1.Rollout, labels map[string] if len(rollout.Spec.Strategy.Canary.TrafficRoutings) > 0 { return false } - workloadRef := rollout.Spec.ObjectRef.WorkloadRef + workloadRef := rollout.Spec.WorkloadRef //currently, only CloneSet, StatefulSet support this policy if workloadRef.Kind == ControllerKindSts.Kind || workloadRef.Kind == ControllerKruiseKindCS.Kind || @@ -131,7 +131,7 @@ func DiscoverGVK(gvk schema.GroupVersionKind) bool { return true } -func GetGVKFrom(workloadRef *rolloutv1beta1.WorkloadRef) schema.GroupVersionKind { +func GetGVKFrom(workloadRef *rolloutv1beta1.ObjectRef) schema.GroupVersionKind { if workloadRef == nil { return schema.GroupVersionKind{} } diff --git a/pkg/webhook/rollout/validating/rollout_create_update_handler.go b/pkg/webhook/rollout/validating/rollout_create_update_handler.go index 9b7103c0..80c90476 100644 --- a/pkg/webhook/rollout/validating/rollout_create_update_handler.go +++ b/pkg/webhook/rollout/validating/rollout_create_update_handler.go @@ -21,7 +21,6 @@ import ( "fmt" "net/http" "reflect" - "strings" appsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1" appsv1beta1 "github.com/openkruise/rollouts/api/v1beta1" @@ -131,15 +130,15 @@ func (h *RolloutCreateUpdateHandler) validateRolloutUpdate(oldObj, newObj *appsv switch latestObject.Status.Phase { // The workloadRef and TrafficRouting are not allowed to be modified in the Progressing, Terminating state case appsv1beta1.RolloutPhaseProgressing, appsv1beta1.RolloutPhaseTerminating: - if !reflect.DeepEqual(oldObj.Spec.ObjectRef, newObj.Spec.ObjectRef) { + if !reflect.DeepEqual(oldObj.Spec.WorkloadRef, newObj.Spec.WorkloadRef) { return field.ErrorList{field.Forbidden(field.NewPath("Spec.ObjectRef"), "Rollout 'ObjectRef' field is immutable")} } // canary strategy if !reflect.DeepEqual(oldObj.Spec.Strategy.Canary.TrafficRoutings, newObj.Spec.Strategy.Canary.TrafficRoutings) { return field.ErrorList{field.Forbidden(field.NewPath("Spec.Strategy.Canary.TrafficRoutings"), "Rollout 'Strategy.Canary.TrafficRoutings' field is immutable")} } - if !strings.EqualFold(oldObj.Annotations[appsv1beta1.RolloutStyleAnnotation], newObj.Annotations[appsv1beta1.RolloutStyleAnnotation]) { - return field.ErrorList{field.Forbidden(field.NewPath("Metadata.Annotation"), "Rollout 'Rolling-Style' annotation is immutable")} + if oldObj.Spec.Strategy.Canary.EnableExtraWorkloadForCanary != newObj.Spec.Strategy.Canary.EnableExtraWorkloadForCanary { + return field.ErrorList{field.Forbidden(field.NewPath("Spec.Strategy.Canary"), "Rollout enableExtraWorkloadForCanary is immutable")} } } @@ -171,7 +170,7 @@ func (h *RolloutCreateUpdateHandler) validateRolloutConflict(rollout *appsv1beta } for i := range rolloutList.Items { r := &rolloutList.Items[i] - if r.Name == rollout.Name || !IsSameWorkloadRefGVKName(r.Spec.ObjectRef.WorkloadRef, rollout.Spec.ObjectRef.WorkloadRef) { + if r.Name == rollout.Name || !IsSameWorkloadRefGVKName(&r.Spec.WorkloadRef, &rollout.Spec.WorkloadRef) { continue } return field.ErrorList{field.Invalid(path, rollout.Name, @@ -181,39 +180,32 @@ func (h *RolloutCreateUpdateHandler) validateRolloutConflict(rollout *appsv1beta } func validateRolloutSpec(rollout *appsv1beta1.Rollout, fldPath *field.Path) field.ErrorList { - errList := validateRolloutSpecObjectRef(&rollout.Spec.ObjectRef, fldPath.Child("ObjectRef")) + errList := validateRolloutSpecObjectRef(&rollout.Spec.WorkloadRef, fldPath.Child("ObjectRef")) errList = append(errList, validateRolloutRollingStyle(rollout, field.NewPath("RollingStyle"))...) errList = append(errList, validateRolloutSpecStrategy(&rollout.Spec.Strategy, fldPath.Child("Strategy"))...) return errList } func validateRolloutRollingStyle(rollout *appsv1beta1.Rollout, fldPath *field.Path) field.ErrorList { - switch strings.ToLower(rollout.Annotations[appsv1beta1.RolloutStyleAnnotation]) { - case "", strings.ToLower(string(appsv1alpha1.CanaryRollingStyle)), strings.ToLower(string(appsv1alpha1.PartitionRollingStyle)): - default: - return field.ErrorList{field.Invalid(fldPath, rollout.Annotations[appsv1beta1.RolloutStyleAnnotation], - "Rolling style must be 'Canary', 'Partition' or empty")} - } - - workloadRef := rollout.Spec.ObjectRef.WorkloadRef - if workloadRef == nil || workloadRef.Kind == util.ControllerKindDep.Kind { + workloadRef := rollout.Spec.WorkloadRef + if workloadRef.Kind == util.ControllerKindDep.Kind { return nil // Deployment support all rolling styles, no need to validate. } - if strings.EqualFold(rollout.Annotations[appsv1beta1.RolloutStyleAnnotation], string(appsv1alpha1.CanaryRollingStyle)) { - return field.ErrorList{field.Invalid(fldPath, rollout.Annotations[appsv1beta1.RolloutStyleAnnotation], - "Only Deployment support canary rolling style")} + if rollout.Spec.Strategy.Canary.EnableExtraWorkloadForCanary { + return field.ErrorList{field.Invalid(fldPath, rollout.Spec.Strategy.Canary.EnableExtraWorkloadForCanary, + "Only Deployment can set enableExtraWorkloadForCanary=true")} } return nil } -func validateRolloutSpecObjectRef(objectRef *appsv1beta1.ObjectRef, fldPath *field.Path) field.ErrorList { - if objectRef.WorkloadRef == nil { - return field.ErrorList{field.Invalid(fldPath.Child("WorkloadRef"), objectRef.WorkloadRef, "WorkloadRef is required")} +func validateRolloutSpecObjectRef(workloadRef *appsv1beta1.ObjectRef, fldPath *field.Path) field.ErrorList { + if workloadRef == nil { + return field.ErrorList{field.Invalid(fldPath.Child("WorkloadRef"), workloadRef, "WorkloadRef is required")} } - gvk := schema.FromAPIVersionAndKind(objectRef.WorkloadRef.APIVersion, objectRef.WorkloadRef.Kind) + gvk := schema.FromAPIVersionAndKind(workloadRef.APIVersion, workloadRef.Kind) if !util.IsSupportedWorkload(gvk) { - return field.ErrorList{field.Invalid(fldPath.Child("WorkloadRef"), objectRef.WorkloadRef, "WorkloadRef kind is not supported")} + return field.ErrorList{field.Invalid(fldPath.Child("WorkloadRef"), workloadRef, "WorkloadRef kind is not supported")} } return nil } @@ -237,7 +229,7 @@ func validateRolloutSpecCanaryStrategy(canary *appsv1beta1.CanaryStrategy, fldPa return errList } -func validateRolloutSpecCanaryTraffic(traffic appsv1alpha1.TrafficRoutingRef, fldPath *field.Path) field.ErrorList { +func validateRolloutSpecCanaryTraffic(traffic appsv1beta1.TrafficRoutingRef, fldPath *field.Path) field.ErrorList { errList := field.ErrorList{} if len(traffic.Service) == 0 { errList = append(errList, field.Invalid(fldPath.Child("Service"), traffic.Service, "TrafficRouting.Service cannot be empty")) @@ -269,16 +261,16 @@ func validateRolloutSpecCanarySteps(steps []appsv1beta1.CanaryStep, fldPath *fie for i := range steps { s := &steps[i] - if s.Weight == nil && s.Replicas == nil { + if s.TrafficWeight == nil && s.DesiredReplicas == nil { return field.ErrorList{field.Invalid(fldPath.Index(i).Child("steps"), steps, `weight and replicas cannot be empty at the same time`)} } - if s.Replicas != nil { - canaryReplicas, err := intstr.GetScaledValueFromIntOrPercent(s.Replicas, 100, true) + if s.DesiredReplicas != nil { + canaryReplicas, err := intstr.GetScaledValueFromIntOrPercent(s.DesiredReplicas, 100, true) if err != nil || canaryReplicas <= 0 || - (canaryReplicas > 100 && s.Replicas.Type == intstr.String) { + (canaryReplicas > 100 && s.DesiredReplicas.Type == intstr.String) { return field.ErrorList{field.Invalid(fldPath.Index(i).Child("Replicas"), - s.Replicas, `replicas must be positive number, or a percentage with "0%" < canaryReplicas <= "100%"`)} + s.DesiredReplicas, `replicas must be positive number, or a percentage with "0%" < canaryReplicas <= "100%"`)} } } } @@ -286,22 +278,22 @@ func validateRolloutSpecCanarySteps(steps []appsv1beta1.CanaryStep, fldPath *fie for i := 1; i < stepCount; i++ { prev := &steps[i-1] curr := &steps[i] - if isTraffic && curr.Weight != nil && prev.Weight != nil && *curr.Weight < *prev.Weight { + if isTraffic && curr.TrafficWeight != nil && prev.TrafficWeight != nil && *curr.TrafficWeight < *prev.TrafficWeight { return field.ErrorList{field.Invalid(fldPath.Child("Weight"), steps, `Steps.Weight must be a non decreasing sequence`)} } // if they are comparable, then compare them - if IsPercentageCanaryReplicasType(prev.Replicas) != IsPercentageCanaryReplicasType(curr.Replicas) { + if IsPercentageCanaryReplicasType(prev.DesiredReplicas) != IsPercentageCanaryReplicasType(curr.DesiredReplicas) { continue } - prevCanaryReplicas, _ := intstr.GetScaledValueFromIntOrPercent(prev.Replicas, 100, true) - currCanaryReplicas, _ := intstr.GetScaledValueFromIntOrPercent(curr.Replicas, 100, true) - if prev.Replicas == nil { - prevCanaryReplicas = int(*prev.Weight) + prevCanaryReplicas, _ := intstr.GetScaledValueFromIntOrPercent(prev.DesiredReplicas, 100, true) + currCanaryReplicas, _ := intstr.GetScaledValueFromIntOrPercent(curr.DesiredReplicas, 100, true) + if prev.DesiredReplicas == nil { + prevCanaryReplicas = int(*prev.TrafficWeight) } - if curr.Replicas == nil { - currCanaryReplicas = int(*curr.Weight) + if curr.DesiredReplicas == nil { + currCanaryReplicas = int(*curr.TrafficWeight) } if currCanaryReplicas < prevCanaryReplicas { return field.ErrorList{field.Invalid(fldPath.Child("CanaryReplicas"), steps, `Steps.CanaryReplicas must be a non decreasing sequence`)} @@ -315,7 +307,7 @@ func IsPercentageCanaryReplicasType(replicas *intstr.IntOrString) bool { return replicas == nil || replicas.Type == intstr.String } -func IsSameWorkloadRefGVKName(a, b *appsv1beta1.WorkloadRef) bool { +func IsSameWorkloadRefGVKName(a, b *appsv1beta1.ObjectRef) bool { if a == nil || b == nil { return false } diff --git a/pkg/webhook/rollout/validating/rollout_create_update_handler_test.go b/pkg/webhook/rollout/validating/rollout_create_update_handler_test.go index be8236f4..bad1cdfa 100644 --- a/pkg/webhook/rollout/validating/rollout_create_update_handler_test.go +++ b/pkg/webhook/rollout/validating/rollout_create_update_handler_test.go @@ -22,7 +22,6 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" rolloutapi "github.com/openkruise/rollouts/api" - appsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1" appsv1beta1 "github.com/openkruise/rollouts/api/v1beta1" apps "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -47,56 +46,54 @@ var ( Annotations: map[string]string{}, }, Spec: appsv1beta1.RolloutSpec{ - ObjectRef: appsv1beta1.ObjectRef{ - WorkloadRef: &appsv1beta1.WorkloadRef{ - APIVersion: apps.SchemeGroupVersion.String(), - Kind: "Deployment", - Name: "deployment-demo", - }, + WorkloadRef: appsv1beta1.ObjectRef{ + APIVersion: apps.SchemeGroupVersion.String(), + Kind: "Deployment", + Name: "deployment-demo", }, Strategy: appsv1beta1.RolloutStrategy{ Canary: &appsv1beta1.CanaryStrategy{ Steps: []appsv1beta1.CanaryStep{ { - TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(10), + TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(10), }, - Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(1)}, - Pause: appsv1beta1.RolloutPause{}, + DesiredReplicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(1)}, + Pause: appsv1beta1.RolloutPause{}, }, { - TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(10), + TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(10), }, - Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(3)}, - Pause: appsv1beta1.RolloutPause{Duration: utilpointer.Int32(1 * 24 * 60 * 60)}, + DesiredReplicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(3)}, + Pause: appsv1beta1.RolloutPause{Duration: utilpointer.Int32(1 * 24 * 60 * 60)}, }, { - TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(30), + TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(30), }, Pause: appsv1beta1.RolloutPause{Duration: utilpointer.Int32(7 * 24 * 60 * 60)}, }, { - TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(100), + TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(100), }, }, { - TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(101), + TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(101), }, }, { - TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{ - Weight: utilpointer.Int32(200), + TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{ + TrafficWeight: utilpointer.Int32(200), }, }, }, - TrafficRoutings: []appsv1alpha1.TrafficRoutingRef{ + TrafficRoutings: []appsv1beta1.TrafficRoutingRef{ { Service: "service-demo", - Ingress: &appsv1alpha1.IngressTrafficRouting{ + Ingress: &appsv1beta1.IngressTrafficRouting{ ClassType: "nginx", Name: "ingress-nginx-demo", }, @@ -135,15 +132,6 @@ func TestRolloutValidateCreate(t *testing.T) { /*********************************************************** The following cases may lead to controller panic **********************************************************/ - { - Name: "WorkloadRef is nil", - Succeed: false, - GetObject: func() []client.Object { - object := rollout.DeepCopy() - object.Spec.ObjectRef.WorkloadRef = nil - return []client.Object{object} - }, - }, { Name: "Canary is nil", Succeed: false, @@ -188,7 +176,7 @@ func TestRolloutValidateCreate(t *testing.T) { Succeed: true, GetObject: func() []client.Object { object := rollout.DeepCopy() - object.Spec.ObjectRef.WorkloadRef = &appsv1beta1.WorkloadRef{ + object.Spec.WorkloadRef = appsv1beta1.ObjectRef{ APIVersion: "apps/v1", Kind: "StatefulSet", Name: "whatever", @@ -197,65 +185,65 @@ func TestRolloutValidateCreate(t *testing.T) { }, }, { - Name: "Steps.Weight is a decreasing sequence", + Name: "Steps.TrafficWeight is a decreasing sequence", Succeed: false, GetObject: func() []client.Object { object := rollout.DeepCopy() - object.Spec.Strategy.Canary.Steps[2].Weight = utilpointer.Int32Ptr(5) + object.Spec.Strategy.Canary.Steps[2].TrafficWeight = utilpointer.Int32Ptr(5) return []client.Object{object} }, }, { - Name: "Steps.Replicas is a decreasing sequence", + Name: "Steps.DesiredReplicas is a decreasing sequence", Succeed: false, GetObject: func() []client.Object { object := rollout.DeepCopy() - object.Spec.Strategy.Canary.Steps[1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "50%"} + object.Spec.Strategy.Canary.Steps[1].DesiredReplicas = &intstr.IntOrString{Type: intstr.String, StrVal: "50%"} return []client.Object{object} }, }, { - Name: "Steps.Replicas is illegal value, '50'", + Name: "Steps.DesiredReplicas is illegal value, '50'", Succeed: false, GetObject: func() []client.Object { object := rollout.DeepCopy() - object.Spec.Strategy.Canary.Steps[1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "50"} + object.Spec.Strategy.Canary.Steps[1].DesiredReplicas = &intstr.IntOrString{Type: intstr.String, StrVal: "50"} return []client.Object{object} }, }, { - Name: "Steps.Replicas is illegal value, '101%'", + Name: "Steps.DesiredReplicas is illegal value, '101%'", Succeed: false, GetObject: func() []client.Object { object := rollout.DeepCopy() - object.Spec.Strategy.Canary.Steps[1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "101%"} + object.Spec.Strategy.Canary.Steps[1].DesiredReplicas = &intstr.IntOrString{Type: intstr.String, StrVal: "101%"} return []client.Object{object} }, }, { - Name: "Steps.Replicas is illegal value, '0%'", + Name: "Steps.DesiredReplicas is illegal value, '0%'", Succeed: false, GetObject: func() []client.Object { object := rollout.DeepCopy() - object.Spec.Strategy.Canary.Steps[1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "0%"} + object.Spec.Strategy.Canary.Steps[1].DesiredReplicas = &intstr.IntOrString{Type: intstr.String, StrVal: "0%"} return []client.Object{object} }, }, { - Name: "Steps.Weight is illegal value, 0", + Name: "Steps.TrafficWeight is illegal value, 0", Succeed: false, GetObject: func() []client.Object { object := rollout.DeepCopy() - object.Spec.Strategy.Canary.Steps[1].Weight = utilpointer.Int32Ptr(0) + object.Spec.Strategy.Canary.Steps[1].TrafficWeight = utilpointer.Int32Ptr(0) return []client.Object{object} }, }, { - Name: "Steps.Weight is illegal value, 101", + Name: "Steps.TrafficWeight is illegal value, 101", Succeed: false, GetObject: func() []client.Object { object := rollout.DeepCopy() - object.Spec.Strategy.Canary.Steps[1].Weight = utilpointer.Int32Ptr(101) + object.Spec.Strategy.Canary.Steps[1].TrafficWeight = utilpointer.Int32Ptr(101) return []client.Object{object} }, }, @@ -264,9 +252,7 @@ func TestRolloutValidateCreate(t *testing.T) { Succeed: true, GetObject: func() []client.Object { object := rollout.DeepCopy() - object.Annotations = map[string]string{ - appsv1beta1.RolloutStyleAnnotation: "Canary", - } + object.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true return []client.Object{object} }, }, @@ -275,20 +261,6 @@ func TestRolloutValidateCreate(t *testing.T) { Succeed: true, GetObject: func() []client.Object { object := rollout.DeepCopy() - object.Annotations = map[string]string{ - appsv1beta1.RolloutStyleAnnotation: "Partition", - } - return []client.Object{object} - }, - }, - { - Name: "Wrong rolling style", - Succeed: false, - GetObject: func() []client.Object { - object := rollout.DeepCopy() - object.Annotations = map[string]string{ - appsv1beta1.RolloutStyleAnnotation: "Other", - } return []client.Object{object} }, }, @@ -297,21 +269,19 @@ func TestRolloutValidateCreate(t *testing.T) { Succeed: false, GetObject: func() []client.Object { object := rollout.DeepCopy() - object.Annotations = map[string]string{ - appsv1beta1.RolloutStyleAnnotation: "Canary", - } - object.Spec.ObjectRef.WorkloadRef.APIVersion = "apps.kruise.io/v1alpha1" - object.Spec.ObjectRef.WorkloadRef.Kind = "CloneSet" + object.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true + object.Spec.WorkloadRef.APIVersion = "apps.kruise.io/v1alpha1" + object.Spec.WorkloadRef.Kind = "CloneSet" return []client.Object{object} }, }, //{ - // Name: "The last Steps.Weight is not 100", + // Name: "The last Steps.TrafficWeight is not 100", // Succeed: false, // GetObject: func() []client.Object { // object := rollout.DeepCopy() // n := len(object.Spec.Strategy.Canary.Steps) - // object.Spec.Strategy.Canary.Steps[n-1].Weight = 80 + // object.Spec.Strategy.Canary.Steps[n-1].TrafficWeight = 80 // return []client.Object{object} // }, //}, @@ -326,15 +296,15 @@ func TestRolloutValidateCreate(t *testing.T) { object1 := rollout.DeepCopy() object1.Name = "object-1" - object1.Spec.ObjectRef.WorkloadRef.Name = "another" + object1.Spec.WorkloadRef.Name = "another" object2 := rollout.DeepCopy() object2.Name = "object-2" - object2.Spec.ObjectRef.WorkloadRef.APIVersion = "another" + object2.Spec.WorkloadRef.APIVersion = "another" object3 := rollout.DeepCopy() object3.Name = "object-3" - object3.Spec.ObjectRef.WorkloadRef.Kind = "another" + object3.Spec.WorkloadRef.Kind = "another" return []client.Object{ object, object1, object2, object3, @@ -349,15 +319,15 @@ func TestRolloutValidateCreate(t *testing.T) { object1 := rollout.DeepCopy() object1.Name = "object-1" - object1.Spec.ObjectRef.WorkloadRef.Name = "another" + object1.Spec.WorkloadRef.Name = "another" object2 := rollout.DeepCopy() object2.Name = "object-2" - object2.Spec.ObjectRef.WorkloadRef.APIVersion = "another" + object2.Spec.WorkloadRef.APIVersion = "another" object3 := rollout.DeepCopy() object3.Name = "object-3" - object3.Spec.ObjectRef.WorkloadRef.Kind = "another" + object3.Spec.WorkloadRef.Kind = "another" object4 := rollout.DeepCopy() object4.Name = "object-4" @@ -400,7 +370,7 @@ func TestRolloutValidateUpdate(t *testing.T) { }, GetNewObject: func() client.Object { object := rollout.DeepCopy() - object.Spec.Strategy.Canary.Steps[0].Weight = utilpointer.Int32Ptr(5) + object.Spec.Strategy.Canary.Steps[0].TrafficWeight = utilpointer.Int32Ptr(5) return object }, }, @@ -430,7 +400,7 @@ func TestRolloutValidateUpdate(t *testing.T) { GetNewObject: func() client.Object { object := rollout.DeepCopy() object.Status.Phase = appsv1beta1.RolloutPhaseProgressing - object.Spec.Strategy.Canary.Steps[0].Weight = utilpointer.Int32Ptr(5) + object.Spec.Strategy.Canary.Steps[0].TrafficWeight = utilpointer.Int32Ptr(5) return object }, }, @@ -439,14 +409,13 @@ func TestRolloutValidateUpdate(t *testing.T) { Succeed: false, GetOldObject: func() client.Object { object := rollout.DeepCopy() - object.Annotations[appsv1beta1.RolloutStyleAnnotation] = "Partition" object.Status.Phase = appsv1beta1.RolloutPhaseProgressing return object }, GetNewObject: func() client.Object { object := rollout.DeepCopy() object.Status.Phase = appsv1beta1.RolloutPhaseProgressing - object.Annotations[appsv1beta1.RolloutStyleAnnotation] = "Canary" + object.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true return object }, }, @@ -476,7 +445,7 @@ func TestRolloutValidateUpdate(t *testing.T) { GetNewObject: func() client.Object { object := rollout.DeepCopy() object.Status.Phase = appsv1beta1.RolloutPhaseInitial - object.Spec.Strategy.Canary.Steps[0].Weight = utilpointer.Int32Ptr(5) + object.Spec.Strategy.Canary.Steps[0].TrafficWeight = utilpointer.Int32Ptr(5) return object }, }, @@ -491,7 +460,7 @@ func TestRolloutValidateUpdate(t *testing.T) { GetNewObject: func() client.Object { object := rollout.DeepCopy() object.Status.Phase = appsv1beta1.RolloutPhaseHealthy - object.Spec.Strategy.Canary.Steps[0].Weight = utilpointer.Int32Ptr(5) + object.Spec.Strategy.Canary.Steps[0].TrafficWeight = utilpointer.Int32Ptr(5) return object }, }, diff --git a/pkg/webhook/rollout/validating/validate_v1alphal_rollout.go b/pkg/webhook/rollout/validating/validate_v1alphal_rollout.go index 58163094..776d358a 100644 --- a/pkg/webhook/rollout/validating/validate_v1alphal_rollout.go +++ b/pkg/webhook/rollout/validating/validate_v1alphal_rollout.go @@ -145,11 +145,35 @@ func validateV1alpha1RolloutSpecCanaryStrategy(canary *appsv1alpha1.CanaryStrate errList = append(errList, field.Invalid(fldPath, canary.TrafficRoutings, "Rollout currently only support single TrafficRouting.")) } for _, traffic := range canary.TrafficRoutings { - errList = append(errList, validateRolloutSpecCanaryTraffic(traffic, fldPath.Child("TrafficRouting"))...) + errList = append(errList, validateV1alpha1RolloutSpecCanaryTraffic(traffic, fldPath.Child("TrafficRouting"))...) } return errList } +func validateV1alpha1RolloutSpecCanaryTraffic(traffic appsv1alpha1.TrafficRoutingRef, fldPath *field.Path) field.ErrorList { + errList := field.ErrorList{} + if len(traffic.Service) == 0 { + errList = append(errList, field.Invalid(fldPath.Child("Service"), traffic.Service, "TrafficRouting.Service cannot be empty")) + } + + if traffic.Gateway == nil && traffic.Ingress == nil && traffic.CustomNetworkRefs == nil { + errList = append(errList, field.Invalid(fldPath.Child("TrafficRoutings"), traffic.Ingress, "TrafficRoutings are not set")) + } + + if traffic.Ingress != nil { + if traffic.Ingress.Name == "" { + errList = append(errList, field.Invalid(fldPath.Child("Ingress"), traffic.Ingress, "TrafficRouting.Ingress.Ingress cannot be empty")) + } + } + if traffic.Gateway != nil { + if traffic.Gateway.HTTPRouteName == nil || *traffic.Gateway.HTTPRouteName == "" { + errList = append(errList, field.Invalid(fldPath.Child("Gateway"), traffic.Gateway, "TrafficRouting.Gateway must set the name of HTTPRoute or HTTPsRoute")) + } + } + + return errList +} + func validateV1alpha1RolloutSpecCanarySteps(steps []appsv1alpha1.CanaryStep, fldPath *field.Path, isTraffic bool) field.ErrorList { stepCount := len(steps) if stepCount == 0 { diff --git a/pkg/webhook/server.go b/pkg/webhook/server.go index a65bea27..1d453be8 100644 --- a/pkg/webhook/server.go +++ b/pkg/webhook/server.go @@ -29,6 +29,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/conversion" ) type GateFunc func() (enabled bool) @@ -90,11 +91,12 @@ func SetupWithManager(mgr manager.Manager) error { server.Register(path, &webhook.Admission{Handler: handler}) klog.V(3).Infof("Registered webhook handler %s", path) } - err := initialize(context.TODO(), mgr.GetConfig()) if err != nil { return err } + // register conversion webhook + server.Register("/convert", &conversion.Webhook{}) klog.Infof("webhook init done") return nil } diff --git a/pkg/webhook/util/controller/webhook_controller.go b/pkg/webhook/util/controller/webhook_controller.go index 30840f54..d3be6276 100644 --- a/pkg/webhook/util/controller/webhook_controller.go +++ b/pkg/webhook/util/controller/webhook_controller.go @@ -24,6 +24,7 @@ import ( webhookutil "github.com/openkruise/rollouts/pkg/webhook/util" "github.com/openkruise/rollouts/pkg/webhook/util/configuration" + "github.com/openkruise/rollouts/pkg/webhook/util/crd" "github.com/openkruise/rollouts/pkg/webhook/util/generator" "github.com/openkruise/rollouts/pkg/webhook/util/writer" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" @@ -266,6 +267,10 @@ func (c *Controller) sync() error { return fmt.Errorf("failed to ensure configuration: %v", err) } + if err := crd.Ensure(c.crdClient, c.crdLister, certs.CACert); err != nil { + return fmt.Errorf("failed to ensure crd: %v", err) + } + onceInit.Do(func() { close(uninit) }) diff --git a/pkg/webhook/util/crd/crd.go b/pkg/webhook/util/crd/crd.go new file mode 100644 index 00000000..cd275e3e --- /dev/null +++ b/pkg/webhook/util/crd/crd.go @@ -0,0 +1,87 @@ +/* +Copyright 2023 The Kruise Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package crd + +import ( + "context" + "fmt" + "reflect" + + rolloutapi "github.com/openkruise/rollouts/api" + webhookutil "github.com/openkruise/rollouts/pkg/webhook/util" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + apiextensionslisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +var ( + scheme = runtime.NewScheme() +) + +func init() { + utilruntime.Must(rolloutapi.AddToScheme(scheme)) +} + +func Ensure(client apiextensionsclientset.Interface, lister apiextensionslisters.CustomResourceDefinitionLister, caBundle []byte) error { + crdList, err := lister.List(labels.Everything()) + if err != nil { + return fmt.Errorf("failed to list crds: %v", err) + } + + webhookConfig := apiextensionsv1.WebhookClientConfig{ + CABundle: caBundle, + } + path := "/convert" + if host := webhookutil.GetHost(); len(host) > 0 { + url := fmt.Sprintf("https://%s:%d%s", host, webhookutil.GetPort(), path) + webhookConfig.URL = &url + } else { + var port int32 = 443 + webhookConfig.Service = &apiextensionsv1.ServiceReference{ + Namespace: webhookutil.GetNamespace(), + Name: webhookutil.GetServiceName(), + Port: &port, + Path: &path, + } + } + + for _, crd := range crdList { + if len(crd.Spec.Versions) == 0 || crd.Spec.Conversion == nil || crd.Spec.Conversion.Strategy != apiextensionsv1.WebhookConverter { + continue + } + if !scheme.Recognizes(schema.GroupVersionKind{Group: crd.Spec.Group, Version: crd.Spec.Versions[0].Name, Kind: crd.Spec.Names.Kind}) { + continue + } + + if crd.Spec.Conversion.Webhook == nil || !reflect.DeepEqual(crd.Spec.Conversion.Webhook.ClientConfig, webhookConfig) { + newCRD := crd.DeepCopy() + newCRD.Spec.Conversion.Webhook = &apiextensionsv1.WebhookConversion{ + ClientConfig: webhookConfig.DeepCopy(), + ConversionReviewVersions: []string{"v1", "v1beta1"}, + } + if _, err := client.ApiextensionsV1().CustomResourceDefinitions().Update(context.TODO(), newCRD, metav1.UpdateOptions{}); err != nil { + return fmt.Errorf("failed to update CRD %s: %v", newCRD.Name, err) + } + } + } + return nil +} diff --git a/pkg/webhook/workload/mutating/workload_update_handler.go b/pkg/webhook/workload/mutating/workload_update_handler.go index b8f08e85..98bedf89 100644 --- a/pkg/webhook/workload/mutating/workload_update_handler.go +++ b/pkg/webhook/workload/mutating/workload_update_handler.go @@ -422,14 +422,14 @@ func (h *WorkloadHandler) fetchMatchedRollout(obj client.Object) (*appsv1beta1.R } for i := range rolloutList.Items { rollout := &rolloutList.Items[i] - if !rollout.DeletionTimestamp.IsZero() || rollout.Spec.ObjectRef.WorkloadRef == nil { + if !rollout.DeletionTimestamp.IsZero() { continue } if rollout.Status.Phase == appsv1beta1.RolloutPhaseDisabled { klog.Infof("Disabled rollout(%s/%s) fetched when fetching matched rollout", rollout.Namespace, rollout.Name) continue } - ref := rollout.Spec.ObjectRef.WorkloadRef + ref := rollout.Spec.WorkloadRef gv, err := schema.ParseGroupVersion(ref.APIVersion) if err != nil { klog.Warningf("ParseGroupVersion rollout(%s/%s) ref failed: %s", rollout.Namespace, rollout.Name, err.Error()) diff --git a/pkg/webhook/workload/mutating/workload_update_handler_test.go b/pkg/webhook/workload/mutating/workload_update_handler_test.go index 5d3b599d..117d32ba 100644 --- a/pkg/webhook/workload/mutating/workload_update_handler_test.go +++ b/pkg/webhook/workload/mutating/workload_update_handler_test.go @@ -274,12 +274,10 @@ var ( Labels: map[string]string{}, }, Spec: appsv1beta1.RolloutSpec{ - ObjectRef: appsv1beta1.ObjectRef{ - WorkloadRef: &appsv1beta1.WorkloadRef{ - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "echoserver", - }, + WorkloadRef: appsv1beta1.ObjectRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "echoserver", }, Strategy: appsv1beta1.RolloutStrategy{ Canary: &appsv1beta1.CanaryStrategy{}, @@ -345,7 +343,7 @@ func TestHandlerDeployment(t *testing.T) { }, getRollout: func() *appsv1beta1.Rollout { obj := rolloutDemo.DeepCopy() - obj.Spec.ObjectRef.WorkloadRef = &appsv1beta1.WorkloadRef{ + obj.Spec.WorkloadRef = appsv1beta1.ObjectRef{ APIVersion: "apps/v1", Kind: "Deployment", Name: "other", @@ -377,10 +375,10 @@ func TestHandlerDeployment(t *testing.T) { getRollout: func() *appsv1beta1.Rollout { demo := rolloutDemo.DeepCopy() demo.Spec.Strategy.Canary = &appsv1beta1.CanaryStrategy{ - TrafficRoutings: []appsv1alpha1.TrafficRoutingRef{ + TrafficRoutings: []appsv1beta1.TrafficRoutingRef{ { Service: "echoserver", - Ingress: &appsv1alpha1.IngressTrafficRouting{ + Ingress: &appsv1beta1.IngressTrafficRouting{ Name: "echoserver", }, }, @@ -582,7 +580,7 @@ func TestHandlerCloneSet(t *testing.T) { }, getRollout: func() *appsv1beta1.Rollout { obj := rolloutDemo.DeepCopy() - obj.Spec.ObjectRef.WorkloadRef = &appsv1beta1.WorkloadRef{ + obj.Spec.WorkloadRef = appsv1beta1.ObjectRef{ APIVersion: "apps.kruise.io/v1alpha1", Kind: "CloneSet", Name: "echoserver", @@ -646,7 +644,7 @@ func TestHandlerDaemonSet(t *testing.T) { }, getRollout: func() *appsv1beta1.Rollout { obj := rolloutDemo.DeepCopy() - obj.Spec.ObjectRef.WorkloadRef = &appsv1beta1.WorkloadRef{ + obj.Spec.WorkloadRef = appsv1beta1.ObjectRef{ APIVersion: "apps.kruise.io/v1alpha1", Kind: "DaemonSet", Name: "echoserver", @@ -710,7 +708,7 @@ func TestHandleStatefulSet(t *testing.T) { }, getRollout: func() *appsv1beta1.Rollout { obj := rolloutDemo.DeepCopy() - obj.Spec.ObjectRef.WorkloadRef = &appsv1beta1.WorkloadRef{ + obj.Spec.WorkloadRef = appsv1beta1.ObjectRef{ APIVersion: "apps.kruise.io/v1beta1", Kind: "StatefulSet", Name: "echoserver",