rollout OCM demo and defer rollout pass owner to handleSucceed (#2121)

* finish test

fix demo

finish test

finish docs

fix rollout template

add more test

add comment

* impove test coverage and wrap error

fix checkdiff

add more time
This commit is contained in:
wyike
2021-08-19 15:33:40 +08:00
committed by GitHub
parent f033768dd2
commit 0c0b9d0774
14 changed files with 638 additions and 22 deletions
@@ -26,7 +26,9 @@ spec:
rolloutStrategy: "IncreaseFirst"
rolloutBatches: parameter.rolloutBatches
targetSize: parameter.targetSize
batchPartition: parameter.batchPartition
if parameter["batchPartition"] != _|_ {
batchPartition: parameter.batchPartition
}
}
}
}
@@ -37,5 +39,4 @@ spec:
batchPartition?: int
}
rolloutBatch: replicas: int
skipRevisionAffect: true
@@ -0,0 +1,119 @@
# Rollout with OCM
In this tutorial, you will use rollout in runtime cluster with OCM.
## Prerequisites
- Have a multi-cluster environment witch have installed OCM by following this [guid](../README.md). The name of managed cluster generally is `poc-01`.
- install vela-rollout chart in managed-cluster with Helm.
```shell
$ helm repo add kubevela https://charts.kubevela.net/core
```
```shell
$ helm isntall vela-rollout --create-namespace -n vela-system kubevela/vela-rollout
```
## Install workflowStepDefinition
Apply workflowStepDefinitions in control-plane cluster.
```shell
$ kubectl apply -f dispatchRevDef.yaml
```
```shell
$ kubectl apply -f dispatchTraits.yaml
```
## Deploy and rollout
1. Apply application in control-plane cluster.
```shell
$ kubectl apply -f app-first-scale.yaml
```
Wait a few minute, check rollout and workload status in managed cluster.
```shell
$ kubectl get rollout
NAME TARGET UPGRADED READY BATCH-STATE ROLLING-STATE AGE
nginx-server 2 2 2 batchReady rolloutSucceed 25h
```
```shell
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-server-v1 2/2 2 2 25h
```
2. Update application to v2.
```shell
$ kubectl apply -f app-update-v2.yaml
```
check rollout and workload status.
```shell
$ kubectl get rollout
NAME TARGET UPGRADED READY BATCH-STATE ROLLING-STATE AGE
nginx-server 2 2 2 batchReady rolloutSucceed 25h
```
```shell
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-server-v2 2/2 2 2 25h
```
3. Scale up.
```shell
$ kubectl apply -f app-v2-scale.yaml
```
check the status of rollout and workload.
```shell
$ kubectl get rollout
NAME TARGET UPGRADED READY BATCH-STATE ROLLING-STATE AGE
nginx-server 4 4 4 batchReady rolloutSucceed 25h
```
```shell
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-server-v2 4/4 4 4 25h
```
4. Roll back to v1.
```shell
$ kubectl apply -f app-revert.yaml
```
check rollout and workload status.
```shell
$ kubectl get rollout
NAME TARGET UPGRADED READY BATCH-STATE ROLLING-STATE AGE
nginx-server 4 4 4 batchReady rolloutSucceed 25h
```
```shell
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-server-v1 4/4 4 4 25h
```
5. Scale down.
```shell
$ kubectl apply -f app-scale-down-v1.yaml
```
check rollout and workload status.
```shell
$ kubectl get rollout
NAME TARGET UPGRADED READY BATCH-STATE ROLLING-STATE AGE
nginx-server 2 2 2 batchReady rolloutSucceed 25h
```
```shell
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-server-v1 2/2 2 2 25h
```
@@ -0,0 +1,35 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: workflow-rollout-demo
namespace: default
spec:
components:
- name: nginx-server
externalRevision: nginx-server-v1
type: webservice
properties:
image: nginx:1.20.0
port: 80
traits:
- type: rollout
properties:
targetRevision: nginx-server-v1
targetSize: 2
rolloutBatches:
- replicas: 1
- replicas: 1
workflow:
steps:
- name: dispatch-comp-rev-v1
type: dispatch-comp-rev
properties:
compRev: nginx-server-v1
cluster: poc-01
- name: dispatchRollout
type: dispatch-traits
properties:
component: nginx-server
cluster: poc-01
@@ -0,0 +1,41 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: workflow-rollout-demo
namespace: default
spec:
components:
- name: nginx-server
externalRevision: nginx-server-v2
type: webservice
properties:
image: nginx:1.21
port: 80
traits:
- type: rollout
properties:
targetRevision: nginx-server-v1
targetSize: 4
rolloutBatches:
- replicas: 2
- replicas: 2
workflow:
steps:
- name: dispatch-comp-rev-v1
type: dispatch-comp-rev
properties:
compRev: nginx-server-v1
cluster: poc-01
- name: dispatch-comp-rev-v2
type: dispatch-comp-rev
properties:
compRev: nginx-server-v2
cluster: poc-01
- name: dispatchRollout
type: dispatch-traits
properties:
component: nginx-server
cluster: poc-01
@@ -0,0 +1,41 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: workflow-rollout-demo
namespace: default
spec:
components:
- name: nginx-server
externalRevision: nginx-server-v2
type: webservice
properties:
image: nginx:1.21
port: 80
traits:
- type: rollout
properties:
targetRevision: nginx-server-v1
targetSize: 2
rolloutBatches:
- replicas: 1
- replicas: 1
workflow:
steps:
- name: dispatch-comp-rev-v1
type: dispatch-comp-rev
properties:
compRev: nginx-server-v1
cluster: cluster2
- name: dispatch-comp-rev-v2
type: dispatch-comp-rev
properties:
compRev: nginx-server-v2
cluster: cluster2
- name: dispatchRollout
type: dispatch-traits
properties:
component: nginx-server
cluster: cluster2
@@ -0,0 +1,41 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: workflow-rollout-demo
namespace: default
spec:
components:
- name: nginx-server
externalRevision: nginx-server-v2
type: webservice
properties:
image: nginx:1.21
port: 80
traits:
- type: rollout
properties:
targetRevision: nginx-server-v2
targetSize: 2
rolloutBatches:
- replicas: 1
- replicas: 1
workflow:
steps:
- name: dispatch-comp-rev-v1
type: dispatch-comp-rev
properties:
compRev: nginx-server-v1
cluster: poc-01
- name: dispatch-comp-rev-v2
type: dispatch-comp-rev
properties:
compRev: nginx-server-v2
cluster: poc-01
- name: dispatchRollout
type: dispatch-traits
properties:
component: nginx-server
cluster: poc-01
@@ -0,0 +1,41 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: workflow-rollout-demo
namespace: default
spec:
components:
- name: nginx-server
externalRevision: nginx-server-v2
type: webservice
properties:
image: nginx:1.21
port: 80
traits:
- type: rollout
properties:
targetRevision: nginx-server-v2
targetSize: 4
rolloutBatches:
- replicas: 1
- replicas: 1
workflow:
steps:
- name: dispatch-comp-rev-v1
type: dispatch-comp-rev
properties:
compRev: nginx-server-v1
cluster: poc-01
- name: dispatch-comp-rev-v2
type: dispatch-comp-rev
properties:
compRev: nginx-server-v2
cluster: poc-01
- name: dispatchRollout
type: dispatch-traits
properties:
component: nginx-server
cluster: poc-01
@@ -0,0 +1,60 @@
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
name: dispatch-comp-rev
namespace: vela-system
spec:
schematic:
cue:
template: |
import ("vela/op")
componentRev: op.#Read & {
value: {
kind: "ControllerRevision"
apiVersion: "apps/v1"
metadata: {
name: parameter.compRev
namespace: context.namespace
}
}
}
apply: op.#Apply & {
value: {
apiVersion: "work.open-cluster-management.io/v1"
kind: "ManifestWork"
metadata: {
namespace: parameter.cluster
name: "comprevision-" + parameter.compRev
}
spec: {
workload: {
manifests: [{
kind: "ControllerRevision"
apiVersion: "apps/v1"
metadata: {
name: parameter.compRev
namespace: context.namespace
}
data: {
apiVersion: "core.oam.dev/v1alpha2"
kind: "Component"
metadata:
name: context.name
namespace: context.namespace
spec:
workload: {
componentRev.value.data.spec.workload
}
}
}]
}
}
}
}
parameter: {
compRev: string
cluster: string
}
@@ -0,0 +1,34 @@
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
name: dispatch-traits
namespace: vela-system
spec:
schematic:
cue:
template: |
import ("vela/op")
comp: op.#Load & {
component: parameter.component
}
apply: op.#Apply & {
value: {
apiVersion: "work.open-cluster-management.io/v1"
kind: "ManifestWork"
metadata: {
namespace: parameter.cluster
name: parameter.component + "-traits"
}
spec: {
workload: manifests : comp.value.auxiliaries
}
}
}
parameter: {
component: string
cluster: string
}
@@ -22,6 +22,8 @@ import (
"github.com/pkg/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -123,10 +125,6 @@ func (h *handler) applyTargetWorkload(ctx context.Context) error {
return err
}
if err := h.recordWorkloadInResourceTracker(ctx); err != nil {
return errors.Wrap(err, "fail to add resourceTracker as owner for workload")
}
klog.InfoS("template rollout target workload", "namespace", h.rollout.Namespace,
"rollout", h.rollout.Name, "targetWorkload", h.targetWorkload.GetName())
return nil
@@ -134,7 +132,22 @@ func (h *handler) applyTargetWorkload(ctx context.Context) error {
// handleFinalizeSucceed gc source workload if target and source are not same one
func (h *handler) handleFinalizeSucceed(ctx context.Context) error {
// this is a scale operation
// patch target workload to pass rollout owner to workload
targetWl := h.targetWorkload.DeepCopy()
if err := h.Get(ctx, types.NamespacedName{Namespace: targetWl.GetNamespace(), Name: targetWl.GetName()}, targetWl); err != nil {
return errors.Wrap(err, "fail to get targetWorkload")
}
wlPatch := client.MergeFrom(targetWl.DeepCopy())
h.passOwnerToTargetWorkload(targetWl)
if err := h.Patch(ctx, targetWl, wlPatch); err != nil {
return errors.Wrap(err, "fail to patch workload to pass rollout owners to workload")
}
// recode targetWorkload
if err := h.recordWorkloadInResourceTracker(ctx); err != nil {
return errors.Wrap(err, "fail to add resourceTracker as owner for workload")
}
if h.sourceWorkload != nil && (h.sourceWorkload.GetName() != h.targetWorkload.GetName()) {
if err := h.Delete(ctx, h.sourceWorkload); err != nil {
return err
@@ -183,9 +196,6 @@ func (h *handler) setWorkloadBaseInfo() {
util.AddLabels(h.targetWorkload, map[string]string{oam.LabelAppComponentRevision: h.targetRevName})
util.AddAnnotations(h.targetWorkload, map[string]string{oam.AnnotationSkipGC: "true"})
// pass rollout's ownerReference to workload
h.passOwnerToTargetWorkload()
if h.sourceWorkload != nil {
h.sourceWorkload.SetName(h.compName)
util.AddLabels(h.sourceWorkload, map[string]string{oam.LabelAppComponentRevision: h.sourceRevName})
@@ -270,11 +280,11 @@ func (h *handler) recordWorkloadInResourceTracker(ctx context.Context) error {
return nil
}
func (h *handler) passOwnerToTargetWorkload() {
func (h *handler) passOwnerToTargetWorkload(wl *unstructured.Unstructured) {
var owners []metav1.OwnerReference
for _, reference := range h.rollout.OwnerReferences {
reference.Controller = pointer.Bool(false)
owners = append(owners, reference)
}
h.targetWorkload.SetOwnerReferences(owners)
wl.SetOwnerReferences(owners)
}
@@ -292,6 +292,75 @@ var _ = Describe("Test rollout related handler func", func() {
Expect(checkRt.Status.TrackedResources[0].Name).Should(BeEquivalentTo("test-workload"))
Expect(checkRt.Status.TrackedResources[0].UID).Should(BeEquivalentTo("test-uid"))
})
It("Test handle succeed func", func() {
ctx := context.Background()
namespaceName := "default"
rtName := "resourcetracker-v1-test-default"
rt := v1beta1.ResourceTracker{
ObjectMeta: metav1.ObjectMeta{
Name: rtName,
},
}
Expect(k8sClient.Create(ctx, &rt)).Should(BeNil())
rollout := v1alpha1.Rollout{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(&rt, v1beta1.ResourceTrackerKindVersionKind),
},
},
}
deploy := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1",
},
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"workload.oam.dev/type": "worker",
},
Name: "test-workload",
Namespace: namespaceName,
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{
"workload.oam.dev/type": "worker",
}},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{
"workload.oam.dev/type": "worker",
}},
Spec: v1.PodSpec{Containers: []v1.Container{{
Image: "busybox",
Name: "comp-name",
Command: []string{"sleep", "1000"},
},
}}},
},
}
u, err := util.Object2Unstructured(deploy)
Expect(err).Should(BeNil())
Expect(k8sClient.Create(ctx, u)).Should(BeNil())
h := &handler{
reconciler: &reconciler{
Client: k8sClient,
record: event.NewNopRecorder(),
},
rollout: &rollout,
targetWorkload: u,
}
Expect(h.handleFinalizeSucceed(ctx)).Should(BeNil())
checkDeploy := new(appsv1.Deployment)
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: u.GetNamespace(), Name: u.GetName()}, checkDeploy)).Should(BeNil())
Expect(len(checkDeploy.OwnerReferences)).Should(BeEquivalentTo(1))
Expect(checkDeploy.OwnerReferences[0].Kind).Should(BeEquivalentTo(v1beta1.ResourceTrackerKind))
Expect(checkDeploy.OwnerReferences[0].Name).Should(BeEquivalentTo(rtName))
checkRt := v1beta1.ResourceTracker{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: rtName}, &checkRt)).Should(BeNil())
Expect(len(checkRt.Status.TrackedResources)).Should(BeEquivalentTo(1))
Expect(checkRt.Status.TrackedResources[0].Name).Should(BeEquivalentTo("test-workload"))
Expect(checkRt.Status.TrackedResources[0].UID).Should(BeEquivalentTo(u.GetUID()))
})
})
})
@@ -17,8 +17,14 @@ limitations under the License.
package rollout
import (
"context"
"fmt"
"strings"
"testing"
"github.com/crossplane/crossplane-runtime/pkg/test"
"sigs.k8s.io/controller-runtime/pkg/client"
"gotest.tools/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -324,10 +330,72 @@ func TestPassOwnerReference(t *testing.T) {
u := &unstructured.Unstructured{}
u.SetName("test-workload")
h := &handler{
rollout: &rollout,
targetWorkload: u,
rollout: &rollout,
}
h.passOwnerToTargetWorkload(u)
assert.Assert(t, len(u.GetOwnerReferences()) == 1)
assert.Assert(t, *u.GetOwnerReferences()[0].Controller == false)
}
func TestHandleSucceedError(t *testing.T) {
getErr := fmt.Errorf("got error")
patchErr := fmt.Errorf("patch error")
object := &unstructured.Unstructured{}
object.SetName("name")
object.SetNamespace("namespace")
getClient := test.NewMockGetFn(getErr)
rollout := oamstandard.Rollout{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{
{
UID: "test-UID",
Kind: v1beta1.ResourceTrackerKind,
},
},
}}
ctx := context.Background()
patchClient := test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
if obj != nil {
obj = object
}
return nil
},
MockPatch: test.NewMockPatchFn(patchErr),
}
testCase := map[string]struct {
h handler
err error
}{
"Test get error": {
h: handler{
targetWorkload: object,
reconciler: &reconciler{
Client: &test.MockClient{
MockGet: getClient,
},
},
},
err: getErr,
},
"Test patch error": {
h: handler{
targetWorkload: object,
reconciler: &reconciler{
Client: &patchClient,
},
rollout: &rollout,
},
err: patchErr,
},
}
for testName, oneCase := range testCase {
t.Run(testName, func(t *testing.T) {
got := oneCase.h.handleFinalizeSucceed(ctx)
if got == nil || !strings.Contains(oneCase.err.Error(), oneCase.err.Error()) {
t.Errorf("handleSucceed () got %v, want %v", got, oneCase.err)
}
})
}
h.passOwnerToTargetWorkload()
assert.Assert(t, len(h.targetWorkload.GetOwnerReferences()) == 1)
assert.Assert(t, *h.targetWorkload.GetOwnerReferences()[0].Controller == false)
}
+57 -2
View File
@@ -126,6 +126,7 @@ var _ = Describe("rollout related e2e-test,rollout trait test", func() {
if len(targerDeploy.OwnerReferences) != 1 {
return fmt.Errorf("workload ownerReference missMatch")
}
// guarantee rollout's owners and workload's owners are same
if targerDeploy.OwnerReferences[0].Kind != rollout.OwnerReferences[0].Kind ||
targerDeploy.OwnerReferences[0].Name != rollout.OwnerReferences[0].Name {
return fmt.Errorf("workload ownerReference missMatch")
@@ -236,6 +237,57 @@ var _ = Describe("rollout related e2e-test,rollout trait test", func() {
return nil
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
verifySuccess("express-server-v4")
By("update application batch upgrade to v5")
Eventually(func() error {
if err = k8sClient.Get(ctx, appKey, checkApp); err != nil {
return err
}
checkApp.Spec.Components[0].Properties.Raw = []byte(`{"image":"stefanprodan/podinfo:4.0.3","cpu":"0.5"}`)
checkApp.Spec.Components[0].Traits[0].Properties.Raw =
[]byte(`{"firstBatchReplicas":2,"secondBatchReplicas":2,"targetSize":4,"batchPartition":0}`)
if err = k8sClient.Update(ctx, checkApp); err != nil {
return err
}
return nil
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
// check rollout paused in partition 0
time.Sleep(5 * time.Second)
Eventually(func() error {
rolloutKey := types.NamespacedName{Namespace: namespaceName, Name: componentName}
if err := k8sClient.Get(ctx, rolloutKey, &rollout); err != nil {
return err
}
if rollout.Spec.TargetRevisionName != "express-server-v5" {
return fmt.Errorf("rollout have not point to right targetRevision")
}
if rollout.Status.RollingState != v1alpha1.RollingInBatchesState {
return fmt.Errorf("error rollout status state %s", rollout.Status.RollingState)
}
if rollout.Status.CurrentBatch != 0 {
return fmt.Errorf("current batchPartition missmatch accutally %d", rollout.Status.CurrentBatch)
}
deployKey := types.NamespacedName{Namespace: namespaceName, Name: compRevName}
if err := k8sClient.Get(ctx, deployKey, &targerDeploy); err != nil {
return err
}
if *targerDeploy.Spec.Replicas != 2 {
return fmt.Errorf("targetDeploy replicas missMatch %d", *targerDeploy.Spec.Replicas)
}
return nil
}, 60*time.Second, 300*time.Millisecond).Should(BeNil())
By("continue rollout upgrade legacy batches")
Eventually(func() error {
if err = k8sClient.Get(ctx, appKey, checkApp); err != nil {
return err
}
checkApp.Spec.Components[0].Traits[0].Properties.Raw =
[]byte(`{"firstBatchReplicas":2,"secondBatchReplicas":2,"targetSize":4,"batchPartition":1}`)
if err = k8sClient.Update(ctx, checkApp); err != nil {
return err
}
return nil
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
verifySuccess("express-server-v5")
By("delete the application, check workload have been removed")
Expect(k8sClient.Delete(ctx, checkApp)).Should(BeNil())
listOptions := []client.ListOption{
@@ -312,7 +364,6 @@ metadata:
name: rollout
spec:
manageWorkload: true
skipRevisionAffect: true
schematic:
cue:
template: |
@@ -332,7 +383,10 @@ spec:
{ replicas: parameter.firstBatchReplicas},
{ replicas: parameter.secondBatchReplicas}]
targetSize: parameter.targetSize
}
if parameter["batchPartition"] != _|_ {
batchPartition: parameter.batchPartition
}
}
}
}
@@ -341,5 +395,6 @@ spec:
targetSize: *2|int
firstBatchReplicas: *1|int
secondBatchReplicas: *1|int
batchPartition?: int
}`
)
@@ -4,8 +4,7 @@ rollout: {
labels: {}
description: "rollout the component"
attributes: {
manageWorkload: true
skipRevisionAffect: true
manageWorkload: true
}
}
template: {
@@ -23,7 +22,9 @@ template: {
rolloutStrategy: "IncreaseFirst"
rolloutBatches: parameter.rolloutBatches
targetSize: parameter.targetSize
batchPartition: parameter.batchPartition
if parameter["batchPartition"] != _|_ {
batchPartition: parameter.batchPartition
}
}
}
}