new v1beta1 apis (#184)

Signed-off-by: liheng.zms <liheng.zms@alibaba-inc.com>
This commit is contained in:
berg 2023-12-04 14:56:47 +08:00 committed by GitHub
parent 07b7f20f6a
commit 9dcf3659d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 1461 additions and 603 deletions

458
api/v1alpha1/conversion.go Normal file
View File

@ -0,0 +1,458 @@
/*
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"
"k8s.io/apimachinery/pkg/util/intstr"
utilpointer "k8s.io/utils/pointer"
"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),
Replicas: step.Replicas,
Pause: v1beta1.RolloutPause{Duration: step.Pause.Duration},
}
if step.Replicas == nil && step.Weight != nil {
o.Replicas = &intstr.IntOrString{
Type: intstr.String,
StrVal: fmt.Sprintf("%d", *step.Weight) + "%",
}
}
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) {
if src.Weight != nil {
dst.Traffic = utilpointer.StringPtr(fmt.Sprintf("%d", *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.Replicas,
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) {
if src.Traffic != nil {
is := intstr.FromString(*src.Traffic)
weight, _ := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
dst.Weight = utilpointer.Int32(int32(weight))
}
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)
}
}

View File

@ -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 (

View File

@ -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

View File

@ -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"`
}

View File

@ -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,7 +112,7 @@ 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"`
@ -142,6 +121,35 @@ type CanaryStep struct {
Pause RolloutPause `json:"pause,omitempty"`
}
type TrafficRoutingStrategy struct {
// Traffic indicate how many percentage of traffic the canary pods should receive
// +optional
Traffic *string `json:"traffic,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

View File

@ -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"`
}

View File

@ -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)
}
@ -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
@ -295,14 +314,24 @@ func (in *HttpRouteMatch) DeepCopy() *HttpRouteMatch {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *IngressTrafficRouting) DeepCopyInto(out *IngressTrafficRouting) {
*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
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 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 WorkloadRef.
func (in *WorkloadRef) DeepCopy() *WorkloadRef {
// 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(WorkloadRef)
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.Traffic != nil {
in, out := &in.Traffic, &out.Traffic
*out = new(string)
**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 TrafficRoutingStrategy.
func (in *TrafficRoutingStrategy) DeepCopy() *TrafficRoutingStrategy {
if in == nil {
return nil
}
out := new(TrafficRoutingStrategy)
in.DeepCopyInto(out)
return out
}

View File

@ -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

View File

@ -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
@ -830,13 +806,15 @@ spec:
- name
x-kubernetes-list-type: map
type: object
weight:
description: Weight indicate how many percentage of
traffic:
description: Traffic indicate how many percentage of
traffic the canary pods should receive
format: int32
type: integer
type: string
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 +828,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 +894,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

View File

@ -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.

View File

@ -13,4 +13,4 @@ spec:
name: webhook-service
path: /convert
conversionReviewVersions:
- v1
- v1beta1

View File

@ -13,4 +13,4 @@ spec:
name: webhook-service
path: /convert
conversionReviewVersions:
- v1
- v1beta1

View File

@ -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
```

View File

@ -14,6 +14,7 @@ import (
lua "github.com/yuin/gopher-lua"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
utilpointer "k8s.io/utils/pointer"
"sigs.k8s.io/yaml"
)
@ -82,8 +83,12 @@ 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 {
var weight *int32
if step.TrafficRoutingStrategy.Traffic != nil {
is := intstr.FromString(*step.TrafficRoutingStrategy.Traffic)
weightInt, _ := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
weight = utilpointer.Int32(int32(weightInt))
} else {
weight = utilpointer.Int32(-1)
}
var canaryService string
@ -111,13 +116,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,

View File

@ -1,17 +1,13 @@
rollout:
apiVersion: rollouts.kruise.io/v1alpha1
rollout:
apiVersion: rollouts.kruise.io/v1beta1
kind: Rollout
metadata:
name: rollouts-demo
annotations:
rollouts.kruise.io/rolling-style: canary
spec:
disabled: false
objectRef:
workloadRef:
apiVersion: apps/v1
kind: Deployment
name: deploy-demo
workloadRef:
apiVersion: apps/v1
kind: Deployment
name: deploy-demo
strategy:
canary:
steps:
@ -32,7 +28,7 @@ rollout:
- type: RegularExpression
name: name
value: ".*demo"
- weight: 50
- traffic: "50%"
trafficRoutings:
- service: svc-demo
customNetworkRefs:

View File

@ -114,7 +114,7 @@ function GenerateRoutes(spec, stableService, canaryService, stableWeight, canary
end
end
if (obj.matches)
if (obj.matches and next(obj.matches) ~= nil)
then
GenerateRoutesWithMatches(spec, obj.matches, obj.stableService, obj.canaryService)
else

View File

@ -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

View File

@ -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),

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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{

View File

@ -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{

View File

@ -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{

View File

@ -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{

View File

@ -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{

View File

@ -159,8 +159,8 @@ func TestSyncDeployment(t *testing.T) {
rs.Status.Replicas = replicas
if strings.HasPrefix(name, "scale") {
rs.Annotations = map[string]string{
util.DesiredReplicasAnnotation: strconv.Itoa(-1),
util.MaxReplicasAnnotation: strconv.Itoa(int(test.dAvailable + test.maxSurge.IntVal)),
util.ReplicasAnnotation: strconv.Itoa(-1),
util.MaxReplicasAnnotation: strconv.Itoa(int(test.dAvailable + test.maxSurge.IntVal)),
}
}
rs.Spec.Template.Spec.Containers[0].Image = fmt.Sprintf("old-version-%d", index)
@ -179,8 +179,8 @@ func TestSyncDeployment(t *testing.T) {
newRS.Spec.Replicas = pointer.Int32(test.newRSReplicas)
if strings.HasPrefix(name, "scale") {
newRS.Annotations = map[string]string{
util.DesiredReplicasAnnotation: strconv.Itoa(-1),
util.MaxReplicasAnnotation: strconv.Itoa(int(test.dAvailable + test.maxSurge.IntVal)),
util.ReplicasAnnotation: strconv.Itoa(-1),
util.MaxReplicasAnnotation: strconv.Itoa(int(test.dAvailable + test.maxSurge.IntVal)),
}
}
newRS.Status.Replicas = test.newRSReplicas

View File

@ -270,9 +270,9 @@ func ScaleDownLimitForOld(oldRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet, dep
klog.V(4).InfoS("Calculate scale down limit for ",
"Deployment", klog.KObj(deployment),
// About the new replica set
"Replicas(New)", *(newRS.Spec.Replicas), "DesiredReplicas(New)", newRSDesiredCount,
"Replicas(New)", *(newRS.Spec.Replicas), "Replicas(New)", newRSDesiredCount,
// About the old replica sets
"ReplicaS(Old)", oldPodsCount, "DesiredReplicas(Old)", oldRSDesiredCount, "ScaleDownLimit(Old)", scaleDownOldLimit,
"ReplicaS(Old)", oldPodsCount, "Replicas(Old)", oldRSDesiredCount, "ScaleDownLimit(Old)", scaleDownOldLimit,
// About the deployment
"Replicas(Deployment)", *(deployment.Spec.Replicas), "Partition(Deployment)", newRSUpdateLimit)

View File

@ -525,7 +525,7 @@ func (dc *DeploymentController) isScalingEvent(ctx context.Context, d *apps.Depl
}
allRSs := append(oldRSs, newRS)
for _, rs := range deploymentutil.FilterActiveReplicaSets(allRSs) {
desired, ok := deploymentutil.GetDesiredReplicasAnnotation(rs)
desired, ok := deploymentutil.GetReplicasAnnotation(rs)
if !ok {
continue
}

View File

@ -44,10 +44,10 @@ const (
RevisionAnnotation = "deployment.kubernetes.io/revision"
// RevisionHistoryAnnotation maintains the history of all old revisions that a replica set has served for a deployment.
RevisionHistoryAnnotation = "deployment.kubernetes.io/revision-history"
// DesiredReplicasAnnotation is the desired replicas for a deployment recorded as an annotation
// ReplicasAnnotation is the desired replicas for a deployment recorded as an annotation
// in its replica sets. Helps in separating scaling events from the rollout process and for
// determining if the new replica set for a deployment is really saturated.
DesiredReplicasAnnotation = "deployment.kubernetes.io/desired-replicas"
ReplicasAnnotation = "deployment.kubernetes.io/desired-replicas"
// MaxReplicasAnnotation is the maximum replicas a deployment can have at a given point, which
// is deployment.spec.replicas + maxSurge. Used by the underlying replica sets to estimate their
// proportions in case the deployment has surge replicas.
@ -267,7 +267,7 @@ var annotationsToSkip = map[string]bool{
v1.LastAppliedConfigAnnotation: true,
RevisionAnnotation: true,
RevisionHistoryAnnotation: true,
DesiredReplicasAnnotation: true,
ReplicasAnnotation: true,
MaxReplicasAnnotation: true,
apps.DeprecatedRollbackTo: true,
}
@ -325,9 +325,9 @@ func FindActiveOrLatest(newRS *apps.ReplicaSet, oldRSs []*apps.ReplicaSet) *apps
}
}
// GetDesiredReplicasAnnotation returns the number of desired replicas
func GetDesiredReplicasAnnotation(rs *apps.ReplicaSet) (int32, bool) {
return getIntFromAnnotation(rs, DesiredReplicasAnnotation)
// GetReplicasAnnotation returns the number of desired replicas
func GetReplicasAnnotation(rs *apps.ReplicaSet) (int32, bool) {
return getIntFromAnnotation(rs, ReplicasAnnotation)
}
func getMaxReplicasAnnotation(rs *apps.ReplicaSet) (int32, bool) {
@ -354,8 +354,8 @@ func SetReplicasAnnotations(rs *apps.ReplicaSet, desiredReplicas, maxReplicas in
rs.Annotations = make(map[string]string)
}
desiredString := fmt.Sprintf("%d", desiredReplicas)
if hasString := rs.Annotations[DesiredReplicasAnnotation]; hasString != desiredString {
rs.Annotations[DesiredReplicasAnnotation] = desiredString
if hasString := rs.Annotations[ReplicasAnnotation]; hasString != desiredString {
rs.Annotations[ReplicasAnnotation] = desiredString
updated = true
}
maxString := fmt.Sprintf("%d", maxReplicas)
@ -372,7 +372,7 @@ func ReplicasAnnotationsNeedUpdate(rs *apps.ReplicaSet, desiredReplicas, maxRepl
return true
}
desiredString := fmt.Sprintf("%d", desiredReplicas)
if hasString := rs.Annotations[DesiredReplicasAnnotation]; hasString != desiredString {
if hasString := rs.Annotations[ReplicasAnnotation]; hasString != desiredString {
return true
}
maxString := fmt.Sprintf("%d", maxReplicas)
@ -742,7 +742,7 @@ func IsSaturated(deployment *apps.Deployment, rs *apps.ReplicaSet) bool {
if rs == nil {
return false
}
desiredString := rs.Annotations[DesiredReplicasAnnotation]
desiredString := rs.Annotations[ReplicasAnnotation]
desired, err := strconv.Atoi(desiredString)
if err != nil {
return false

View File

@ -1104,15 +1104,15 @@ func TestAnnotationUtils(t *testing.T) {
if !updated {
t.Errorf("SetReplicasAnnotations() failed")
}
value, ok := tRS.Annotations[DesiredReplicasAnnotation]
value, ok := tRS.Annotations[ReplicasAnnotation]
if !ok {
t.Errorf("SetReplicasAnnotations did not set DesiredReplicasAnnotation")
t.Errorf("SetReplicasAnnotations did not set ReplicasAnnotation")
}
if value != "10" {
t.Errorf("SetReplicasAnnotations did not set DesiredReplicasAnnotation correctly value=%s", value)
t.Errorf("SetReplicasAnnotations did not set ReplicasAnnotation correctly value=%s", value)
}
if value, ok = tRS.Annotations[MaxReplicasAnnotation]; !ok {
t.Errorf("SetReplicasAnnotations did not set DesiredReplicasAnnotation")
t.Errorf("SetReplicasAnnotations did not set ReplicasAnnotation")
}
if value != "11" {
t.Errorf("SetReplicasAnnotations did not set MaxReplicasAnnotation correctly value=%s", value)
@ -1120,7 +1120,7 @@ func TestAnnotationUtils(t *testing.T) {
})
//Test Case 3: Check if annotations reflect deployments state
tRS.Annotations[DesiredReplicasAnnotation] = "1"
tRS.Annotations[ReplicasAnnotation] = "1"
tRS.Status.AvailableReplicas = 1
tRS.Spec.Replicas = new(int32)
*tRS.Spec.Replicas = 1
@ -1160,7 +1160,7 @@ func TestReplicasAnnotationsNeedUpdate(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "hello",
Namespace: "test",
Annotations: map[string]string{DesiredReplicasAnnotation: "8", MaxReplicasAnnotation: maxReplicas},
Annotations: map[string]string{ReplicasAnnotation: "8", MaxReplicasAnnotation: maxReplicas},
},
Spec: apps.ReplicaSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
@ -1174,7 +1174,7 @@ func TestReplicasAnnotationsNeedUpdate(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "hello",
Namespace: "test",
Annotations: map[string]string{DesiredReplicasAnnotation: desiredReplicas, MaxReplicasAnnotation: "16"},
Annotations: map[string]string{ReplicasAnnotation: desiredReplicas, MaxReplicasAnnotation: "16"},
},
Spec: apps.ReplicaSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
@ -1188,7 +1188,7 @@ func TestReplicasAnnotationsNeedUpdate(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "hello",
Namespace: "test",
Annotations: map[string]string{DesiredReplicasAnnotation: desiredReplicas, MaxReplicasAnnotation: maxReplicas},
Annotations: map[string]string{ReplicasAnnotation: desiredReplicas, MaxReplicasAnnotation: maxReplicas},
},
Spec: apps.ReplicaSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},

View File

@ -20,7 +20,6 @@ import (
"context"
"fmt"
"reflect"
"strconv"
"time"
"github.com/openkruise/rollouts/api/v1alpha1"
@ -32,7 +31,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
@ -70,7 +68,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.Traffic == nil && len(currentStep.Matches) == 0 {
tr := newTrafficRoutingContext(c)
done, err := m.trafficRoutingManager.FinalisingTrafficRouting(tr, false)
c.NewStatus.CanaryStatus.LastUpdateTime = tr.LastUpdateTime
@ -201,8 +199,7 @@ 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.Replicas != nil && currentStep.Replicas.StrVal == "100%" {
return true, nil
}
}
@ -276,7 +273,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 +310,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,11 +337,7 @@ 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)) + "%")})
} else {
batches = append(batches, v1beta1.ReleaseBatch{CanaryReplicas: *step.Replicas})
}
batches = append(batches, v1beta1.ReleaseBatch{CanaryReplicas: *step.Replicas})
}
br := &v1beta1.BatchRelease{
ObjectMeta: metav1.ObjectMeta{
@ -354,19 +346,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 +365,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
}

View File

@ -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
},
},

View File

@ -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

View File

@ -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{
Traffic: utilpointer.String("5%"),
},
Replicas: &intstr.IntOrString{IntVal: 1},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("20%"),
},
Replicas: &intstr.IntOrString{IntVal: 2},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("60%"),
},
Replicas: &intstr.IntOrString{IntVal: 6},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("100%"),
},
Replicas: &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{},

View File

@ -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)

View File

@ -19,7 +19,6 @@ package rollout
import (
"context"
"fmt"
"strconv"
"time"
"github.com/openkruise/rollouts/api/v1alpha1"
@ -391,12 +390,7 @@ 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)
} else {
replicas := intstr.FromString(strconv.Itoa(int(*step.Weight)) + "%")
desiredReplicas, _ = intstr.GetScaledValueFromIntOrPercent(&replicas, int(c.Workload.Replicas), true)
}
desiredReplicas, _ = intstr.GetScaledValueFromIntOrPercent(step.Replicas, int(c.Workload.Replicas), true)
stepIndex = int32(i + 1)
if currentReplicas <= desiredReplicas {
break

View File

@ -660,19 +660,13 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
obj := rolloutDemo.DeepCopy()
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "20%"},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(50),
},
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "50%"},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "100%"},
},
}
return obj
@ -705,19 +699,13 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
obj := rolloutDemo.DeepCopy()
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "20%"},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "40%"},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "100%"},
},
}
return obj
@ -750,19 +738,13 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
obj := rolloutDemo.DeepCopy()
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "40%"},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "60%"},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "100%"},
},
}
return obj
@ -795,19 +777,13 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
obj := rolloutDemo.DeepCopy()
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(10),
},
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "10%"},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(30),
},
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "30%"},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "100%"},
},
}
return obj
@ -840,8 +816,8 @@ 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{
Traffic: utilpointer.String("2%"),
},
Replicas: &intstr.IntOrString{
Type: intstr.String,
@ -849,8 +825,8 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(3),
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("3%"),
},
Replicas: &intstr.IntOrString{
Type: intstr.String,

View File

@ -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 "75746v7d5z9x59v5c7dff4wd9cv9cc28czf6c2z664w7zbb7vw2bzv76v99z6bd9"
},
},
{
@ -56,7 +55,7 @@ func TestCalculateRolloutHash(t *testing.T) {
return obj
},
expectHash: func() string {
return "626fd556c5d5v2d9b4f7c2xvbc9dxddxzd48xvb9w9wfcdvdz6v959fbzd84b57x"
return "75746v7d5z9x59v5c7dff4wd9cv9cc28czf6c2z664w7zbb7vw2bzv76v99z6bd9"
},
},
{
@ -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{
Traffic: utilpointer.String("50%"),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("100%"),
},
},
}
return obj
},
expectHash: func() string {
return "8c449wxc46x8dd764x4v4wzvc7454f48478vd9db27fv8v9dw5cwbcb6b42b75dc"
return "db9c2x47d282c84z6684d598bzwf9b4x6ffb45fc456xdfv97945v2vb79w72c7z"
},
},
}

View File

@ -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
}

View File

@ -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.Traffic == 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.Traffic = utilpointer.StringPtr("0%")
verify, err = trController.EnsureRoutes(context.TODO(), &c.Strategy)
if err != nil {
return false, err

View File

@ -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{
Traffic: utilpointer.String("5%"),
},
Replicas: &intstr.IntOrString{IntVal: 1},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("20%"),
},
Replicas: &intstr.IntOrString{IntVal: 2},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("60%"),
},
Replicas: &intstr.IntOrString{IntVal: 6},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("100%"),
},
Replicas: &intstr.IntOrString{IntVal: 10},
},
},
TrafficRoutings: []v1alpha1.TrafficRoutingRef{
TrafficRoutings: []v1beta1.TrafficRoutingRef{
{
Service: "echoserver",
Ingress: &v1alpha1.IngressTrafficRouting{
Ingress: &v1beta1.IngressTrafficRouting{
Name: "echoserver",
},
},

View File

@ -23,20 +23,20 @@ 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/apimachinery/pkg/util/intstr"
"k8s.io/klog/v2"
utilpointer "k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@ -50,7 +50,7 @@ type LuaData struct {
Data Data
CanaryWeight int32
StableWeight int32
Matches []rolloutv1alpha1.HttpRouteMatch
Matches []v1beta1.HttpRouteMatch
CanaryService string
StableService string
}
@ -72,7 +72,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 +107,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.Traffic != nil && *strategy.Traffic == "0%" {
return true, nil
}
var err error
@ -254,8 +254,13 @@ 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) {
var weight *int32
if strategy.Traffic != nil {
is := intstr.FromString(*strategy.Traffic)
weightInt, _ := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
weight = utilpointer.Int32(int32(weightInt))
}
matches := strategy.Matches
if weight == nil {
// the lua script does not have a pointer type,
@ -296,7 +301,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]

View File

@ -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"
@ -38,6 +38,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/klog/v2"
utilpointer "k8s.io/utils/pointer"
@ -141,7 +142,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 +175,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 +238,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 +246,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{
Traffic: utilpointer.String("5%"),
}
},
getUnstructureds: func() []*unstructured.Unstructured {
@ -268,7 +269,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 +318,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{
Traffic: utilpointer.String("5%"),
}
},
getUnstructureds: func() []*unstructured.Unstructured {
@ -340,7 +341,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 +447,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 +483,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,8 +523,12 @@ func TestLuaScript(t *testing.T) {
if rollout != nil {
steps := rollout.Spec.Strategy.Canary.Steps
for i, step := range steps {
weight := step.TrafficRoutingStrategy.Weight
if weight == nil {
var weight *int32
if step.TrafficRoutingStrategy.Traffic != nil {
is := intstr.FromString(*step.TrafficRoutingStrategy.Traffic)
weightInt, _ := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
weight = utilpointer.Int32(int32(weightInt))
} else {
weight = utilpointer.Int32(-1)
}
var canaryService string
@ -563,13 +568,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,
@ -615,6 +626,7 @@ func getLuaTestCase(t *testing.T, path string) (*TestCase, error) {
t.Errorf("failed to read file %s", path)
return nil, err
}
fmt.Println(string(yamlFile))
luaTestCase := &TestCase{}
err = yaml.Unmarshal(yamlFile, luaTestCase)
if err != nil {

View File

@ -17,10 +17,11 @@ 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"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
utilpointer "k8s.io/utils/pointer"
@ -34,7 +35,7 @@ type Config struct {
Namespace string
CanaryService string
StableService string
TrafficConf *rolloutv1alpha1.GatewayTrafficRouting
TrafficConf *v1beta1.GatewayTrafficRouting
}
type gatewayController struct {
@ -56,8 +57,13 @@ 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) {
var weight *int32
if strategy.Traffic != nil {
is := intstr.FromString(*strategy.Traffic)
weightInt, _ := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
weight = utilpointer.Int32(int32(weightInt))
}
matches := strategy.Matches
// headerModifier := strategy.RequestHeaderModifier
var httpRoute gatewayv1alpha2.HTTPRoute
@ -118,7 +124,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 +152,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 {

View File

@ -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 {

View File

@ -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"
@ -35,6 +35,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/klog/v2"
utilpointer "k8s.io/utils/pointer"
@ -56,7 +57,7 @@ type Config struct {
Namespace string
CanaryService string
StableService string
TrafficConf *rolloutv1alpha1.IngressTrafficRouting
TrafficConf *v1beta1.IngressTrafficRouting
OwnerRef metav1.OwnerReference
}
@ -82,8 +83,13 @@ 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) {
var weight *int32
if strategy.Traffic != nil {
is := intstr.FromString(*strategy.Traffic)
weightInt, _ := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
weight = utilpointer.Int32(int32(weightInt))
}
matches := strategy.Matches
headerModifier := strategy.RequestHeaderModifier
@ -217,7 +223,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 +234,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
}

View File

@ -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{
Traffic: 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{
Traffic: utilpointer.String("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",
},
}

View File

@ -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

View File

@ -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)
if workload != nil || err != nil {
return workload, err
}
}
default:
workloadRef := rollout.Spec.WorkloadRef
if rollout.Spec.Strategy.Canary.EnableExtraWorkloadForCanary {
for _, finder := range append(r.canaryStyleFinders(), r.partitionStyleFinders()...) {
workload, err := finder(rollout.Namespace, workloadRef)
workload, err := finder(rollout.Namespace, &workloadRef)
if workload != nil || err != nil {
return workload, err
}
}
} 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

View File

@ -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{}
}

View File

@ -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,40 +261,38 @@ func validateRolloutSpecCanarySteps(steps []appsv1beta1.CanaryStep, fldPath *fie
for i := range steps {
s := &steps[i]
if s.Weight == nil && s.Replicas == 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 {
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("steps"), steps, `replicas cannot be empty`)}
}
if s.Replicas != nil {
canaryReplicas, err := intstr.GetScaledValueFromIntOrPercent(s.Replicas, 100, true)
if err != nil ||
canaryReplicas <= 0 ||
(canaryReplicas > 100 && s.Replicas.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%"`)}
}
canaryReplicas, err := intstr.GetScaledValueFromIntOrPercent(s.Replicas, 100, true)
if err != nil ||
canaryReplicas <= 0 ||
(canaryReplicas > 100 && s.Replicas.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%"`)}
}
if !isTraffic {
continue
}
if s.Traffic == nil {
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("steps"), steps, `traffic cannot be empty`)}
}
is := intstr.FromString(*s.Traffic)
weight, err := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
if err != nil || weight <= 0 || weight > 100 {
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("steps"), steps, `traffic must be percentage with "0%" < traffic <= "100%"`)}
}
}
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 {
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) {
continue
}
prevCanaryReplicas, _ := intstr.GetScaledValueFromIntOrPercent(prev.Replicas, 100, true)
currCanaryReplicas, _ := intstr.GetScaledValueFromIntOrPercent(curr.Replicas, 100, true)
if prev.Replicas == nil {
prevCanaryReplicas = int(*prev.Weight)
}
if curr.Replicas == nil {
currCanaryReplicas = int(*curr.Weight)
}
if currCanaryReplicas < prevCanaryReplicas {
return field.ErrorList{field.Invalid(fldPath.Child("CanaryReplicas"), steps, `Steps.CanaryReplicas must be a non decreasing sequence`)}
}
@ -315,7 +305,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
}

View File

@ -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,46 @@ 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{
Traffic: utilpointer.String("10%"),
},
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(1)},
Pause: appsv1beta1.RolloutPause{},
},
{
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(10),
TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("10%"),
},
Replicas: &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{
Traffic: utilpointer.String("30%"),
},
Pause: appsv1beta1.RolloutPause{Duration: utilpointer.Int32(7 * 24 * 60 * 60)},
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(10)},
Pause: appsv1beta1.RolloutPause{Duration: utilpointer.Int32(7 * 24 * 60 * 60)},
},
{
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
},
{
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(101),
},
},
{
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(200),
TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("100%"),
},
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(20)},
},
},
TrafficRoutings: []appsv1alpha1.TrafficRoutingRef{
TrafficRoutings: []appsv1beta1.TrafficRoutingRef{
{
Service: "service-demo",
Ingress: &appsv1alpha1.IngressTrafficRouting{
Ingress: &appsv1beta1.IngressTrafficRouting{
ClassType: "nginx",
Name: "ingress-nginx-demo",
},
@ -129,21 +118,13 @@ func TestRolloutValidateCreate(t *testing.T) {
Name: "Normal case",
Succeed: true,
GetObject: func() []client.Object {
return []client.Object{rollout.DeepCopy()}
obj := rollout.DeepCopy()
return []client.Object{obj}
},
},
/***********************************************************
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,29 +169,21 @@ 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",
}
return []client.Object{object}
},
},
{
Name: "Steps.Weight is a decreasing sequence",
Name: "Steps.Traffic is a decreasing sequence",
Succeed: false,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object.Spec.Strategy.Canary.Steps[2].Weight = utilpointer.Int32Ptr(5)
return []client.Object{object}
},
},
{
Name: "Steps.Replicas 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[2].Traffic = utilpointer.String("%5")
return []client.Object{object}
},
},
@ -242,20 +215,20 @@ func TestRolloutValidateCreate(t *testing.T) {
},
},
{
Name: "Steps.Weight is illegal value, 0",
Name: "Steps.Traffic 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].Traffic = utilpointer.String("0%")
return []client.Object{object}
},
},
{
Name: "Steps.Weight is illegal value, 101",
Name: "Steps.Traffic 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].Traffic = utilpointer.String("101%")
return []client.Object{object}
},
},
@ -264,9 +237,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 +246,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 +254,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.Traffic 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].Traffic = 80
// return []client.Object{object}
// },
//},
@ -326,15 +281,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 +304,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 +355,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].Traffic = utilpointer.String("5%")
return object
},
},
@ -430,7 +385,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].Traffic = utilpointer.String("5%")
return object
},
},
@ -439,14 +394,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 +430,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].Traffic = utilpointer.String("5%")
return object
},
},
@ -491,7 +445,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].Traffic = utilpointer.String("5%")
return object
},
},

View File

@ -107,15 +107,6 @@ func validateV1alpha1RolloutRollingStyle(rollout *appsv1alpha1.Rollout, fldPath
return field.ErrorList{field.Invalid(fldPath, rollout.Annotations[appsv1alpha1.RolloutStyleAnnotation],
"Rolling style must be 'Canary', 'Partition' or empty")}
}
workloadRef := rollout.Spec.ObjectRef.WorkloadRef
if workloadRef == nil || workloadRef.Kind == util.ControllerKindDep.Kind {
return nil // Deployment support all rolling styles, no need to validate.
}
if strings.EqualFold(rollout.Annotations[appsv1alpha1.RolloutStyleAnnotation], string(appsv1alpha1.CanaryRollingStyle)) {
return field.ErrorList{field.Invalid(fldPath, rollout.Annotations[appsv1alpha1.RolloutStyleAnnotation],
"Only Deployment support canary rolling style")}
}
return nil
}
@ -145,11 +136,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 {

View File

@ -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
}

View File

@ -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)
})

View File

@ -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
}

View File

@ -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())

View File

@ -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",