kubevela/test/e2e-test/rollout_trait_test.go

421 lines
16 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"
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"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
common2 "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils/common"
. "github.com/onsi/ginkgo/v2"
. "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
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}
rollout = v1alpha1.Rollout{}
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 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 namespace %s deployName %s", namespaceName, rollout.Status.LastSourceRevision)
}
return nil
}, time.Second*60, 300*time.Millisecond).Should(BeNil())
}
BeforeEach(func() {
By("Start to run a test, init whole env")
namespaceName = randomNamespaceName("rollout-trait-e2e-test")
app = v1beta1.Application{}
createNamespace()
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","rolloutBatches":[{"replicas":1},{"replicas":1}],"targetSize":2}`)
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,"rolloutBatches":[{"replicas":1},{"replicas":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(`{"rolloutBatches":[{"replicas":2},{"replicas":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.31"}`)
checkApp.Spec.Components[0].Traits[0].Properties.Raw =
[]byte(`{"rolloutBatches":[{"replicas":2},{"replicas":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(`{"rolloutBatches":[{"replicas":2},{"replicas":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())
})
It("rollout scale up and down without rollout batches", 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")
By("scale again to targetSize 4")
appKey := types.NamespacedName{Namespace: namespaceName, Name: app.Name}
checkApp := &v1beta1.Application{}
Eventually(func() error {
if err = k8sClient.Get(ctx, appKey, checkApp); err != nil {
return err
}
// scale up without rollout batches, test rollout controller will fill default batches
checkApp.Spec.Components[0].Traits[0].Properties.Raw =
[]byte(`{"targetSize":4}`)
if err = k8sClient.Update(ctx, checkApp); err != nil {
return err
}
return nil
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
Eventually(func() error {
checkRollout := v1alpha1.Rollout{}
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespaceName, Name: componentName}, &checkRollout); err != nil {
return err
}
if *checkRollout.Spec.RolloutPlan.TargetSize != 4 {
return fmt.Errorf("rollout targetSize haven't update")
}
if len(checkRollout.Spec.RolloutPlan.RolloutBatches) != 1 {
return fmt.Errorf("fail to fill rollout batches")
}
if checkRollout.Spec.RolloutPlan.RolloutBatches[0].Replicas != intstr.FromInt(2) {
return fmt.Errorf("fill rollout batches missmatch")
}
return nil
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
verifySuccess("express-server-v1")
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"}`)
checkApp.Spec.Components[0].Traits[0].Properties.Raw =
[]byte(`{"rolloutBatches":[{"replicas":2},{"replicas":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-v2")
By("scale down to targetSize 2")
appKey = types.NamespacedName{Namespace: namespaceName, Name: app.Name}
checkApp = &v1beta1.Application{}
Eventually(func() error {
if err = k8sClient.Get(ctx, appKey, checkApp); err != nil {
return err
}
// scale down without rollout batches, test rollout controller will fill default batches
checkApp.Spec.Components[0].Traits[0].Properties.Raw =
[]byte(`{"targetSize":2}`)
if err = k8sClient.Update(ctx, checkApp); err != nil {
return err
}
return nil
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
Eventually(func() error {
checkRollout := v1alpha1.Rollout{}
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespaceName, Name: componentName}, &checkRollout); err != nil {
return err
}
if *checkRollout.Spec.RolloutPlan.TargetSize != 2 {
return fmt.Errorf("rollout targetSize haven't update")
}
if len(checkRollout.Spec.RolloutPlan.RolloutBatches) != 1 {
return fmt.Errorf("fail to fill rollout batches")
}
if checkRollout.Spec.RolloutPlan.RolloutBatches[0].Replicas != intstr.FromInt(2) {
return fmt.Errorf("fill rollout batches missmatch")
}
return nil
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
verifySuccess("express-server-v2")
})
It("Delete a component with rollout trait from an application should delete this workload", func() {
By("first scale operation")
Expect(common.ReadYamlToObject("testdata/rollout/deployment/multi_comp_app.yaml", &app)).Should(BeNil())
app.Namespace = namespaceName
Expect(k8sClient.Create(ctx, &app)).Should(BeNil())
verifySuccess("express-server-v1")
componentName = "express-server-another"
verifySuccess("express-server-another-v1")
By("delete a component")
Eventually(func() error {
checkApp := &v1beta1.Application{}
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespaceName, Name: app.Name}, checkApp); err != nil {
return err
}
checkApp.Spec.Components = []common2.ApplicationComponent{checkApp.Spec.Components[0]}
if err := k8sClient.Update(ctx, checkApp); err != nil {
return err
}
return nil
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
By("check deployment have been gc")
Eventually(func() error {
checkApp := &v1beta1.Application{}
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespaceName, Name: app.Name}, checkApp); err != nil {
return err
}
if len(checkApp.Spec.Components) != 1 || checkApp.Spec.Components[0].Name != "express-server" {
return fmt.Errorf("app hasn't update yet")
}
deploy := v1.Deployment{}
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespaceName, Name: "express-server-another-v1"}, &deploy); err == nil || !apierrors.IsNotFound(err) {
return fmt.Errorf("another deployment haven't been delete")
}
return nil
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
})
})