Support for application full-link greyscale

Signed-off-by: liheng.zms <liheng.zms@alibaba-inc.com>
This commit is contained in:
liheng.zms 2023-03-17 14:52:14 +08:00
parent 3578b399a6
commit e2e91f3420
40 changed files with 2124 additions and 561 deletions

View File

@ -50,6 +50,10 @@ type ReleasePlan struct {
// FinalizingPolicy define the behavior of controller when phase enter Finalizing
// Defaults to "Immediate"
FinalizingPolicy FinalizingPolicyType `json:"finalizingPolicy,omitempty"`
// PatchPodTemplateMetadata indicates patch configuration(e.g. labels, annotations) to the canary deployment podTemplateSpec.metadata
// only support for canary deployment
// +optional
PatchPodTemplateMetadata *PatchPodTemplateMetadata `json:"patchPodTemplateMetadata,omitempty"`
}
type FinalizingPolicyType string

View File

@ -51,6 +51,12 @@ const (
// 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
@ -78,12 +84,12 @@ type ObjectRef struct {
//RevisionRef *ControllerRevisionRef `json:"revisionRef,omitempty"`
}
type ObjectRefType string
/*type ObjectRefType string
const (
WorkloadRefType = "workloadRef"
RevisionRefType = "revisionRef"
)
)*/
// WorkloadRef holds a references to the Kubernetes object
type WorkloadRef struct {
@ -106,12 +112,12 @@ type RolloutStrategy struct {
// BlueGreen *BlueGreenStrategy `json:"blueGreen,omitempty"`
}
type RolloutStrategyType string
/*type RolloutStrategyType string
const (
RolloutStrategyCanary RolloutStrategyType = "canary"
RolloutStrategyBlueGreen RolloutStrategyType = "blueGreen"
)
)*/
// CanaryStrategy defines parameters for a Replica Based Canary
type CanaryStrategy struct {
@ -119,33 +125,36 @@ type CanaryStrategy struct {
// +optional
Steps []CanaryStep `json:"steps,omitempty"`
// TrafficRoutings hosts all the supported service meshes supported to enable more fine-grained traffic routing
// todo current only support one TrafficRouting
TrafficRoutings []*TrafficRouting `json:"trafficRoutings,omitempty"`
// 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"`
// MetricsAnalysis *MetricsAnalysisBackground `json:"metricsAnalysis,omitempty"`
// PatchPodTemplateMetadata indicates patch configuration(e.g. labels, annotations) to the canary deployment podTemplateSpec.metadata
// only support for canary deployment
// +optional
PatchPodTemplateMetadata *PatchPodTemplateMetadata `json:"patchPodTemplateMetadata,omitempty"`
}
type PatchPodTemplateMetadata struct {
// annotations
Annotations map[string]string `json:"annotations,omitempty"`
// labels
Labels map[string]string `json:"labels,omitempty"`
}
// CanaryStep defines a step of a canary workload.
type CanaryStep struct {
// Weight indicate how many percentage of traffic the canary pods should receive
// +optional
Weight *int32 `json:"weight,omitempty"`
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"`
// Pause defines a pause stage for a rollout, manual or auto
// +optional
Pause RolloutPause `json:"pause,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 {
@ -163,37 +172,6 @@ type RolloutPause struct {
Duration *int32 `json:"duration,omitempty"`
}
// TrafficRouting hosts all the different configuration for supported service meshes to enable more fine-grained traffic routing
type TrafficRouting 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"`
}
// 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"`
}
// RolloutStatus defines the observed state of Rollout
type RolloutStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster

View File

@ -0,0 +1,152 @@
/*
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 (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
)
const (
ProgressingRolloutFinalizerPrefix = "progressing.rollouts.kruise.io"
)
// 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"`
}
// 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"`
}
type TrafficRoutingSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// ObjectRef indicates trafficRouting ref
ObjectRef []TrafficRoutingRef `json:"objectRef"`
// trafficrouting strategy
Strategy TrafficRoutingStrategy `json:"strategy"`
}
type TrafficRoutingStrategy struct {
// Weight indicate how many percentage of traffic the canary pods should receive
// +optional
Weight *int32 `json:"weight,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 TrafficRoutingStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
// observedGeneration is the most recent generation observed for this Rollout.
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Phase is the trafficRouting phase.
Phase TrafficRoutingPhase `json:"phase,omitempty"`
// Message provides details on why the rollout is in its current phase
Message string `json:"message,omitempty"`
}
// TrafficRoutingPhase are a set of phases that this rollout
type TrafficRoutingPhase string
const (
// TrafficRoutingPhaseInitial indicates a traffic routing is Initial
TrafficRoutingPhaseInitial TrafficRoutingPhase = "Initial"
// TrafficRoutingPhaseHealthy indicates a traffic routing is healthy
TrafficRoutingPhaseHealthy TrafficRoutingPhase = "Healthy"
// TrafficRoutingPhaseProgressing indicates a traffic routing is not yet healthy but still making progress towards a healthy state
TrafficRoutingPhaseProgressing TrafficRoutingPhase = "Progressing"
TrafficRoutingPhaseFinalizing TrafficRoutingPhase = "Finalizing"
// TrafficRoutingPhaseTerminating indicates a traffic routing is terminated
TrafficRoutingPhaseTerminating TrafficRoutingPhase = "Terminating"
)
// +genclient
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="STATUS",type="string",JSONPath=".status.phase",description="The TrafficRouting status phase"
// +kubebuilder:printcolumn:name="MESSAGE",type="string",JSONPath=".status.message",description="The TrafficRouting canary status message"
// +kubebuilder:printcolumn:name="AGE",type=date,JSONPath=".metadata.creationTimestamp"
// TrafficRouting is the Schema for the TrafficRoutings API
type TrafficRouting struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec TrafficRoutingSpec `json:"spec,omitempty"`
Status TrafficRoutingStatus `json:"status,omitempty"`
}
//+kubebuilder:object:root=true
// TrafficRoutingList contains a list of TrafficRouting
type TrafficRoutingList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []TrafficRouting `json:"items"`
}
func init() {
SchemeBuilder.Register(&TrafficRouting{}, &TrafficRoutingList{})
}

View File

@ -178,24 +178,13 @@ func (in *CanaryStatus) DeepCopy() *CanaryStatus {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CanaryStep) DeepCopyInto(out *CanaryStep) {
*out = *in
if in.Weight != nil {
in, out := &in.Weight, &out.Weight
*out = new(int32)
**out = **in
}
in.TrafficRoutingStrategy.DeepCopyInto(&out.TrafficRoutingStrategy)
if in.Replicas != nil {
in, out := &in.Replicas, &out.Replicas
*out = new(intstr.IntOrString)
**out = **in
}
in.Pause.DeepCopyInto(&out.Pause)
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 CanaryStep.
@ -240,13 +229,9 @@ func (in *CanaryStrategy) DeepCopyInto(out *CanaryStrategy) {
}
if in.TrafficRoutings != nil {
in, out := &in.TrafficRoutings, &out.TrafficRoutings
*out = make([]*TrafficRouting, len(*in))
*out = make([]TrafficRoutingRef, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(TrafficRouting)
(*in).DeepCopyInto(*out)
}
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.FailureThreshold != nil {
@ -254,6 +239,11 @@ func (in *CanaryStrategy) DeepCopyInto(out *CanaryStrategy) {
*out = new(intstr.IntOrString)
**out = **in
}
if in.PatchPodTemplateMetadata != nil {
in, out := &in.PatchPodTemplateMetadata, &out.PatchPodTemplateMetadata
*out = new(PatchPodTemplateMetadata)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CanaryStrategy.
@ -427,6 +417,35 @@ 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 *PatchPodTemplateMetadata) DeepCopyInto(out *PatchPodTemplateMetadata) {
*out = *in
if in.Annotations != nil {
in, out := &in.Annotations, &out.Annotations
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Labels != nil {
in, out := &in.Labels, &out.Labels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchPodTemplateMetadata.
func (in *PatchPodTemplateMetadata) DeepCopy() *PatchPodTemplateMetadata {
if in == nil {
return nil
}
out := new(PatchPodTemplateMetadata)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Pod) DeepCopyInto(out *Pod) {
*out = *in
@ -476,6 +495,11 @@ func (in *ReleasePlan) DeepCopyInto(out *ReleasePlan) {
*out = new(intstr.IntOrString)
**out = **in
}
if in.PatchPodTemplateMetadata != nil {
in, out := &in.PatchPodTemplateMetadata, &out.PatchPodTemplateMetadata
*out = new(PatchPodTemplateMetadata)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReleasePlan.
@ -783,16 +807,10 @@ func (in *ServiceInfo) DeepCopy() *ServiceInfo {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TrafficRouting) DeepCopyInto(out *TrafficRouting) {
*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)
}
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRouting.
@ -805,6 +823,14 @@ func (in *TrafficRouting) DeepCopy() *TrafficRouting {
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TrafficRouting) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TrafficRoutingInfo) DeepCopyInto(out *TrafficRoutingInfo) {
*out = *in
@ -830,6 +856,133 @@ func (in *TrafficRoutingInfo) DeepCopy() *TrafficRoutingInfo {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TrafficRoutingList) DeepCopyInto(out *TrafficRoutingList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]TrafficRouting, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRoutingList.
func (in *TrafficRoutingList) DeepCopy() *TrafficRoutingList {
if in == nil {
return nil
}
out := new(TrafficRoutingList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TrafficRoutingList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
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)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRoutingRef.
func (in *TrafficRoutingRef) DeepCopy() *TrafficRoutingRef {
if in == nil {
return nil
}
out := new(TrafficRoutingRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TrafficRoutingSpec) DeepCopyInto(out *TrafficRoutingSpec) {
*out = *in
if in.ObjectRef != nil {
in, out := &in.ObjectRef, &out.ObjectRef
*out = make([]TrafficRoutingRef, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.Strategy.DeepCopyInto(&out.Strategy)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRoutingSpec.
func (in *TrafficRoutingSpec) DeepCopy() *TrafficRoutingSpec {
if in == nil {
return nil
}
out := new(TrafficRoutingSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TrafficRoutingStatus) DeepCopyInto(out *TrafficRoutingStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRoutingStatus.
func (in *TrafficRoutingStatus) DeepCopy() *TrafficRoutingStatus {
if in == nil {
return nil
}
out := new(TrafficRoutingStatus)
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.Weight != nil {
in, out := &in.Weight, &out.Weight
*out = new(int32)
**out = **in
}
if in.RequestHeaderModifier != nil {
in, out := &in.RequestHeaderModifier, &out.RequestHeaderModifier
*out = new(v1alpha2.HTTPRequestHeaderFilter)
(*in).DeepCopyInto(*out)
}
if in.Matches != nil {
in, out := &in.Matches, &out.Matches
*out = make([]HttpRouteMatch, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRoutingStrategy.
func (in *TrafficRoutingStrategy) DeepCopy() *TrafficRoutingStrategy {
if in == nil {
return nil
}
out := new(TrafficRoutingStrategy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkloadInfo) DeepCopyInto(out *WorkloadInfo) {
*out = *in

View File

@ -102,6 +102,22 @@ spec:
description: FinalizingPolicy define the behavior of controller
when phase enter Finalizing Defaults to "Immediate"
type: string
patchPodTemplateMetadata:
description: PatchPodTemplateMetadata indicates patch configuration(e.g.
labels, annotations) to the canary deployment podTemplateSpec.metadata
only support for canary deployment
properties:
annotations:
additionalProperties:
type: string
description: annotations
type: object
labels:
additionalProperties:
type: string
description: labels
type: object
type: object
rolloutID:
description: RolloutID indicates an id for each rollout progress
type: string

View File

@ -104,6 +104,22 @@ spec:
is nil, Rollout will use the MaxUnavailable of workload
as its FailureThreshold. Defaults to nil.
x-kubernetes-int-or-string: true
patchPodTemplateMetadata:
description: PatchPodTemplateMetadata indicates patch configuration(e.g.
labels, annotations) to the canary deployment podTemplateSpec.metadata
only support for canary deployment
properties:
annotations:
additionalProperties:
type: string
description: annotations
type: object
labels:
additionalProperties:
type: string
description: labels
type: object
type: object
steps:
description: Steps define the order of phases to execute release
in batches(20%, 40%, 60%, 80%, 100%)
@ -199,6 +215,110 @@ spec:
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
weight:
description: Weight indicate how many percentage of
traffic the canary pods should receive
@ -209,9 +329,9 @@ spec:
trafficRoutings:
description: TrafficRoutings hosts all the supported service
meshes supported to enable more fine-grained traffic routing
todo current only support one TrafficRouting
and current only support one TrafficRouting
items:
description: TrafficRouting hosts all the different configuration
description: TrafficRoutingRef hosts all the different configuration
for supported service meshes to enable more fine-grained
traffic routing
properties:

View File

@ -0,0 +1,300 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.7.0
creationTimestamp: null
name: trafficroutings.rollouts.kruise.io
spec:
group: rollouts.kruise.io
names:
kind: TrafficRouting
listKind: TrafficRoutingList
plural: trafficroutings
singular: trafficrouting
scope: Namespaced
versions:
- additionalPrinterColumns:
- description: The TrafficRouting status phase
jsonPath: .status.phase
name: STATUS
type: string
- description: The TrafficRouting canary status message
jsonPath: .status.message
name: MESSAGE
type: string
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: TrafficRouting is the Schema for the TrafficRoutings API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
properties:
objectRef:
description: ObjectRef indicates trafficRouting ref
items:
description: TrafficRoutingRef hosts all the different configuration
for supported service meshes to enable more fine-grained traffic
routing
properties:
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
strategy:
description: trafficrouting strategy
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
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
weight:
description: Weight indicate how many percentage of traffic the
canary pods should receive
format: int32
type: integer
type: object
required:
- objectRef
- strategy
type: object
status:
properties:
message:
description: Message provides details on why the rollout is in its
current phase
type: string
observedGeneration:
description: observedGeneration is the most recent generation observed
for this Rollout.
format: int64
type: integer
phase:
description: Phase is the trafficRouting phase.
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -5,6 +5,7 @@ resources:
- bases/rollouts.kruise.io_rollouts.yaml
- bases/rollouts.kruise.io_batchreleases.yaml
- bases/rollouts.kruise.io_rollouthistories.yaml
- bases/rollouts.kruise.io_trafficroutings.yaml
#+kubebuilder:scaffold:crdkustomizeresource
patchesStrategicMerge:

View File

@ -340,3 +340,29 @@ rules:
- get
- patch
- update
- apiGroups:
- rollouts.kruise.io
resources:
- trafficroutings
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- rollouts.kruise.io
resources:
- trafficroutings/finalizers
verbs:
- update
- apiGroups:
- rollouts.kruise.io
resources:
- trafficroutings/status
verbs:
- get
- patch
- update

View File

@ -8,6 +8,26 @@ webhooks:
matchExpressions:
- key: rollouts.kruise.io/workload-type
operator: Exists
- name: mcloneset.kb.io
objectSelector:
matchExpressions:
- key: rollouts.kruise.io/workload-type
operator: Exists
- name: mdaemonset.kb.io
objectSelector:
matchExpressions:
- key: rollouts.kruise.io/workload-type
operator: Exists
- name: mstatefulset.kb.io
objectSelector:
matchExpressions:
- key: rollouts.kruise.io/workload-type
operator: Exists
- name: madvancedstatefulset.kb.io
objectSelector:
matchExpressions:
- key: rollouts.kruise.io/workload-type
operator: Exists
- name: mdeployment.kb.io
objectSelector:
matchExpressions:
@ -15,3 +35,5 @@ webhooks:
operator: NotIn
values:
- controller-manager
- key: rollouts.kruise.io/workload-type
operator: Exists

View File

@ -0,0 +1,45 @@
function split(input, delimiter)
local arr = {}
string.gsub(input, '[^' .. delimiter ..']+', function(w) table.insert(arr, w) end)
return arr
end
annotations = obj.annotations
annotations["nginx.ingress.kubernetes.io/canary"] = "true"
annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = nil
annotations["nginx.ingress.kubernetes.io/canary-by-header"] = nil
annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = nil
annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = nil
annotations["nginx.ingress.kubernetes.io/canary-weight"] = nil
if ( obj.weight ~= "-1" )
then
annotations["nginx.ingress.kubernetes.io/canary-weight"] = obj.weight
end
if ( obj.requestHeaderModifier )
then
local str = ''
for _,header in ipairs(obj.requestHeaderModifier.set) do
str = str..string.format("%s %s", header.name, header.value)
end
annotations["mse.ingress.kubernetes.io/request-header-control-update"] = str
end
if ( not obj.matches )
then
return annotations
end
for _,match in ipairs(obj.matches) do
header = match.headers[1]
if ( header.name == "canary-by-cookie" )
then
annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = header.value
else
annotations["nginx.ingress.kubernetes.io/canary-by-header"] = header.name
if ( header.type == "RegularExpression" )
then
annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = header.value
else
annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = header.value
end
end
end
return annotations

10
main.go
View File

@ -27,6 +27,7 @@ import (
"github.com/openkruise/rollouts/pkg/controller/deployment"
"github.com/openkruise/rollouts/pkg/controller/rollout"
"github.com/openkruise/rollouts/pkg/controller/rollouthistory"
"github.com/openkruise/rollouts/pkg/controller/trafficrouting"
utilclient "github.com/openkruise/rollouts/pkg/util/client"
utilfeature "github.com/openkruise/rollouts/pkg/util/feature"
"github.com/openkruise/rollouts/pkg/webhook"
@ -110,6 +111,15 @@ func main() {
os.Exit(1)
}
if err = (&trafficrouting.TrafficRoutingReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorderFor("trafficrouting-controller"),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "TrafficRouting")
os.Exit(1)
}
if err = br.Add(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "BatchRelease")
os.Exit(1)

View File

@ -132,6 +132,16 @@ func (r *realCanaryController) create(release *v1alpha1.BatchRelease, template *
// spec
canary.Spec = *template.Spec.DeepCopy()
// patch canary pod metadata
if release.Spec.ReleasePlan.PatchPodTemplateMetadata != nil {
patch := release.Spec.ReleasePlan.PatchPodTemplateMetadata
for k, v := range patch.Labels {
canary.Spec.Template.Labels[k] = v
}
for k, v := range patch.Annotations {
canary.Spec.Template.Annotations[k] = v
}
}
canary.Spec.Replicas = pointer.Int32Ptr(0)
canary.Spec.Paused = false
@ -168,7 +178,7 @@ func (r *realCanaryController) listDeployment(release *v1alpha1.BatchRelease, op
}
// return the latest deployment with the newer creation time
func filterCanaryDeployment(ds []*apps.Deployment, template *corev1.PodTemplateSpec) *apps.Deployment {
func filterCanaryDeployment(release *v1alpha1.BatchRelease, ds []*apps.Deployment, template *corev1.PodTemplateSpec) *apps.Deployment {
if len(ds) == 0 {
return nil
}
@ -179,7 +189,18 @@ func filterCanaryDeployment(ds []*apps.Deployment, template *corev1.PodTemplateS
return ds[0]
}
for _, d := range ds {
if util.EqualIgnoreHash(template, &d.Spec.Template) {
dClone := d.DeepCopy()
// remove the canary pod metadata
if release.Spec.ReleasePlan.PatchPodTemplateMetadata != nil {
patch := release.Spec.ReleasePlan.PatchPodTemplateMetadata
for k := range patch.Labels {
delete(dClone.Spec.Template.Labels, k)
}
for k := range patch.Annotations {
delete(dClone.Spec.Template.Annotations, k)
}
}
if util.EqualIgnoreHash(template, &dClone.Spec.Template) {
return d
}
}

View File

@ -73,8 +73,7 @@ func (rc *realController) BuildCanaryController(release *v1alpha1.BatchRelease)
if client.IgnoreNotFound(err) != nil {
return rc, err
}
rc.canaryObject = filterCanaryDeployment(util.FilterActiveDeployment(ds), template)
rc.canaryObject = filterCanaryDeployment(release, util.FilterActiveDeployment(ds), template)
if rc.canaryObject == nil {
return rc, control.GenerateNotFoundError(fmt.Sprintf("%v-canary", rc.stableKey), "Deployment")
}

View File

@ -316,7 +316,7 @@ func getCanaryDeployment(release *v1alpha1.BatchRelease, stable *apps.Deployment
if len(ds) == 0 {
return nil
}
return filterCanaryDeployment(ds, &stable.Spec.Template)
return filterCanaryDeployment(release, ds, &stable.Spec.Template)
}
func checkWorkloadInfo(stableInfo *util.WorkloadInfo, deployment *apps.Deployment) {

View File

@ -45,7 +45,7 @@ type canaryReleaseManager struct {
recorder record.EventRecorder
}
func (m *canaryReleaseManager) runCanary(c *util.RolloutContext) error {
func (m *canaryReleaseManager) runCanary(c *RolloutContext) error {
canaryStatus := c.NewStatus.CanaryStatus
if br, err := m.fetchBatchRelease(c.Rollout.Namespace, c.Rollout.Name); err != nil && !errors.IsNotFound(err) {
klog.Errorf("rollout(%s/%s) fetch batchRelease failed: %s", c.Rollout.Namespace, c.Rollout.Name, err.Error())
@ -70,7 +70,9 @@ func (m *canaryReleaseManager) runCanary(c *util.RolloutContext) error {
// 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 {
done, err := m.trafficRoutingManager.FinalisingTrafficRouting(c, false)
tr := newTrafficRoutingContext(c)
done, err := m.trafficRoutingManager.FinalisingTrafficRouting(tr, false)
c.NewStatus.CanaryStatus.LastUpdateTime = tr.LastUpdateTime
if err != nil {
return err
} else if !done {
@ -95,7 +97,9 @@ func (m *canaryReleaseManager) runCanary(c *util.RolloutContext) error {
case v1alpha1.CanaryStepStateTrafficRouting:
klog.Infof("rollout(%s/%s) run canary strategy, and state(%s)", c.Rollout.Namespace, c.Rollout.Name, v1alpha1.CanaryStepStateTrafficRouting)
done, err := m.trafficRoutingManager.DoTrafficRouting(c)
tr := newTrafficRoutingContext(c)
done, err := m.trafficRoutingManager.DoTrafficRouting(tr)
c.NewStatus.CanaryStatus.LastUpdateTime = tr.LastUpdateTime
if err != nil {
return err
} else if done {
@ -142,6 +146,7 @@ func (m *canaryReleaseManager) runCanary(c *util.RolloutContext) error {
klog.Infof("rollout(%s/%s) canary run all steps, and completed", c.Rollout.Namespace, c.Rollout.Name)
canaryStatus.LastUpdateTime = &metav1.Time{Time: time.Now()}
canaryStatus.CurrentStepState = v1alpha1.CanaryStepStateCompleted
return nil
}
klog.Infof("rollout(%s/%s) step(%d) state from(%s) -> to(%s)", c.Rollout.Namespace, c.Rollout.Name,
canaryStatus.CurrentStepIndex, v1alpha1.CanaryStepStateReady, canaryStatus.CurrentStepState)
@ -153,7 +158,7 @@ func (m *canaryReleaseManager) runCanary(c *util.RolloutContext) error {
return nil
}
func (m *canaryReleaseManager) doCanaryUpgrade(c *util.RolloutContext) (bool, error) {
func (m *canaryReleaseManager) doCanaryUpgrade(c *RolloutContext) (bool, error) {
// verify whether batchRelease configuration is the latest
steps := len(c.Rollout.Spec.Strategy.Canary.Steps)
canaryStatus := c.NewStatus.CanaryStatus
@ -184,12 +189,12 @@ func (m *canaryReleaseManager) doCanaryUpgrade(c *util.RolloutContext) (bool, er
return true, nil
}
func (m *canaryReleaseManager) doCanaryMetricsAnalysis(c *util.RolloutContext) (bool, error) {
func (m *canaryReleaseManager) doCanaryMetricsAnalysis(c *RolloutContext) (bool, error) {
// todo
return true, nil
}
func (m *canaryReleaseManager) doCanaryPaused(c *util.RolloutContext) (bool, error) {
func (m *canaryReleaseManager) doCanaryPaused(c *RolloutContext) (bool, error) {
canaryStatus := c.NewStatus.CanaryStatus
currentStep := c.Rollout.Spec.Strategy.Canary.Steps[canaryStatus.CurrentStepIndex-1]
steps := len(c.Rollout.Spec.Strategy.Canary.Steps)
@ -223,7 +228,7 @@ func (m *canaryReleaseManager) doCanaryPaused(c *util.RolloutContext) (bool, err
}
// cleanup after rollout is completed or finished
func (m *canaryReleaseManager) doCanaryFinalising(c *util.RolloutContext) (bool, error) {
func (m *canaryReleaseManager) doCanaryFinalising(c *RolloutContext) (bool, error) {
// when CanaryStatus is nil, which means canary action hasn't started yet, don't need doing cleanup
if c.NewStatus.CanaryStatus == nil {
return true, nil
@ -233,8 +238,10 @@ func (m *canaryReleaseManager) doCanaryFinalising(c *util.RolloutContext) (bool,
if err != nil {
return false, err
}
tr := newTrafficRoutingContext(c)
// 2. remove stable service the pod revision selector, so stable service will be selector all version pods.
done, err := m.trafficRoutingManager.FinalisingTrafficRouting(c, true)
done, err := m.trafficRoutingManager.FinalisingTrafficRouting(tr, true)
c.NewStatus.CanaryStatus.LastUpdateTime = tr.LastUpdateTime
if err != nil || !done {
return done, err
}
@ -244,7 +251,8 @@ func (m *canaryReleaseManager) doCanaryFinalising(c *util.RolloutContext) (bool,
return done, err
}
// 4. modify network api(ingress or gateway api) configuration, and route 100% traffic to stable pods.
done, err = m.trafficRoutingManager.FinalisingTrafficRouting(c, false)
done, err = m.trafficRoutingManager.FinalisingTrafficRouting(tr, false)
c.NewStatus.CanaryStatus.LastUpdateTime = tr.LastUpdateTime
if err != nil || !done {
return done, err
}
@ -260,7 +268,7 @@ func (m *canaryReleaseManager) doCanaryFinalising(c *util.RolloutContext) (bool,
return true, nil
}
func (m *canaryReleaseManager) removeRolloutProgressingAnnotation(c *util.RolloutContext) error {
func (m *canaryReleaseManager) removeRolloutProgressingAnnotation(c *RolloutContext) error {
if c.Workload == nil {
return nil
}
@ -270,10 +278,8 @@ func (m *canaryReleaseManager) removeRolloutProgressingAnnotation(c *util.Rollou
workloadRef := c.Rollout.Spec.ObjectRef.WorkloadRef
workloadGVK := schema.FromAPIVersionAndKind(workloadRef.APIVersion, workloadRef.Kind)
obj := util.GetEmptyWorkloadObject(workloadGVK)
if err := m.Get(context.TODO(), types.NamespacedName{Name: c.Workload.Name, Namespace: c.Workload.Namespace}, obj); err != nil {
klog.Errorf("getting updated workload(%s.%s) failed: %s", c.Workload.Namespace, c.Workload.Name, err.Error())
return err
}
obj.SetNamespace(c.Workload.Namespace)
obj.SetName(c.Workload.Name)
body := fmt.Sprintf(`{"metadata":{"annotations":{"%s":null}}}`, util.InRolloutProgressingAnnotation)
if err := m.Patch(context.TODO(), obj, client.RawPatch(types.MergePatchType, []byte(body))); err != nil {
klog.Errorf("rollout(%s/%s) patch workload(%s) failed: %s", c.Rollout.Namespace, c.Rollout.Name, c.Workload.Name, err.Error())
@ -359,6 +365,7 @@ func createBatchRelease(rollout *v1alpha1.Rollout, rolloutID string, batch int32
RolloutID: rolloutID,
BatchPartition: utilpointer.Int32Ptr(batch),
FailureThreshold: rollout.Spec.Strategy.Canary.FailureThreshold,
PatchPodTemplateMetadata: rollout.Spec.Strategy.Canary.PatchPodTemplateMetadata,
},
},
}
@ -375,7 +382,7 @@ func createBatchRelease(rollout *v1alpha1.Rollout, rolloutID string, batch int32
return br
}
func (m *canaryReleaseManager) removeBatchRelease(c *util.RolloutContext) (bool, error) {
func (m *canaryReleaseManager) removeBatchRelease(c *RolloutContext) (bool, error) {
batch := &v1alpha1.BatchRelease{}
err := m.Get(context.TODO(), client.ObjectKey{Namespace: c.Rollout.Namespace, Name: c.Rollout.Name}, batch)
if err != nil && errors.IsNotFound(err) {
@ -399,7 +406,7 @@ func (m *canaryReleaseManager) removeBatchRelease(c *util.RolloutContext) (bool,
return false, nil
}
func (m *canaryReleaseManager) finalizingBatchRelease(c *util.RolloutContext) (bool, error) {
func (m *canaryReleaseManager) finalizingBatchRelease(c *RolloutContext) (bool, error) {
br, err := m.fetchBatchRelease(c.Rollout.Namespace, c.Rollout.Name)
if err != nil {
if errors.IsNotFound(err) {

View File

@ -18,6 +18,7 @@ package rollout
import (
"context"
"fmt"
"reflect"
"testing"
@ -233,13 +234,17 @@ func TestRunCanary(t *testing.T) {
trafficRoutingManager: r.trafficRoutingManager,
recorder: r.Recorder,
}
workload, _ := r.finder.GetWorkloadForRef(rollout)
c := &util.RolloutContext{
workload, err := r.finder.GetWorkloadForRef(rollout)
if err != nil {
t.Fatalf("GetWorkloadForRef failed: %s", err.Error())
}
c := &RolloutContext{
Rollout: rollout,
NewStatus: rollout.Status.DeepCopy(),
Workload: workload,
}
err := r.canaryManager.runCanary(c)
fmt.Println(util.DumpJSON(c))
err = r.canaryManager.runCanary(c)
if err != nil {
t.Fatalf("reconcileRolloutProgressing failed: %s", err.Error())
}
@ -310,7 +315,7 @@ func TestRunCanaryPaused(t *testing.T) {
trafficRoutingManager: r.trafficRoutingManager,
recorder: r.Recorder,
}
c := &util.RolloutContext{
c := &RolloutContext{
Rollout: rollout,
NewStatus: rollout.Status.DeepCopy(),
}

View File

@ -57,23 +57,31 @@ var (
Canary: &v1alpha1.CanaryStrategy{
Steps: []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(5),
},
Replicas: &intstr.IntOrString{IntVal: 1},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Replicas: &intstr.IntOrString{IntVal: 2},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Replicas: &intstr.IntOrString{IntVal: 6},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Replicas: &intstr.IntOrString{IntVal: 10},
},
},
TrafficRoutings: []*v1alpha1.TrafficRouting{
TrafficRoutings: []v1alpha1.TrafficRoutingRef{
{
Service: "echoserver",
Ingress: &v1alpha1.IngressTrafficRouting{

View File

@ -17,20 +17,36 @@ limitations under the License.
package rollout
import (
"context"
"fmt"
"strconv"
"time"
"github.com/openkruise/rollouts/api/v1alpha1"
"github.com/openkruise/rollouts/pkg/trafficrouting"
"github.com/openkruise/rollouts/pkg/util"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
var defaultGracePeriodSeconds int32 = 3
type RolloutContext struct {
Rollout *v1alpha1.Rollout
NewStatus *v1alpha1.RolloutStatus
// related workload
Workload *util.Workload
// reconcile RequeueAfter recheckTime
RecheckTime *time.Time
// wait stable workload pods ready
WaitReady bool
}
// parameter1 retryReconcile, parameter2 error
func (r *RolloutReconciler) reconcileRolloutProgressing(rollout *v1alpha1.Rollout, newStatus *v1alpha1.RolloutStatus) (*time.Time, error) {
cond := util.GetRolloutCondition(rollout.Status, v1alpha1.RolloutConditionProgressing)
@ -46,7 +62,7 @@ func (r *RolloutReconciler) reconcileRolloutProgressing(rollout *v1alpha1.Rollou
klog.Infof("rollout(%s/%s) workload status is inconsistent, then wait a moment", rollout.Namespace, rollout.Name)
return nil, nil
}
rolloutContext := &util.RolloutContext{Rollout: rollout, NewStatus: newStatus, Workload: workload}
rolloutContext := &RolloutContext{Rollout: rollout, NewStatus: newStatus, Workload: workload}
switch cond.Reason {
case v1alpha1.ProgressingReasonInitializing:
klog.Infof("rollout(%s/%s) is Progressing, and in reason(%s)", rollout.Namespace, rollout.Name, cond.Reason)
@ -132,10 +148,10 @@ func (r *RolloutReconciler) reconcileRolloutProgressing(rollout *v1alpha1.Rollou
return rolloutContext.RecheckTime, nil
}
func (r *RolloutReconciler) doProgressingInitializing(c *util.RolloutContext) (bool, error) {
func (r *RolloutReconciler) doProgressingInitializing(c *RolloutContext) (bool, error) {
// Traffic routing
if len(c.Rollout.Spec.Strategy.Canary.TrafficRoutings) > 0 {
if err := r.trafficRoutingManager.InitializeTrafficRouting(c); err != nil {
if err := r.trafficRoutingManager.InitializeTrafficRouting(newTrafficRoutingContext(c)); err != nil {
return false, err
}
}
@ -149,10 +165,15 @@ func (r *RolloutReconciler) doProgressingInitializing(c *util.RolloutContext) (b
klog.Infof("verify rollout(%s/%s) TrafficRouting, and wait a moment", c.Rollout.Namespace, c.Rollout.Name)
return false, nil
}
// handler TrafficRouting
if c.Rollout.Annotations[v1alpha1.TrafficRoutingAnnotation] != "" {
return r.handlerTrafficRouting(c.Rollout.Namespace, c.Rollout.Name, c.Rollout.Annotations[v1alpha1.TrafficRoutingAnnotation])
}
return true, nil
}
func (r *RolloutReconciler) doProgressingInRolling(c *util.RolloutContext) error {
func (r *RolloutReconciler) doProgressingInRolling(c *RolloutContext) error {
// Handle the 5 special cases firstly, and we had better keep the order of following cases:
switch {
@ -185,7 +206,7 @@ func (r *RolloutReconciler) handleRolloutPaused(rollout *v1alpha1.Rollout, newSt
return nil
}
func (r *RolloutReconciler) handleContinuousRelease(c *util.RolloutContext) error {
func (r *RolloutReconciler) handleContinuousRelease(c *RolloutContext) error {
r.Recorder.Eventf(c.Rollout, corev1.EventTypeNormal, "Progressing", "workload continuous publishing canaryRevision, then restart publishing")
klog.Infof("rollout(%s/%s) workload continuous publishing canaryRevision from(%s) -> to(%s), then restart publishing",
c.Rollout.Namespace, c.Rollout.Name, c.NewStatus.CanaryStatus.CanaryRevision, c.Workload.CanaryRevision)
@ -226,7 +247,7 @@ func (r *RolloutReconciler) handleRollbackInBatches(rollout *v1alpha1.Rollout, w
return nil
}
func (r *RolloutReconciler) handleRolloutPlanChanged(c *util.RolloutContext) error {
func (r *RolloutReconciler) handleRolloutPlanChanged(c *RolloutContext) error {
newStepIndex, err := r.recalculateCanaryStep(c)
if err != nil {
klog.Errorf("rollout(%s/%s) reCalculate Canary StepIndex failed: %s", c.Rollout.Namespace, c.Rollout.Name, err.Error())
@ -242,14 +263,63 @@ func (r *RolloutReconciler) handleRolloutPlanChanged(c *util.RolloutContext) err
return nil
}
func (r *RolloutReconciler) handleNormalRolling(c *util.RolloutContext) error {
func (r *RolloutReconciler) handleNormalRolling(c *RolloutContext) error {
// check if canary is done
if c.NewStatus.CanaryStatus.CurrentStepState == v1alpha1.CanaryStepStateCompleted {
klog.Infof("rollout(%s/%s) progressing rolling done", c.Rollout.Namespace, c.Rollout.Name)
progressingStateTransition(c.NewStatus, corev1.ConditionTrue, v1alpha1.ProgressingReasonFinalising, "Rollout has been completed and some closing work is being done")
} else { // rollout is in rolling
return nil
}
return r.canaryManager.runCanary(c)
}
// name is rollout name, tr is trafficRouting name
func (r *RolloutReconciler) handlerTrafficRouting(namespace, name, tr string) (bool, error) {
obj := &v1alpha1.TrafficRouting{}
err := r.Get(context.TODO(), client.ObjectKey{Namespace: namespace, Name: tr}, obj)
if err != nil {
if errors.IsNotFound(err) {
klog.Warningf("rollout(%s/%s) trafficRouting(%s) Not Found, and wait a moment", namespace, name, tr)
return false, nil
}
return false, err
}
if controllerutil.ContainsFinalizer(obj, util.ProgressingRolloutFinalizer(name)) {
return true, nil
}
if obj.Status.Phase == v1alpha1.TrafficRoutingPhaseFinalizing || obj.Status.Phase == v1alpha1.TrafficRoutingPhaseTerminating {
klog.Infof("rollout(%s/%s) trafficRouting(%s) phase(%s), and wait a moment", namespace, name, tr, obj.Status.Phase)
return false, nil
}
err = util.UpdateFinalizer(r.Client, obj, util.AddFinalizerOpType, util.ProgressingRolloutFinalizer(name))
if err != nil {
klog.Errorf("rollout(%s/%s) add trafficRouting(%s) finalizer failed: %s", namespace, name, tr, err.Error())
return false, err
}
klog.Infof("rollout(%s/%s) add trafficRouting(%s) finalizer(%s) success", namespace, name, tr, util.ProgressingRolloutFinalizer(name))
return false, nil
}
func (r *RolloutReconciler) finalizerTrafficRouting(namespace, name, tr string) error {
obj := &v1alpha1.TrafficRouting{}
err := r.Get(context.TODO(), client.ObjectKey{Namespace: namespace, Name: tr}, obj)
if err != nil {
if errors.IsNotFound(err) {
return nil
}
return err
}
// progressing.rollouts.kruise.io/rollout-b
finalizer := util.ProgressingRolloutFinalizer(name)
if controllerutil.ContainsFinalizer(obj, finalizer) {
err = util.UpdateFinalizer(r.Client, obj, util.RemoveFinalizerOpType, finalizer)
if err != nil {
klog.Errorf("rollout(%s/%s) remove trafficRouting(%s) finalizer failed: %s", namespace, name, tr, err.Error())
return err
}
klog.Infof("rollout(%s/%s) remove trafficRouting(%s) remove finalizer(%s) success", namespace, name, tr, finalizer)
return nil
}
return nil
}
@ -288,10 +358,12 @@ func isRollingBackInBatches(rollout *v1alpha1.Rollout, workload *util.Workload)
// 1. modify network api(ingress or gateway api) configuration, and route 100% traffic to stable pods
// 2. remove batchRelease CR.
func (r *RolloutReconciler) doProgressingReset(c *util.RolloutContext) (bool, error) {
func (r *RolloutReconciler) doProgressingReset(c *RolloutContext) (bool, error) {
if len(c.Rollout.Spec.Strategy.Canary.TrafficRoutings) > 0 {
// modify network api(ingress or gateway api) configuration, and route 100% traffic to stable pods
done, err := r.trafficRoutingManager.FinalisingTrafficRouting(c, false)
tr := newTrafficRoutingContext(c)
done, err := r.trafficRoutingManager.FinalisingTrafficRouting(tr, false)
c.NewStatus.CanaryStatus.LastUpdateTime = tr.LastUpdateTime
if err != nil || !done {
return done, err
}
@ -306,7 +378,7 @@ func (r *RolloutReconciler) doProgressingReset(c *util.RolloutContext) (bool, er
return true, nil
}
func (r *RolloutReconciler) recalculateCanaryStep(c *util.RolloutContext) (int32, error) {
func (r *RolloutReconciler) recalculateCanaryStep(c *RolloutContext) (int32, error) {
batch, err := r.canaryManager.fetchBatchRelease(c.Rollout.Namespace, c.Rollout.Name)
if errors.IsNotFound(err) {
return 1, nil
@ -332,8 +404,14 @@ func (r *RolloutReconciler) recalculateCanaryStep(c *util.RolloutContext) (int32
return stepIndex, nil
}
func (r *RolloutReconciler) doFinalising(c *util.RolloutContext) (bool, error) {
func (r *RolloutReconciler) doFinalising(c *RolloutContext) (bool, error) {
klog.Infof("reconcile rollout(%s/%s) doFinalising", c.Rollout.Namespace, c.Rollout.Name)
if c.Rollout.Annotations[v1alpha1.TrafficRoutingAnnotation] != "" {
err := r.finalizerTrafficRouting(c.Rollout.Namespace, c.Rollout.Name, c.Rollout.Annotations[v1alpha1.TrafficRoutingAnnotation])
if err != nil {
return false, err
}
}
done, err := r.canaryManager.doCanaryFinalising(c)
if err != nil {
klog.Errorf("rollout(%s/%s) Progressing failed: %s", c.Rollout.Namespace, c.Rollout.Name, err.Error())
@ -370,3 +448,22 @@ func setRolloutSucceededCondition(status *v1alpha1.RolloutStatus, condStatus cor
}
util.SetRolloutCondition(status, *cond)
}
func newTrafficRoutingContext(c *RolloutContext) *trafficrouting.TrafficRoutingContext {
currentStep := c.Rollout.Spec.Strategy.Canary.Steps[c.NewStatus.CanaryStatus.CurrentStepIndex-1]
var revisionLabelKey string
if c.Workload != nil {
revisionLabelKey = c.Workload.RevisionLabelKey
}
return &trafficrouting.TrafficRoutingContext{
Key: fmt.Sprintf("Rollout(%s/%s)", c.Rollout.Namespace, c.Rollout.Name),
Namespace: c.Rollout.Namespace,
ObjectRef: c.Rollout.Spec.Strategy.Canary.TrafficRoutings,
Strategy: currentStep.TrafficRoutingStrategy,
OwnerRef: *metav1.NewControllerRef(c.Rollout, rolloutControllerKind),
RevisionLabelKey: revisionLabelKey,
StableRevision: c.NewStatus.CanaryStatus.StableRevision,
CanaryRevision: c.NewStatus.CanaryStatus.PodTemplateHash,
LastUpdateTime: c.NewStatus.CanaryStatus.LastUpdateTime,
}
}

View File

@ -348,7 +348,7 @@ func TestReconcileRolloutProgressing(t *testing.T) {
},
},
{
name: "ReconcileRolloutProgressing rolling -> rollback",
name: "ReconcileRolloutProgressing rolling -> rollback1",
getObj: func() ([]*apps.Deployment, []*apps.ReplicaSet) {
dep1 := deploymentDemo.DeepCopy()
dep1.Spec.Template.Spec.Containers[0].Image = "echoserver:v1"
@ -405,7 +405,7 @@ func TestReconcileRolloutProgressing(t *testing.T) {
},
},
{
name: "ReconcileRolloutProgressing rolling -> rollback",
name: "ReconcileRolloutProgressing rolling -> rollback2",
getObj: func() ([]*apps.Deployment, []*apps.ReplicaSet) {
dep1 := deploymentDemo.DeepCopy()
dep1.Spec.Template.Spec.Containers[0].Image = "echoserver:v1"
@ -604,14 +604,20 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
obj := rolloutDemo.DeepCopy()
obj.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
{
Weight: utilpointer.Int32(50),
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(50),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
},
}
return obj
},
@ -643,14 +649,20 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
obj := rolloutDemo.DeepCopy()
obj.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
{
Weight: utilpointer.Int32(40),
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
},
}
return obj
},
@ -682,14 +694,20 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
obj := rolloutDemo.DeepCopy()
obj.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
{
Weight: utilpointer.Int32(60),
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
},
}
return obj
},
@ -721,14 +739,20 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
obj := rolloutDemo.DeepCopy()
obj.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(10),
},
{
Weight: utilpointer.Int32(30),
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(30),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
},
}
return obj
},
@ -760,14 +784,18 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
obj := rolloutDemo.DeepCopy()
obj.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(2),
},
Replicas: &intstr.IntOrString{
Type: intstr.String,
StrVal: "10%",
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(3),
},
Replicas: &intstr.IntOrString{
Type: intstr.String,
StrVal: "10%",
@ -819,7 +847,7 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
if err != nil {
t.Fatalf(err.Error())
}
c := &util.RolloutContext{Rollout: rollout, Workload: workload}
c := &RolloutContext{Rollout: rollout, Workload: workload}
newStepIndex, err := reconciler.recalculateCanaryStep(c)
if err != nil {
t.Fatalf(err.Error())

View File

@ -25,6 +25,7 @@ import (
"github.com/openkruise/rollouts/api/v1alpha1"
"github.com/openkruise/rollouts/pkg/util"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/rand"
"k8s.io/client-go/util/retry"
@ -66,6 +67,32 @@ func (r *RolloutReconciler) calculateRolloutStatus(rollout *v1alpha1.Rollout) (r
klog.Infof("rollout(%s/%s) workload not found, and reset status be Initial", rollout.Namespace, rollout.Name)
return false, newStatus, nil
}
klog.V(5).Infof("rollout(%s/%s) workload(%s)", rollout.Namespace, rollout.Name, util.DumpJSON(workload))
// Patch objectSelector in workload labels[rollouts.kruise.io/workload-type],
// then rollout only webhook the workload which contains labels[rollouts.kruise.io/workload-type].
var workloadType util.WorkloadType
switch workload.Kind {
case util.ControllerKruiseKindCS.Kind:
workloadType = util.CloneSetType
case util.ControllerKindDep.Kind:
workloadType = util.DeploymentType
case util.ControllerKindSts.Kind:
workloadType = util.StatefulSetType
case util.ControllerKruiseKindDS.Kind:
workloadType = util.DaemonSetType
}
if workload.Annotations[util.WorkloadTypeLabel] == "" && workloadType != "" {
workloadGVK := schema.FromAPIVersionAndKind(workload.APIVersion, workload.Kind)
obj := util.GetEmptyWorkloadObject(workloadGVK)
obj.SetNamespace(workload.Namespace)
obj.SetName(workload.Name)
body := fmt.Sprintf(`{"metadata":{"labels":{"%s":"%s"}}}`, util.WorkloadTypeLabel, workloadType)
if err = r.Patch(context.TODO(), obj, client.RawPatch(types.MergePatchType, []byte(body))); err != nil {
klog.Errorf("rollout(%s/%s) patch workload(%s) failed: %s", rollout.Namespace, rollout.Name, workload.Name, err.Error())
return false, nil, err
}
klog.Infof("rollout(%s/%s) patch workload(%s) labels[%s] success", rollout.Namespace, rollout.Name, workload.Name, util.WorkloadTypeLabel)
}
// workload status generation is not equal to workload.generation
if !workload.IsStatusConsistent {
klog.Infof("rollout(%s/%s) workload status is inconsistent, then wait a moment", rollout.Namespace, rollout.Name)
@ -187,7 +214,7 @@ func (r *RolloutReconciler) reconcileRolloutTerminating(rollout *v1alpha1.Rollou
klog.Errorf("rollout(%s/%s) get workload failed: %s", rollout.Namespace, rollout.Name, err.Error())
return nil, err
}
c := &util.RolloutContext{Rollout: rollout, NewStatus: newStatus, Workload: workload}
c := &RolloutContext{Rollout: rollout, NewStatus: newStatus, Workload: workload}
done, err := r.doFinalising(c)
if err != nil {
return nil, err

View File

@ -63,11 +63,15 @@ func TestCalculateRolloutHash(t *testing.T) {
obj := rolloutDemo.DeepCopy()
obj.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(50),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
},
}
return obj
},

View File

@ -137,23 +137,29 @@ var (
Canary: &rolloutv1alpha1.CanaryStrategy{
Steps: []rolloutv1alpha1.CanaryStep{
{
TrafficRoutingStrategy: rolloutv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(5),
},
Pause: rolloutv1alpha1.RolloutPause{
Duration: utilpointer.Int32(0),
},
},
{
TrafficRoutingStrategy: rolloutv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
Pause: rolloutv1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: rolloutv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: rolloutv1alpha1.RolloutPause{
Duration: utilpointer.Int32(0),
},
},
},
TrafficRoutings: []*rolloutv1alpha1.TrafficRouting{
TrafficRoutings: []rolloutv1alpha1.TrafficRoutingRef{
{
Service: "service-demo",
Ingress: &rolloutv1alpha1.IngressTrafficRouting{

View File

@ -0,0 +1,227 @@
/*
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 trafficrouting
import (
"context"
"flag"
"fmt"
"reflect"
"strings"
"time"
"github.com/openkruise/rollouts/api/v1alpha1"
"github.com/openkruise/rollouts/pkg/trafficrouting"
"github.com/openkruise/rollouts/pkg/util"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/source"
)
var (
concurrentReconciles = 3
defaultGracePeriodSeconds int32 = 3
trControllerKind = v1alpha1.SchemeGroupVersion.WithKind("TrafficRouting")
)
func init() {
flag.IntVar(&concurrentReconciles, "trafficrouting-workers", 3, "Max concurrent workers for trafficrouting controller.")
}
// TrafficRoutingReconciler reconciles a TrafficRouting object
type TrafficRoutingReconciler struct {
client.Client
Scheme *runtime.Scheme
Recorder record.EventRecorder
trafficRoutingManager *trafficrouting.Manager
}
//+kubebuilder:rbac:groups=rollouts.kruise.io,resources=trafficroutings,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=rollouts.kruise.io,resources=trafficroutings/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=rollouts.kruise.io,resources=trafficroutings/finalizers,verbs=update
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Rollout object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile
func (r *TrafficRoutingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// Fetch the Rollout instance
tr := &v1alpha1.TrafficRouting{}
err := r.Get(context.TODO(), req.NamespacedName, tr)
if err != nil {
if errors.IsNotFound(err) {
// Object not found, return. Created objects are automatically garbage collected.
// For additional cleanup logic use finalizers.
return ctrl.Result{}, nil
}
// Error reading the object - requeue the request.
return ctrl.Result{}, err
}
klog.Infof("Begin to reconcile TrafficRouting %v", klog.KObj(tr))
// handle finalizer
err = r.handleFinalizer(tr)
if err != nil {
return ctrl.Result{}, err
}
newStatus := tr.Status.DeepCopy()
if newStatus.Phase == "" {
newStatus.Phase = v1alpha1.TrafficRoutingPhaseInitial
}
if !tr.DeletionTimestamp.IsZero() {
newStatus.Phase = v1alpha1.TrafficRoutingPhaseTerminating
}
var done = true
switch newStatus.Phase {
case v1alpha1.TrafficRoutingPhaseInitial:
err = r.trafficRoutingManager.InitializeTrafficRouting(newTrafficRoutingContext(tr))
if err == nil {
newStatus.Phase = v1alpha1.TrafficRoutingPhaseHealthy
newStatus.Message = "TrafficRouting is Healthy"
}
case v1alpha1.TrafficRoutingPhaseHealthy:
if rolloutProgressingFinalizer(tr).Len() > 0 {
newStatus.Phase = v1alpha1.TrafficRoutingPhaseProgressing
newStatus.Message = "TrafficRouting is Progressing"
}
case v1alpha1.TrafficRoutingPhaseProgressing:
if rolloutProgressingFinalizer(tr).Len() == 0 {
newStatus.Phase = v1alpha1.TrafficRoutingPhaseFinalizing
newStatus.Message = "TrafficRouting is Finalizing"
} else {
done, err = r.trafficRoutingManager.DoTrafficRouting(newTrafficRoutingContext(tr))
}
case v1alpha1.TrafficRoutingPhaseFinalizing:
done, err = r.trafficRoutingManager.FinalisingTrafficRouting(newTrafficRoutingContext(tr), false)
if done {
newStatus.Phase = v1alpha1.TrafficRoutingPhaseHealthy
newStatus.Message = "TrafficRouting is Healthy"
}
case v1alpha1.TrafficRoutingPhaseTerminating:
done, err = r.trafficRoutingManager.FinalisingTrafficRouting(newTrafficRoutingContext(tr), false)
if done {
// remove trafficRouting finalizer
err = r.handleFinalizer(tr)
}
}
if err != nil {
return ctrl.Result{}, err
} else if !done {
recheckTime := time.Now().Add(time.Duration(defaultGracePeriodSeconds) * time.Second)
return ctrl.Result{RequeueAfter: time.Until(recheckTime)}, nil
}
newStatus.ObservedGeneration = tr.Generation
return ctrl.Result{}, r.updateTrafficRoutingStatus(tr, *newStatus)
}
func (r *TrafficRoutingReconciler) updateTrafficRoutingStatus(tr *v1alpha1.TrafficRouting, newStatus v1alpha1.TrafficRoutingStatus) error {
if reflect.DeepEqual(tr.Status, newStatus) {
return nil
}
trClone := tr.DeepCopy()
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
if err := r.Client.Get(context.TODO(), types.NamespacedName{Namespace: tr.Namespace, Name: tr.Name}, trClone); err != nil {
klog.Errorf("error getting updated trafficRouting(%s/%s) from client", tr.Namespace, tr.Name)
return err
}
trClone.Status = newStatus
return r.Client.Status().Update(context.TODO(), trClone)
}); err != nil {
klog.Errorf("update trafficRouting(%s/%s) status failed: %s", tr.Namespace, tr.Name, err.Error())
return err
}
klog.Infof("trafficRouting(%s/%s) status from(%s) -> to(%s) success", tr.Namespace, tr.Name, util.DumpJSON(tr.Status), util.DumpJSON(newStatus))
return nil
}
// handle adding and handle finalizer logic, it turns if we should continue to reconcile
func (r *TrafficRoutingReconciler) handleFinalizer(tr *v1alpha1.TrafficRouting) error {
// delete trafficRouting crd, remove finalizer
if !tr.DeletionTimestamp.IsZero() {
err := util.UpdateFinalizer(r.Client, tr, util.RemoveFinalizerOpType, util.TrafficRoutingFinalizer)
if err != nil {
klog.Errorf("remove trafficRouting(%s/%s) finalizer failed: %s", tr.Namespace, tr.Name, err.Error())
return err
}
klog.Infof("remove trafficRouting(%s/%s) finalizer success", tr.Namespace, tr.Name)
return nil
}
// create trafficRouting crd, add finalizer
if !controllerutil.ContainsFinalizer(tr, util.TrafficRoutingFinalizer) {
err := util.UpdateFinalizer(r.Client, tr, util.AddFinalizerOpType, util.TrafficRoutingFinalizer)
if err != nil {
klog.Errorf("register trafficRouting(%s/%s) finalizer failed: %s", tr.Namespace, tr.Name, err.Error())
return err
}
klog.Infof("register trafficRouting(%s/%s) finalizer success", tr.Namespace, tr.Name)
}
return nil
}
func rolloutProgressingFinalizer(tr *v1alpha1.TrafficRouting) sets.String {
progressing := sets.String{}
for _, s := range tr.GetFinalizers() {
if strings.Contains(s, v1alpha1.ProgressingRolloutFinalizerPrefix) {
progressing.Insert(s)
}
}
return progressing
}
func (r *TrafficRoutingReconciler) SetupWithManager(mgr ctrl.Manager) error {
// Create a new controller
c, err := controller.New("trafficrouting-controller", mgr, controller.Options{
Reconciler: r, MaxConcurrentReconciles: concurrentReconciles})
if err != nil {
return err
}
// Watch for changes to trafficrouting
if err = c.Watch(&source.Kind{Type: &v1alpha1.TrafficRouting{}}, &handler.EnqueueRequestForObject{}); err != nil {
return err
}
r.trafficRoutingManager = trafficrouting.NewTrafficRoutingManager(mgr.GetClient())
return nil
}
func newTrafficRoutingContext(tr *v1alpha1.TrafficRouting) *trafficrouting.TrafficRoutingContext {
return &trafficrouting.TrafficRoutingContext{
Key: fmt.Sprintf("TrafficRouting(%s/%s)", tr.Namespace, tr.Name),
Namespace: tr.Namespace,
ObjectRef: tr.Spec.ObjectRef,
Strategy: tr.Spec.Strategy,
OwnerRef: *metav1.NewControllerRef(tr, trControllerKind),
OnlyTrafficRouting: true,
}
}

View File

@ -37,9 +37,27 @@ import (
var (
defaultGracePeriodSeconds int32 = 3
rolloutControllerKind = v1alpha1.SchemeGroupVersion.WithKind("Rollout")
)
type TrafficRoutingContext struct {
// only for log info
Key string
Namespace string
ObjectRef []v1alpha1.TrafficRoutingRef
Strategy v1alpha1.TrafficRoutingStrategy
// OnlyTrafficRouting
OnlyTrafficRouting bool
OwnerRef metav1.OwnerReference
// workload.RevisionLabelKey
RevisionLabelKey string
// status.CanaryStatus.StableRevision
StableRevision string
// status.CanaryStatus.PodTemplateHash
CanaryRevision string
// newStatus.canaryStatus.LastUpdateTime
LastUpdateTime *metav1.Time
}
// Manager responsible for adjusting network resources
// such as Service, Ingress, Gateway API, etc., to achieve traffic grayscale.
type Manager struct {
@ -52,48 +70,44 @@ func NewTrafficRoutingManager(c client.Client) *Manager {
// InitializeTrafficRouting determine if the network resources(service & ingress & gateway api) exist.
// If it is Ingress, init method will create the canary ingress resources, and set weight=0.
func (m *Manager) InitializeTrafficRouting(c *util.RolloutContext) error {
if len(c.Rollout.Spec.Strategy.Canary.TrafficRoutings) == 0 {
func (m *Manager) InitializeTrafficRouting(c *TrafficRoutingContext) error {
if len(c.ObjectRef) == 0 {
return nil
}
sService := c.Rollout.Spec.Strategy.Canary.TrafficRoutings[0].Service
objectRef := c.ObjectRef[0]
sService := objectRef.Service
// check service
service := &corev1.Service{}
if err := m.Get(context.TODO(), types.NamespacedName{Namespace: c.Rollout.Namespace, Name: sService}, service); err != nil {
if err := m.Get(context.TODO(), types.NamespacedName{Namespace: c.Namespace, Name: sService}, service); err != nil {
return err
}
cService := fmt.Sprintf("%s-canary", sService)
cService := getCanaryServiceName(sService, c.OnlyTrafficRouting)
// new network provider, ingress or gateway
trController, err := newNetworkProvider(m.Client, c.Rollout, c.NewStatus, sService, cService)
trController, err := newNetworkProvider(m.Client, c, sService, cService)
if err != nil {
klog.Errorf("rollout(%s/%s) newNetworkProvider failed: %s", c.Rollout.Namespace, c.Rollout.Name, err.Error())
klog.Errorf("%s newNetworkProvider failed: %s", c.Key, err.Error())
return err
}
return trController.Initialize(context.TODO())
}
func (m *Manager) DoTrafficRouting(c *util.RolloutContext) (bool, error) {
if len(c.Rollout.Spec.Strategy.Canary.TrafficRoutings) == 0 {
func (m *Manager) DoTrafficRouting(c *TrafficRoutingContext) (bool, error) {
if len(c.ObjectRef) == 0 {
return true, nil
}
trafficRouting := c.Rollout.Spec.Strategy.Canary.TrafficRoutings[0]
trafficRouting := c.ObjectRef[0]
if trafficRouting.GracePeriodSeconds <= 0 {
trafficRouting.GracePeriodSeconds = defaultGracePeriodSeconds
}
canaryStatus := c.NewStatus.CanaryStatus
currentStep := c.Rollout.Spec.Strategy.Canary.Steps[canaryStatus.CurrentStepIndex-1]
if currentStep.Weight == nil && len(currentStep.Matches) == 0 {
if c.Strategy.Weight == nil && len(c.Strategy.Matches) == 0 {
return true, nil
}
if canaryStatus.StableRevision == "" || canaryStatus.PodTemplateHash == "" {
klog.Warningf("rollout(%s/%s) stableRevision or podTemplateHash can not be empty, and wait a moment", c.Rollout.Namespace, c.Rollout.Name)
return false, nil
}
//fetch stable service
stableService := &corev1.Service{}
err := m.Get(context.TODO(), client.ObjectKey{Namespace: c.Rollout.Namespace, Name: trafficRouting.Service}, stableService)
err := m.Get(context.TODO(), client.ObjectKey{Namespace: c.Namespace, Name: trafficRouting.Service}, stableService)
if err != nil {
klog.Errorf("rollout(%s/%s) get stable service(%s) failed: %s", c.Rollout.Namespace, c.Rollout.Name, trafficRouting.Service, err.Error())
klog.Errorf("%s get stable service(%s) failed: %s", c.Key, trafficRouting.Service, err.Error())
// not found, wait a moment, retry
if errors.IsNotFound(err) {
return false, nil
@ -101,12 +115,24 @@ func (m *Manager) DoTrafficRouting(c *util.RolloutContext) (bool, error) {
return false, err
}
// canary service name
canaryServiceName := fmt.Sprintf("%s-canary", trafficRouting.Service)
// fetch canary service
canaryServiceName := getCanaryServiceName(trafficRouting.Service, c.OnlyTrafficRouting)
canaryService := &corev1.Service{}
err = m.Get(context.TODO(), client.ObjectKey{Namespace: c.Rollout.Namespace, Name: canaryServiceName}, canaryService)
canaryService.Namespace = stableService.Namespace
canaryService.Name = canaryServiceName
// In full-link grayscale scenario(a -> b -> c), if only b or c is released,
//and a is not released in this scenario, then the canary service is not needed.
if c.OnlyTrafficRouting {
goto HandlerNetworkProvider
}
if c.StableRevision == "" || c.CanaryRevision == "" {
klog.Warningf("%s stableRevision or podTemplateHash can not be empty, and wait a moment", c.Key)
return false, nil
}
// fetch canary service
err = m.Get(context.TODO(), client.ObjectKey{Namespace: c.Namespace, Name: canaryServiceName}, canaryService)
if err != nil && !errors.IsNotFound(err) {
klog.Errorf("rollout(%s/%s) get canary service(%s) failed: %s", c.Rollout.Namespace, c.Rollout.Name, canaryServiceName, err.Error())
klog.Errorf("%s get canary service(%s) failed: %s", c.Key, canaryServiceName, err.Error())
return false, err
} else if errors.IsNotFound(err) {
canaryService, err = m.createCanaryService(c, canaryServiceName, *stableService.Spec.DeepCopy())
@ -116,78 +142,75 @@ func (m *Manager) DoTrafficRouting(c *util.RolloutContext) (bool, error) {
}
// patch canary service only selector the canary pods
if canaryService.Spec.Selector[c.Workload.RevisionLabelKey] != canaryStatus.PodTemplateHash {
body := fmt.Sprintf(`{"spec":{"selector":{"%s":"%s"}}}`, c.Workload.RevisionLabelKey, canaryStatus.PodTemplateHash)
if canaryService.Spec.Selector[c.RevisionLabelKey] != c.CanaryRevision {
body := fmt.Sprintf(`{"spec":{"selector":{"%s":"%s"}}}`, c.RevisionLabelKey, c.CanaryRevision)
if err = m.Patch(context.TODO(), canaryService, client.RawPatch(types.StrategicMergePatchType, []byte(body))); err != nil {
klog.Errorf("rollout(%s/%s) patch canary service(%s) selector failed: %s", c.Rollout.Namespace, c.Rollout.Name, canaryService.Name, err.Error())
klog.Errorf("%s patch canary service(%s) selector failed: %s", c.Key, canaryService.Name, err.Error())
return false, err
}
// update canary service time, and wait 3 seconds, just to be safe
canaryStatus.LastUpdateTime = &metav1.Time{Time: time.Now()}
klog.Infof("rollout(%s/%s) patch canary service(%s) selector(%s=%s) success",
c.Rollout.Namespace, c.Rollout.Name, canaryService.Name, c.Workload.RevisionLabelKey, canaryStatus.PodTemplateHash)
c.LastUpdateTime = &metav1.Time{Time: time.Now()}
klog.Infof("%s patch canary service(%s) selector(%s=%s) success",
c.Key, canaryService.Name, c.RevisionLabelKey, c.CanaryRevision)
}
// patch stable service only selector the stable pods
if stableService.Spec.Selector[c.Workload.RevisionLabelKey] != canaryStatus.StableRevision {
body := fmt.Sprintf(`{"spec":{"selector":{"%s":"%s"}}}`, c.Workload.RevisionLabelKey, canaryStatus.StableRevision)
if stableService.Spec.Selector[c.RevisionLabelKey] != c.StableRevision {
body := fmt.Sprintf(`{"spec":{"selector":{"%s":"%s"}}}`, c.RevisionLabelKey, c.StableRevision)
if err = m.Patch(context.TODO(), stableService, client.RawPatch(types.StrategicMergePatchType, []byte(body))); err != nil {
klog.Errorf("rollout(%s/%s) patch stable service(%s) selector failed: %s", c.Rollout.Namespace, c.Rollout.Name, stableService.Name, err.Error())
klog.Errorf("%s patch stable service(%s) selector failed: %s", c.Key, stableService.Name, err.Error())
return false, err
}
// update stable service time, and wait 3 seconds, just to be safe
canaryStatus.LastUpdateTime = &metav1.Time{Time: time.Now()}
klog.Infof("add rollout(%s/%s) stable service(%s) selector(%s=%s) success",
c.Rollout.Namespace, c.Rollout.Name, stableService.Name, c.Workload.RevisionLabelKey, canaryStatus.StableRevision)
c.LastUpdateTime = &metav1.Time{Time: time.Now()}
klog.Infof("add %s stable service(%s) selector(%s=%s) success",
c.Key, stableService.Name, c.RevisionLabelKey, c.StableRevision)
return false, nil
}
// After modify stable service configuration, give the network provider 3 seconds to react
if verifyTime := canaryStatus.LastUpdateTime.Add(time.Second * time.Duration(trafficRouting.GracePeriodSeconds)); verifyTime.After(time.Now()) {
klog.Infof("rollout(%s/%s) update service selector, and wait 3 seconds", c.Rollout.Namespace, c.Rollout.Name)
if verifyTime := c.LastUpdateTime.Add(time.Second * time.Duration(trafficRouting.GracePeriodSeconds)); verifyTime.After(time.Now()) {
klog.Infof("%s update service selector, and wait 3 seconds", c.Key)
return false, nil
}
HandlerNetworkProvider:
// new network provider, ingress or gateway
trController, err := newNetworkProvider(m.Client, c.Rollout, c.NewStatus, stableService.Name, canaryService.Name)
trController, err := newNetworkProvider(m.Client, c, stableService.Name, canaryService.Name)
if err != nil {
klog.Errorf("rollout(%s/%s) newNetworkProvider failed: %s", c.Rollout.Namespace, c.Rollout.Name, err.Error())
klog.Errorf("%s newNetworkProvider failed: %s", c.Key, err.Error())
return false, err
}
cStep := c.Rollout.Spec.Strategy.Canary.Steps[canaryStatus.CurrentStepIndex-1]
steps := len(c.Rollout.Spec.Strategy.Canary.Steps)
cond := util.GetRolloutCondition(*c.NewStatus, v1alpha1.RolloutConditionProgressing)
cond.Message = fmt.Sprintf("Rollout is in step(%d/%d), and doing traffic routing", canaryStatus.CurrentStepIndex, steps)
verify, err := trController.EnsureRoutes(context.TODO(), cStep.Weight, cStep.Matches)
verify, err := trController.EnsureRoutes(context.TODO(), &c.Strategy)
if err != nil {
return false, err
} else if !verify {
klog.Infof("rollout(%s/%s) is doing step(%d) trafficRouting(%s)", c.Rollout.Namespace, c.Rollout.Name, canaryStatus.CurrentStepIndex, util.DumpJSON(cStep))
klog.Infof("%s is doing trafficRouting(%s), and wait a moment", c.Key, util.DumpJSON(c.Strategy))
return false, nil
}
klog.Infof("rollout(%s/%s) do step(%d) trafficRouting(%s) success", c.Rollout.Namespace, c.Rollout.Name, canaryStatus.CurrentStepIndex, util.DumpJSON(cStep))
klog.Infof("%s do trafficRouting(%s) success", c.Key, util.DumpJSON(c.Strategy))
return true, nil
}
func (m *Manager) FinalisingTrafficRouting(c *util.RolloutContext, onlyRestoreStableService bool) (bool, error) {
if len(c.Rollout.Spec.Strategy.Canary.TrafficRoutings) == 0 {
func (m *Manager) FinalisingTrafficRouting(c *TrafficRoutingContext, onlyRestoreStableService bool) (bool, error) {
if len(c.ObjectRef) == 0 {
return true, nil
}
trafficRouting := c.Rollout.Spec.Strategy.Canary.TrafficRoutings[0]
trafficRouting := c.ObjectRef[0]
if trafficRouting.GracePeriodSeconds <= 0 {
trafficRouting.GracePeriodSeconds = defaultGracePeriodSeconds
}
cServiceName := fmt.Sprintf("%s-canary", trafficRouting.Service)
trController, err := newNetworkProvider(m.Client, c.Rollout, c.NewStatus, trafficRouting.Service, cServiceName)
cServiceName := getCanaryServiceName(trafficRouting.Service, c.OnlyTrafficRouting)
trController, err := newNetworkProvider(m.Client, c, trafficRouting.Service, cServiceName)
if err != nil {
klog.Errorf("rollout(%s/%s) newTrafficRoutingController failed: %s", c.Rollout.Namespace, c.Rollout.Name, err.Error())
klog.Errorf("%s newTrafficRoutingController failed: %s", c.Key, err.Error())
return false, err
}
cService := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: c.Rollout.Namespace, Name: cServiceName}}
cService := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: c.Namespace, Name: cServiceName}}
// if canary svc has been already cleaned up, just return
if err = m.Get(context.TODO(), client.ObjectKeyFromObject(cService), cService); err != nil {
if !errors.IsNotFound(err) {
klog.Errorf("rollout(%s/%s) get canary service(%s) failed: %s", c.Rollout.Namespace, c.Rollout.Name, cServiceName, err.Error())
klog.Errorf("%s get canary service(%s) failed: %s", c.Key, cServiceName, err.Error())
return false, err
}
// In rollout failure case, no canary-service will be created, this step ensures that the canary-ingress can be deleted in a time.
@ -197,10 +220,7 @@ func (m *Manager) FinalisingTrafficRouting(c *util.RolloutContext, onlyRestoreSt
return true, nil
}
if c.NewStatus.CanaryStatus == nil {
c.NewStatus.CanaryStatus = &v1alpha1.CanaryStatus{}
}
klog.Infof("rollout(%s/%s) start finalising traffic routing", c.Rollout.Namespace, c.Rollout.Name)
klog.Infof("%s start finalising traffic routing", c.Key)
// remove stable service the pod revision selector, so stable service will be selector all version pods.
verify, err := m.restoreStableService(c)
if err != nil || !verify {
@ -210,17 +230,18 @@ func (m *Manager) FinalisingTrafficRouting(c *util.RolloutContext, onlyRestoreSt
}
// First route 100% traffic to stable service
verify, err = trController.EnsureRoutes(context.TODO(), utilpointer.Int32(0), nil)
c.Strategy.Weight = utilpointer.Int32(0)
verify, err = trController.EnsureRoutes(context.TODO(), &c.Strategy)
if err != nil {
return false, err
} else if !verify {
c.NewStatus.CanaryStatus.LastUpdateTime = &metav1.Time{Time: time.Now()}
c.LastUpdateTime = &metav1.Time{Time: time.Now()}
return false, nil
}
if c.NewStatus.CanaryStatus.LastUpdateTime != nil {
if c.LastUpdateTime != nil {
// After restore the stable service configuration, give network provider 3 seconds to react
if verifyTime := c.NewStatus.CanaryStatus.LastUpdateTime.Add(time.Second * time.Duration(trafficRouting.GracePeriodSeconds)); verifyTime.After(time.Now()) {
klog.Infof("rollout(%s/%s) route 100% traffic to stable service, and wait a moment", c.Rollout.Namespace, c.Rollout.Name)
if verifyTime := c.LastUpdateTime.Add(time.Second * time.Duration(trafficRouting.GracePeriodSeconds)); verifyTime.After(time.Now()) {
klog.Infof("%s route 100% traffic to stable service, and wait a moment", c.Key)
return false, nil
}
}
@ -232,29 +253,29 @@ func (m *Manager) FinalisingTrafficRouting(c *util.RolloutContext, onlyRestoreSt
// remove canary service
err = m.Delete(context.TODO(), cService)
if err != nil && !errors.IsNotFound(err) {
klog.Errorf("rollout(%s/%s) remove canary service(%s) failed: %s", c.Rollout.Namespace, c.Rollout.Name, cService.Name, err.Error())
klog.Errorf("%s remove canary service(%s) failed: %s", c.Key, cService.Name, err.Error())
return false, err
}
klog.Infof("rollout(%s/%s) remove canary service(%s) success", c.Rollout.Namespace, c.Rollout.Name, cService.Name)
klog.Infof("%s remove canary service(%s) success", c.Key, cService.Name)
return true, nil
}
func newNetworkProvider(c client.Client, rollout *v1alpha1.Rollout, newStatus *v1alpha1.RolloutStatus, sService, cService string) (network.NetworkProvider, error) {
trafficRouting := rollout.Spec.Strategy.Canary.TrafficRoutings[0]
func newNetworkProvider(c client.Client, con *TrafficRoutingContext, sService, cService string) (network.NetworkProvider, error) {
trafficRouting := con.ObjectRef[0]
if trafficRouting.Ingress != nil {
return ingress.NewIngressTrafficRouting(c, ingress.Config{
RolloutName: rollout.Name,
RolloutNs: rollout.Namespace,
Key: con.Key,
Namespace: con.Namespace,
CanaryService: cService,
StableService: sService,
TrafficConf: trafficRouting.Ingress,
OwnerRef: *metav1.NewControllerRef(rollout, rolloutControllerKind),
OwnerRef: con.OwnerRef,
})
}
if trafficRouting.Gateway != nil {
return gateway.NewGatewayTrafficRouting(c, gateway.Config{
RolloutName: rollout.Name,
RolloutNs: rollout.Namespace,
Key: con.Key,
Namespace: con.Namespace,
CanaryService: cService,
StableService: sService,
TrafficConf: trafficRouting.Gateway,
@ -263,12 +284,12 @@ func newNetworkProvider(c client.Client, rollout *v1alpha1.Rollout, newStatus *v
return nil, fmt.Errorf("TrafficRouting current only support Ingress or Gateway API")
}
func (m *Manager) createCanaryService(c *util.RolloutContext, cService string, spec corev1.ServiceSpec) (*corev1.Service, error) {
func (m *Manager) createCanaryService(c *TrafficRoutingContext, cService string, spec corev1.ServiceSpec) (*corev1.Service, error) {
canaryService := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: c.Rollout.Namespace,
Namespace: c.Namespace,
Name: cService,
OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(c.Rollout, rolloutControllerKind)},
OwnerReferences: []metav1.OwnerReference{c.OwnerRef},
},
Spec: spec,
}
@ -280,50 +301,57 @@ func (m *Manager) createCanaryService(c *util.RolloutContext, cService string, s
canaryService.Spec.IPFamilyPolicy = nil
canaryService.Spec.IPFamilies = nil
canaryService.Spec.LoadBalancerIP = ""
canaryService.Spec.Selector[c.Workload.RevisionLabelKey] = c.NewStatus.CanaryStatus.PodTemplateHash
canaryService.Spec.Selector[c.RevisionLabelKey] = c.CanaryRevision
err := m.Create(context.TODO(), canaryService)
if err != nil && !errors.IsAlreadyExists(err) {
klog.Errorf("rollout(%s/%s) create canary service(%s) failed: %s", c.Rollout.Namespace, c.Rollout.Name, cService, err.Error())
klog.Errorf("%s create canary service(%s) failed: %s", c.Key, cService, err.Error())
return nil, err
}
klog.Infof("rollout(%s/%s) create canary service(%s) success", c.Rollout.Namespace, c.Rollout.Name, util.DumpJSON(canaryService))
klog.Infof("%s create canary service(%s) success", c.Key, util.DumpJSON(canaryService))
return canaryService, nil
}
// remove stable service the pod revision selector, so stable service will be selector all version pods.
func (m *Manager) restoreStableService(c *util.RolloutContext) (bool, error) {
if c.Workload == nil {
return true, nil
func (m *Manager) restoreStableService(c *TrafficRoutingContext) (bool, error) {
trafficRouting := c.ObjectRef[0]
if trafficRouting.GracePeriodSeconds <= 0 {
trafficRouting.GracePeriodSeconds = defaultGracePeriodSeconds
}
trafficRouting := c.Rollout.Spec.Strategy.Canary.TrafficRoutings[0]
//fetch stable service
stableService := &corev1.Service{}
err := m.Get(context.TODO(), client.ObjectKey{Namespace: c.Rollout.Namespace, Name: trafficRouting.Service}, stableService)
err := m.Get(context.TODO(), client.ObjectKey{Namespace: c.Namespace, Name: trafficRouting.Service}, stableService)
if err != nil {
if errors.IsNotFound(err) {
return true, nil
}
klog.Errorf("rollout(%s/%s) get stable service(%s) failed: %s", c.Rollout.Namespace, c.Rollout.Name, trafficRouting.Service, err.Error())
klog.Errorf("%s get stable service(%s) failed: %s", c.Key, trafficRouting.Service, err.Error())
return false, err
}
if stableService.Spec.Selector[c.Workload.RevisionLabelKey] != "" {
body := fmt.Sprintf(`{"spec":{"selector":{"%s":null}}}`, c.Workload.RevisionLabelKey)
if stableService.Spec.Selector[c.RevisionLabelKey] != "" {
body := fmt.Sprintf(`{"spec":{"selector":{"%s":null}}}`, c.RevisionLabelKey)
if err = m.Patch(context.TODO(), stableService, client.RawPatch(types.StrategicMergePatchType, []byte(body))); err != nil {
klog.Errorf("rollout(%s/%s) patch stable service(%s) failed: %s", c.Rollout.Namespace, c.Rollout.Name, trafficRouting.Service, err.Error())
klog.Errorf("%s patch stable service(%s) failed: %s", c.Key, trafficRouting.Service, err.Error())
return false, err
}
klog.Infof("remove rollout(%s/%s) stable service(%s) pod revision selector, and wait a moment", c.Rollout.Namespace, c.Rollout.Name, trafficRouting.Service)
c.NewStatus.CanaryStatus.LastUpdateTime = &metav1.Time{Time: time.Now()}
klog.Infof("remove %s stable service(%s) pod revision selector, and wait a moment", c.Key, trafficRouting.Service)
c.LastUpdateTime = &metav1.Time{Time: time.Now()}
return false, nil
}
if c.NewStatus.CanaryStatus.LastUpdateTime == nil {
if c.LastUpdateTime == nil {
return true, nil
}
// After restore the stable service configuration, give network provider 3 seconds to react
if verifyTime := c.NewStatus.CanaryStatus.LastUpdateTime.Add(time.Second * time.Duration(trafficRouting.GracePeriodSeconds)); verifyTime.After(time.Now()) {
klog.Infof("rollout(%s/%s) restoring stable service(%s), and wait a moment", c.Rollout.Namespace, c.Rollout.Name, trafficRouting.Service)
if verifyTime := c.LastUpdateTime.Add(time.Second * time.Duration(trafficRouting.GracePeriodSeconds)); verifyTime.After(time.Now()) {
klog.Infof("%s restoring stable service(%s), and wait a moment", c.Key, trafficRouting.Service)
return false, nil
}
klog.Infof("rollout(%s/%s) doFinalising stable service(%s) success", c.Rollout.Namespace, c.Rollout.Name, trafficRouting.Service)
klog.Infof("%s doFinalising stable service(%s) success", c.Key, trafficRouting.Service)
return true, nil
}
func getCanaryServiceName(sService string, onlyTrafficRouting bool) string {
if onlyTrafficRouting {
return sService
}
return fmt.Sprintf("%s-canary", sService)
}

View File

@ -123,23 +123,31 @@ var (
Canary: &v1alpha1.CanaryStrategy{
Steps: []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(5),
},
Replicas: &intstr.IntOrString{IntVal: 1},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Replicas: &intstr.IntOrString{IntVal: 2},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Replicas: &intstr.IntOrString{IntVal: 6},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Replicas: &intstr.IntOrString{IntVal: 10},
},
},
TrafficRoutings: []*v1alpha1.TrafficRouting{
TrafficRoutings: []v1alpha1.TrafficRoutingRef{
{
Service: "echoserver",
Ingress: &v1alpha1.IngressTrafficRouting{
@ -393,9 +401,20 @@ func TestDoTrafficRouting(t *testing.T) {
if len(ig) == 2 {
_ = client.Create(context.TODO(), ig[1])
}
c := &util.RolloutContext{}
c.Rollout, c.Workload = cs.getRollout()
c.NewStatus = c.Rollout.Status.DeepCopy()
rollout, workload := cs.getRollout()
newStatus := rollout.Status.DeepCopy()
currentStep := rollout.Spec.Strategy.Canary.Steps[newStatus.CanaryStatus.CurrentStepIndex-1]
c := &TrafficRoutingContext{
Key: fmt.Sprintf("Rollout(%s/%s)", rollout.Namespace, rollout.Name),
Namespace: rollout.Namespace,
ObjectRef: rollout.Spec.Strategy.Canary.TrafficRoutings,
Strategy: currentStep.TrafficRoutingStrategy,
OwnerRef: *metav1.NewControllerRef(rollout, v1alpha1.SchemeGroupVersion.WithKind("Rollout")),
RevisionLabelKey: workload.RevisionLabelKey,
StableRevision: newStatus.CanaryStatus.StableRevision,
CanaryRevision: newStatus.CanaryStatus.PodTemplateHash,
LastUpdateTime: newStatus.CanaryStatus.LastUpdateTime,
}
manager := NewTrafficRoutingManager(client)
err := manager.InitializeTrafficRouting(c)
if err != nil {
@ -621,9 +640,20 @@ func TestFinalisingTrafficRouting(t *testing.T) {
if len(ig) == 2 {
_ = client.Create(context.TODO(), ig[1])
}
c := &util.RolloutContext{}
c.Rollout, c.Workload = cs.getRollout()
c.NewStatus = c.Rollout.Status.DeepCopy()
rollout, workload := cs.getRollout()
newStatus := rollout.Status.DeepCopy()
currentStep := rollout.Spec.Strategy.Canary.Steps[newStatus.CanaryStatus.CurrentStepIndex-1]
c := &TrafficRoutingContext{
Key: fmt.Sprintf("Rollout(%s/%s)", rollout.Namespace, rollout.Name),
Namespace: rollout.Namespace,
ObjectRef: rollout.Spec.Strategy.Canary.TrafficRoutings,
Strategy: currentStep.TrafficRoutingStrategy,
OwnerRef: *metav1.NewControllerRef(rollout, v1alpha1.SchemeGroupVersion.WithKind("Rollout")),
RevisionLabelKey: workload.RevisionLabelKey,
StableRevision: newStatus.CanaryStatus.StableRevision,
CanaryRevision: newStatus.CanaryStatus.PodTemplateHash,
LastUpdateTime: newStatus.CanaryStatus.LastUpdateTime,
}
manager := NewTrafficRoutingManager(client)
done, err := manager.FinalisingTrafficRouting(c, cs.onlyRestoreStableService)
if err != nil {

View File

@ -29,8 +29,9 @@ import (
)
type Config struct {
RolloutName string
RolloutNs string
// only for log info
Key string
Namespace string
CanaryService string
StableService string
TrafficConf *rolloutv1alpha1.GatewayTrafficRouting
@ -52,17 +53,20 @@ func NewGatewayTrafficRouting(client client.Client, conf Config) (network.Networ
func (r *gatewayController) Initialize(ctx context.Context) error {
route := &gatewayv1alpha2.HTTPRoute{}
return r.Get(ctx, types.NamespacedName{Namespace: r.conf.RolloutNs, Name: *r.conf.TrafficConf.HTTPRouteName}, route)
return r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: *r.conf.TrafficConf.HTTPRouteName}, route)
}
func (r *gatewayController) EnsureRoutes(ctx context.Context, weight *int32, matches []rolloutv1alpha1.HttpRouteMatch) (bool, error) {
func (r *gatewayController) EnsureRoutes(ctx context.Context, strategy *rolloutv1alpha1.TrafficRoutingStrategy) (bool, error) {
weight := strategy.Weight
matches := strategy.Matches
headerModifier := strategy.RequestHeaderModifier
var httpRoute gatewayv1alpha2.HTTPRoute
err := r.Get(ctx, types.NamespacedName{Namespace: r.conf.RolloutNs, Name: *r.conf.TrafficConf.HTTPRouteName}, &httpRoute)
err := r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: *r.conf.TrafficConf.HTTPRouteName}, &httpRoute)
if err != nil {
return false, err
}
// desired route
desiredRule := r.buildDesiredHTTPRoute(httpRoute.Spec.Rules, weight, matches)
desiredRule := r.buildDesiredHTTPRoute(httpRoute.Spec.Rules, weight, matches, headerModifier)
if reflect.DeepEqual(httpRoute.Spec.Rules, desiredRule) {
return true, nil
}
@ -76,25 +80,25 @@ func (r *gatewayController) EnsureRoutes(ctx context.Context, weight *int32, mat
routeClone.Spec.Rules = desiredRule
return r.Client.Update(context.TODO(), routeClone)
}); err != nil {
klog.Errorf("update rollout(%s/%s) httpRoute(%s) failed: %s", r.conf.RolloutNs, r.conf.RolloutName, httpRoute.Name, err.Error())
klog.Errorf("update %s httpRoute(%s) failed: %s", r.conf.Key, httpRoute.Name, err.Error())
return false, err
}
klog.Infof("rollout(%s/%s) set HTTPRoute(name:%s weight:%d) success", r.conf.RolloutNs, r.conf.RolloutName, *r.conf.TrafficConf.HTTPRouteName, *weight)
klog.Infof("%s set HTTPRoute(name:%s weight:%d) success", r.conf.Key, *r.conf.TrafficConf.HTTPRouteName, *weight)
return false, nil
}
func (r *gatewayController) Finalise(ctx context.Context) error {
httpRoute := &gatewayv1alpha2.HTTPRoute{}
err := r.Get(ctx, types.NamespacedName{Namespace: r.conf.RolloutNs, Name: *r.conf.TrafficConf.HTTPRouteName}, httpRoute)
err := r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: *r.conf.TrafficConf.HTTPRouteName}, httpRoute)
if err != nil {
if errors.IsNotFound(err) {
return nil
}
klog.Errorf("rollout(%s/%s) get HTTPRoute failed: %s", r.conf.RolloutNs, r.conf.RolloutName, err.Error())
klog.Errorf("%s get HTTPRoute failed: %s", r.conf.Key, err.Error())
return err
}
// desired rule
desiredRule := r.buildDesiredHTTPRoute(httpRoute.Spec.Rules, utilpointer.Int32(-1), nil)
desiredRule := r.buildDesiredHTTPRoute(httpRoute.Spec.Rules, utilpointer.Int32(-1), nil, nil)
if reflect.DeepEqual(httpRoute.Spec.Rules, desiredRule) {
return nil
}
@ -107,14 +111,15 @@ func (r *gatewayController) Finalise(ctx context.Context) error {
routeClone.Spec.Rules = desiredRule
return r.Client.Update(context.TODO(), routeClone)
}); err != nil {
klog.Errorf("update rollout(%s/%s) httpRoute(%s) failed: %s", r.conf.RolloutNs, r.conf.RolloutName, httpRoute.Name, err.Error())
klog.Errorf("update %s httpRoute(%s) failed: %s", r.conf.Key, httpRoute.Name, err.Error())
return err
}
klog.Infof("rollout(%s/%s) TrafficRouting Finalise success", r.conf.RolloutNs, r.conf.RolloutName)
klog.Infof("%s TrafficRouting Finalise success", r.conf.Key)
return nil
}
func (r *gatewayController) buildDesiredHTTPRoute(rules []gatewayv1alpha2.HTTPRouteRule, weight *int32, matches []rolloutv1alpha1.HttpRouteMatch) []gatewayv1alpha2.HTTPRouteRule {
func (r *gatewayController) buildDesiredHTTPRoute(rules []gatewayv1alpha2.HTTPRouteRule, weight *int32, matches []rolloutv1alpha1.HttpRouteMatch,
rh *gatewayv1alpha2.HTTPRequestHeaderFilter) []gatewayv1alpha2.HTTPRouteRule {
var desired []gatewayv1alpha2.HTTPRouteRule
// Only when finalize method parameter weight=-1,
// then we need to remove the canary route policy and restore to the original configuration

View File

@ -443,7 +443,7 @@ func TestBuildDesiredHTTPRoute(t *testing.T) {
}
conf := Config{
RolloutName: "rollout-demo",
Key: "rollout-demo",
CanaryService: "store-svc-canary",
StableService: "store-svc",
}
@ -452,7 +452,7 @@ func TestBuildDesiredHTTPRoute(t *testing.T) {
t.Run(cs.name, func(t *testing.T) {
controller := &gatewayController{conf: conf}
weight, matches := cs.getRoutes()
current := controller.buildDesiredHTTPRoute(cs.getRouteRules(), weight, matches)
current := controller.buildDesiredHTTPRoute(cs.getRouteRules(), weight, matches, nil)
desired := cs.desiredRules()
if !reflect.DeepEqual(current, desired) {
t.Fatalf("expect: %v, but get %v", util.DumpJSON(desired), util.DumpJSON(current))

View File

@ -39,6 +39,7 @@ import (
"k8s.io/klog/v2"
utilpointer "k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
)
type ingressController struct {
@ -50,8 +51,9 @@ type ingressController struct {
}
type Config struct {
RolloutName string
RolloutNs string
// only for log info
Key string
Namespace string
CanaryService string
StableService string
TrafficConf *rolloutv1alpha1.IngressTrafficRouting
@ -76,14 +78,14 @@ func NewIngressTrafficRouting(client client.Client, conf Config) (network.Networ
// Initialize verify the existence of the ingress resource and generate the canary ingress
func (r *ingressController) Initialize(ctx context.Context) error {
ingress := &netv1.Ingress{}
err := r.Get(ctx, types.NamespacedName{Namespace: r.conf.RolloutNs, Name: r.conf.TrafficConf.Name}, ingress)
err := r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: r.conf.TrafficConf.Name}, ingress)
if err != nil {
return err
}
canaryIngress := &netv1.Ingress{}
err = r.Get(ctx, types.NamespacedName{Namespace: r.conf.RolloutNs, Name: r.canaryIngressName}, canaryIngress)
err = r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: r.canaryIngressName}, canaryIngress)
if err != nil && !errors.IsNotFound(err) {
klog.Errorf("rollout(%s/%s) get canary ingress(%s) failed: %s", r.conf.RolloutNs, r.conf.RolloutName, r.canaryIngressName, err.Error())
klog.Errorf("%s get canary ingress(%s) failed: %s", r.conf.Key, r.canaryIngressName, err.Error())
return err
} else if err == nil {
return nil
@ -91,32 +93,36 @@ func (r *ingressController) Initialize(ctx context.Context) error {
// build and create canary ingress
canaryIngress = r.buildCanaryIngress(ingress)
canaryIngress.Annotations, err = r.executeLuaForCanary(canaryIngress.Annotations, utilpointer.Int32(0), nil)
canaryIngress.Annotations, err = r.executeLuaForCanary(canaryIngress.Annotations, utilpointer.Int32(0), nil, nil)
if err != nil {
klog.Errorf("rollout(%s/%s) execute lua failed: %s", r.conf.RolloutNs, r.conf.RolloutName, err.Error())
klog.Errorf("%s execute lua failed: %s", r.conf.Key, err.Error())
return err
}
if err = r.Create(ctx, canaryIngress); err != nil {
klog.Errorf("rollout(%s/%s) create canary ingress failed: %s", r.conf.RolloutNs, r.conf.RolloutName, err.Error())
klog.Errorf("%s create canary ingress failed: %s", r.conf.Key, err.Error())
return err
}
klog.Infof("rollout(%s/%s) create canary ingress(%s) success", r.conf.RolloutNs, r.conf.RolloutName, util.DumpJSON(canaryIngress))
klog.Infof("%s create canary ingress(%s) success", r.conf.Key, util.DumpJSON(canaryIngress))
return nil
}
func (r *ingressController) EnsureRoutes(ctx context.Context, weight *int32, matches []rolloutv1alpha1.HttpRouteMatch) (bool, error) {
func (r *ingressController) EnsureRoutes(ctx context.Context, strategy *rolloutv1alpha1.TrafficRoutingStrategy) (bool, error) {
weight := strategy.Weight
matches := strategy.Matches
headerModifier := strategy.RequestHeaderModifier
canaryIngress := &netv1.Ingress{}
err := r.Get(ctx, types.NamespacedName{Namespace: r.conf.RolloutNs, Name: defaultCanaryIngressName(r.conf.TrafficConf.Name)}, canaryIngress)
err := r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: defaultCanaryIngressName(r.conf.TrafficConf.Name)}, canaryIngress)
if err != nil {
if weight != nil && *weight == 0 && errors.IsNotFound(err) {
return true, nil
}
klog.Errorf("rollout(%s/%s) get canary ingress failed: %s", r.conf.RolloutNs, r.conf.RolloutName, err.Error())
klog.Errorf("%s get canary ingress failed: %s", r.conf.Key, err.Error())
return false, err
}
newAnnotations, err := r.executeLuaForCanary(canaryIngress.Annotations, weight, matches)
newAnnotations, err := r.executeLuaForCanary(canaryIngress.Annotations, weight, matches, headerModifier)
if err != nil {
klog.Errorf("rollout(%s/%s) execute lua failed: %s", r.conf.RolloutNs, r.conf.RolloutName, err.Error())
klog.Errorf("%s execute lua failed: %s", r.conf.Key, err.Error())
return false, err
}
if reflect.DeepEqual(canaryIngress.Annotations, newAnnotations) {
@ -126,23 +132,23 @@ func (r *ingressController) EnsureRoutes(ctx context.Context, weight *int32, mat
byte2, _ := json.Marshal(metav1.ObjectMeta{Annotations: newAnnotations})
patch, err := jsonpatch.CreateMergePatch(byte1, byte2)
if err != nil {
klog.Errorf("rollout(%s/%s) create merge patch failed: %s", r.conf.RolloutNs, r.conf.RolloutName, err.Error())
klog.Errorf("%s create merge patch failed: %s", r.conf.Key, err.Error())
return false, err
}
body := fmt.Sprintf(`{"metadata":%s}`, string(patch))
if err = r.Patch(ctx, canaryIngress, client.RawPatch(types.MergePatchType, []byte(body))); err != nil {
klog.Errorf("rollout(%s/%s) set canary ingress(%s) failed: %s", r.conf.RolloutNs, r.conf.RolloutName, canaryIngress.Name, err.Error())
klog.Errorf("%s set canary ingress(%s) failed: %s", r.conf.Key, canaryIngress.Name, err.Error())
return false, err
}
klog.Infof("rollout(%s/%s) set canary ingress(%s) annotations(%s) success", r.conf.RolloutNs, r.conf.RolloutName, canaryIngress.Name, util.DumpJSON(newAnnotations))
klog.Infof("%s set canary ingress(%s) annotations(%s) success", r.conf.Key, canaryIngress.Name, util.DumpJSON(newAnnotations))
return false, nil
}
func (r *ingressController) Finalise(ctx context.Context) error {
canaryIngress := &netv1.Ingress{}
err := r.Get(ctx, types.NamespacedName{Namespace: r.conf.RolloutNs, Name: r.canaryIngressName}, canaryIngress)
err := r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: r.canaryIngressName}, canaryIngress)
if err != nil && !errors.IsNotFound(err) {
klog.Errorf("rollout(%s/%s) get canary ingress(%s) failed: %s", r.conf.RolloutNs, r.conf.RolloutName, r.canaryIngressName, err.Error())
klog.Errorf("%s get canary ingress(%s) failed: %s", r.conf.Key, r.canaryIngressName, err.Error())
return err
}
if errors.IsNotFound(err) || !canaryIngress.DeletionTimestamp.IsZero() {
@ -150,10 +156,10 @@ func (r *ingressController) Finalise(ctx context.Context) error {
}
// immediate delete canary ingress
if err = r.Delete(ctx, canaryIngress); err != nil {
klog.Errorf("rollout(%s/%s) remove canary ingress(%s) failed: %s", r.conf.RolloutNs, r.conf.RolloutName, canaryIngress.Name, err.Error())
klog.Errorf("%s remove canary ingress(%s) failed: %s", r.conf.Key, canaryIngress.Name, err.Error())
return err
}
klog.Infof("rollout(%s/%s) remove canary ingress(%s) success", r.conf.RolloutNs, r.conf.RolloutName, canaryIngress.Name)
klog.Infof("%s remove canary ingress(%s) success", r.conf.Key, canaryIngress.Name)
return nil
}
@ -214,7 +220,9 @@ func defaultCanaryIngressName(name string) string {
return fmt.Sprintf("%s-canary", name)
}
func (r *ingressController) executeLuaForCanary(annotations map[string]string, weight *int32, matches []rolloutv1alpha1.HttpRouteMatch) (map[string]string, error) {
func (r *ingressController) executeLuaForCanary(annotations map[string]string, weight *int32, matches []rolloutv1alpha1.HttpRouteMatch,
headerModifier *gatewayv1alpha2.HTTPRequestHeaderFilter) (map[string]string, error) {
if weight == nil {
// the lua script does not have a pointer type,
// so we need to pass weight=-1 to indicate the case where weight is nil.
@ -225,13 +233,16 @@ func (r *ingressController) executeLuaForCanary(annotations map[string]string, w
Weight string
Matches []rolloutv1alpha1.HttpRouteMatch
CanaryService string
RequestHeaderModifier *gatewayv1alpha2.HTTPRequestHeaderFilter
}
data := &LuaData{
Annotations: annotations,
Weight: fmt.Sprintf("%d", *weight),
Matches: matches,
CanaryService: r.conf.CanaryService,
RequestHeaderModifier: headerModifier,
}
fmt.Println(util.DumpJSON(data))
unObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(data)
if err != nil {
return nil, err

View File

@ -44,6 +44,12 @@ var (
},
Data: map[string]string{
fmt.Sprintf("%s.nginx", configuration.LuaTrafficRoutingIngressTypePrefix): `
function split(input, delimiter)
local arr = {}
string.gsub(input, '[^' .. delimiter ..']+', function(w) table.insert(arr, w) end)
return arr
end
annotations = obj.annotations
annotations["nginx.ingress.kubernetes.io/canary"] = "true"
annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = nil
@ -54,6 +60,14 @@ var (
if ( obj.weight ~= "-1" )
then
annotations["nginx.ingress.kubernetes.io/canary-weight"] = obj.weight
end
if ( obj.requestHeaderModifier )
then
local str = ''
for _,header in ipairs(obj.requestHeaderModifier.set) do
str = str..string.format("%s %s\n", header.name, header.value)
end
annotations["mse.ingress.kubernetes.io/request-header-control-update"] = str
end
if ( not obj.matches )
then
@ -267,7 +281,7 @@ func TestInitialize(t *testing.T) {
}
config := Config{
RolloutName: "rollout-demo",
Key: "rollout-demo",
StableService: "echoserver",
CanaryService: "echoserver-canary",
TrafficConf: &rolloutsv1alpha1.IngressTrafficRouting{
@ -311,7 +325,7 @@ func TestEnsureRoutes(t *testing.T) {
name string
getConfigmap func() *corev1.ConfigMap
getIngress func() []*netv1.Ingress
getRoutes func() (*int32, []rolloutsv1alpha1.HttpRouteMatch)
getRoutes func() *rolloutsv1alpha1.CanaryStep
expectIngress func() *netv1.Ingress
ingressType string
}{
@ -330,8 +344,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() (*int32, []rolloutsv1alpha1.HttpRouteMatch) {
return nil, []rolloutsv1alpha1.HttpRouteMatch{
getRoutes: func() *rolloutsv1alpha1.CanaryStep {
return &rolloutsv1alpha1.CanaryStep{
TrafficRoutingStrategy: rolloutsv1alpha1.TrafficRoutingStrategy{
Weight: nil,
Matches: []rolloutsv1alpha1.HttpRouteMatch{
// header
{
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
@ -350,6 +367,20 @@ func TestEnsureRoutes(t *testing.T) {
},
},
},
},
RequestHeaderModifier: &gatewayv1alpha2.HTTPRequestHeaderFilter{
Set: []gatewayv1alpha2.HTTPHeader{
{
Name: "gray",
Value: "blue",
},
{
Name: "gray",
Value: "green",
},
},
},
},
}
},
expectIngress: func() *netv1.Ingress {
@ -359,6 +390,7 @@ func TestEnsureRoutes(t *testing.T) {
expect.Annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = "demo"
expect.Annotations["nginx.ingress.kubernetes.io/canary-by-header"] = "user_id"
expect.Annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = "123456"
expect.Annotations["mse.ingress.kubernetes.io/request-header-control-update"] = "gray blue\ngray green\n"
expect.Spec.Rules[0].HTTP.Paths = expect.Spec.Rules[0].HTTP.Paths[:1]
expect.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
expect.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
@ -382,8 +414,12 @@ 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() (*int32, []rolloutsv1alpha1.HttpRouteMatch) {
return utilpointer.Int32(40), nil
getRoutes: func() *rolloutsv1alpha1.CanaryStep {
return &rolloutsv1alpha1.CanaryStep{
TrafficRoutingStrategy: rolloutsv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
}
},
expectIngress: func() *netv1.Ingress {
expect := demoIngress.DeepCopy()
@ -413,9 +449,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() (*int32, []rolloutsv1alpha1.HttpRouteMatch) {
getRoutes: func() *rolloutsv1alpha1.CanaryStep {
iType := gatewayv1alpha2.HeaderMatchRegularExpression
return nil, []rolloutsv1alpha1.HttpRouteMatch{
return &rolloutsv1alpha1.CanaryStep{
TrafficRoutingStrategy: rolloutsv1alpha1.TrafficRoutingStrategy{
Matches: []rolloutsv1alpha1.HttpRouteMatch{
// header
{
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
@ -426,6 +464,8 @@ func TestEnsureRoutes(t *testing.T) {
},
},
},
},
},
}
},
expectIngress: func() *netv1.Ingress {
@ -457,8 +497,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() (*int32, []rolloutsv1alpha1.HttpRouteMatch) {
return nil, []rolloutsv1alpha1.HttpRouteMatch{
getRoutes: func() *rolloutsv1alpha1.CanaryStep {
return &rolloutsv1alpha1.CanaryStep{
TrafficRoutingStrategy: rolloutsv1alpha1.TrafficRoutingStrategy{
Matches: []rolloutsv1alpha1.HttpRouteMatch{
// header
{
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
@ -476,6 +518,8 @@ func TestEnsureRoutes(t *testing.T) {
},
},
},
},
},
}
},
expectIngress: func() *netv1.Ingress {
@ -493,7 +537,7 @@ func TestEnsureRoutes(t *testing.T) {
}
config := Config{
RolloutName: "rollout-demo",
Key: "rollout-demo",
StableService: "echoserver",
CanaryService: "echoserver-canary",
TrafficConf: &rolloutsv1alpha1.IngressTrafficRouting{
@ -502,6 +546,7 @@ func TestEnsureRoutes(t *testing.T) {
}
for _, cs := range cases {
t.Run(cs.name, func(t *testing.T) {
fmt.Println("==========", cs.name)
fakeCli := fake.NewClientBuilder().WithScheme(scheme).Build()
fakeCli.Create(context.TODO(), cs.getConfigmap())
for _, ingress := range cs.getIngress() {
@ -513,8 +558,8 @@ func TestEnsureRoutes(t *testing.T) {
t.Fatalf("NewIngressTrafficRouting failed: %s", err.Error())
return
}
weight, matches := cs.getRoutes()
_, err = controller.EnsureRoutes(context.TODO(), weight, matches)
step := cs.getRoutes()
_, err = controller.EnsureRoutes(context.TODO(), &step.TrafficRoutingStrategy)
if err != nil {
t.Fatalf("EnsureRoutes failed: %s", err.Error())
return
@ -528,7 +573,7 @@ func TestEnsureRoutes(t *testing.T) {
expect := cs.expectIngress()
if !reflect.DeepEqual(canaryIngress.Annotations, expect.Annotations) ||
!reflect.DeepEqual(canaryIngress.Spec, expect.Spec) {
t.Fatalf("expect(%s), but get(%s)", util.DumpJSON(expect), util.DumpJSON(canaryIngress))
t.Fatalf("but get(%s)", util.DumpJSON(canaryIngress))
}
})
}
@ -563,7 +608,7 @@ func TestFinalise(t *testing.T) {
}
config := Config{
RolloutName: "rollout-demo",
Key: "rollout-demo",
StableService: "echoserver",
CanaryService: "echoserver-canary",
TrafficConf: &rolloutsv1alpha1.IngressTrafficRouting{

View File

@ -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, weight *int32, matches []rolloutv1alpha1.HttpRouteMatch) (bool, error)
EnsureRoutes(ctx context.Context, strategy *rolloutv1alpha1.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

@ -16,6 +16,12 @@ limitations under the License.
package util
import (
"fmt"
"github.com/openkruise/rollouts/api/v1alpha1"
)
// For Rollout and BatchRelease
const (
// BatchReleaseControlAnnotation is controller info about batchRelease when rollout
@ -41,6 +47,10 @@ const (
DeploymentRevisionAnnotation = "deployment.kubernetes.io/revision"
)
const (
TrafficRoutingFinalizer = "rollouts.kruise.io/trafficrouting"
)
// For Pods
const (
// NoNeedUpdatePodLabel will be patched to pod when rollback in batches if the pods no need to rollback
@ -57,6 +67,7 @@ const (
CloneSetType WorkloadType = "cloneset"
DeploymentType WorkloadType = "deployment"
StatefulSetType WorkloadType = "statefulset"
DaemonSetType WorkloadType = "daemonset"
AddFinalizerOpType FinalizerOpType = "Add"
RemoveFinalizerOpType FinalizerOpType = "Remove"
@ -65,3 +76,7 @@ const (
type WorkloadType string
type FinalizerOpType string
func ProgressingRolloutFinalizer(name string) string {
return fmt.Sprintf("%s/%s", v1alpha1.ProgressingRolloutFinalizerPrefix, name)
}

View File

@ -1,34 +0,0 @@
/*
Copyright 2022 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 util
import (
"time"
"github.com/openkruise/rollouts/api/v1alpha1"
)
type RolloutContext struct {
Rollout *v1alpha1.Rollout
NewStatus *v1alpha1.RolloutStatus
// related workload
Workload *Workload
// reconcile RequeueAfter recheckTime
RecheckTime *time.Time
// wait stable workload pods ready
WaitReady bool
}

View File

@ -37,6 +37,7 @@ import (
// Workload is used to return (controller, scale, selector) fields from the
// controller finder functions.
type Workload struct {
metav1.TypeMeta
metav1.ObjectMeta
// replicas
@ -49,6 +50,8 @@ type Workload struct {
PodTemplateHash string
// Revision hash key
RevisionLabelKey string
// label selector
Selector *metav1.LabelSelector
// Is it in rollback phase
IsInRollback bool
@ -159,6 +162,7 @@ func (r *ControllerFinder) getKruiseCloneSet(namespace string, ref *rolloutv1alp
StableRevision: cloneSet.Status.CurrentRevision[strings.LastIndex(cloneSet.Status.CurrentRevision, "-")+1:],
CanaryRevision: cloneSet.Status.UpdateRevision[strings.LastIndex(cloneSet.Status.UpdateRevision, "-")+1:],
ObjectMeta: cloneSet.ObjectMeta,
TypeMeta: cloneSet.TypeMeta,
Replicas: *cloneSet.Spec.Replicas,
PodTemplateHash: cloneSet.Status.UpdateRevision[strings.LastIndex(cloneSet.Status.UpdateRevision, "-")+1:],
IsStatusConsistent: true,
@ -200,6 +204,7 @@ func (r *ControllerFinder) getKruiseDaemonSet(namespace string, ref *rolloutv1al
//StableRevision: daemonSet.Status.CurrentRevision[strings.LastIndex(cloneSet.Status.CurrentRevision, "-")+1:],
CanaryRevision: daemonSet.Status.DaemonSetHash[strings.LastIndex(daemonSet.Status.DaemonSetHash, "-")+1:],
ObjectMeta: daemonSet.ObjectMeta,
TypeMeta: daemonSet.TypeMeta,
Replicas: daemonSet.Status.DesiredNumberScheduled,
PodTemplateHash: daemonSet.Status.DaemonSetHash[strings.LastIndex(daemonSet.Status.DaemonSetHash, "-")+1:],
IsStatusConsistent: true,
@ -246,6 +251,7 @@ func (r *ControllerFinder) getAdvancedDeployment(namespace string, ref *rolloutv
StableRevision: stableRevision,
CanaryRevision: ComputeHash(&deployment.Spec.Template, nil),
ObjectMeta: deployment.ObjectMeta,
TypeMeta: deployment.TypeMeta,
Replicas: *deployment.Spec.Replicas,
IsStatusConsistent: true,
}
@ -299,11 +305,13 @@ func (r *ControllerFinder) getDeployment(namespace string, ref *rolloutv1alpha1.
workload := &Workload{
ObjectMeta: stable.ObjectMeta,
TypeMeta: stable.TypeMeta,
Replicas: *stable.Spec.Replicas,
IsStatusConsistent: true,
StableRevision: stableRs.Labels[apps.DefaultDeploymentUniqueLabelKey],
CanaryRevision: ComputeHash(&stable.Spec.Template, nil),
RevisionLabelKey: apps.DefaultDeploymentUniqueLabelKey,
Selector: stable.Spec.Selector,
}
// not in rollout progressing
@ -357,6 +365,7 @@ func (r *ControllerFinder) getStatefulSetLikeWorkload(namespace string, ref *rol
StableRevision: workloadInfo.Status.StableRevision,
CanaryRevision: workloadInfo.Status.UpdateRevision,
ObjectMeta: workloadInfo.ObjectMeta,
TypeMeta: workloadInfo.TypeMeta,
Replicas: workloadInfo.Replicas,
PodTemplateHash: workloadInfo.Status.UpdateRevision,
IsStatusConsistent: true,

View File

@ -42,6 +42,7 @@ func ParseWorkload(object client.Object) *WorkloadInfo {
return &WorkloadInfo{
LogKey: fmt.Sprintf("%s (%s)", key, gvk),
ObjectMeta: *GetMetadata(object),
TypeMeta: *GetTypeMeta(object),
Replicas: GetReplicas(object),
Status: *ParseWorkloadStatus(object),
}
@ -332,6 +333,27 @@ func GetMetadata(object client.Object) *metav1.ObjectMeta {
}
}
// GetTypeMeta can parse the whole TypeMeta field from client workload object
func GetTypeMeta(object client.Object) *metav1.TypeMeta {
switch o := object.(type) {
case *apps.Deployment:
return &o.TypeMeta
case *appsv1alpha1.CloneSet:
return &o.TypeMeta
case *apps.StatefulSet:
return &o.TypeMeta
case *appsv1beta1.StatefulSet:
return &o.TypeMeta
case *appsv1alpha1.DaemonSet:
return &o.TypeMeta
case *unstructured.Unstructured:
gvk := object.GetObjectKind().GroupVersionKind()
return &metav1.TypeMeta{APIVersion: gvk.GroupVersion().String(), Kind: gvk.Kind}
default:
panic("unsupported workload type to ParseSelector function")
}
}
// parseReplicasFromUnstructured parses replicas from unstructured workload object
func parseReplicasFromUnstructured(object *unstructured.Unstructured) int32 {
replicas := int32(1)

View File

@ -71,6 +71,7 @@ type WorkloadStatus struct {
}
type WorkloadInfo struct {
metav1.TypeMeta
metav1.ObjectMeta
LogKey string
Replicas int32

View File

@ -67,11 +67,15 @@ func (h *RolloutCreateUpdateHandler) Handle(ctx context.Context, req admission.R
if err := h.Decoder.Decode(req, obj); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
errList := h.validateRollout(obj)
if len(errList) != 0 {
return admission.Errored(http.StatusUnprocessableEntity, errList.ToAggregate())
}
oldObj := &appsv1alpha1.Rollout{}
if err := h.Decoder.DecodeRaw(req.AdmissionRequest.OldObject, oldObj); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
errList := h.validateRolloutUpdate(oldObj, obj)
errList = h.validateRolloutUpdate(oldObj, obj)
if len(errList) != 0 {
return admission.Errored(http.StatusUnprocessableEntity, errList.ToAggregate())
}
@ -199,11 +203,7 @@ func validateRolloutSpecCanaryStrategy(canary *appsv1alpha1.CanaryStrategy, fldP
return errList
}
func validateRolloutSpecCanaryTraffic(traffic *appsv1alpha1.TrafficRouting, fldPath *field.Path) field.ErrorList {
if traffic == nil {
return field.ErrorList{field.Invalid(fldPath, nil, "Canary.TrafficRoutings cannot be empty")}
}
func validateRolloutSpecCanaryTraffic(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"))

View File

@ -56,30 +56,42 @@ var (
Canary: &appsv1alpha1.CanaryStrategy{
Steps: []appsv1alpha1.CanaryStep{
{
Weight: utilpointer.Int32Ptr(10),
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(10),
},
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(1)},
Pause: appsv1alpha1.RolloutPause{},
},
{
Weight: utilpointer.Int32Ptr(10),
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(10),
},
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(3)},
Pause: appsv1alpha1.RolloutPause{Duration: utilpointer.Int32(1 * 24 * 60 * 60)},
},
{
Weight: utilpointer.Int32Ptr(30),
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(30),
},
Pause: appsv1alpha1.RolloutPause{Duration: utilpointer.Int32(7 * 24 * 60 * 60)},
},
{
Weight: utilpointer.Int32Ptr(100),
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
},
{
Weight: utilpointer.Int32Ptr(101),
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(101),
},
},
{
Weight: utilpointer.Int32Ptr(200),
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(200),
},
},
TrafficRoutings: []*appsv1alpha1.TrafficRouting{
},
TrafficRoutings: []appsv1alpha1.TrafficRoutingRef{
{
Service: "service-demo",
Ingress: &appsv1alpha1.IngressTrafficRouting{

View File

@ -376,7 +376,7 @@ func TestHandlerDeployment(t *testing.T) {
getRollout: func() *appsv1alpha1.Rollout {
demo := rolloutDemo.DeepCopy()
demo.Spec.Strategy.Canary = &appsv1alpha1.CanaryStrategy{
TrafficRoutings: []*appsv1alpha1.TrafficRouting{
TrafficRoutings: []appsv1alpha1.TrafficRoutingRef{
{
Service: "echoserver",
Ingress: &appsv1alpha1.IngressTrafficRouting{
@ -550,8 +550,7 @@ func TestHandlerDeployment(t *testing.T) {
}
delete(newObj.Labels, appsv1alpha1.DeploymentStableRevisionLabel)
if !reflect.DeepEqual(newObj, cs.expectObj()) {
by, _ := json.Marshal(newObj)
t.Fatalf("handlerDeployment failed, and new(%s)", string(by))
t.Fatalf("handlerDeployment failed, and expect(%s) new(%s)", util.DumpJSON(cs.expectObj()), util.DumpJSON(newObj))
}
})
}

View File

@ -415,20 +415,30 @@ var _ = SIGDescribe("Rollout", func() {
Expect(ReadYamlToObject("./test_data/rollout/rollout_canary_base.yaml", rollout)).ToNot(HaveOccurred())
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
{
Weight: utilpointer.Int32(80),
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(80),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(90),
},
},
}
CreateObject(rollout)
@ -561,12 +571,16 @@ var _ = SIGDescribe("Rollout", func() {
replicas := intstr.FromInt(2)
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Replicas: &replicas,
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
}
@ -996,23 +1010,31 @@ var _ = SIGDescribe("Rollout", func() {
Expect(ReadYamlToObject("./test_data/rollout/rollout_canary_base.yaml", rollout)).ToNot(HaveOccurred())
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(10),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(10),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(0),
},
@ -1129,23 +1151,31 @@ var _ = SIGDescribe("Rollout", func() {
Expect(ReadYamlToObject("./test_data/rollout/rollout_canary_base.yaml", rollout)).ToNot(HaveOccurred())
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(10),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(10),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(0),
},
@ -1258,31 +1288,41 @@ var _ = SIGDescribe("Rollout", func() {
Expect(ReadYamlToObject("./test_data/rollout/rollout_canary_base.yaml", rollout)).ToNot(HaveOccurred())
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(5),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(5),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(5),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(80),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(5),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(0),
},
@ -1389,19 +1429,25 @@ var _ = SIGDescribe("Rollout", func() {
Expect(ReadYamlToObject("./test_data/rollout/rollout_canary_base.yaml", rollout)).ToNot(HaveOccurred())
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(10),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(10),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(10),
},
@ -1485,19 +1531,25 @@ var _ = SIGDescribe("Rollout", func() {
Expect(ReadYamlToObject("./test_data/rollout/rollout_canary_base.yaml", rollout)).ToNot(HaveOccurred())
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(5),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(5),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(5),
},
@ -1631,15 +1683,21 @@ var _ = SIGDescribe("Rollout", func() {
// update rollout step configuration
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(10),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(30),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(5),
},
@ -1732,6 +1790,7 @@ var _ = SIGDescribe("Rollout", func() {
replica2 := intstr.FromInt(2)
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Matches: []v1alpha1.HttpRouteMatch{
{
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
@ -1751,11 +1810,14 @@ var _ = SIGDescribe("Rollout", func() {
},
},
},
},
Pause: v1alpha1.RolloutPause{},
Replicas: &replica1,
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(30),
},
Replicas: &replica2,
Pause: v1alpha1.RolloutPause{},
},
@ -1894,6 +1956,7 @@ var _ = SIGDescribe("Rollout", func() {
replica2 := intstr.FromInt(2)
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Matches: []v1alpha1.HttpRouteMatch{
{
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
@ -1912,11 +1975,14 @@ var _ = SIGDescribe("Rollout", func() {
},
},
},
},
Pause: v1alpha1.RolloutPause{},
Replicas: &replica1,
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(30),
},
Replicas: &replica2,
Pause: v1alpha1.RolloutPause{},
},
@ -2062,6 +2128,7 @@ var _ = SIGDescribe("Rollout", func() {
}
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Matches: []v1alpha1.HttpRouteMatch{
{
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
@ -2080,6 +2147,7 @@ var _ = SIGDescribe("Rollout", func() {
},
},
},
},
Pause: v1alpha1.RolloutPause{},
Replicas: &replica1,
},
@ -2220,20 +2288,30 @@ var _ = SIGDescribe("Rollout", func() {
Expect(ReadYamlToObject("./test_data/gateway/rollout-test.yaml", rollout)).ToNot(HaveOccurred())
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
{
Weight: utilpointer.Int32(80),
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(80),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(90),
},
},
}
CreateObject(rollout)
@ -2378,11 +2456,15 @@ var _ = SIGDescribe("Rollout", func() {
Expect(ReadYamlToObject("./test_data/rollout/rollout_canary_base.yaml", rollout)).ToNot(HaveOccurred())
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
}
@ -2873,7 +2955,7 @@ var _ = SIGDescribe("Rollout", func() {
WaitRolloutWorkloadGeneration(rollout.Name, workload.Generation)
})
It("V1->V2: Percentage, 20%,40%,60%,80%,100%, no traffic, Succeeded", func() {
It("CloneSet V1->V2: Percentage, 20%,40%,60%,80%,100%, no traffic, Succeeded", func() {
By("Creating Rollout...")
rollout := &v1alpha1.Rollout{}
Expect(ReadYamlToObject("./test_data/rollout/rollout_canary_base.yaml", rollout)).ToNot(HaveOccurred())
@ -2987,11 +3069,15 @@ var _ = SIGDescribe("Rollout", func() {
Expect(ReadYamlToObject("./test_data/rollout/rollout_canary_base.yaml", rollout)).ToNot(HaveOccurred())
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
}
@ -3493,11 +3579,15 @@ var _ = SIGDescribe("Rollout", func() {
Expect(ReadYamlToObject("./test_data/rollout/rollout_canary_base.yaml", rollout)).ToNot(HaveOccurred())
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
}
@ -4069,23 +4159,31 @@ var _ = SIGDescribe("Rollout", func() {
}
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(10),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(10),
},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(10),
},
@ -4165,23 +4263,33 @@ var _ = SIGDescribe("Rollout", func() {
}
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(80),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(0),
},
@ -4271,23 +4379,33 @@ var _ = SIGDescribe("Rollout", func() {
}
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(80),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(0),
},
@ -4361,23 +4479,33 @@ var _ = SIGDescribe("Rollout", func() {
}
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(40),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(80),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{
Duration: utilpointer.Int32(0),
},
@ -4451,19 +4579,27 @@ var _ = SIGDescribe("Rollout", func() {
FailureThreshold: &intstr.IntOrString{Type: intstr.String, StrVal: "20%"},
Steps: []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(10),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(30),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{},
},
},
@ -4524,11 +4660,15 @@ var _ = SIGDescribe("Rollout", func() {
}
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
}
@ -4682,15 +4822,21 @@ var _ = SIGDescribe("Rollout", func() {
rollout.Spec.Strategy.Canary.TrafficRoutings = nil
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{Duration: utilpointer.Int32(0)},
},
}
@ -4788,15 +4934,21 @@ var _ = SIGDescribe("Rollout", func() {
rollout.Spec.Strategy.Canary.TrafficRoutings = nil
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{Duration: utilpointer.Int32(0)},
},
}
@ -4871,15 +5023,21 @@ var _ = SIGDescribe("Rollout", func() {
rollout.Spec.Strategy.Canary.TrafficRoutings = nil
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{Duration: utilpointer.Int32(0)},
},
}
@ -4942,15 +5100,21 @@ var _ = SIGDescribe("Rollout", func() {
rollout.Spec.Strategy.Canary.TrafficRoutings = nil
rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(20),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(60),
},
Pause: v1alpha1.RolloutPause{},
},
{
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
Weight: utilpointer.Int32(100),
},
Pause: v1alpha1.RolloutPause{Duration: utilpointer.Int32(0)},
},
}