rename applicationDeployment to appRollout (#1146)

* rename applicationDeployment to appRollout

* Update test/e2e-test/rollout_plan_test.go

Co-authored-by: Jianbo Sun <wonderflow.sun@gmail.com>

Co-authored-by: Jianbo Sun <wonderflow.sun@gmail.com>
This commit is contained in:
Ryan Zhang
2021-03-05 14:28:53 -08:00
committed by GitHub
parent 994ef23c0c
commit 28fce2d7de
11 changed files with 892 additions and 189 deletions

View File

@@ -22,11 +22,11 @@ import (
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
)
// ApplicationDeploymentSpec defines how to describe an upgrade between different application
type ApplicationDeploymentSpec struct {
// TargetApplicationName contains the name of the applicationConfiguration that we need to upgrade to.
// AppRolloutSpec defines how to describe an upgrade between different apps
type AppRolloutSpec struct {
// TargetAppRevisionName contains the name of the applicationConfiguration that we need to upgrade to.
// Here we use an applicationConfiguration as a revision of an application, thus the name alone is suffice
TargetApplicationName string `json:"targetApplicationName"`
TargetAppRevisionName string `json:"targetApplicationName"`
// SourceApplicationName contains the name of the applicationConfiguration that we need to upgrade from.
// it can be empty only when it's the first time to deploy the application
@@ -42,40 +42,40 @@ type ApplicationDeploymentSpec struct {
RolloutPlan v1alpha1.RolloutPlan `json:"rolloutPlan"`
// RevertOnDelete revert the rollout when the rollout CR is deleted
// It will remove the target application from the kubernetes if it's set to true
// It will remove the target app from the kubernetes if it's set to true
// +optional
RevertOnDelete *bool `json:"revertOnDelete,omitempty"`
}
// ApplicationDeploymentStatus defines the observed state of ApplicationDeployment
type ApplicationDeploymentStatus struct {
// AppRolloutStatus defines the observed state of AppRollout
type AppRolloutStatus struct {
v1alpha1.RolloutStatus `json:",inline"`
// LastTargetApplicationName contains the name of the application that we upgraded to
// LastTargetAppName contains the name of the app that we upgraded to
// We will restart the rollout if this is not the same as the spec
LastTargetApplicationName string `json:"lastTargetApplicationName"`
LastTargetAppName string `json:"lastTargetAppName"`
// LastSourceApplicationName contains the name of the application that we need to upgrade from.
// LastSourceAppName contains the name of the app that we need to upgrade from.
// We will restart the rollout if this is not the same as the spec
LastSourceApplicationName string `json:"lastSourceApplicationName,omitempty"`
LastSourceAppName string `json:"LastSourceAppName,omitempty"`
}
// ApplicationDeployment is the Schema for the ApplicationDeployment API
// AppRollout is the Schema for the AppRollout API
// +kubebuilder:object:root=true
// +kubebuilder:resource:categories={oam}
// +kubebuilder:subresource:status
type ApplicationDeployment struct {
type AppRollout struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ApplicationDeploymentSpec `json:"spec,omitempty"`
Status ApplicationDeploymentStatus `json:"status,omitempty"`
Spec AppRolloutSpec `json:"spec,omitempty"`
Status AppRolloutStatus `json:"status,omitempty"`
}
// ApplicationDeploymentList contains a list of ApplicationDeployment
// AppRolloutList contains a list of AppRollout
// +kubebuilder:object:root=true
type ApplicationDeploymentList struct {
type AppRolloutList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ApplicationDeployment `json:"items"`
Items []AppRollout `json:"items"`
}

View File

@@ -111,10 +111,10 @@ var (
// Application type metadata.
var (
ApplicationDeploymentKind = reflect.TypeOf(ApplicationDeployment{}).Name()
ApplicationDeploymentGroupKind = schema.GroupKind{Group: Group, Kind: ApplicationDeploymentKind}.String()
ApplicationDeploymentKindAPIVersion = ApplicationKind + "." + SchemeGroupVersion.String()
ApplicationDeploymentKindVersionKind = SchemeGroupVersion.WithKind(ApplicationDeploymentKind)
AppRolloutKind = reflect.TypeOf(AppRollout{}).Name()
AppRolloutGroupKind = schema.GroupKind{Group: Group, Kind: AppRolloutKind}.String()
AppRolloutKindAPIVersion = ApplicationKind + "." + SchemeGroupVersion.String()
AppRolloutKindVersionKind = SchemeGroupVersion.WithKind(AppRolloutKind)
)
func init() {
@@ -127,5 +127,5 @@ func init() {
SchemeBuilder.Register(&ManualScalerTrait{}, &ManualScalerTraitList{})
SchemeBuilder.Register(&HealthScope{}, &HealthScopeList{})
SchemeBuilder.Register(&Application{}, &ApplicationList{})
SchemeBuilder.Register(&ApplicationDeployment{}, &ApplicationDeploymentList{})
SchemeBuilder.Register(&AppRollout{}, &AppRolloutList{})
}

View File

@@ -26,6 +26,107 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AppRollout) DeepCopyInto(out *AppRollout) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppRollout.
func (in *AppRollout) DeepCopy() *AppRollout {
if in == nil {
return nil
}
out := new(AppRollout)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AppRollout) 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 *AppRolloutList) DeepCopyInto(out *AppRolloutList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]AppRollout, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppRolloutList.
func (in *AppRolloutList) DeepCopy() *AppRolloutList {
if in == nil {
return nil
}
out := new(AppRolloutList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AppRolloutList) 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 *AppRolloutSpec) DeepCopyInto(out *AppRolloutSpec) {
*out = *in
if in.ComponentList != nil {
in, out := &in.ComponentList, &out.ComponentList
*out = make([]string, len(*in))
copy(*out, *in)
}
in.RolloutPlan.DeepCopyInto(&out.RolloutPlan)
if in.RevertOnDelete != nil {
in, out := &in.RevertOnDelete, &out.RevertOnDelete
*out = new(bool)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppRolloutSpec.
func (in *AppRolloutSpec) DeepCopy() *AppRolloutSpec {
if in == nil {
return nil
}
out := new(AppRolloutSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AppRolloutStatus) DeepCopyInto(out *AppRolloutStatus) {
*out = *in
in.RolloutStatus.DeepCopyInto(&out.RolloutStatus)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppRolloutStatus.
func (in *AppRolloutStatus) DeepCopy() *AppRolloutStatus {
if in == nil {
return nil
}
out := new(AppRolloutStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AppStatus) DeepCopyInto(out *AppStatus) {
*out = *in
@@ -292,107 +393,6 @@ func (in *ApplicationConfigurationStatus) DeepCopy() *ApplicationConfigurationSt
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationDeployment) DeepCopyInto(out *ApplicationDeployment) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDeployment.
func (in *ApplicationDeployment) DeepCopy() *ApplicationDeployment {
if in == nil {
return nil
}
out := new(ApplicationDeployment)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ApplicationDeployment) 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 *ApplicationDeploymentList) DeepCopyInto(out *ApplicationDeploymentList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]ApplicationDeployment, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDeploymentList.
func (in *ApplicationDeploymentList) DeepCopy() *ApplicationDeploymentList {
if in == nil {
return nil
}
out := new(ApplicationDeploymentList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ApplicationDeploymentList) 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 *ApplicationDeploymentSpec) DeepCopyInto(out *ApplicationDeploymentSpec) {
*out = *in
if in.ComponentList != nil {
in, out := &in.ComponentList, &out.ComponentList
*out = make([]string, len(*in))
copy(*out, *in)
}
in.RolloutPlan.DeepCopyInto(&out.RolloutPlan)
if in.RevertOnDelete != nil {
in, out := &in.RevertOnDelete, &out.RevertOnDelete
*out = new(bool)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDeploymentSpec.
func (in *ApplicationDeploymentSpec) DeepCopy() *ApplicationDeploymentSpec {
if in == nil {
return nil
}
out := new(ApplicationDeploymentSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationDeploymentStatus) DeepCopyInto(out *ApplicationDeploymentStatus) {
*out = *in
in.RolloutStatus.DeepCopyInto(&out.RolloutStatus)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDeploymentStatus.
func (in *ApplicationDeploymentStatus) DeepCopy() *ApplicationDeploymentStatus {
if in == nil {
return nil
}
out := new(ApplicationDeploymentStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationList) DeepCopyInto(out *ApplicationList) {
*out = *in

View File

@@ -0,0 +1,351 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.4
creationTimestamp: null
name: approllouts.core.oam.dev
spec:
group: core.oam.dev
names:
categories:
- oam
kind: AppRollout
listKind: AppRolloutList
plural: approllouts
singular: approllout
scope: Namespaced
versions:
- name: v1alpha2
schema:
openAPIV3Schema:
description: AppRollout is the Schema for the AppRollout 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:
description: AppRolloutSpec defines how to describe an upgrade between different apps
properties:
componentList:
description: 'The list of component to upgrade in the application. We only support single component application so far TODO: (RZ) Support multiple components in an application'
items:
type: string
type: array
revertOnDelete:
description: RevertOnDelete revert the rollout when the rollout CR is deleted It will remove the target app from the kubernetes if it's set to true
type: boolean
rolloutPlan:
description: RolloutPlan is the details on how to rollout the resources
properties:
batchPartition:
description: All pods in the batches up to the batchPartition (included) will have the target resource specification while the rest still have the source resource This is designed for the operators to manually rollout Default is the the number of batches which will rollout all the batches
format: int32
type: integer
canaryMetric:
description: CanaryMetric provides a way for the rollout process to automatically check certain metrics before complete the process
items:
description: CanaryMetric holds the reference to metrics used for canary analysis
properties:
interval:
description: Interval represents the windows size
type: string
metricsRange:
description: Range value accepted for this metric
properties:
max:
anyOf:
- type: integer
- type: string
description: Maximum value
x-kubernetes-int-or-string: true
min:
anyOf:
- type: integer
- type: string
description: Minimum value
x-kubernetes-int-or-string: true
type: object
name:
description: Name of the metric
type: string
templateRef:
description: TemplateRef references a metric template object
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
required:
- name
type: object
type: array
numBatches:
description: The number of batches, default = 1
format: int32
type: integer
paused:
description: Paused the rollout, default is false
type: boolean
rolloutBatches:
description: The exact distribution among batches. its size has to be exactly the same as the NumBatches (if set) The total number cannot exceed the targetSize or the size of the source resource We will IGNORE the last batch's replica field if it's a percentage since round errors can lead to inaccurate sum We highly recommend to leave the last batch's replica field empty
items:
description: RolloutBatch is used to describe how the each batch rollout should be
properties:
batchRolloutWebhooks:
description: RolloutWebhooks provides a way for the batch rollout to interact with an external process
items:
description: RolloutWebhook holds the reference to external checks used for canary analysis
properties:
expectedStatus:
description: ExpectedStatus contains all the expected http status code that we will accept as success
items:
type: integer
type: array
metadata:
additionalProperties:
type: string
description: Metadata (key-value pairs) for this webhook
type: object
method:
description: Method the HTTP call method, default is POST
type: string
name:
description: Name of this webhook
type: string
type:
description: Type of this webhook
type: string
url:
description: URL address of this webhook
type: string
required:
- name
- type
- url
type: object
type: array
canaryMetric:
description: CanaryMetric provides a way for the batch rollout process to automatically check certain metrics before moving to the next batch
items:
description: CanaryMetric holds the reference to metrics used for canary analysis
properties:
interval:
description: Interval represents the windows size
type: string
metricsRange:
description: Range value accepted for this metric
properties:
max:
anyOf:
- type: integer
- type: string
description: Maximum value
x-kubernetes-int-or-string: true
min:
anyOf:
- type: integer
- type: string
description: Minimum value
x-kubernetes-int-or-string: true
type: object
name:
description: Name of the metric
type: string
templateRef:
description: TemplateRef references a metric template object
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
required:
- name
type: object
type: array
instanceInterval:
description: The wait time, in seconds, between instances upgrades, default = 0
format: int32
type: integer
maxUnavailable:
anyOf:
- type: integer
- type: string
description: MaxUnavailable is the max allowed number of pods that is unavailable during the upgrade. We will mark the batch as ready as long as there are less or equal number of pods unavailable than this number. default = 0
x-kubernetes-int-or-string: true
podList:
description: The list of Pods to get upgraded it is mutually exclusive with the Replicas field
items:
type: string
type: array
replicas:
anyOf:
- type: integer
- type: string
description: 'Replicas is the number of pods to upgrade in this batch it can be an absolute number (ex: 5) or a percentage of total pods we will ignore the percentage of the last batch to just fill the gap it is mutually exclusive with the PodList field'
x-kubernetes-int-or-string: true
type: object
type: array
rolloutStrategy:
description: RolloutStrategy defines strategies for the rollout plan
type: string
rolloutWebhooks:
description: RolloutWebhooks provide a way for the rollout to interact with an external process
items:
description: RolloutWebhook holds the reference to external checks used for canary analysis
properties:
expectedStatus:
description: ExpectedStatus contains all the expected http status code that we will accept as success
items:
type: integer
type: array
metadata:
additionalProperties:
type: string
description: Metadata (key-value pairs) for this webhook
type: object
method:
description: Method the HTTP call method, default is POST
type: string
name:
description: Name of this webhook
type: string
type:
description: Type of this webhook
type: string
url:
description: URL address of this webhook
type: string
required:
- name
- type
- url
type: object
type: array
targetSize:
description: The size of the target resource. The default is the same as the size of the source resource.
format: int32
type: integer
type: object
sourceApplicationName:
description: SourceApplicationName contains the name of the applicationConfiguration that we need to upgrade from. it can be empty only when it's the first time to deploy the application
type: string
targetApplicationName:
description: TargetAppRevisionName contains the name of the applicationConfiguration that we need to upgrade to. Here we use an applicationConfiguration as a revision of an application, thus the name alone is suffice
type: string
required:
- rolloutPlan
- targetApplicationName
type: object
status:
description: AppRolloutStatus defines the observed state of AppRollout
properties:
LastSourceAppName:
description: LastSourceAppName contains the name of the app that we need to upgrade from. We will restart the rollout if this is not the same as the spec
type: string
batchRollingState:
description: BatchRollingState only meaningful when the Status is rolling
type: string
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from one status to another.
type: string
status:
description: Status of this condition; is it currently True, False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
currentBatch:
description: The current batch the rollout is working on/blocked it starts from 0
format: int32
type: integer
lastAppliedPodTemplateIdentifier:
description: lastAppliedPodTemplateIdentifier is a string that uniquely represent the last pod template each workload type could use different ways to identify that so we cannot compare between resources We update this field only after a successful rollout
type: string
lastTargetAppName:
description: LastTargetAppName contains the name of the app that we upgraded to We will restart the rollout if this is not the same as the spec
type: string
rollingState:
description: RollingState is the Rollout State
type: string
targetGeneration:
description: NewPodTemplateIdentifier is a string that uniquely represent the new pod template each workload type could use different ways to identify that so we cannot compare between resources
type: string
upgradedReadyReplicas:
description: UpgradedReplicas is the number of Pods upgraded by the rollout controller that have a Ready Condition.
format: int32
type: integer
upgradedReplicas:
description: UpgradedReplicas is the number of Pods upgraded by the rollout controller
format: int32
type: integer
required:
- currentBatch
- lastTargetAppName
- rollingState
- upgradedReadyReplicas
- upgradedReplicas
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -0,0 +1,352 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.4
creationTimestamp: null
name: approllouts.core.oam.dev
spec:
group: core.oam.dev
names:
categories:
- oam
kind: AppRollout
listKind: AppRolloutList
plural: approllouts
singular: approllout
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
description: AppRollout is the Schema for the AppRollout 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:
description: AppRolloutSpec defines how to describe an upgrade between different apps
properties:
componentList:
description: 'The list of component to upgrade in the application. We only support single component application so far TODO: (RZ) Support multiple components in an application'
items:
type: string
type: array
revertOnDelete:
description: RevertOnDelete revert the rollout when the rollout CR is deleted It will remove the target app from the kubernetes if it's set to true
type: boolean
rolloutPlan:
description: RolloutPlan is the details on how to rollout the resources
properties:
batchPartition:
description: All pods in the batches up to the batchPartition (included) will have the target resource specification while the rest still have the source resource This is designed for the operators to manually rollout Default is the the number of batches which will rollout all the batches
format: int32
type: integer
canaryMetric:
description: CanaryMetric provides a way for the rollout process to automatically check certain metrics before complete the process
items:
description: CanaryMetric holds the reference to metrics used for canary analysis
properties:
interval:
description: Interval represents the windows size
type: string
metricsRange:
description: Range value accepted for this metric
properties:
max:
anyOf:
- type: integer
- type: string
description: Maximum value
x-kubernetes-int-or-string: true
min:
anyOf:
- type: integer
- type: string
description: Minimum value
x-kubernetes-int-or-string: true
type: object
name:
description: Name of the metric
type: string
templateRef:
description: TemplateRef references a metric template object
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
required:
- name
type: object
type: array
numBatches:
description: The number of batches, default = 1
format: int32
type: integer
paused:
description: Paused the rollout, default is false
type: boolean
rolloutBatches:
description: The exact distribution among batches. its size has to be exactly the same as the NumBatches (if set) The total number cannot exceed the targetSize or the size of the source resource We will IGNORE the last batch's replica field if it's a percentage since round errors can lead to inaccurate sum We highly recommend to leave the last batch's replica field empty
items:
description: RolloutBatch is used to describe how the each batch rollout should be
properties:
batchRolloutWebhooks:
description: RolloutWebhooks provides a way for the batch rollout to interact with an external process
items:
description: RolloutWebhook holds the reference to external checks used for canary analysis
properties:
expectedStatus:
description: ExpectedStatus contains all the expected http status code that we will accept as success
items:
type: integer
type: array
metadata:
additionalProperties:
type: string
description: Metadata (key-value pairs) for this webhook
type: object
method:
description: Method the HTTP call method, default is POST
type: string
name:
description: Name of this webhook
type: string
type:
description: Type of this webhook
type: string
url:
description: URL address of this webhook
type: string
required:
- name
- type
- url
type: object
type: array
canaryMetric:
description: CanaryMetric provides a way for the batch rollout process to automatically check certain metrics before moving to the next batch
items:
description: CanaryMetric holds the reference to metrics used for canary analysis
properties:
interval:
description: Interval represents the windows size
type: string
metricsRange:
description: Range value accepted for this metric
properties:
max:
anyOf:
- type: integer
- type: string
description: Maximum value
x-kubernetes-int-or-string: true
min:
anyOf:
- type: integer
- type: string
description: Minimum value
x-kubernetes-int-or-string: true
type: object
name:
description: Name of the metric
type: string
templateRef:
description: TemplateRef references a metric template object
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
required:
- name
type: object
type: array
instanceInterval:
description: The wait time, in seconds, between instances upgrades, default = 0
format: int32
type: integer
maxUnavailable:
anyOf:
- type: integer
- type: string
description: MaxUnavailable is the max allowed number of pods that is unavailable during the upgrade. We will mark the batch as ready as long as there are less or equal number of pods unavailable than this number. default = 0
x-kubernetes-int-or-string: true
podList:
description: The list of Pods to get upgraded it is mutually exclusive with the Replicas field
items:
type: string
type: array
replicas:
anyOf:
- type: integer
- type: string
description: 'Replicas is the number of pods to upgrade in this batch it can be an absolute number (ex: 5) or a percentage of total pods we will ignore the percentage of the last batch to just fill the gap it is mutually exclusive with the PodList field'
x-kubernetes-int-or-string: true
type: object
type: array
rolloutStrategy:
description: RolloutStrategy defines strategies for the rollout plan
type: string
rolloutWebhooks:
description: RolloutWebhooks provide a way for the rollout to interact with an external process
items:
description: RolloutWebhook holds the reference to external checks used for canary analysis
properties:
expectedStatus:
description: ExpectedStatus contains all the expected http status code that we will accept as success
items:
type: integer
type: array
metadata:
additionalProperties:
type: string
description: Metadata (key-value pairs) for this webhook
type: object
method:
description: Method the HTTP call method, default is POST
type: string
name:
description: Name of this webhook
type: string
type:
description: Type of this webhook
type: string
url:
description: URL address of this webhook
type: string
required:
- name
- type
- url
type: object
type: array
targetSize:
description: The size of the target resource. The default is the same as the size of the source resource.
format: int32
type: integer
type: object
sourceApplicationName:
description: SourceApplicationName contains the name of the applicationConfiguration that we need to upgrade from. it can be empty only when it's the first time to deploy the application
type: string
targetApplicationName:
description: TargetAppRevisionName contains the name of the applicationConfiguration that we need to upgrade to. Here we use an applicationConfiguration as a revision of an application, thus the name alone is suffice
type: string
required:
- rolloutPlan
- targetApplicationName
type: object
status:
description: AppRolloutStatus defines the observed state of AppRollout
properties:
LastSourceAppName:
description: LastSourceAppName contains the name of the app that we need to upgrade from. We will restart the rollout if this is not the same as the spec
type: string
batchRollingState:
description: BatchRollingState only meaningful when the Status is rolling
type: string
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from one status to another.
type: string
status:
description: Status of this condition; is it currently True, False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
currentBatch:
description: The current batch the rollout is working on/blocked it starts from 0
format: int32
type: integer
lastAppliedPodTemplateIdentifier:
description: lastAppliedPodTemplateIdentifier is a string that uniquely represent the last pod template each workload type could use different ways to identify that so we cannot compare between resources We update this field only after a successful rollout
type: string
lastTargetAppName:
description: LastTargetAppName contains the name of the app that we upgraded to We will restart the rollout if this is not the same as the spec
type: string
rollingState:
description: RollingState is the Rollout State
type: string
targetGeneration:
description: NewPodTemplateIdentifier is a string that uniquely represent the new pod template each workload type could use different ways to identify that so we cannot compare between resources
type: string
upgradedReadyReplicas:
description: UpgradedReplicas is the number of Pods upgraded by the rollout controller that have a Ready Condition.
format: int32
type: integer
upgradedReplicas:
description: UpgradedReplicas is the number of Pods upgraded by the rollout controller
format: int32
type: integer
required:
- currentBatch
- lastTargetAppName
- rollingState
- upgradedReadyReplicas
- upgradedReplicas
type: object
type: object
version: v1alpha2
versions:
- name: v1alpha2
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -1653,7 +1653,7 @@ func TestIsControlledByApp(t *testing.T) {
// still true when it's not the only the controller
ac.OwnerReferences = append(ac.OwnerReferences, metav1.OwnerReference{
APIVersion: v1alpha2.SchemeGroupVersion.String(),
Kind: v1alpha2.ApplicationDeploymentKind,
Kind: v1alpha2.AppRolloutKind,
Controller: pointer.BoolPtr(true),
})
assert.True(t, isControlledByApp(ac))

View File

@@ -37,14 +37,14 @@ type Reconciler struct {
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=core.oam.dev,resources=applicationdeployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core.oam.dev,resources=applicationdeployments/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=core.oam.dev,resources=approllouts,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core.oam.dev,resources=approllouts/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=core.oam.dev,resources=applications,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core.oam.dev,resources=applications/status,verbs=get;update;patch
// Reconcile is the main logic of applicationdeployment controller
func (r *Reconciler) Reconcile(req ctrl.Request) (res reconcile.Result, retErr error) {
var appDeploy oamv1alpha2.ApplicationDeployment
var appRollout oamv1alpha2.AppRollout
ctx, cancel := context.WithTimeout(context.TODO(), reconcileTimeOut)
defer cancel()
@@ -62,23 +62,23 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (res reconcile.Result, retErr e
}
}()
if err := r.Get(ctx, req.NamespacedName, &appDeploy); err != nil {
if err := r.Get(ctx, req.NamespacedName, &appRollout); err != nil {
if apierrors.IsNotFound(err) {
klog.InfoS("application deployment does not exist", "appDeploy", klog.KRef(req.Namespace, req.Name))
klog.InfoS("application deployment does not exist", "appRollout", klog.KRef(req.Namespace, req.Name))
}
return ctrl.Result{}, client.IgnoreNotFound(err)
}
klog.InfoS("Start to reconcile ", "application deployment", klog.KObj(&appDeploy))
klog.InfoS("Start to reconcile ", "application deployment", klog.KObj(&appRollout))
// TODO: check if the target/source has changed
r.handleFinalizer(&appDeploy)
r.handleFinalizer(&appRollout)
ctx = oamutil.SetNnamespaceInCtx(ctx, appDeploy.Namespace)
ctx = oamutil.SetNnamespaceInCtx(ctx, appRollout.Namespace)
// Get the target application
var targetApp oamv1alpha2.ApplicationConfiguration
sourceApp := &oamv1alpha2.ApplicationConfiguration{}
targetAppName := appDeploy.Spec.TargetApplicationName
targetAppName := appRollout.Spec.TargetAppRevisionName
if err := r.Get(ctx, ktypes.NamespacedName{Namespace: req.Namespace, Name: targetAppName},
&targetApp); err != nil {
klog.ErrorS(err, "cannot locate target application", "target application",
@@ -87,7 +87,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (res reconcile.Result, retErr e
}
// Get the source application
sourceAppName := appDeploy.Spec.SourceApplicationName
sourceAppName := appRollout.Spec.SourceApplicationName
if sourceAppName == "" {
klog.Info("source app fields not filled, we assume it is deployed for the first time")
sourceApp = nil
@@ -97,11 +97,11 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (res reconcile.Result, retErr e
return ctrl.Result{}, err
}
targetWorkload, sourceWorkload, err := r.extractWorkloads(ctx, appDeploy.Spec.ComponentList, &targetApp, sourceApp)
targetWorkload, sourceWorkload, err := r.extractWorkloads(ctx, appRollout.Spec.ComponentList, &targetApp, sourceApp)
if err != nil {
klog.ErrorS(err, "cannot fetch the workloads to upgrade", "target application",
klog.KRef(req.Namespace, targetAppName), "source application", klog.KRef(req.Namespace, sourceAppName),
"commonComponent", appDeploy.Spec.ComponentList)
"commonComponent", appRollout.Spec.ComponentList)
return ctrl.Result{RequeueAfter: 5 * time.Second}, client.IgnoreNotFound(err)
}
klog.InfoS("get the target workload we need to work on", "targetWorkload", klog.KObj(targetWorkload))
@@ -111,11 +111,11 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (res reconcile.Result, retErr e
}
// reconcile the rollout part of the spec given the target and source workload
rolloutPlanController := rollout.NewRolloutPlanController(r, &appDeploy, r.record,
&appDeploy.Spec.RolloutPlan, &appDeploy.Status.RolloutStatus, targetWorkload, sourceWorkload)
rolloutPlanController := rollout.NewRolloutPlanController(r, &appRollout, r.record,
&appRollout.Spec.RolloutPlan, &appRollout.Status.RolloutStatus, targetWorkload, sourceWorkload)
result, rolloutStatus := rolloutPlanController.Reconcile(ctx)
// make sure that the new status is copied back
appDeploy.Status.RolloutStatus = *rolloutStatus
appRollout.Status.RolloutStatus = *rolloutStatus
if rolloutStatus.RollingState == v1alpha1.RolloutSucceedState {
// remove the rollout annotation so that the target appConfig controller can take over the rest of the work
oamutil.RemoveAnnotations(&targetApp, []string{oam.AnnotationAppRollout})
@@ -125,29 +125,29 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (res reconcile.Result, retErr e
return ctrl.Result{}, err
}
}
// update the appDeploy status
return result, r.updateStatus(ctx, &appDeploy)
// update the appRollout status
return result, r.updateStatus(ctx, &appRollout)
}
// UpdateStatus updates v1alpha2.ApplicationDeployment's Status with retry.RetryOnConflict
func (r *Reconciler) updateStatus(ctx context.Context, app *oamv1alpha2.ApplicationDeployment, opts ...client.UpdateOption) error {
status := app.DeepCopy().Status
// UpdateStatus updates v1alpha2.AppRollout's Status with retry.RetryOnConflict
func (r *Reconciler) updateStatus(ctx context.Context, appRollout *oamv1alpha2.AppRollout, opts ...client.UpdateOption) error {
status := appRollout.DeepCopy().Status
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
if err = r.Get(ctx, client.ObjectKey{Namespace: app.Namespace, Name: app.Name}, app); err != nil {
if err = r.Get(ctx, client.ObjectKey{Namespace: appRollout.Namespace, Name: appRollout.Name}, appRollout); err != nil {
return
}
app.Status = status
return r.Status().Update(ctx, app, opts...)
appRollout.Status = status
return r.Status().Update(ctx, appRollout, opts...)
})
}
func (r *Reconciler) handleFinalizer(appDeploy *oamv1alpha2.ApplicationDeployment) {
if appDeploy.DeletionTimestamp.IsZero() {
if !slice.ContainsString(appDeploy.Finalizers, appDeployFinalizer, nil) {
func (r *Reconciler) handleFinalizer(appRollout *oamv1alpha2.AppRollout) {
if appRollout.DeletionTimestamp.IsZero() {
if !slice.ContainsString(appRollout.Finalizers, appDeployFinalizer, nil) {
// TODO: add finalizer
klog.Info("add finalizer")
}
} else if slice.ContainsString(appDeploy.Finalizers, appDeployFinalizer, nil) {
} else if slice.ContainsString(appRollout.Finalizers, appDeployFinalizer, nil) {
// TODO: perform finalize
klog.Info("perform clean up")
}
@@ -158,7 +158,7 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
r.record = event.NewAPIRecorder(mgr.GetEventRecorderFor("ApplicationDeployment")).
WithAnnotations("controller", "ApplicationDeployment")
return ctrl.NewControllerManagedBy(mgr).
For(&oamv1alpha2.ApplicationDeployment{}).
For(&oamv1alpha2.AppRollout{}).
Owns(&oamv1alpha2.Application{}).
Complete(r)
}

View File

@@ -31,7 +31,7 @@ var _ admission.Handler = &MutatingHandler{}
// Handle handles admission requests.
func (h *MutatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
obj := &v1alpha2.ApplicationDeployment{}
obj := &v1alpha2.AppRollout{}
err := h.Decoder.Decode(req, obj)
if err != nil {
@@ -52,7 +52,7 @@ func (h *MutatingHandler) Handle(ctx context.Context, req admission.Request) adm
}
// DefaultApplicationDeployment will set the default value for the ApplicationDeployment
func DefaultApplicationDeployment(obj *v1alpha2.ApplicationDeployment) {
func DefaultApplicationDeployment(obj *v1alpha2.AppRollout) {
klog.InfoS("default", "name", obj.Name)
if obj.Spec.RevertOnDelete == nil {
klog.V(common.LogDebug).Info("default RevertOnDelete as false")

View File

@@ -27,7 +27,7 @@ var _ admission.Handler = &ValidatingHandler{}
// Handle handles admission requests.
func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
obj := &v1alpha2.ApplicationDeployment{}
obj := &v1alpha2.AppRollout{}
err := h.Decoder.Decode(req, obj)
if err != nil {
@@ -42,7 +42,7 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a
return admission.Errored(http.StatusUnprocessableEntity, allErrs.ToAggregate())
}
case admissionv1beta1.Update:
oldObj := &v1alpha2.ApplicationDeployment{}
oldObj := &v1alpha2.AppRollout{}
if err := h.Decoder.DecodeRaw(req.AdmissionRequest.OldObject, oldObj); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

View File

@@ -14,13 +14,13 @@ import (
)
// ValidateCreate validates the ApplicationDeployment on creation
func (h *ValidatingHandler) ValidateCreate(appDeploy *v1alpha2.ApplicationDeployment) field.ErrorList {
klog.InfoS("validate create", "name", appDeploy.Name)
allErrs := apimachineryvalidation.ValidateObjectMeta(&appDeploy.ObjectMeta, true,
func (h *ValidatingHandler) ValidateCreate(appRollout *v1alpha2.AppRollout) field.ErrorList {
klog.InfoS("validate create", "name", appRollout.Name)
allErrs := apimachineryvalidation.ValidateObjectMeta(&appRollout.ObjectMeta, true,
apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
fldPath := field.NewPath("spec")
target := appDeploy.Spec.TargetApplicationName
target := appRollout.Spec.TargetAppRevisionName
if len(target) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("targetApplicationName"),
"target application name cannot be empty"))
@@ -29,31 +29,31 @@ func (h *ValidatingHandler) ValidateCreate(appDeploy *v1alpha2.ApplicationDeploy
}
var targetApp, sourceApp v1alpha2.ApplicationConfiguration
targetAppName := appDeploy.Spec.TargetApplicationName
if err := h.Get(context.Background(), ktypes.NamespacedName{Namespace: appDeploy.Namespace, Name: targetAppName},
targetAppName := appRollout.Spec.TargetAppRevisionName
if err := h.Get(context.Background(), ktypes.NamespacedName{Namespace: appRollout.Namespace, Name: targetAppName},
&targetApp); err != nil {
klog.ErrorS(err, "cannot locate target application", "target application",
klog.KRef(appDeploy.Namespace, targetAppName))
klog.KRef(appRollout.Namespace, targetAppName))
allErrs = append(allErrs, field.NotFound(fldPath.Child("targetApplicationName"), targetAppName))
// can't continue without target
return allErrs
}
sourceAppName := appDeploy.Spec.SourceApplicationName
sourceAppName := appRollout.Spec.SourceApplicationName
if sourceAppName != "" {
if err := h.Get(context.Background(), ktypes.NamespacedName{Namespace: appDeploy.Namespace, Name: sourceAppName},
if err := h.Get(context.Background(), ktypes.NamespacedName{Namespace: appRollout.Namespace, Name: sourceAppName},
&sourceApp); err != nil {
klog.ErrorS(err, "cannot locate source application", "source application",
klog.KRef(appDeploy.Namespace, sourceAppName))
klog.KRef(appRollout.Namespace, sourceAppName))
allErrs = append(allErrs, field.NotFound(fldPath.Child("sourceApplicationName"), sourceAppName))
}
}
// validate the component spec
allErrs = append(allErrs, validateComponent(appDeploy.Spec.ComponentList, &targetApp, &sourceApp,
allErrs = append(allErrs, validateComponent(appRollout.Spec.ComponentList, &targetApp, &sourceApp,
fldPath.Child("componentList"))...)
// validate the rollout plan spec
allErrs = append(allErrs, rollout.ValidateCreate(&appDeploy.Spec.RolloutPlan, fldPath.Child("rolloutPlan"))...)
allErrs = append(allErrs, rollout.ValidateCreate(&appRollout.Spec.RolloutPlan, fldPath.Child("rolloutPlan"))...)
return allErrs
}
@@ -103,7 +103,7 @@ func validateComponent(componentList []string, targetApp, sourceApp *v1alpha2.Ap
}
// ValidateUpdate validates the ApplicationDeployment on update
func (h *ValidatingHandler) ValidateUpdate(new, old *v1alpha2.ApplicationDeployment) field.ErrorList {
func (h *ValidatingHandler) ValidateUpdate(new, old *v1alpha2.AppRollout) field.ErrorList {
klog.InfoS("validate update", "name", new.Name)
errList := h.ValidateCreate(new)
if len(errList) > 0 {

View File

@@ -140,24 +140,24 @@ var _ = Describe("Test Rolling out Application", func() {
Expect(kc.Spec.UpdateStrategy.Paused).Should(BeTrue())
By("Apply the application rollout that stops after two batches")
var appDeploy v1alpha2.ApplicationDeployment
Expect(readYaml("testdata/rollout/app-deploy-pause.yaml", &appDeploy)).Should(BeNil())
appDeploy.Namespace = namespace
Expect(k8sClient.Create(ctx, &appDeploy)).Should(Succeed())
var appRollout v1alpha2.AppRollout
Expect(readYaml("testdata/rollout/app-deploy-pause.yaml", &appRollout)).Should(BeNil())
appRollout.Namespace = namespace
Expect(k8sClient.Create(ctx, &appRollout)).Should(Succeed())
By("Wait for the rollout phase change to rolling in batches")
Eventually(
func() oamstd.RollingState {
k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appDeploy.Name}, &appDeploy)
return appDeploy.Status.RollingState
k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appRollout.Name}, &appRollout)
return appRollout.Status.RollingState
},
time.Second*60, time.Millisecond*500).Should(BeEquivalentTo(oamstd.RollingInBatchesState))
By("Wait for rollout to finish two batches")
Eventually(
func() int32 {
k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appDeploy.Name}, &appDeploy)
return appDeploy.Status.CurrentBatch
k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appRollout.Name}, &appRollout)
return appRollout.Status.CurrentBatch
},
time.Second*60, time.Millisecond*500).Should(BeEquivalentTo(1))
@@ -165,15 +165,15 @@ var _ = Describe("Test Rolling out Application", func() {
// wait for the batch to be ready
Eventually(
func() oamstd.BatchRollingState {
k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appDeploy.Name}, &appDeploy)
return appDeploy.Status.BatchRollingState
k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appRollout.Name}, &appRollout)
return appRollout.Status.BatchRollingState
},
time.Second*60, time.Millisecond*500).Should(Equal(oamstd.BatchReadyState))
// wait for 30 seconds, it should still be at 1
time.Sleep(30 * time.Second)
k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appDeploy.Name}, &appDeploy)
Expect(appDeploy.Status.CurrentBatch).Should(BeEquivalentTo(1))
Expect(appDeploy.Status.BatchRollingState).Should(BeEquivalentTo(oamstd.BatchReadyState))
k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appRollout.Name}, &appRollout)
Expect(appRollout.Status.CurrentBatch).Should(BeEquivalentTo(1))
Expect(appRollout.Status.BatchRollingState).Should(BeEquivalentTo(oamstd.BatchReadyState))
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: workloadName},
&kc)).ShouldNot(HaveOccurred())
@@ -181,15 +181,15 @@ var _ = Describe("Test Rolling out Application", func() {
Expect(kc.Status.UpdatedReadyReplicas).Should(BeEquivalentTo(3))
By("Finish the application rollout")
Expect(readYaml("testdata/rollout/app-deploy-finish.yaml", &appDeploy)).Should(BeNil())
appDeploy.Namespace = namespace
Expect(k8sClient.Update(ctx, &appDeploy)).Should(Succeed())
Expect(readYaml("testdata/rollout/app-deploy-finish.yaml", &appRollout)).Should(BeNil())
appRollout.Namespace = namespace
Expect(k8sClient.Update(ctx, &appRollout)).Should(Succeed())
By("Wait for the rollout phase change to succeeded")
Eventually(
func() oamstd.RollingState {
k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appDeploy.Name}, &appDeploy)
return appDeploy.Status.RollingState
k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appRollout.Name}, &appRollout)
return appRollout.Status.RollingState
},
time.Second*60, time.Millisecond*500).Should(Equal(oamstd.RolloutSucceedState))
@@ -199,7 +199,7 @@ var _ = Describe("Test Rolling out Application", func() {
Expect(kc.Status.UpdatedReplicas).Should(BeEquivalentTo(5))
Expect(kc.Status.UpdatedReadyReplicas).Should(BeEquivalentTo(5))
// Clean up
k8sClient.Delete(ctx, &appDeploy)
k8sClient.Delete(ctx, &appRollout)
k8sClient.Delete(ctx, &appConfig2)
k8sClient.Delete(ctx, &appConfig1)
k8sClient.Delete(ctx, &app)