parent
1d343d5a26
commit
88e4bb7679
|
|
@ -72,6 +72,10 @@ type RolloutSpec struct {
|
|||
// RolloutID should be changed before each workload revision publication.
|
||||
// It is to distinguish consecutive multiple workload publications and rollout progress.
|
||||
DeprecatedRolloutID string `json:"rolloutID,omitempty"`
|
||||
// if a rollout disabled, then the rollout would not watch changes of workload
|
||||
//+kubebuilder:validation:Optional
|
||||
//+kubebuilder:default=false
|
||||
Disabled bool `json:"disabled"`
|
||||
}
|
||||
|
||||
type ObjectRef struct {
|
||||
|
|
@ -257,6 +261,10 @@ const (
|
|||
RolloutPhaseProgressing RolloutPhase = "Progressing"
|
||||
// RolloutPhaseTerminating indicates a rollout is terminated
|
||||
RolloutPhaseTerminating RolloutPhase = "Terminating"
|
||||
// RolloutPhaseDisabled indicates a rollout is disabled
|
||||
RolloutPhaseDisabled RolloutPhase = "Disabled"
|
||||
// RolloutPhaseDisabling indicates a rollout is disabling and releasing resources
|
||||
RolloutPhaseDisabling RolloutPhase = "Disabling"
|
||||
)
|
||||
|
||||
// +genclient
|
||||
|
|
|
|||
|
|
@ -56,6 +56,11 @@ spec:
|
|||
spec:
|
||||
description: RolloutSpec defines the desired state of Rollout
|
||||
properties:
|
||||
disabled:
|
||||
default: false
|
||||
description: if a rollout disabled, then the rollout would not watch
|
||||
changes of workload
|
||||
type: boolean
|
||||
objectRef:
|
||||
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||
Important: Run "make" to regenerate code after modifying this file
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ func (r *RolloutReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
|
|||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// sync rollout status
|
||||
retry, newStatus, err := r.calculateRolloutStatus(rollout)
|
||||
if err != nil {
|
||||
|
|
@ -139,11 +140,14 @@ func (r *RolloutReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
|
|||
return ctrl.Result{RequeueAfter: time.Until(recheckTime)}, nil
|
||||
}
|
||||
var recheckTime *time.Time
|
||||
|
||||
switch rollout.Status.Phase {
|
||||
case v1alpha1.RolloutPhaseProgressing:
|
||||
recheckTime, err = r.reconcileRolloutProgressing(rollout, newStatus)
|
||||
case v1alpha1.RolloutPhaseTerminating:
|
||||
recheckTime, err = r.reconcileRolloutTerminating(rollout, newStatus)
|
||||
case v1alpha1.RolloutPhaseDisabling:
|
||||
recheckTime, err = r.reconcileRolloutDisabling(rollout, newStatus)
|
||||
}
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
|
|
|
|||
|
|
@ -49,6 +49,18 @@ func (r *RolloutReconciler) calculateRolloutStatus(rollout *v1alpha1.Rollout) (r
|
|||
}
|
||||
return false, newStatus, nil
|
||||
}
|
||||
|
||||
if rollout.Spec.Disabled && newStatus.Phase != v1alpha1.RolloutPhaseDisabled && newStatus.Phase != v1alpha1.RolloutPhaseDisabling {
|
||||
// if rollout in progressing, indicates a working rollout is disabled, then the rollout should be finalized
|
||||
if newStatus.Phase == v1alpha1.RolloutPhaseProgressing {
|
||||
newStatus.Phase = v1alpha1.RolloutPhaseDisabling
|
||||
newStatus.Message = "Disabling rollout, release resources"
|
||||
} else {
|
||||
newStatus.Phase = v1alpha1.RolloutPhaseDisabled
|
||||
newStatus.Message = "Rollout is disabled"
|
||||
}
|
||||
}
|
||||
|
||||
if newStatus.Phase == "" {
|
||||
newStatus.Phase = v1alpha1.RolloutPhaseInitial
|
||||
}
|
||||
|
|
@ -58,12 +70,14 @@ func (r *RolloutReconciler) calculateRolloutStatus(rollout *v1alpha1.Rollout) (r
|
|||
klog.Errorf("rollout(%s/%s) get workload failed: %s", rollout.Namespace, rollout.Name, err.Error())
|
||||
return false, nil, err
|
||||
} else if workload == nil {
|
||||
if !rollout.Spec.Disabled {
|
||||
newStatus = &v1alpha1.RolloutStatus{
|
||||
ObservedGeneration: rollout.Generation,
|
||||
Phase: v1alpha1.RolloutPhaseInitial,
|
||||
Message: "Workload Not Found",
|
||||
}
|
||||
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))
|
||||
|
|
@ -122,6 +136,11 @@ func (r *RolloutReconciler) calculateRolloutStatus(rollout *v1alpha1.Rollout) (r
|
|||
}
|
||||
newStatus.Message = "workload deployment is completed"
|
||||
}
|
||||
case v1alpha1.RolloutPhaseDisabled:
|
||||
if !rollout.Spec.Disabled {
|
||||
newStatus.Phase = v1alpha1.RolloutPhaseHealthy
|
||||
newStatus.Message = "rollout is healthy"
|
||||
}
|
||||
}
|
||||
return false, newStatus, nil
|
||||
}
|
||||
|
|
@ -207,6 +226,29 @@ func (r *RolloutReconciler) reconcileRolloutTerminating(rollout *v1alpha1.Rollou
|
|||
return c.RecheckTime, nil
|
||||
}
|
||||
|
||||
func (r *RolloutReconciler) reconcileRolloutDisabling(rollout *v1alpha1.Rollout, newStatus *v1alpha1.RolloutStatus) (*time.Time, error) {
|
||||
workload, err := r.finder.GetWorkloadForRef(rollout)
|
||||
if err != nil {
|
||||
klog.Errorf("rollout(%s/%s) get workload failed: %s", rollout.Namespace, rollout.Name, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
c := &RolloutContext{Rollout: rollout, NewStatus: newStatus, Workload: workload}
|
||||
done, err := r.doFinalising(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if done {
|
||||
klog.Infof("rollout(%s/%s) is disabled", rollout.Namespace, rollout.Name)
|
||||
newStatus.Phase = v1alpha1.RolloutPhaseDisabled
|
||||
newStatus.Message = "Rollout is disabled"
|
||||
} else {
|
||||
// Incomplete, recheck
|
||||
expectedTime := time.Now().Add(time.Duration(defaultGracePeriodSeconds) * time.Second)
|
||||
c.RecheckTime = &expectedTime
|
||||
klog.Infof("rollout(%s/%s) disabling is incomplete, and recheck(%s)", rollout.Namespace, rollout.Name, expectedTime.String())
|
||||
}
|
||||
return c.RecheckTime, nil
|
||||
}
|
||||
|
||||
// handle adding and handle finalizer logic, it turns if we should continue to reconcile
|
||||
func (r *RolloutReconciler) handleFinalizer(rollout *v1alpha1.Rollout) error {
|
||||
// delete rollout crd, remove finalizer
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package rollout
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/openkruise/rollouts/api/v1alpha1"
|
||||
|
|
@ -104,3 +105,70 @@ func TestCalculateRolloutHash(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateRolloutStatus(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
getRollout func() *v1alpha1.Rollout
|
||||
expectPhase v1alpha1.RolloutPhase
|
||||
}{
|
||||
{
|
||||
name: "apply an enabled rollout",
|
||||
getRollout: func() *v1alpha1.Rollout {
|
||||
obj := rolloutDemo.DeepCopy()
|
||||
obj.Name = "Rollout-demo1"
|
||||
obj.Status = v1alpha1.RolloutStatus{}
|
||||
obj.Spec.Disabled = false
|
||||
return obj
|
||||
},
|
||||
expectPhase: v1alpha1.RolloutPhaseInitial,
|
||||
},
|
||||
{
|
||||
name: "disable an working rollout",
|
||||
getRollout: func() *v1alpha1.Rollout {
|
||||
obj := rolloutDemo.DeepCopy()
|
||||
obj.Name = "Rollout-demo1"
|
||||
obj.Status = v1alpha1.RolloutStatus{}
|
||||
obj.Spec.Disabled = true
|
||||
return obj
|
||||
},
|
||||
expectPhase: v1alpha1.RolloutPhaseDisabled,
|
||||
},
|
||||
{
|
||||
name: "enable an disabled rollout",
|
||||
getRollout: func() *v1alpha1.Rollout {
|
||||
obj := rolloutDemo.DeepCopy()
|
||||
obj.Name = "Rollout-demo2"
|
||||
obj.Status = v1alpha1.RolloutStatus{}
|
||||
obj.Spec.Disabled = false
|
||||
return obj
|
||||
},
|
||||
expectPhase: v1alpha1.RolloutPhaseInitial,
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("RolloutStatus test", func(t *testing.T) {
|
||||
fc := fake.NewClientBuilder().WithScheme(scheme).Build()
|
||||
r := &RolloutReconciler{
|
||||
Client: fc,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(10),
|
||||
finder: util.NewControllerFinder(fc),
|
||||
trafficRoutingManager: trafficrouting.NewTrafficRoutingManager(fc),
|
||||
}
|
||||
r.canaryManager = &canaryReleaseManager{
|
||||
Client: fc,
|
||||
trafficRoutingManager: r.trafficRoutingManager,
|
||||
recorder: r.Recorder,
|
||||
}
|
||||
for _, cs := range cases {
|
||||
rollout := cs.getRollout()
|
||||
fc.Create(context.TODO(), rollout)
|
||||
_, newStatus, _ := r.calculateRolloutStatus(rollout)
|
||||
r.updateRolloutStatusInternal(rollout, *newStatus)
|
||||
if cs.expectPhase != newStatus.Phase {
|
||||
t.Fatalf("expect phase %s, get %s, for rollout %s", cs.expectPhase, newStatus.Phase, rollout.Name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -333,6 +333,7 @@ func TestRolloutValidateCreate(t *testing.T) {
|
|||
object3 := rollout.DeepCopy()
|
||||
object3.Name = "object-3"
|
||||
object3.Spec.ObjectRef.WorkloadRef.Kind = "another"
|
||||
|
||||
return []client.Object{
|
||||
object, object1, object2, object3,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -409,6 +409,10 @@ func (h *WorkloadHandler) fetchMatchedRollout(obj client.Object) (*appsv1alpha1.
|
|||
if !rollout.DeletionTimestamp.IsZero() || rollout.Spec.ObjectRef.WorkloadRef == nil {
|
||||
continue
|
||||
}
|
||||
if rollout.Status.Phase == appsv1alpha1.RolloutPhaseDisabled {
|
||||
klog.Infof("Disabled rollout(%s/%s) fetched when fetching matched rollout", rollout.Namespace, rollout.Name)
|
||||
continue
|
||||
}
|
||||
ref := rollout.Spec.ObjectRef.WorkloadRef
|
||||
gv, err := schema.ParseGroupVersion(ref.APIVersion)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -5485,6 +5485,88 @@ var _ = SIGDescribe("Rollout", func() {
|
|||
})
|
||||
|
||||
})
|
||||
|
||||
KruiseDescribe("Disabled rollout tests", func() {
|
||||
rollout := &v1alpha1.Rollout{}
|
||||
Expect(ReadYamlToObject("./test_data/rollout/rollout_disabled.yaml", rollout)).ToNot(HaveOccurred())
|
||||
It("Rollout status tests", func() {
|
||||
By("Create an enabled rollout")
|
||||
rollout1 := rollout.DeepCopy()
|
||||
rollout1.Name = "rollout-demo1"
|
||||
rollout1.Spec.Disabled = false
|
||||
CreateObject(rollout1)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
By("Create another enabled rollout")
|
||||
rollout2 := rollout.DeepCopy()
|
||||
rollout2.Name = "rollout-demo2"
|
||||
rollout2.Spec.Disabled = false
|
||||
rollout2.SetNamespace(namespace)
|
||||
Expect(k8sClient.Create(context.TODO(), rollout2)).Should(HaveOccurred())
|
||||
|
||||
By("Creating a disabled rollout")
|
||||
rollout3 := rollout.DeepCopy()
|
||||
rollout3.Name = "rollout-demo3"
|
||||
rollout3.Spec.Disabled = true
|
||||
rollout2.SetNamespace(namespace)
|
||||
Expect(k8sClient.Create(context.TODO(), rollout2)).Should(HaveOccurred())
|
||||
// wait for reconciling
|
||||
time.Sleep(3 * time.Second)
|
||||
Expect(GetObject(rollout1.Name, rollout1)).NotTo(HaveOccurred())
|
||||
Expect(rollout1.Status.Phase).Should(Equal(v1alpha1.RolloutPhaseInitial))
|
||||
|
||||
By("Create workload")
|
||||
deploy := &apps.Deployment{}
|
||||
Expect(ReadYamlToObject("./test_data/rollout/deployment_disabled.yaml", deploy)).ToNot(HaveOccurred())
|
||||
CreateObject(deploy)
|
||||
WaitDeploymentAllPodsReady(deploy)
|
||||
Expect(GetObject(rollout1.Name, rollout1)).NotTo(HaveOccurred())
|
||||
Expect(rollout1.Status.Phase).Should(Equal(v1alpha1.RolloutPhaseHealthy))
|
||||
|
||||
By("Updating deployment version-1 to version-2")
|
||||
Expect(GetObject(deploy.Name, deploy)).NotTo(HaveOccurred())
|
||||
newEnvs := mergeEnvVar(deploy.Spec.Template.Spec.Containers[0].Env, v1.EnvVar{Name: "VERSION", Value: "version-2"})
|
||||
deploy.Spec.Template.Spec.Containers[0].Env = newEnvs
|
||||
UpdateDeployment(deploy)
|
||||
WaitRolloutCanaryStepPaused(rollout1.Name, 1)
|
||||
Expect(GetObject(rollout1.Name, rollout1)).NotTo(HaveOccurred())
|
||||
Expect(rollout1.Status.CanaryStatus.CanaryReplicas).Should(BeNumerically("==", 2))
|
||||
Expect(rollout1.Status.CanaryStatus.CanaryReadyReplicas).Should(BeNumerically("==", 2))
|
||||
Expect(GetObject(deploy.Name, deploy)).NotTo(HaveOccurred())
|
||||
Expect(deploy.Spec.Paused).Should(BeTrue())
|
||||
|
||||
By("Disable a rolling rollout")
|
||||
rollout1.Spec.Disabled = true
|
||||
UpdateRollout(rollout1)
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
By("Rolling should be resumed")
|
||||
Expect(GetObject(deploy.Name, deploy)).NotTo(HaveOccurred())
|
||||
Expect(deploy.Spec.Paused).Should(BeFalse())
|
||||
|
||||
By("Batchrelease should be deleted")
|
||||
key := types.NamespacedName{Namespace: namespace, Name: rollout1.Name}
|
||||
Expect(k8sClient.Get(context.TODO(), key, &v1alpha1.BatchRelease{})).Should(HaveOccurred())
|
||||
Expect(GetObject(rollout1.Name, rollout1)).NotTo(HaveOccurred())
|
||||
Expect(rollout1.Status.Phase).Should(Equal(v1alpha1.RolloutPhaseDisabled))
|
||||
|
||||
By("Updating deployment version-2 to version-3")
|
||||
Expect(GetObject(deploy.Name, deploy)).NotTo(HaveOccurred())
|
||||
newEnvs = mergeEnvVar(deploy.Spec.Template.Spec.Containers[0].Env, v1.EnvVar{Name: "VERSION", Value: "version-3"})
|
||||
deploy.Spec.Template.Spec.Containers[0].Env = newEnvs
|
||||
UpdateDeployment(deploy)
|
||||
time.Sleep(3 * time.Second)
|
||||
Expect(GetObject(deploy.Name, deploy)).NotTo(HaveOccurred())
|
||||
Expect(deploy.Spec.Paused).Should(BeFalse())
|
||||
|
||||
By("Enable a disabled rollout")
|
||||
rollout1.Spec.Disabled = false
|
||||
UpdateRollout(rollout1)
|
||||
time.Sleep(3 * time.Second)
|
||||
Expect(GetObject(rollout1.Name, rollout1)).NotTo(HaveOccurred())
|
||||
Expect(rollout1.Status.Phase).Should(Equal(v1alpha1.RolloutPhaseHealthy))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func mergeEnvVar(original []v1.EnvVar, add v1.EnvVar) []v1.EnvVar {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: workload-demo
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 10
|
||||
selector:
|
||||
matchLabels:
|
||||
app: demo
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: demo
|
||||
spec:
|
||||
containers:
|
||||
- name: busybox
|
||||
image: busybox:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["/bin/sh", "-c", "sleep 100d"]
|
||||
env:
|
||||
- name: VERSION
|
||||
value: "version-1"
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
apiVersion: rollouts.kruise.io/v1alpha1
|
||||
kind: Rollout
|
||||
metadata:
|
||||
name: rollouts-demo
|
||||
namespace: default
|
||||
annotations:
|
||||
rollouts.kruise.io/rolling-style: partition
|
||||
spec:
|
||||
disabled: false
|
||||
objectRef:
|
||||
workloadRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: workload-demo
|
||||
strategy:
|
||||
canary:
|
||||
steps:
|
||||
- replicas: 2
|
||||
- replicas: 50%
|
||||
Loading…
Reference in New Issue