mirror of https://github.com/kubevela/kubevela.git
418 lines
15 KiB
Go
418 lines
15 KiB
Go
/*
|
|
Copyright 2021 The KubeVela 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 controllers_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/oam-dev/kubevela/pkg/oam"
|
|
|
|
"sigs.k8s.io/yaml"
|
|
|
|
v1 "k8s.io/api/apps/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
|
"github.com/oam-dev/kubevela/pkg/oam/util"
|
|
"github.com/oam-dev/kubevela/pkg/utils/common"
|
|
|
|
. "github.com/onsi/ginkgo"
|
|
. "github.com/onsi/gomega"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
|
)
|
|
|
|
var _ = Describe("rollout related e2e-test,rollout trait test", func() {
|
|
ctx := context.Background()
|
|
var namespaceName, componentName, compRevName string
|
|
var ns corev1.Namespace
|
|
var app v1beta1.Application
|
|
var rollout v1alpha1.Rollout
|
|
var targerDeploy, sourceDeploy v1.Deployment
|
|
var err error
|
|
|
|
createAllDef := func() {
|
|
By("install all related definition")
|
|
var cd v1beta1.ComponentDefinition
|
|
Expect(yaml.Unmarshal([]byte(rolloutTestWd), &cd))
|
|
// create the componentDefinition if not exist
|
|
cd.Namespace = namespaceName
|
|
Eventually(
|
|
func() error {
|
|
return k8sClient.Create(ctx, &cd)
|
|
},
|
|
time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
|
var td v1beta1.TraitDefinition
|
|
Expect(yaml.Unmarshal([]byte(rolloutTestTd), &td)).Should(BeNil())
|
|
td.Namespace = namespaceName
|
|
Eventually(func() error {
|
|
return k8sClient.Create(ctx, &td)
|
|
},
|
|
time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
|
}
|
|
|
|
createNamespace := func() {
|
|
ns = corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: namespaceName,
|
|
},
|
|
}
|
|
// delete the namespaceName with all its resources
|
|
Eventually(
|
|
func() error {
|
|
return k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))
|
|
},
|
|
time.Second*120, time.Millisecond*500).Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{}))
|
|
By("make sure all the resources are removed")
|
|
objectKey := client.ObjectKey{
|
|
Name: namespaceName,
|
|
}
|
|
res := &corev1.Namespace{}
|
|
Eventually(
|
|
func() error {
|
|
return k8sClient.Get(ctx, objectKey, res)
|
|
},
|
|
time.Second*120, time.Millisecond*500).Should(&util.NotFoundMatcher{})
|
|
Eventually(
|
|
func() error {
|
|
return k8sClient.Create(ctx, &ns)
|
|
},
|
|
time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
|
}
|
|
|
|
verifySuccess := func(componentRevision string) {
|
|
By("check rollout status have succeed")
|
|
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 != componentRevision {
|
|
return fmt.Errorf("rollout have not point to right targetRevision")
|
|
}
|
|
if rollout.Status.RollingState != v1alpha1.RolloutSucceedState {
|
|
return fmt.Errorf("error rollout status state %s", rollout.Status.RollingState)
|
|
}
|
|
compRevName = rollout.Spec.TargetRevisionName
|
|
if rollout.GetAnnotations() == nil || rollout.GetAnnotations()[oam.AnnotationWorkloadName] != componentRevision {
|
|
return fmt.Errorf("target workload name annotation missmatch want %s acctually %s",
|
|
rollout.GetAnnotations()[oam.AnnotationWorkloadName], componentRevision)
|
|
}
|
|
deployKey := types.NamespacedName{Namespace: namespaceName, Name: compRevName}
|
|
if err := k8sClient.Get(ctx, deployKey, &targerDeploy); err != nil {
|
|
return err
|
|
}
|
|
gvkStr := rollout.GetAnnotations()[oam.AnnotationWorkloadGVK]
|
|
gvk := map[string]string{}
|
|
if err := json.Unmarshal([]byte(gvkStr), &gvk); err != nil {
|
|
return err
|
|
}
|
|
if gvk["apiVersion"] != "apps/v1" || gvk["kind"] != "Deployment" {
|
|
return fmt.Errorf("error targetWorkload gvk")
|
|
}
|
|
if *targerDeploy.Spec.Replicas != *rollout.Spec.RolloutPlan.TargetSize {
|
|
return fmt.Errorf("targetDeploy replicas missMatch %d, %d", targerDeploy.Spec.Replicas, rollout.Spec.RolloutPlan.TargetSize)
|
|
}
|
|
if targerDeploy.Status.UpdatedReplicas != *targerDeploy.Spec.Replicas {
|
|
return fmt.Errorf("update not finish")
|
|
}
|
|
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")
|
|
}
|
|
if rollout.Status.LastSourceRevision == "" {
|
|
return nil
|
|
}
|
|
deployKey = types.NamespacedName{Namespace: namespaceName, Name: rollout.Status.LastSourceRevision}
|
|
if err := k8sClient.Get(ctx, deployKey, &sourceDeploy); err == nil || !apierrors.IsNotFound(err) {
|
|
return fmt.Errorf("source deploy still exist")
|
|
}
|
|
return nil
|
|
}, time.Second*360, 300*time.Millisecond).Should(BeNil())
|
|
}
|
|
|
|
BeforeEach(func() {
|
|
By("Start to run a test, init whole env")
|
|
namespaceName = randomNamespaceName("rollout-trait-e2e-test")
|
|
createNamespace()
|
|
createAllDef()
|
|
componentName = "express-server"
|
|
})
|
|
|
|
AfterEach(func() {
|
|
By("Clean up resources after a test")
|
|
Eventually(func() error {
|
|
err := k8sClient.Delete(ctx, &app)
|
|
if err == nil || apierrors.IsNotFound(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}, 15*time.Second, 300*time.Microsecond).Should(BeNil())
|
|
By(fmt.Sprintf("Delete the entire namespaceName %s", ns.Name))
|
|
// delete the namespaceName with all its resources
|
|
Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationBackground))).Should(BeNil())
|
|
})
|
|
|
|
It("rollout as a trait whole process e2e-test", func() {
|
|
By("first scale operation")
|
|
Expect(common.ReadYamlToObject("testdata/rollout/deployment/application.yaml", &app)).Should(BeNil())
|
|
app.Namespace = namespaceName
|
|
Expect(k8sClient.Create(ctx, &app)).Should(BeNil())
|
|
verifySuccess("express-server-v1")
|
|
appKey := types.NamespacedName{Namespace: namespaceName, Name: app.Name}
|
|
checkApp := &v1beta1.Application{}
|
|
By("update application upgrade to v2")
|
|
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.1"}`)
|
|
if err = k8sClient.Update(ctx, checkApp); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
|
|
verifySuccess("express-server-v2")
|
|
By("update application upgrade to v3")
|
|
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.2"}`)
|
|
if err = k8sClient.Update(ctx, checkApp); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
|
|
verifySuccess("express-server-v3")
|
|
By("roll back to v2")
|
|
time.Sleep(30 * time.Second)
|
|
Eventually(func() error {
|
|
if err = k8sClient.Get(ctx, appKey, checkApp); err != nil {
|
|
return err
|
|
}
|
|
checkApp.Spec.Components[0].Traits[0].Properties.Raw = []byte(`{"targetRevision":"express-server-v2"}`)
|
|
if err = k8sClient.Update(ctx, checkApp); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
|
|
verifySuccess("express-server-v2")
|
|
By("modify targetSize to scale")
|
|
Eventually(func() error {
|
|
if err = k8sClient.Get(ctx, appKey, checkApp); err != nil {
|
|
return err
|
|
}
|
|
checkApp.Spec.Components[0].Traits[0].Properties.Raw =
|
|
[]byte(`{"targetRevision":"express-server-v2","targetSize":4,"firstBatchReplicas":1,"secondBatchReplicas":1}`)
|
|
if err = k8sClient.Update(ctx, checkApp); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
|
|
time.Sleep(12 * time.Second)
|
|
verifySuccess("express-server-v2")
|
|
By("update application upgrade to v4")
|
|
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.3"}`)
|
|
checkApp.Spec.Components[0].Traits[0].Properties.Raw =
|
|
[]byte(`{"firstBatchReplicas":2,"secondBatchReplicas":2,"targetSize":4}`)
|
|
if err = k8sClient.Update(ctx, checkApp); err != nil {
|
|
return err
|
|
}
|
|
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)
|
|
sourceDeploy := v1.Deployment{}
|
|
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, &sourceDeploy); err != nil {
|
|
return err
|
|
}
|
|
if *sourceDeploy.Spec.Replicas != 2 {
|
|
return fmt.Errorf("targetDeploy replicas missMatch %d", *sourceDeploy.Spec.Replicas)
|
|
}
|
|
return nil
|
|
}, 300*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{
|
|
client.InNamespace(namespaceName),
|
|
}
|
|
deployList := &v1.DeploymentList{}
|
|
Eventually(func() error {
|
|
if err := k8sClient.List(ctx, deployList, listOptions...); err != nil {
|
|
return err
|
|
}
|
|
if len(deployList.Items) != 0 {
|
|
return fmt.Errorf("workload have not been removed")
|
|
}
|
|
return nil
|
|
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
|
|
})
|
|
})
|
|
|
|
const (
|
|
rolloutTestWd = `# Code generated by KubeVela templates. DO NOT EDIT.
|
|
apiVersion: core.oam.dev/v1beta1
|
|
kind: ComponentDefinition
|
|
metadata:
|
|
name: webservice
|
|
spec:
|
|
workload:
|
|
definition:
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
schematic:
|
|
cue:
|
|
template: |
|
|
output: {
|
|
apiVersion: "apps/v1"
|
|
kind: "Deployment"
|
|
spec: {
|
|
selector: matchLabels: {
|
|
"app.oam.dev/component": context.name
|
|
}
|
|
|
|
template: {
|
|
metadata: labels: {
|
|
"app.oam.dev/component": context.name
|
|
}
|
|
spec: {
|
|
containers: [{
|
|
name: context.name
|
|
image: parameter.image
|
|
if parameter["cpu"] != _|_ {
|
|
resources: {
|
|
limits:
|
|
cpu: parameter.cpu
|
|
requests: cpu: "0"
|
|
}
|
|
}
|
|
}]
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
parameter: {
|
|
// +usage=Which image would you like to use for your service
|
|
// +short=i
|
|
image: string
|
|
|
|
cpu?: 0.5|string
|
|
}
|
|
|
|
`
|
|
rolloutTestTd = `apiVersion: core.oam.dev/v1beta1
|
|
kind: TraitDefinition
|
|
metadata:
|
|
name: rollout
|
|
spec:
|
|
manageWorkload: true
|
|
schematic:
|
|
cue:
|
|
template: |
|
|
outputs: rollout: {
|
|
apiVersion: "standard.oam.dev/v1alpha1"
|
|
kind: "Rollout"
|
|
metadata: {
|
|
name: context.name
|
|
namespace: context.namespace
|
|
}
|
|
spec: {
|
|
targetRevisionName: parameter.targetRevision
|
|
componentName: context.name
|
|
rolloutPlan: {
|
|
rolloutStrategy: "DecreaseFirst"
|
|
rolloutBatches:[
|
|
{ replicas: parameter.firstBatchReplicas},
|
|
{ replicas: parameter.secondBatchReplicas}]
|
|
targetSize: parameter.targetSize
|
|
if parameter["batchPartition"] != _|_ {
|
|
batchPartition: parameter.batchPartition
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
parameter: {
|
|
targetRevision: *context.revision|string
|
|
targetSize: *2|int
|
|
firstBatchReplicas: *1|int
|
|
secondBatchReplicas: *1|int
|
|
batchPartition?: int
|
|
}`
|
|
)
|