update api for future bluegreen (#214)

add status conversion



nextStepIndex default value from 0 to -1



restore the enableExtra field in BR

Signed-off-by: yunbo <yunbo10124scut@gmail.com>
Co-authored-by: yunbo <yunbo10124scut@gmail.com>
This commit is contained in:
myname4423 2024-06-12 13:27:41 +08:00 committed by GitHub
parent 62794dc883
commit 1e8af4a4c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 1137 additions and 123 deletions

View File

@ -54,6 +54,16 @@ type ReleasePlan struct {
// only support for canary deployment
// +optional
PatchPodTemplateMetadata *PatchPodTemplateMetadata `json:"patchPodTemplateMetadata,omitempty"`
// RollingStyle can be "Canary", "Partiton" or "BlueGreen"
RollingStyle RollingStyleType `json:"rollingStyle,omitempty"`
// EnableExtraWorkloadForCanary indicates whether to create extra workload for canary
// True corresponds to RollingStyle "Canary".
// False corresponds to RollingStyle "Partiton".
// Ignored in BlueGreen-style.
// This field is about to deprecate, use RollingStyle instead.
// If both of them are set, controller will only consider this
// filed when RollingStyle is empty
EnableExtraWorkloadForCanary bool `json:"enableExtraWorkloadForCanary"`
}
type FinalizingPolicyType string

View File

@ -104,18 +104,22 @@ func (src *Rollout) ConvertTo(dst conversion.Hub) error {
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,
CommonStatus: v1beta1.CommonStatus{
ObservedWorkloadGeneration: src.Status.CanaryStatus.ObservedWorkloadGeneration,
ObservedRolloutID: src.Status.CanaryStatus.ObservedRolloutID,
RolloutHash: src.Status.CanaryStatus.RolloutHash,
StableRevision: src.Status.CanaryStatus.StableRevision,
PodTemplateHash: src.Status.CanaryStatus.PodTemplateHash,
CurrentStepIndex: src.Status.CanaryStatus.CurrentStepIndex,
CurrentStepState: v1beta1.CanaryStepState(src.Status.CanaryStatus.CurrentStepState),
Message: src.Status.CanaryStatus.Message,
LastUpdateTime: src.Status.CanaryStatus.LastUpdateTime,
FinalisingStep: v1beta1.FinalisingStepType(src.Status.CanaryStatus.FinalisingStep),
NextStepIndex: src.Status.CanaryStatus.NextStepIndex,
},
CanaryRevision: src.Status.CanaryStatus.CanaryRevision,
CanaryReplicas: src.Status.CanaryStatus.CanaryReplicas,
CanaryReadyReplicas: src.Status.CanaryStatus.CanaryReadyReplicas,
}
return nil
default:
@ -167,7 +171,9 @@ func (dst *Rollout) ConvertFrom(src conversion.Hub) error {
case *v1beta1.Rollout:
srcV1beta1 := src.(*v1beta1.Rollout)
dst.ObjectMeta = srcV1beta1.ObjectMeta
if !srcV1beta1.Spec.Strategy.IsCanaryStragegy() {
return fmt.Errorf("v1beta1 Rollout with %s strategy cannot be converted to v1alpha1", srcV1beta1.Spec.Strategy.GetRollingStyle())
}
// spec
dst.Spec = RolloutSpec{
ObjectRef: ObjectRef{
@ -254,6 +260,8 @@ func (dst *Rollout) ConvertFrom(src conversion.Hub) error {
CurrentStepState: CanaryStepState(srcV1beta1.Status.CanaryStatus.CurrentStepState),
Message: srcV1beta1.Status.CanaryStatus.Message,
LastUpdateTime: srcV1beta1.Status.CanaryStatus.LastUpdateTime,
FinalisingStep: FinalizeStateType(srcV1beta1.Status.CanaryStatus.FinalisingStep),
NextStepIndex: srcV1beta1.Status.CanaryStatus.NextStepIndex,
}
return nil
default:
@ -338,9 +346,18 @@ func (src *BatchRelease) ConvertTo(dst conversion.Hub) error {
obj.Spec.ReleasePlan.PatchPodTemplateMetadata.Labels[k] = v
}
}
if !strings.EqualFold(src.Annotations[RolloutStyleAnnotation], string(PartitionRollingStyle)) {
obj.Spec.ReleasePlan.EnableExtraWorkloadForCanary = true
if strings.EqualFold(src.Annotations[RolloutStyleAnnotation], string(PartitionRollingStyle)) {
obj.Spec.ReleasePlan.RollingStyle = v1beta1.PartitionRollingStyle
}
if strings.EqualFold(src.Annotations[RolloutStyleAnnotation], string(CanaryRollingStyle)) {
obj.Spec.ReleasePlan.RollingStyle = v1beta1.CanaryRollingStyle
}
if strings.EqualFold(src.Annotations[RolloutStyleAnnotation], string(BlueGreenRollingStyle)) {
obj.Spec.ReleasePlan.RollingStyle = v1beta1.BlueGreenRollingStyle
}
obj.Spec.ReleasePlan.EnableExtraWorkloadForCanary = srcSpec.ReleasePlan.EnableExtraWorkloadForCanary
// status
obj.Status = v1beta1.BatchReleaseStatus{
@ -417,11 +434,9 @@ func (dst *BatchRelease) ConvertFrom(src conversion.Hub) error {
if dst.Annotations == nil {
dst.Annotations = map[string]string{}
}
if srcV1beta1.Spec.ReleasePlan.EnableExtraWorkloadForCanary {
dst.Annotations[RolloutStyleAnnotation] = strings.ToLower(string(CanaryRollingStyle))
} else {
dst.Annotations[RolloutStyleAnnotation] = strings.ToLower(string(PartitionRollingStyle))
}
dst.Annotations[RolloutStyleAnnotation] = strings.ToLower(string(srcV1beta1.Spec.ReleasePlan.RollingStyle))
dst.Spec.ReleasePlan.RollingStyle = RollingStyleType(srcV1beta1.Spec.ReleasePlan.RollingStyle)
dst.Spec.ReleasePlan.EnableExtraWorkloadForCanary = srcV1beta1.Spec.ReleasePlan.EnableExtraWorkloadForCanary
// status
dst.Status = BatchReleaseStatus{

View File

@ -59,6 +59,8 @@ const (
PartitionRollingStyle RollingStyleType = "Partition"
// CanaryRollingStyle means rolling in canary way, and will create a canary Deployment.
CanaryRollingStyle RollingStyleType = "Canary"
// BlueGreenRollingStyle means rolling in blue-green way, and will NOT create a canary Deployment.
BlueGreenRollingStyle RollingStyleType = "BlueGreen"
)
// DeploymentExtraStatus is extra status field for Advanced Deployment
@ -74,7 +76,7 @@ type DeploymentExtraStatus struct {
}
func SetDefaultDeploymentStrategy(strategy *DeploymentStrategy) {
if strategy.RollingStyle == CanaryRollingStyle {
if strategy.RollingStyle != PartitionRollingStyle {
return
}
if strategy.RollingUpdate == nil {

View File

@ -242,16 +242,28 @@ type CanaryStatus struct {
CanaryReplicas int32 `json:"canaryReplicas"`
// CanaryReadyReplicas the numbers of ready canary revision pods
CanaryReadyReplicas int32 `json:"canaryReadyReplicas"`
// CurrentStepIndex defines the current step of the rollout is on. If the current step index is null, the
// controller will execute the rollout.
// NextStepIndex defines the next step of the rollout is on.
// In normal case, NextStepIndex is equal to CurrentStepIndex + 1
// If the current step is the last step, NextStepIndex is equal to -1
// Before the release, NextStepIndex is also equal to -1
// 0 is not used and won't appear in any case
// It is allowed to patch NextStepIndex by design,
// e.g. if CurrentStepIndex is 2, user can patch NextStepIndex to 3 (if exists) to
// achieve batch jump, or patch NextStepIndex to 1 to implement a re-execution of step 1
// Patching it with a non-positive value is meaningless, which will be corrected
// in the next reconciliation
// achieve batch jump, or patch NextStepIndex to 1 to implement a re-execution of step 1
NextStepIndex int32 `json:"nextStepIndex"`
// +optional
CurrentStepIndex int32 `json:"currentStepIndex"`
CurrentStepState CanaryStepState `json:"currentStepState"`
Message string `json:"message,omitempty"`
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
CurrentStepIndex int32 `json:"currentStepIndex"`
CurrentStepState CanaryStepState `json:"currentStepState"`
Message string `json:"message,omitempty"`
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
FinalisingStep FinalizeStateType `json:"finalisingStep"`
}
type CanaryStepState string
type FinalizeStateType string
const (
CanaryStepStateUpgrade CanaryStepState = "StepUpgrade"

View File

@ -54,9 +54,15 @@ 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
// RollingStyle can be "Canary", "Partiton" or "BlueGreen"
RollingStyle RollingStyleType `json:"rollingStyle,omitempty"`
// EnableExtraWorkloadForCanary indicates whether to create extra workload for canary
// True corresponds to RollingStyle "Canary".
// False corresponds to RollingStyle "Partiton".
// Ignored in BlueGreen-style.
// This field is about to deprecate, use RollingStyle instead.
// If both of them are set, controller will only consider this
// filed when RollingStyle is empty
EnableExtraWorkloadForCanary bool `json:"enableExtraWorkloadForCanary"`
}

View File

@ -37,6 +37,16 @@ const (
// AdvancedDeploymentControlLabel is label for deployment,
// which labels whether the deployment is controlled by advanced-deployment-controller.
AdvancedDeploymentControlLabel = "rollouts.kruise.io/controlled-by-advanced-deployment-controller"
// OriginalDeploymentStrategyAnnotation is annotation for workload in BlueGreen Release,
// it will store the original setting of the workload, which will be used to restore the workload
OriginalDeploymentStrategyAnnotation = "rollouts.kruise.io/original-deployment-strategy"
// MaxProgressSeconds is the value we set for ProgressDeadlineSeconds
// MaxReadySeconds is the value we set for MinReadySeconds, which is one less than ProgressDeadlineSeconds
// MaxInt32: 2147483647, ≈ 68 years
MaxProgressSeconds = 1<<31 - 1
MaxReadySeconds = MaxProgressSeconds - 1
)
// DeploymentStrategy is strategy field for Advanced Deployment
@ -52,6 +62,31 @@ type DeploymentStrategy struct {
Partition intstr.IntOrString `json:"partition,omitempty"`
}
// OriginalDeploymentStrategy stores part of the fileds of a workload,
// so that it can be restored when finalizing.
// It is only used for BlueGreen Release
// Similar to DeploymentStrategy, it is an annotation used in workload
// However, unlike DeploymentStrategy, it is only used to store and restore the user's strategy
type OriginalDeploymentStrategy struct {
// The deployment strategy to use to replace existing pods with new ones.
// +optional
// +patchStrategy=retainKeys
Strategy *apps.DeploymentStrategy `json:"strategy,omitempty" patchStrategy:"retainKeys" protobuf:"bytes,4,opt,name=strategy"`
// Minimum number of seconds for which a newly created pod should be ready
// without any of its container crashing, for it to be considered available.
// Defaults to 0 (pod will be considered available as soon as it is ready)
// +optional
MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,5,opt,name=minReadySeconds"`
// The maximum time in seconds for a deployment to make progress before it
// is considered to be failed. The deployment controller will continue to
// process failed deployments and a condition with a ProgressDeadlineExceeded
// reason will be surfaced in the deployment status. Note that progress will
// not be estimated during the time a deployment is paused. Defaults to 600s.
ProgressDeadlineSeconds *int32 `json:"progressDeadlineSeconds,omitempty" protobuf:"varint,9,opt,name=progressDeadlineSeconds"`
}
type RollingStyleType string
const (
@ -59,6 +94,8 @@ const (
PartitionRollingStyle RollingStyleType = "Partition"
// CanaryRollingStyle means rolling in canary way, and will create a canary Deployment.
CanaryRollingStyle RollingStyleType = "Canary"
// BlueGreenRollingStyle means rolling in blue-green way, and will NOT create a extra Deployment.
BlueGreenRollingStyle RollingStyleType = "BlueGreen"
)
// DeploymentExtraStatus is extra status field for Advanced Deployment
@ -74,7 +111,7 @@ type DeploymentExtraStatus struct {
}
func SetDefaultDeploymentStrategy(strategy *DeploymentStrategy) {
if strategy.RollingStyle == CanaryRollingStyle {
if strategy.RollingStyle != PartitionRollingStyle {
return
}
if strategy.RollingUpdate == nil {
@ -101,3 +138,44 @@ func SetDefaultDeploymentStrategy(strategy *DeploymentStrategy) {
}
}
}
func SetDefaultSetting(setting *OriginalDeploymentStrategy) {
if setting.ProgressDeadlineSeconds == nil {
setting.ProgressDeadlineSeconds = new(int32)
*setting.ProgressDeadlineSeconds = 600
}
if setting.Strategy == nil {
setting.Strategy = &apps.DeploymentStrategy{}
}
if setting.Strategy.Type == "" {
setting.Strategy.Type = apps.RollingUpdateDeploymentStrategyType
}
if setting.Strategy.Type == apps.RecreateDeploymentStrategyType {
return
}
strategy := setting.Strategy
if strategy.RollingUpdate == nil {
strategy.RollingUpdate = &apps.RollingUpdateDeployment{}
}
if strategy.RollingUpdate.MaxUnavailable == nil {
// Set MaxUnavailable as 25% by default
maxUnavailable := intstr.FromString("25%")
strategy.RollingUpdate.MaxUnavailable = &maxUnavailable
}
if strategy.RollingUpdate.MaxSurge == nil {
// Set MaxSurge as 25% by default
maxSurge := intstr.FromString("25%")
strategy.RollingUpdate.MaxUnavailable = &maxSurge
}
// Cannot allow maxSurge==0 && MaxUnavailable==0, otherwise, no pod can be updated when rolling update.
maxSurge, _ := intstr.GetScaledValueFromIntOrPercent(strategy.RollingUpdate.MaxSurge, 100, true)
maxUnavailable, _ := intstr.GetScaledValueFromIntOrPercent(strategy.RollingUpdate.MaxUnavailable, 100, true)
if maxSurge == 0 && maxUnavailable == 0 {
strategy.RollingUpdate = &apps.RollingUpdateDeployment{
MaxSurge: &intstr.IntOrString{Type: intstr.Int, IntVal: 0},
MaxUnavailable: &intstr.IntOrString{Type: intstr.Int, IntVal: 1},
}
}
}

View File

@ -75,6 +75,92 @@ type RolloutStrategy struct {
Paused bool `json:"paused,omitempty"`
// +optional
Canary *CanaryStrategy `json:"canary,omitempty"`
// +optional
BlueGreen *BlueGreenStrategy `json:"blueGreen,omitempty" protobuf:"bytes,1,opt,name=blueGreen"`
}
// Get the rolling style based on the strategy
func (r *RolloutStrategy) GetRollingStyle() RollingStyleType {
if r.BlueGreen != nil {
return BlueGreenRollingStyle
}
//NOTE - even EnableExtraWorkloadForCanary is true, as long as it is not Deployment,
//we won't do canary release. BatchRelease will treat it as Partiton release
if r.Canary.EnableExtraWorkloadForCanary {
return CanaryRollingStyle
}
return PartitionRollingStyle
}
// r.GetRollingStyle() == BlueGreenRollingStyle
func (r *RolloutStrategy) IsBlueGreenRelease() bool {
return r.GetRollingStyle() == BlueGreenRollingStyle
}
// r.GetRollingStyle() == CanaryRollingStyle || r.GetRollingStyle() == PartitionRollingStyle
func (r *RolloutStrategy) IsCanaryStragegy() bool {
return r.GetRollingStyle() == CanaryRollingStyle || r.GetRollingStyle() == PartitionRollingStyle
}
// Get the steps based on the rolling style
func (r *RolloutStrategy) GetSteps() []CanaryStep {
switch r.GetRollingStyle() {
case BlueGreenRollingStyle:
return r.BlueGreen.Steps
case CanaryRollingStyle, PartitionRollingStyle:
return r.Canary.Steps
default:
return nil
}
}
// Get the traffic routing based on the rolling style
func (r *RolloutStrategy) GetTrafficRouting() []TrafficRoutingRef {
switch r.GetRollingStyle() {
case BlueGreenRollingStyle:
return r.BlueGreen.TrafficRoutings
case CanaryRollingStyle, PartitionRollingStyle:
return r.Canary.TrafficRoutings
default:
return nil
}
}
// Check if there are traffic routings
func (r *RolloutStrategy) HasTrafficRoutings() bool {
return len(r.GetTrafficRouting()) > 0
}
// Check the value of DisableGenerateCanaryService
func (r *RolloutStrategy) DisableGenerateCanaryService() bool {
switch r.GetRollingStyle() {
case BlueGreenRollingStyle:
return r.BlueGreen.DisableGenerateCanaryService
case CanaryRollingStyle, PartitionRollingStyle:
return r.Canary.DisableGenerateCanaryService
default:
return false
}
}
// BlueGreenStrategy defines parameters for Blue Green Release
type BlueGreenStrategy struct {
// Steps define the order of phases to execute release in batches(20%, 40%, 60%, 80%, 100%)
// +optional
Steps []CanaryStep `json:"steps,omitempty"`
// TrafficRoutings support ingress, gateway api and custom network resource(e.g. istio, apisix) to enable more fine-grained traffic routing
// and current only support one TrafficRouting
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
// FailureThreshold.
// Defaults to nil.
FailureThreshold *intstr.IntOrString `json:"failureThreshold,omitempty"`
// TrafficRoutingRef is TrafficRouting's Name
TrafficRoutingRef string `json:"trafficRoutingRef,omitempty"`
// canary service will not be generated if DisableGenerateCanaryService is true
DisableGenerateCanaryService bool `json:"disableGenerateCanaryService,omitempty"`
}
// CanaryStrategy defines parameters for a Replica Based Canary
@ -206,6 +292,9 @@ type RolloutStatus struct {
// Canary describes the state of the canary rollout
// +optional
CanaryStatus *CanaryStatus `json:"canaryStatus,omitempty"`
// BlueGreen describes the state of the blueGreen rollout
// +optional
BlueGreenStatus *BlueGreenStatus `json:"blueGreenStatus,omitempty"`
// Conditions a list of conditions a rollout can have.
// +optional
Conditions []RolloutCondition `json:"conditions,omitempty"`
@ -259,10 +348,26 @@ const (
// Terminating Reason
TerminatingReasonInTerminating = "InTerminating"
TerminatingReasonCompleted = "Completed"
// Finalise Reason
// Finalise when the last batch is released and all pods will update to new version
FinaliseReasonSuccess = "Success"
// Finalise when rollback detected
FinaliseReasonRollback = "Rollback"
// Finalise when Continuous Release detected
FinaliseReasonContinuous = "Continuous"
// Finalise when Rollout is disabling
FinaliseReasonDisalbed = "RolloutDisabled"
// Finalise when Rollout is deleting
FinaliseReasonDelete = "RolloutDeleting"
)
// CanaryStatus status fields that only pertain to the canary rollout
type CanaryStatus struct {
// fields in CommonStatus are shared between canary status and bluegreen status
// if a field is accessed in strategy-agnostic way, e.g. accessed from rollout_progressing.go, or rollout_status.go
// then it can be put into CommonStatus
// if a field is only accessed in strategy-specific way, e.g. accessed from rollout_canary.go or rollout_bluegreen.go
// then it should stay behind with CanaryStatus or BlueGreenStatus
type CommonStatus struct {
// observedWorkloadGeneration is the most recent generation observed for this Rollout ref workload generation.
ObservedWorkloadGeneration int64 `json:"observedWorkloadGeneration,omitempty"`
// ObservedRolloutID will record the newest spec.RolloutID if status.canaryRevision equals to workload.updateRevision
@ -271,27 +376,126 @@ type CanaryStatus struct {
RolloutHash string `json:"rolloutHash,omitempty"`
// StableRevision indicates the revision of stable pods
StableRevision string `json:"stableRevision,omitempty"`
// pod template hash is used as service selector label
PodTemplateHash string `json:"podTemplateHash"`
// CurrentStepIndex defines the current step of the rollout is on.
// +optional
CurrentStepIndex int32 `json:"currentStepIndex"`
// NextStepIndex defines the next step of the rollout is on.
// In normal case, NextStepIndex is equal to CurrentStepIndex + 1
// If the current step is the last step, NextStepIndex is equal to -1
// Before the release, NextStepIndex is also equal to -1
// 0 is not used and won't appear in any case
// It is allowed to patch NextStepIndex by design,
// e.g. if CurrentStepIndex is 2, user can patch NextStepIndex to 3 (if exists) to
// achieve batch jump, or patch NextStepIndex to 1 to implement a re-execution of step 1
// Patching it with a non-positive value is useless and meaningless, which will be corrected
// in the next reconciliation
NextStepIndex int32 `json:"nextStepIndex"`
// FinalisingStep the step of finalising
FinalisingStep FinalisingStepType `json:"finalisingStep"`
CurrentStepState CanaryStepState `json:"currentStepState"`
Message string `json:"message,omitempty"`
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
}
// CanaryStatus status fields that only pertain to the canary rollout
type CanaryStatus struct {
// must be inline
CommonStatus `json:",inline"`
// CanaryRevision is calculated by rollout based on podTemplateHash, and the internal logic flow uses
// It may be different from rs podTemplateHash in different k8s versions, so it cannot be used as service selector label
CanaryRevision string `json:"canaryRevision"`
// pod template hash is used as service selector label
PodTemplateHash string `json:"podTemplateHash"`
// CanaryReplicas the numbers of canary revision pods
CanaryReplicas int32 `json:"canaryReplicas"`
// CanaryReadyReplicas the numbers of ready canary revision pods
CanaryReadyReplicas int32 `json:"canaryReadyReplicas"`
// CurrentStepIndex defines the current step of the rollout is on. If the current step index is null, the
// controller will execute the rollout.
// +optional
CurrentStepIndex int32 `json:"currentStepIndex"`
CurrentStepState CanaryStepState `json:"currentStepState"`
Message string `json:"message,omitempty"`
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
}
// BlueGreenStatus status fields that only pertain to the blueGreen rollout
type BlueGreenStatus struct {
CommonStatus `json:",inline"`
// CanaryRevision is calculated by rollout based on podTemplateHash, and the internal logic flow uses
// It may be different from rs podTemplateHash in different k8s versions, so it cannot be used as service selector label
UpdatedRevision string `json:"updatedRevision"`
// UpdatedReplicas the numbers of updated pods
UpdatedReplicas int32 `json:"updatedReplicas"`
// UpdatedReadyReplicas the numbers of updated ready pods
UpdatedReadyReplicas int32 `json:"updatedReadyReplicas"`
}
// GetSubStatus returns the ethier canary or bluegreen status
func (r *RolloutStatus) GetSubStatus() *CommonStatus {
if r.CanaryStatus != nil {
return &(r.CanaryStatus.CommonStatus)
}
return &(r.BlueGreenStatus.CommonStatus)
}
func (r *RolloutStatus) IsSubStatusEmpty() bool {
return r.CanaryStatus == nil && r.BlueGreenStatus == nil
}
func (r *RolloutStatus) Clear() {
r.CanaryStatus = nil
r.BlueGreenStatus = nil
}
//TODO - the following functions seem awkward, is there better way for our case?
func (r *RolloutStatus) GetCanaryRevision() string {
if r.CanaryStatus != nil {
return r.CanaryStatus.CanaryRevision
}
return r.BlueGreenStatus.UpdatedRevision
}
func (r *RolloutStatus) SetCanaryRevision(revision string) {
if r.CanaryStatus != nil {
r.CanaryStatus.CanaryRevision = revision
}
if r.BlueGreenStatus != nil {
r.BlueGreenStatus.UpdatedRevision = revision
}
}
func (r *RolloutStatus) GetCanaryReplicas() int32 {
if r.CanaryStatus != nil {
return r.CanaryStatus.CanaryReplicas
}
return r.BlueGreenStatus.UpdatedReplicas
}
func (r *RolloutStatus) SetCanaryReplicas(replicas int32) {
if r.CanaryStatus != nil {
r.CanaryStatus.CanaryReplicas = replicas
}
if r.BlueGreenStatus != nil {
r.BlueGreenStatus.UpdatedReplicas = replicas
}
}
func (r *RolloutStatus) GetCanaryReadyReplicas() int32 {
if r.CanaryStatus != nil {
return r.CanaryStatus.CanaryReadyReplicas
}
return r.BlueGreenStatus.UpdatedReadyReplicas
}
func (r *RolloutStatus) SetCanaryReadyReplicas(replicas int32) {
if r.CanaryStatus != nil {
r.CanaryStatus.CanaryReadyReplicas = replicas
}
if r.BlueGreenStatus != nil {
r.BlueGreenStatus.UpdatedReadyReplicas = replicas
}
}
type CanaryStepState string
const (
// the first step, handle some special cases before step upgrade, to prevent traffic loss
CanaryStepStateInit CanaryStepState = "BeforeStepUpgrade"
CanaryStepStateUpgrade CanaryStepState = "StepUpgrade"
CanaryStepStateTrafficRouting CanaryStepState = "StepTrafficRouting"
CanaryStepStateMetricsAnalysis CanaryStepState = "StepMetricsAnalysis"
@ -318,6 +522,34 @@ const (
RolloutPhaseDisabling RolloutPhase = "Disabling"
)
type FinalisingStepType string
const (
// some work that should be done before pod scaling down.
// For BlueGreenStrategy:
// we rout all traffic to stable or new version based on FinaliseReason
// For CanaryStrategy:
// we remove the selector of stable service
FinalisingStepTypePreparing FinalisingStepType = "Preparing"
// Patch Batch Release to scale down (exception: the canary Deployment will be
// scaled down in FinalisingStepTypeDeleteBR step)
// For Both BlueGreenStrategy and CanaryStrategy:
// set workload.pause=false, set workload.partition=0
FinalisingStepTypeBatchRelease FinalisingStepType = "PatchBatchRelease"
//TODO - Currently, the next three steps are in the same function, FinalisingTrafficRouting
// we should try to separate the FinalisingStepTypeGateway and FinalisingStepTypeCanaryService
// with graceful time to prevent some potential issues
// Restore the stable Service (i.e. remove corresponding selector)
FinalisingStepTypeStableService FinalisingStepType = "RestoreStableService"
// Restore the GatewayAPI/Ingress/Istio
FinalisingStepTypeGateway FinalisingStepType = "RestoreGateway"
// Delete Canary Service
FinalisingStepTypeCanaryService FinalisingStepType = "DeleteCanayService"
// Delete Batch Release
FinalisingStepTypeDeleteBR FinalisingStepType = "DeleteBatchRelease"
)
// +genclient
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status

View File

@ -156,13 +156,60 @@ func (in *BatchReleaseStatus) DeepCopy() *BatchReleaseStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BlueGreenStatus) DeepCopyInto(out *BlueGreenStatus) {
*out = *in
in.CommonStatus.DeepCopyInto(&out.CommonStatus)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BlueGreenStatus.
func (in *BlueGreenStatus) DeepCopy() *BlueGreenStatus {
if in == nil {
return nil
}
out := new(BlueGreenStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BlueGreenStrategy) DeepCopyInto(out *BlueGreenStrategy) {
*out = *in
if in.Steps != nil {
in, out := &in.Steps, &out.Steps
*out = make([]CanaryStep, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.TrafficRoutings != nil {
in, out := &in.TrafficRoutings, &out.TrafficRoutings
*out = make([]TrafficRoutingRef, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.FailureThreshold != nil {
in, out := &in.FailureThreshold, &out.FailureThreshold
*out = new(intstr.IntOrString)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BlueGreenStrategy.
func (in *BlueGreenStrategy) DeepCopy() *BlueGreenStrategy {
if in == nil {
return nil
}
out := new(BlueGreenStrategy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CanaryStatus) DeepCopyInto(out *CanaryStatus) {
*out = *in
if in.LastUpdateTime != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime
*out = (*in).DeepCopy()
}
in.CommonStatus.DeepCopyInto(&out.CommonStatus)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CanaryStatus.
@ -236,6 +283,25 @@ func (in *CanaryStrategy) DeepCopy() *CanaryStrategy {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommonStatus) DeepCopyInto(out *CommonStatus) {
*out = *in
if in.LastUpdateTime != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime
*out = (*in).DeepCopy()
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonStatus.
func (in *CommonStatus) DeepCopy() *CommonStatus {
if in == nil {
return nil
}
out := new(CommonStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DeploymentExtraStatus) DeepCopyInto(out *DeploymentExtraStatus) {
*out = *in
@ -356,6 +422,31 @@ func (in *ObjectRef) DeepCopy() *ObjectRef {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OriginalDeploymentStrategy) DeepCopyInto(out *OriginalDeploymentStrategy) {
*out = *in
if in.Strategy != nil {
in, out := &in.Strategy, &out.Strategy
*out = new(v1.DeploymentStrategy)
(*in).DeepCopyInto(*out)
}
if in.ProgressDeadlineSeconds != nil {
in, out := &in.ProgressDeadlineSeconds, &out.ProgressDeadlineSeconds
*out = new(int32)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OriginalDeploymentStrategy.
func (in *OriginalDeploymentStrategy) DeepCopy() *OriginalDeploymentStrategy {
if in == nil {
return nil
}
out := new(OriginalDeploymentStrategy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PatchPodTemplateMetadata) DeepCopyInto(out *PatchPodTemplateMetadata) {
*out = *in
@ -557,6 +648,11 @@ func (in *RolloutStatus) DeepCopyInto(out *RolloutStatus) {
*out = new(CanaryStatus)
(*in).DeepCopyInto(*out)
}
if in.BlueGreenStatus != nil {
in, out := &in.BlueGreenStatus, &out.BlueGreenStatus
*out = new(BlueGreenStatus)
(*in).DeepCopyInto(*out)
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]RolloutCondition, len(*in))
@ -584,6 +680,11 @@ func (in *RolloutStrategy) DeepCopyInto(out *RolloutStrategy) {
*out = new(CanaryStrategy)
(*in).DeepCopyInto(*out)
}
if in.BlueGreen != nil {
in, out := &in.BlueGreen, &out.BlueGreen
*out = new(BlueGreenStrategy)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RolloutStrategy.

View File

@ -88,6 +88,14 @@ spec:
- canaryReplicas
type: object
type: array
enableExtraWorkloadForCanary:
description: EnableExtraWorkloadForCanary indicates whether to
create extra workload for canary True corresponds to RollingStyle
"Canary". False corresponds to RollingStyle "Partiton". Ignored
in BlueGreen-style. This field is about to deprecate, use RollingStyle
instead. If both of them are set, controller will only consider
this filed when RollingStyle is empty
type: boolean
failureThreshold:
anyOf:
- type: integer
@ -118,9 +126,14 @@ spec:
description: labels
type: object
type: object
rollingStyle:
description: RollingStyle can be "Canary", "Partiton" or "BlueGreen"
type: string
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
@ -346,11 +359,12 @@ spec:
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'
description: EnableExtraWorkloadForCanary indicates whether to
create extra workload for canary True corresponds to RollingStyle
"Canary". False corresponds to RollingStyle "Partiton". Ignored
in BlueGreen-style. This field is about to deprecate, use RollingStyle
instead. If both of them are set, controller will only consider
this filed when RollingStyle is empty
type: boolean
failureThreshold:
anyOf:
@ -382,6 +396,9 @@ spec:
description: labels
type: object
type: object
rollingStyle:
description: RollingStyle can be "Canary", "Partiton" or "BlueGreen"
type: string
rolloutID:
description: RolloutID indicates an id for each rollout progress
type: string

View File

@ -435,18 +435,32 @@ spec:
so it cannot be used as service selector label
type: string
currentStepIndex:
description: CurrentStepIndex defines the current step of the
rollout is on. If the current step index is null, the controller
will execute the rollout.
format: int32
type: integer
currentStepState:
type: string
finalisingStep:
type: string
lastUpdateTime:
format: date-time
type: string
message:
type: string
nextStepIndex:
description: NextStepIndex defines the next step of the rollout
is on. In normal case, NextStepIndex is equal to CurrentStepIndex
+ 1 If the current step is the last step, NextStepIndex is equal
to -1 Before the release, NextStepIndex is also equal to -1
0 is not used and won't appear in any case It is allowed to
patch NextStepIndex by design, e.g. if CurrentStepIndex is 2,
user can patch NextStepIndex to 3 (if exists) to achieve batch
jump, or patch NextStepIndex to 1 to implement a re-execution
of step 1 Patching it with a non-positive value is meaningless,
which will be corrected in the next reconciliation achieve batch
jump, or patch NextStepIndex to 1 to implement a re-execution
of step 1
format: int32
type: integer
observedRolloutID:
description: ObservedRolloutID will record the newest spec.RolloutID
if status.canaryRevision equals to workload.updateRevision
@ -470,6 +484,8 @@ spec:
- canaryReplicas
- canaryRevision
- currentStepState
- finalisingStep
- nextStepIndex
- podTemplateHash
type: object
conditions:
@ -574,6 +590,308 @@ spec:
strategy:
description: rollout strategy
properties:
blueGreen:
description: BlueGreenStrategy defines parameters for Blue Green
Release
properties:
disableGenerateCanaryService:
description: canary service will not be generated if DisableGenerateCanaryService
is true
type: boolean
failureThreshold:
anyOf:
- type: integer
- type: string
description: 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 FailureThreshold. Defaults to nil.
x-kubernetes-int-or-string: true
steps:
description: Steps define the order of phases to execute release
in batches(20%, 40%, 60%, 80%, 100%)
items:
description: CanaryStep defines a step of a canary workload.
properties:
matches:
description: 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.
items:
properties:
headers:
description: Headers specifies HTTP request header
matchers. Multiple match values are ANDed together,
meaning, a request must match all the specified
headers to select the route.
items:
description: HTTPHeaderMatch describes how to
select a HTTP route by matching HTTP request
headers.
properties:
name:
description: "Name is the name of the HTTP
Header to be matched. Name matching MUST
be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).
\n If multiple entries specify equivalent
header names, only the first entry with
an equivalent name MUST be considered
for a match. Subsequent entries with an
equivalent header name MUST be ignored.
Due to the case-insensitivity of header
names, \"foo\" and \"Foo\" are considered
equivalent. \n When a header is repeated
in an HTTP request, it is implementation-specific
behavior as to how this is represented.
Generally, proxies should follow the guidance
from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2
regarding processing a repeated header,
with special handling for \"Set-Cookie\"."
maxLength: 256
minLength: 1
pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$
type: string
type:
default: Exact
description: "Type specifies how to match
against the value of the header. \n Support:
Core (Exact) \n Support: Custom (RegularExpression)
\n Since RegularExpression HeaderMatchType
has custom conformance, implementations
can support POSIX, PCRE or any other dialects
of regular expressions. Please read the
implementation's documentation to determine
the supported dialect."
enum:
- Exact
- RegularExpression
type: string
value:
description: Value is the value of HTTP
Header to be matched.
maxLength: 4096
minLength: 1
type: string
required:
- name
- value
type: object
maxItems: 16
type: array
type: object
type: array
pause:
description: Pause defines a pause stage for a rollout,
manual or auto
properties:
duration:
description: Duration the amount of time to wait
before moving to the next step.
format: int32
type: integer
type: object
replicas:
anyOf:
- type: integer
- type: string
description: 'Replicas is the number of expected canary
pods in this batch it can be an absolute number (ex:
5) or a percentage of total pods.'
x-kubernetes-int-or-string: true
requestHeaderModifier:
description: "Set overwrites the request with the given
header (name, value) before the action. \n Input:
\ GET /foo HTTP/1.1 my-header: foo \n requestHeaderModifier:
\ set: - name: \"my-header\" value: \"bar\"
\n Output: GET /foo HTTP/1.1 my-header: bar"
properties:
add:
description: "Add adds the given header(s) (name,
value) to the request before the action. It appends
to any existing values associated with the header
name. \n Input: GET /foo HTTP/1.1 my-header:
foo \n Config: add: - name: \"my-header\"
\ value: \"bar\" \n Output: GET /foo HTTP/1.1
\ my-header: foo my-header: bar"
items:
description: HTTPHeader represents an HTTP Header
name and value as defined by RFC 7230.
properties:
name:
description: "Name is the name of the HTTP
Header to be matched. Name matching MUST
be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).
\n If multiple entries specify equivalent
header names, the first entry with an equivalent
name MUST be considered for a match. Subsequent
entries with an equivalent header name MUST
be ignored. Due to the case-insensitivity
of header names, \"foo\" and \"Foo\" are
considered equivalent."
maxLength: 256
minLength: 1
pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$
type: string
value:
description: Value is the value of HTTP Header
to be matched.
maxLength: 4096
minLength: 1
type: string
required:
- name
- value
type: object
maxItems: 16
type: array
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
remove:
description: "Remove the given header(s) from the
HTTP request before the action. The value of Remove
is a list of HTTP header names. Note that the
header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).
\n Input: GET /foo HTTP/1.1 my-header1: foo
\ my-header2: bar my-header3: baz \n Config:
\ remove: [\"my-header1\", \"my-header3\"] \n
Output: GET /foo HTTP/1.1 my-header2: bar"
items:
type: string
maxItems: 16
type: array
set:
description: "Set overwrites the request with the
given header (name, value) before the action.
\n Input: GET /foo HTTP/1.1 my-header: foo
\n Config: set: - name: \"my-header\" value:
\"bar\" \n Output: GET /foo HTTP/1.1 my-header:
bar"
items:
description: HTTPHeader represents an HTTP Header
name and value as defined by RFC 7230.
properties:
name:
description: "Name is the name of the HTTP
Header to be matched. Name matching MUST
be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).
\n If multiple entries specify equivalent
header names, the first entry with an equivalent
name MUST be considered for a match. Subsequent
entries with an equivalent header name MUST
be ignored. Due to the case-insensitivity
of header names, \"foo\" and \"Foo\" are
considered equivalent."
maxLength: 256
minLength: 1
pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$
type: string
value:
description: Value is the value of HTTP Header
to be matched.
maxLength: 4096
minLength: 1
type: string
required:
- name
- value
type: object
maxItems: 16
type: array
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
type: object
traffic:
description: Traffic indicate how many percentage of
traffic the canary pods should receive Value is of
string type and is a percentage, e.g. 5%.
type: string
type: object
type: array
trafficRoutingRef:
description: TrafficRoutingRef is TrafficRouting's Name
type: string
trafficRoutings:
description: TrafficRoutings support ingress, gateway api
and custom network resource(e.g. istio, apisix) to enable
more fine-grained traffic routing and current only support
one TrafficRouting
items:
description: TrafficRoutingRef hosts all the different configuration
for supported service meshes to enable more fine-grained
traffic routing
properties:
customNetworkRefs:
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
- kind
- name
type: object
type: array
gateway:
description: Gateway holds Gateway specific configuration
to route traffic Gateway configuration only supports
>= v0.4.0 (v1alpha2).
properties:
httpRouteName:
description: HTTPRouteName refers to the name of
an `HTTPRoute` resource in the same namespace
as the `Rollout`
type: string
type: object
gracePeriodSeconds:
description: Optional duration in seconds the traffic
provider(e.g. nginx ingress controller) consumes the
service, ingress configuration changes gracefully.
format: int32
type: integer
ingress:
description: Ingress holds Ingress specific configuration
to route traffic, e.g. Nginx, Alb.
properties:
classType:
description: ClassType refers to the type of `Ingress`.
current support nginx, aliyun-alb. default is
nginx.
type: string
name:
description: Name refers to the name of an `Ingress`
resource in the same namespace as the `Rollout`
type: string
required:
- name
type: object
service:
description: Service holds the name of a service which
selects pods with stable version and don't select
any pods with canary version.
type: string
required:
- service
type: object
type: array
type: object
canary:
description: CanaryStrategy defines parameters for a Replica Based
Canary
@ -1013,6 +1331,79 @@ spec:
status:
description: RolloutStatus defines the observed state of Rollout
properties:
blueGreenStatus:
description: BlueGreen describes the state of the blueGreen rollout
properties:
currentStepIndex:
description: CurrentStepIndex defines the current step of the
rollout is on.
format: int32
type: integer
currentStepState:
type: string
finalisingStep:
description: FinalisingStep the step of finalising
type: string
lastUpdateTime:
format: date-time
type: string
message:
type: string
nextStepIndex:
description: NextStepIndex defines the next step of the rollout
is on. In normal case, NextStepIndex is equal to CurrentStepIndex
+ 1 If the current step is the last step, NextStepIndex is equal
to -1 Before the release, NextStepIndex is also equal to -1
0 is not used and won't appear in any case It is allowed to
patch NextStepIndex by design, e.g. if CurrentStepIndex is 2,
user can patch NextStepIndex to 3 (if exists) to achieve batch
jump, or patch NextStepIndex to 1 to implement a re-execution
of step 1 Patching it with a non-positive value is useless and
meaningless, which will be corrected in the next reconciliation
format: int32
type: integer
observedRolloutID:
description: ObservedRolloutID will record the newest spec.RolloutID
if status.canaryRevision equals to workload.updateRevision
type: string
observedWorkloadGeneration:
description: observedWorkloadGeneration is the most recent generation
observed for this Rollout ref workload generation.
format: int64
type: integer
podTemplateHash:
description: pod template hash is used as service selector label
type: string
rolloutHash:
description: RolloutHash from rollout.spec object
type: string
stableRevision:
description: StableRevision indicates the revision of stable pods
type: string
updatedReadyReplicas:
description: UpdatedReadyReplicas the numbers of updated ready
pods
format: int32
type: integer
updatedReplicas:
description: UpdatedReplicas the numbers of updated pods
format: int32
type: integer
updatedRevision:
description: CanaryRevision is calculated by rollout based on
podTemplateHash, and the internal logic flow uses It may be
different from rs podTemplateHash in different k8s versions,
so it cannot be used as service selector label
type: string
required:
- currentStepState
- finalisingStep
- nextStepIndex
- podTemplateHash
- updatedReadyReplicas
- updatedReplicas
- updatedRevision
type: object
canaryStatus:
description: Canary describes the state of the canary rollout
properties:
@ -1033,17 +1424,32 @@ spec:
type: string
currentStepIndex:
description: CurrentStepIndex defines the current step of the
rollout is on. If the current step index is null, the controller
will execute the rollout.
rollout is on.
format: int32
type: integer
currentStepState:
type: string
finalisingStep:
description: FinalisingStep the step of finalising
type: string
lastUpdateTime:
format: date-time
type: string
message:
type: string
nextStepIndex:
description: NextStepIndex defines the next step of the rollout
is on. In normal case, NextStepIndex is equal to CurrentStepIndex
+ 1 If the current step is the last step, NextStepIndex is equal
to -1 Before the release, NextStepIndex is also equal to -1
0 is not used and won't appear in any case It is allowed to
patch NextStepIndex by design, e.g. if CurrentStepIndex is 2,
user can patch NextStepIndex to 3 (if exists) to achieve batch
jump, or patch NextStepIndex to 1 to implement a re-execution
of step 1 Patching it with a non-positive value is useless and
meaningless, which will be corrected in the next reconciliation
format: int32
type: integer
observedRolloutID:
description: ObservedRolloutID will record the newest spec.RolloutID
if status.canaryRevision equals to workload.updateRevision
@ -1067,6 +1473,8 @@ spec:
- canaryReplicas
- canaryRevision
- currentStepState
- finalisingStep
- nextStepIndex
- podTemplateHash
type: object
conditions:

View File

@ -67,8 +67,8 @@ var (
Name: "sample",
},
ReleasePlan: v1beta1.ReleasePlan{
EnableExtraWorkloadForCanary: true,
BatchPartition: pointer.Int32(0),
RollingStyle: v1beta1.CanaryRollingStyle,
BatchPartition: pointer.Int32(0),
Batches: []v1beta1.ReleaseBatch{
{
CanaryReplicas: intstr.FromString("10%"),
@ -147,6 +147,7 @@ var (
},
ReleasePlan: v1beta1.ReleasePlan{
BatchPartition: pointer.Int32Ptr(0),
RollingStyle: v1beta1.PartitionRollingStyle,
Batches: []v1beta1.ReleaseBatch{
{
CanaryReplicas: intstr.FromString("10%"),

View File

@ -197,28 +197,43 @@ func (r *Executor) getReleaseController(release *v1beta1.BatchRelease, newStatus
Namespace: release.Namespace,
Name: targetRef.Name,
}
rollingStyle := release.Spec.ReleasePlan.RollingStyle
if len(rollingStyle) == 0 && release.Spec.ReleasePlan.EnableExtraWorkloadForCanary {
rollingStyle = v1beta1.CanaryRollingStyle
}
klog.Infof("BatchRelease(%v) using %s-style release controller for this batch release", klog.KObj(release), rollingStyle)
switch rollingStyle {
case v1beta1.BlueGreenRollingStyle:
// if targetRef.APIVersion == appsv1alpha1.GroupVersion.String() && targetRef.Kind == reflect.TypeOf(appsv1alpha1.CloneSet{}).Name() {
// klog.InfoS("Using CloneSet bluegreen-style release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
// return partitionstyle.NewControlPlane(cloneset.NewController, r.client, r.recorder, release, newStatus, targetKey, gvk), nil
// }
// if targetRef.APIVersion == apps.SchemeGroupVersion.String() && targetRef.Kind == reflect.TypeOf(apps.Deployment{}).Name() {
// klog.InfoS("Using Deployment bluegreen-style release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
// return bluegreenstyle.NewControlPlane(deployment.NewController, r.client, r.recorder, release, newStatus, targetKey, gvk), nil
// }
switch targetRef.APIVersion {
case appsv1alpha1.GroupVersion.String():
if targetRef.Kind == reflect.TypeOf(appsv1alpha1.CloneSet{}).Name() {
case v1beta1.CanaryRollingStyle:
if targetRef.APIVersion == apps.SchemeGroupVersion.String() && targetRef.Kind == reflect.TypeOf(apps.Deployment{}).Name() {
klog.InfoS("Using Deployment canary-style release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
return canarystyle.NewControlPlane(canarydeployment.NewController, r.client, r.recorder, release, newStatus, targetKey), nil
}
fallthrough
case v1beta1.PartitionRollingStyle, "":
if targetRef.APIVersion == appsv1alpha1.GroupVersion.String() && targetRef.Kind == reflect.TypeOf(appsv1alpha1.CloneSet{}).Name() {
klog.InfoS("Using CloneSet partition-style release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
return partitionstyle.NewControlPlane(cloneset.NewController, r.client, r.recorder, release, newStatus, targetKey, gvk), nil
}
if targetRef.Kind == reflect.TypeOf(appsv1alpha1.DaemonSet{}).Name() {
if targetRef.APIVersion == appsv1alpha1.GroupVersion.String() && targetRef.Kind == reflect.TypeOf(appsv1alpha1.DaemonSet{}).Name() {
klog.InfoS("Using DaemonSet partition-style release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
return partitionstyle.NewControlPlane(daemonset.NewController, r.client, r.recorder, release, newStatus, targetKey, gvk), nil
}
case apps.SchemeGroupVersion.String():
if targetRef.Kind == reflect.TypeOf(apps.Deployment{}).Name() {
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 {
klog.InfoS("Using Deployment canary-style release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
return canarystyle.NewControlPlane(canarydeployment.NewController, r.client, r.recorder, release, newStatus, targetKey), nil
}
if targetRef.APIVersion == apps.SchemeGroupVersion.String() && targetRef.Kind == reflect.TypeOf(apps.Deployment{}).Name() {
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
}
klog.Info("Partition, but use StatefulSet-Like partition-style release controller for this batch release")
}
// try to use StatefulSet-like rollout controller by default

View File

@ -364,6 +364,7 @@ func createBatchRelease(rollout *v1beta1.Rollout, rolloutID string, batch int32,
BatchPartition: utilpointer.Int32Ptr(batch),
FailureThreshold: rollout.Spec.Strategy.Canary.FailureThreshold,
PatchPodTemplateMetadata: rollout.Spec.Strategy.Canary.PatchPodTemplateMetadata,
RollingStyle: rollout.Spec.Strategy.GetRollingStyle(),
EnableExtraWorkloadForCanary: rollout.Spec.Strategy.Canary.EnableExtraWorkloadForCanary,
},
},

View File

@ -100,6 +100,7 @@ func TestRunCanary(t *testing.T) {
}
br.Spec.ReleasePlan.BatchPartition = utilpointer.Int32(0)
br.Spec.ReleasePlan.EnableExtraWorkloadForCanary = true
br.Spec.ReleasePlan.RollingStyle = v1beta1.CanaryRollingStyle
return br
},
},
@ -159,9 +160,12 @@ func TestRunCanary(t *testing.T) {
}
br.Spec.ReleasePlan.BatchPartition = utilpointer.Int32(0)
br.Spec.ReleasePlan.EnableExtraWorkloadForCanary = true
br.Spec.ReleasePlan.RollingStyle = v1beta1.CanaryRollingStyle
br.Status = v1beta1.BatchReleaseStatus{
ObservedGeneration: 1,
ObservedReleasePlanHash: "d444a1007776da957d7d8549e3375c96179621b85670ad1e2bb0fc5fea16446a",
ObservedGeneration: 1,
// since we use RollingStyle over EnableExtraWorkloadForCanary now, former hardcoded hash
// should be re-calculated
ObservedReleasePlanHash: util.HashReleasePlanBatches(&br.Spec.ReleasePlan),
CanaryStatus: v1beta1.BatchReleaseCanaryStatus{
CurrentBatchState: v1beta1.ReadyBatchState,
CurrentBatch: 0,
@ -205,6 +209,7 @@ func TestRunCanary(t *testing.T) {
}
br.Spec.ReleasePlan.BatchPartition = utilpointer.Int32(0)
br.Spec.ReleasePlan.EnableExtraWorkloadForCanary = true
br.Spec.ReleasePlan.RollingStyle = v1beta1.CanaryRollingStyle
return br
},
},

View File

@ -66,17 +66,31 @@ func (r *RolloutReconciler) reconcileRolloutProgressing(rollout *v1beta1.Rollout
switch cond.Reason {
case v1alpha1.ProgressingReasonInitializing:
klog.Infof("rollout(%s/%s) is Progressing, and in reason(%s)", rollout.Namespace, rollout.Name, cond.Reason)
// new canaryStatus
newStatus.CanaryStatus = &v1beta1.CanaryStatus{
// clear and create
newStatus.Clear()
commonStatus := v1beta1.CommonStatus{
ObservedWorkloadGeneration: rolloutContext.Workload.Generation,
RolloutHash: rolloutContext.Rollout.Annotations[util.RolloutHashAnnotation],
ObservedRolloutID: getRolloutID(rolloutContext.Workload),
StableRevision: rolloutContext.Workload.StableRevision,
CanaryRevision: rolloutContext.Workload.CanaryRevision,
CurrentStepIndex: 1,
CurrentStepState: v1beta1.CanaryStepStateUpgrade,
NextStepIndex: util.NextBatchIndex(rollout, 1),
CurrentStepState: v1beta1.CanaryStepStateInit,
LastUpdateTime: &metav1.Time{Time: time.Now()},
}
if rollout.Spec.Strategy.IsBlueGreenRelease() {
newStatus.BlueGreenStatus = &v1beta1.BlueGreenStatus{
CommonStatus: commonStatus,
UpdatedRevision: rolloutContext.Workload.CanaryRevision,
}
} else {
commonStatus.CurrentStepState = v1beta1.CanaryStepStateUpgrade
newStatus.CanaryStatus = &v1beta1.CanaryStatus{
CommonStatus: commonStatus,
CanaryRevision: rolloutContext.Workload.CanaryRevision,
}
}
done, err := r.doProgressingInitializing(rolloutContext)
if err != nil {
klog.Errorf("rollout(%s/%s) doProgressingInitializing error(%s)", rollout.Namespace, rollout.Name, err.Error())

View File

@ -67,6 +67,7 @@ func TestReconcileRolloutProgressing(t *testing.T) {
s.CanaryStatus.StableRevision = "pod-template-hash-v1"
s.CanaryStatus.CanaryRevision = "6f8cc56547"
s.CanaryStatus.CurrentStepIndex = 1
s.CanaryStatus.NextStepIndex = 2
s.CanaryStatus.CurrentStepState = v1beta1.CanaryStepStateUpgrade
return s
},
@ -99,6 +100,7 @@ func TestReconcileRolloutProgressing(t *testing.T) {
s.CanaryStatus.StableRevision = "pod-template-hash-v1"
s.CanaryStatus.CanaryRevision = "6f8cc56547"
s.CanaryStatus.CurrentStepIndex = 1
s.CanaryStatus.NextStepIndex = 2
s.CanaryStatus.CurrentStepState = v1beta1.CanaryStepStateUpgrade
cond := util.GetRolloutCondition(*s, v1beta1.RolloutConditionProgressing)
cond.Reason = v1alpha1.ProgressingReasonInRolling

View File

@ -123,17 +123,28 @@ func (r *RolloutReconciler) calculateRolloutStatus(rollout *v1beta1.Rollout) (re
// But at the first deployment of Rollout/Workload, CanaryStatus isn't set due to no rollout progression,
// and PaaS platform cannot judge whether the deployment is completed base on the code above. So we have
// to update the status just like the rollout was completed.
newStatus.CanaryStatus = &v1beta1.CanaryStatus{
commonStatus := v1beta1.CommonStatus{
ObservedRolloutID: getRolloutID(workload),
ObservedWorkloadGeneration: workload.Generation,
PodTemplateHash: workload.PodTemplateHash,
CanaryRevision: workload.CanaryRevision,
StableRevision: workload.StableRevision,
CurrentStepIndex: int32(len(rollout.Spec.Strategy.Canary.Steps)),
CurrentStepIndex: int32(len(rollout.Spec.Strategy.GetSteps())),
NextStepIndex: util.NextBatchIndex(rollout, int32(len(rollout.Spec.Strategy.GetSteps()))),
CurrentStepState: v1beta1.CanaryStepStateCompleted,
RolloutHash: rollout.Annotations[util.RolloutHashAnnotation],
}
if rollout.Spec.Strategy.IsBlueGreenRelease() {
newStatus.BlueGreenStatus = &v1beta1.BlueGreenStatus{
CommonStatus: commonStatus,
UpdatedRevision: workload.CanaryRevision,
}
} else {
newStatus.CanaryStatus = &v1beta1.CanaryStatus{
CommonStatus: commonStatus,
CanaryRevision: workload.CanaryRevision,
}
}
newStatus.Message = "workload deployment is completed"
}
case v1beta1.RolloutPhaseDisabled:

View File

@ -163,15 +163,17 @@ var (
Status: v1beta1.RolloutStatus{
Phase: v1beta1.RolloutPhaseProgressing,
CanaryStatus: &v1beta1.CanaryStatus{
ObservedWorkloadGeneration: 1,
RolloutHash: "rollout-hash-v1",
ObservedRolloutID: "rollout-id-1",
StableRevision: "podtemplatehash-v1",
CanaryRevision: "revision-v2",
CurrentStepIndex: 1,
CurrentStepState: v1beta1.CanaryStepStateTrafficRouting,
PodTemplateHash: "podtemplatehash-v2",
LastUpdateTime: &metav1.Time{Time: time.Now()},
CommonStatus: v1beta1.CommonStatus{
ObservedWorkloadGeneration: 1,
RolloutHash: "rollout-hash-v1",
ObservedRolloutID: "rollout-id-1",
StableRevision: "podtemplatehash-v1",
CurrentStepIndex: 1,
CurrentStepState: v1beta1.CanaryStepStateTrafficRouting,
PodTemplateHash: "podtemplatehash-v2",
LastUpdateTime: &metav1.Time{Time: time.Now()},
},
CanaryRevision: "revision-v2",
},
Conditions: []v1beta1.RolloutCondition{
{
@ -249,15 +251,17 @@ var (
Status: v1beta1.RolloutStatus{
Phase: v1beta1.RolloutPhaseProgressing,
CanaryStatus: &v1beta1.CanaryStatus{
ObservedWorkloadGeneration: 1,
RolloutHash: "rollout-hash-v1",
ObservedRolloutID: "rollout-id-1",
StableRevision: "podtemplatehash-v1",
CanaryRevision: "revision-v2",
CurrentStepIndex: 1,
CurrentStepState: v1beta1.CanaryStepStateTrafficRouting,
PodTemplateHash: "podtemplatehash-v2",
LastUpdateTime: &metav1.Time{Time: time.Now()},
CommonStatus: v1beta1.CommonStatus{
ObservedWorkloadGeneration: 1,
RolloutHash: "rollout-hash-v1",
ObservedRolloutID: "rollout-id-1",
StableRevision: "podtemplatehash-v1",
CurrentStepIndex: 1,
CurrentStepState: v1beta1.CanaryStepStateTrafficRouting,
PodTemplateHash: "podtemplatehash-v2",
LastUpdateTime: &metav1.Time{Time: time.Now()},
},
CanaryRevision: "revision-v2",
},
Conditions: []v1beta1.RolloutCondition{
{

View File

@ -164,3 +164,15 @@ func DumpJSON(o interface{}) string {
func EncodeHash(data string) string {
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
}
// calculate the next batch index
func NextBatchIndex(rollout *rolloutv1beta1.Rollout, CurrentStepIndex int32) int32 {
if rollout == nil {
return -1
}
allSteps := int32(len(rollout.Spec.Strategy.GetSteps()))
if CurrentStepIndex >= allSteps {
return -1
}
return CurrentStepIndex + 1
}

View File

@ -133,12 +133,11 @@ func (h *RolloutCreateUpdateHandler) validateRolloutUpdate(oldObj, newObj *appsv
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 !reflect.DeepEqual(oldObj.Spec.Strategy.GetTrafficRouting(), newObj.Spec.Strategy.GetTrafficRouting()) {
return field.ErrorList{field.Forbidden(field.NewPath("Spec.Strategy.Canary|BlueGreen.TrafficRoutings"), "Rollout 'Strategy.Canary|BlueGreen.TrafficRoutings' field 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")}
if oldObj.Spec.Strategy.GetRollingStyle() != newObj.Spec.Strategy.GetRollingStyle() {
return field.ErrorList{field.Forbidden(field.NewPath("Spec.Strategy.Canary|BlueGreen"), "Rollout style and enableExtraWorkloadForCanary are immutable")}
}
}
@ -198,15 +197,32 @@ func validateRolloutSpecObjectRef(workloadRef *appsv1beta1.ObjectRef, fldPath *f
}
func validateRolloutSpecStrategy(strategy *appsv1beta1.RolloutStrategy, fldPath *field.Path) field.ErrorList {
if strategy.Canary == nil && strategy.BlueGreen == nil {
return field.ErrorList{field.Invalid(fldPath, nil, "Canary and BlueGreen cannot both be empty")}
}
if strategy.Canary != nil && strategy.BlueGreen != nil {
return field.ErrorList{field.Invalid(fldPath, nil, "Canary and BlueGreen cannot both be set")}
}
if strategy.BlueGreen != nil {
return validateRolloutSpecBlueGreenStrategy(strategy.BlueGreen, fldPath.Child("BlueGreen"))
}
return validateRolloutSpecCanaryStrategy(strategy.Canary, fldPath.Child("Canary"))
}
func validateRolloutSpecCanaryStrategy(canary *appsv1beta1.CanaryStrategy, fldPath *field.Path) field.ErrorList {
if canary == nil {
return field.ErrorList{field.Invalid(fldPath, nil, "Canary cannot be empty")}
}
type TrafficRule string
errList := validateRolloutSpecCanarySteps(canary.Steps, fldPath.Child("Steps"), len(canary.TrafficRoutings) > 0)
const (
TrafficRuleCanary TrafficRule = "Canary"
TrafficRuleBlueGreen TrafficRule = "BlueGreen"
NoTraffic TrafficRule = "NoTraffic"
)
func validateRolloutSpecCanaryStrategy(canary *appsv1beta1.CanaryStrategy, fldPath *field.Path) field.ErrorList {
trafficRule := NoTraffic
if len(canary.TrafficRoutings) > 0 {
trafficRule = TrafficRuleCanary
}
errList := validateRolloutSpecCanarySteps(canary.Steps, fldPath.Child("Steps"), trafficRule)
if len(canary.TrafficRoutings) > 1 {
errList = append(errList, field.Invalid(fldPath, canary.TrafficRoutings, "Rollout currently only support single TrafficRouting."))
}
@ -216,6 +232,21 @@ func validateRolloutSpecCanaryStrategy(canary *appsv1beta1.CanaryStrategy, fldPa
return errList
}
func validateRolloutSpecBlueGreenStrategy(blueGreen *appsv1beta1.BlueGreenStrategy, fldPath *field.Path) field.ErrorList {
trafficRule := NoTraffic
if len(blueGreen.TrafficRoutings) > 0 {
trafficRule = TrafficRuleBlueGreen
}
errList := validateRolloutSpecCanarySteps(blueGreen.Steps, fldPath.Child("Steps"), trafficRule)
if len(blueGreen.TrafficRoutings) > 1 {
errList = append(errList, field.Invalid(fldPath, blueGreen.TrafficRoutings, "Rollout currently only support single TrafficRouting."))
}
for _, traffic := range blueGreen.TrafficRoutings {
errList = append(errList, validateRolloutSpecCanaryTraffic(traffic, fldPath.Child("TrafficRouting"))...)
}
return errList
}
func validateRolloutSpecCanaryTraffic(traffic appsv1beta1.TrafficRoutingRef, fldPath *field.Path) field.ErrorList {
errList := field.ErrorList{}
if len(traffic.Service) == 0 {
@ -240,7 +271,7 @@ func validateRolloutSpecCanaryTraffic(traffic appsv1beta1.TrafficRoutingRef, fld
return errList
}
func validateRolloutSpecCanarySteps(steps []appsv1beta1.CanaryStep, fldPath *field.Path, isTraffic bool) field.ErrorList {
func validateRolloutSpecCanarySteps(steps []appsv1beta1.CanaryStep, fldPath *field.Path, trafficRule TrafficRule) field.ErrorList {
stepCount := len(steps)
if stepCount == 0 {
return field.ErrorList{field.Invalid(fldPath, steps, "The number of Canary.Steps cannot be empty")}
@ -258,14 +289,21 @@ func validateRolloutSpecCanarySteps(steps []appsv1beta1.CanaryStep, fldPath *fie
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 {
if trafficRule == NoTraffic || s.Traffic == nil {
continue
}
if s.Traffic != nil {
is := intstr.FromString(*s.Traffic)
weight, err := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
is := intstr.FromString(*s.Traffic)
weight, err := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
switch trafficRule {
case TrafficRuleBlueGreen:
// traffic "0%" is allowed in blueGreen strategy
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%" in blueGreen strategy`)}
}
default:
// traffic "0%" is not allowed in canary strategy
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%"`)}
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("steps"), steps, `traffic must be percentage with "0%" < traffic <= "100%" in canary strategy`)}
}
}
}

View File

@ -96,7 +96,9 @@ var (
},
Status: appsv1beta1.RolloutStatus{
CanaryStatus: &appsv1beta1.CanaryStatus{
CurrentStepState: appsv1beta1.CanaryStepStateCompleted,
CommonStatus: appsv1beta1.CommonStatus{
CurrentStepState: appsv1beta1.CanaryStepStateCompleted,
},
},
},
}

View File

@ -26,6 +26,16 @@ import (
var _ = SIGDescribe("Advanced Deployment", func() {
var namespace string
DumpAllResources := func() {
deploy := &apps.DeploymentList{}
k8sClient.List(context.TODO(), deploy, client.InNamespace(namespace))
fmt.Println(util.DumpJSON(deploy))
rs := &apps.ReplicaSetList{}
k8sClient.List(context.TODO(), rs, client.InNamespace(namespace))
fmt.Println(util.DumpJSON(rs))
}
defaultRetry := wait.Backoff{
Steps: 10,
Duration: 10 * time.Millisecond,
@ -132,7 +142,12 @@ var _ = SIGDescribe("Advanced Deployment", func() {
CheckReplicas := func(deployment *apps.Deployment, replicas, available, updated int32) {
var clone *apps.Deployment
start := time.Now()
Eventually(func() bool {
if start.Add(time.Minute * 2).Before(time.Now()) {
DumpAllResources()
Expect(true).Should(BeFalse())
}
clone = &apps.Deployment{}
err := GetObject(deployment.Namespace, deployment.Name, clone)
Expect(err).NotTo(HaveOccurred())
@ -239,6 +254,7 @@ var _ = SIGDescribe("Advanced Deployment", func() {
deployment.Namespace = namespace
Expect(ReadYamlToObject("./test_data/deployment/deployment.yaml", deployment)).ToNot(HaveOccurred())
CreateObject(deployment)
CheckReplicas(deployment, 5, 5, 5)
UpdateDeployment(deployment, "version2")
UpdatePartitionWithCheck(deployment, intstr.FromInt(0))
UpdatePartitionWithCheck(deployment, intstr.FromInt(1))
@ -255,6 +271,7 @@ var _ = SIGDescribe("Advanced Deployment", func() {
Expect(ReadYamlToObject("./test_data/deployment/deployment.yaml", deployment)).ToNot(HaveOccurred())
deployment.Spec.Replicas = pointer.Int32(10)
CreateObject(deployment)
CheckReplicas(deployment, 10, 10, 10)
UpdateDeployment(deployment, "version2")
UpdatePartitionWithCheck(deployment, intstr.FromString("0%"))
UpdatePartitionWithCheck(deployment, intstr.FromString("40%"))
@ -287,6 +304,7 @@ var _ = SIGDescribe("Advanced Deployment", func() {
`{"rollingStyle":"Partition","rollingUpdate":{"maxUnavailable":1,"maxSurge":0}}`
deployment.Spec.MinReadySeconds = 10
CreateObject(deployment)
CheckReplicas(deployment, 5, 5, 5)
UpdateDeployment(deployment, "version2")
UpdatePartitionWithCheck(deployment, intstr.FromInt(0))
UpdatePartitionWithoutCheck(deployment, intstr.FromInt(3))
@ -303,6 +321,7 @@ var _ = SIGDescribe("Advanced Deployment", func() {
deployment.Namespace = namespace
Expect(ReadYamlToObject("./test_data/deployment/deployment.yaml", deployment)).ToNot(HaveOccurred())
CreateObject(deployment)
CheckReplicas(deployment, 5, 5, 5)
UpdateDeployment(deployment, "version2")
UpdatePartitionWithCheck(deployment, intstr.FromInt(0))
UpdatePartitionWithCheck(deployment, intstr.FromInt(2))
@ -317,6 +336,7 @@ var _ = SIGDescribe("Advanced Deployment", func() {
deployment.Namespace = namespace
Expect(ReadYamlToObject("./test_data/deployment/deployment.yaml", deployment)).ToNot(HaveOccurred())
CreateObject(deployment)
CheckReplicas(deployment, 5, 5, 5)
UpdateDeployment(deployment, "version2")
UpdatePartitionWithCheck(deployment, intstr.FromInt(0))
UpdatePartitionWithCheck(deployment, intstr.FromInt(2))
@ -335,6 +355,7 @@ var _ = SIGDescribe("Advanced Deployment", func() {
Expect(ReadYamlToObject("./test_data/deployment/deployment.yaml", deployment)).ToNot(HaveOccurred())
deployment.Annotations["rollouts.kruise.io/deployment-strategy"] = `{"rollingUpdate":{"maxUnavailable":0,"maxSurge":1}}`
CreateObject(deployment)
CheckReplicas(deployment, 5, 5, 5)
UpdateDeployment(deployment, "version2", "busybox:not-exists")
UpdatePartitionWithoutCheck(deployment, intstr.FromInt(1))
CheckReplicas(deployment, 6, 5, 1)

View File

@ -215,18 +215,25 @@ var _ = SIGDescribe("Rollout", func() {
}
ResumeRolloutCanary := func(name string) {
clone := &v1alpha1.Rollout{}
Expect(GetObject(name, clone)).NotTo(HaveOccurred())
currentIndex := clone.Status.CanaryStatus.CurrentStepIndex
Eventually(func() bool {
clone := &v1alpha1.Rollout{}
Expect(GetObject(name, clone)).NotTo(HaveOccurred())
if clone.Status.CanaryStatus.CurrentStepState != v1alpha1.CanaryStepStatePaused {
if clone.Status.CanaryStatus.CurrentStepIndex == currentIndex && clone.Status.CanaryStatus.CurrentStepState == v1alpha1.CanaryStepStatePaused {
klog.Info("patch to stepReady")
body := fmt.Sprintf(`{"status":{"canaryStatus":{"currentStepState":"%s"}}}`, v1alpha1.CanaryStepStateReady)
Expect(k8sClient.Status().Patch(context.TODO(), clone, client.RawPatch(types.MergePatchType, []byte(body)))).NotTo(HaveOccurred())
return false
} else {
fmt.Println("resume rollout success, and CurrentStepState", util.DumpJSON(clone.Status))
return true
}
body := fmt.Sprintf(`{"status":{"canaryStatus":{"currentStepState":"%s"}}}`, v1alpha1.CanaryStepStateReady)
Expect(k8sClient.Status().Patch(context.TODO(), clone, client.RawPatch(types.MergePatchType, []byte(body)))).NotTo(HaveOccurred())
return false
}, 10*time.Second, time.Second).Should(BeTrue())
// interval was critical before:
// too small: StepReady could be overidden by StepPaused
// too big: StepReady could progress to StepPaused of next Step
}, 10*time.Second, 2*time.Second).Should(BeTrue())
}
ResumeRolloutCanaryV1beta1 := func(name string) {