/* 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" "fmt" "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/oam-dev/kubevela/apis/core.oam.dev/common" "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" velatypes "github.com/oam-dev/kubevela/apis/types" "github.com/oam-dev/kubevela/pkg/oam/util" ) var _ = Describe("Initializer Normal tests", func() { ctx := context.Background() var namespace string var ns corev1.Namespace BeforeEach(func() { namespace = randomNamespaceName("initializer-e2e-test") ns = corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} Eventually(func() error { return k8sClient.Create(ctx, &ns) }, time.Second*3, time.Microsecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) worker := workerWithNoTemplate.DeepCopy() worker.Spec.Workload = common.WorkloadTypeDescriptor{ Definition: common.WorkloadGVK{ APIVersion: "apps/v1", Kind: "Deployment", }, } worker.Spec.Schematic.CUE.Template = workerV2Template worker.SetNamespace(namespace) Expect(k8sClient.Create(ctx, worker)).Should(Succeed()) }) AfterEach(func() { By("Clean up resources after a test") k8sClient.DeleteAllOf(ctx, &v1beta1.Initializer{}, client.InNamespace(namespace)) k8sClient.DeleteAllOf(ctx, &v1beta1.Application{}, client.InNamespace(namespace)) k8sClient.DeleteAllOf(ctx, &v1beta1.ComponentDefinition{}, client.InNamespace(namespace)) k8sClient.DeleteAllOf(ctx, &v1beta1.WorkloadDefinition{}, client.InNamespace(namespace)) k8sClient.DeleteAllOf(ctx, &v1beta1.TraitDefinition{}, client.InNamespace(namespace)) k8sClient.DeleteAllOf(ctx, &v1beta1.DefinitionRevision{}, client.InNamespace(namespace)) By(fmt.Sprintf("Delete the entire namespaceName %s", ns.Name)) Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(Succeed()) }) It("Test apply initializer without dependsOn", func() { compName := "env1-comp" init := &v1beta1.Initializer{ TypeMeta: metav1.TypeMeta{ Kind: "Initializer", APIVersion: "core.oam.dev/v1beta1", }, ObjectMeta: metav1.ObjectMeta{ Name: "env1", Namespace: namespace, }, Spec: v1beta1.InitializerSpec{ AppTemplate: v1beta1.Application{ Spec: v1beta1.ApplicationSpec{ Components: []common.ApplicationComponent{ { Name: compName, Type: "worker", Properties: util.Object2RawExtension(map[string]interface{}{ "image": "busybox", "cmd": []string{"sleep", "10000"}, }), }, }, }, }, }, } By("Create Initializer") Eventually(func() error { return k8sClient.Create(ctx, init) }, 10*time.Second, 500*time.Millisecond).Should(Succeed()) By("Verify the application is created successfully") app := new(v1beta1.Application) Eventually(func() error { return k8sClient.Get(ctx, client.ObjectKey{Name: init.Name, Namespace: namespace}, app) }, 30*time.Second, 500*time.Millisecond).Should(Succeed()) By("Verify the initializer successfully initialized the environment") Eventually(func() error { err := k8sClient.Get(ctx, client.ObjectKey{Name: init.Name, Namespace: namespace}, init) if err != nil { return err } if init.Status.Phase != v1beta1.InitializerSuccess { return fmt.Errorf("environment was not successfully initialized") } return nil }, 30*time.Second, 5*time.Second).Should(Succeed()) }) It("Test apply initializer which will create namespace", func() { randomNs := randomNamespaceName("initializer-createns") init := &v1beta1.Initializer{ TypeMeta: metav1.TypeMeta{ Kind: "Initializer", APIVersion: "core.oam.dev/v1beta1", }, ObjectMeta: metav1.ObjectMeta{ Name: "env4", Namespace: namespace, }, Spec: v1beta1.InitializerSpec{ AppTemplate: v1beta1.Application{ Spec: v1beta1.ApplicationSpec{ Components: []common.ApplicationComponent{ { Name: randomNs, Type: "raw", Properties: util.Object2RawExtension(map[string]interface{}{ "apiVersion": "v1", "kind": "Namespace", "metadata": map[string]interface{}{ "name": randomNs, }, }), }, }, }, }, }, } By("Create Initializer createNamespaceInit") Eventually(func() error { return k8sClient.Create(ctx, init) }, 10*time.Second, 500*time.Millisecond).Should(Succeed()) By("Verify the application is created successfully") app := new(v1beta1.Application) Eventually(func() error { return k8sClient.Get(ctx, client.ObjectKey{Name: init.Name, Namespace: namespace}, app) }, 60*time.Second, 2*time.Millisecond).Should(Succeed()) By("Verify the initializer env3 successfully initialized the environment") Eventually(func() error { err := k8sClient.Get(ctx, client.ObjectKey{Name: init.Name, Namespace: namespace}, init) if err != nil { return err } if init.Status.Phase != v1beta1.InitializerSuccess { return fmt.Errorf("environment was not successfully initialized") } return nil }, 30*time.Second, 5*time.Second).Should(Succeed()) }) Context("Test apply initializer depends on other initializer", func() { It("initializer depends on existing initializer", func() { compName := "env2-comp" init := &v1beta1.Initializer{ TypeMeta: metav1.TypeMeta{ Kind: "Initializer", APIVersion: "core.oam.dev/v1beta1", }, ObjectMeta: metav1.ObjectMeta{ Name: "env2", Namespace: namespace, }, Spec: v1beta1.InitializerSpec{ AppTemplate: v1beta1.Application{ Spec: v1beta1.ApplicationSpec{ Components: []common.ApplicationComponent{ { Name: compName, Type: "worker", Properties: util.Object2RawExtension(map[string]interface{}{ "image": "busybox", "cmd": []string{"sleep", "10000"}, }), }, }, }, }, }, } By("Create Initializer env2") Eventually(func() error { return k8sClient.Create(ctx, init) }, 10*time.Second, 500*time.Millisecond).Should(Succeed()) compName2 := "env3-comp" init2 := &v1beta1.Initializer{ TypeMeta: metav1.TypeMeta{ Kind: "Initializer", APIVersion: "core.oam.dev/v1beta1", }, ObjectMeta: metav1.ObjectMeta{ Name: "env3", Namespace: namespace, }, Spec: v1beta1.InitializerSpec{ AppTemplate: v1beta1.Application{ Spec: v1beta1.ApplicationSpec{ Components: []common.ApplicationComponent{ { Name: compName2, Type: "worker", Properties: util.Object2RawExtension(map[string]interface{}{ "image": "busybox", "cmd": []string{"sleep", "10000"}, }), }, }, }, }, DependsOn: []v1beta1.DependsOn{ { Ref: corev1.ObjectReference{ APIVersion: "core.oam.dev/v1beta1", Kind: "Initializer", Name: "env2", Namespace: namespace, }, }, }, }, } By("Create Initializer env3 which depends on env2") Eventually(func() error { return k8sClient.Create(ctx, init2) }, 10*time.Second, 500*time.Millisecond).Should(Succeed()) By("Verify the application is created successfully") app2 := new(v1beta1.Application) Eventually(func() error { return k8sClient.Get(ctx, client.ObjectKey{Name: init2.Name, Namespace: namespace}, app2) }, 60*time.Second, 2*time.Millisecond).Should(Succeed()) By("Verify the initializer env3 successfully initialized the environment") Eventually(func() error { err := k8sClient.Get(ctx, client.ObjectKey{Name: init2.Name, Namespace: namespace}, init2) if err != nil { return err } if init2.Status.Phase != v1beta1.InitializerSuccess { return fmt.Errorf("environment was not successfully initialized") } return nil }, 30*time.Second, 5*time.Second).Should(Succeed()) }) It("initializer depends on not non-built-in initializer not found, should be rejected by webhook", func() { init := &v1beta1.Initializer{ TypeMeta: metav1.TypeMeta{ Kind: "Initializer", APIVersion: "core.oam.dev/v1beta1", }, ObjectMeta: metav1.ObjectMeta{ Name: "env-depends", Namespace: namespace, }, Spec: v1beta1.InitializerSpec{ AppTemplate: v1beta1.Application{ Spec: v1beta1.ApplicationSpec{ Components: []common.ApplicationComponent{{ Name: "comp1", Type: "worker", Properties: util.Object2RawExtension(map[string]interface{}{ "image": "busybox", "cmd": []string{"sleep", "10000"}, }), }, }, }, }, DependsOn: []v1beta1.DependsOn{ { Ref: corev1.ObjectReference{ APIVersion: "core.oam.dev/v1beta1", Kind: "Initializer", Name: "non-built-in", }, }, }, }, } By("Create Initializer env-depends") Expect(k8sClient.Create(ctx, init)).Should(HaveOccurred()) }) It("initializer depends on built-in initializer", func() { initCm := &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: "built-in", Namespace: velatypes.DefaultKubeVelaNS, Labels: map[string]string{ "addons.oam.dev/type": "built-in", }, }, Data: map[string]string{ "initializer": initYaml, }, } By("create built-in addon") Expect(k8sClient.Create(ctx, initCm)).Should(Succeed()) init := &v1beta1.Initializer{ TypeMeta: metav1.TypeMeta{ Kind: "Initializer", APIVersion: "core.oam.dev/v1beta1", }, ObjectMeta: metav1.ObjectMeta{ Name: "env-depends-builtin", Namespace: namespace, }, Spec: v1beta1.InitializerSpec{ AppTemplate: v1beta1.Application{ Spec: v1beta1.ApplicationSpec{ Components: []common.ApplicationComponent{ { Name: "comp1", Type: "worker", Properties: util.Object2RawExtension(map[string]interface{}{ "image": "busybox", "cmd": []string{"sleep", "10000"}, }), }, }, }, }, DependsOn: []v1beta1.DependsOn{ { Ref: corev1.ObjectReference{ APIVersion: "core.oam.dev/v1beta1", Kind: "Initializer", Name: "built-in", Namespace: velatypes.DefaultKubeVelaNS, }, }, }, }, } By("Create Initializer env-depends") Expect(k8sClient.Create(ctx, init)).Should(Succeed()) By("Check built-in Initializer is ready") buildInInit := new(v1beta1.Initializer) Eventually(func() error { err := k8sClient.Get(ctx, client.ObjectKey{Name: "built-in", Namespace: "vela-system"}, buildInInit) if err != nil { return err } if buildInInit.Status.Phase == v1beta1.InitializerSuccess { return nil } return fmt.Errorf("initializer %s is not ready", buildInInit.Name) }, 30*time.Second, 500*time.Millisecond).Should(Succeed()) By("Check Initializer env-depends-builtin is ready") Eventually(func() error { err := k8sClient.Get(ctx, client.ObjectKey{Name: "env-depends-builtin", Namespace: namespace}, buildInInit) if err != nil { return err } if buildInInit.Status.Phase == v1beta1.InitializerSuccess { return nil } return errors.New("initializer env-depends-builtin is not ready") }, 120*time.Second, 500*time.Millisecond).Should(Succeed()) }) It("Deleting initializer depended by other initializer should be rejected by webhook", func() { initA := &v1beta1.Initializer{ TypeMeta: metav1.TypeMeta{ Kind: "Initializer", APIVersion: "core.oam.dev/v1beta1", }, ObjectMeta: metav1.ObjectMeta{ Name: "init-depended", Namespace: namespace, }, Spec: v1beta1.InitializerSpec{ AppTemplate: v1beta1.Application{ Spec: v1beta1.ApplicationSpec{ Components: []common.ApplicationComponent{ { Name: "comp1", Type: "worker", Properties: util.Object2RawExtension(map[string]interface{}{ "image": "busybox", "cmd": []string{"sleep", "10000"}, }), }, }, }, }, }, } initDependingA := &v1beta1.Initializer{ TypeMeta: metav1.TypeMeta{ Kind: "Initializer", APIVersion: "core.oam.dev/v1beta1", }, ObjectMeta: metav1.ObjectMeta{ Name: "init-depending", Namespace: namespace, }, Spec: v1beta1.InitializerSpec{ AppTemplate: v1beta1.Application{ Spec: v1beta1.ApplicationSpec{ Components: []common.ApplicationComponent{ { Name: "", Type: "worker", Properties: util.Object2RawExtension(map[string]interface{}{ "image": "busybox", "cmd": []string{"sleep", "10000"}, }), }, }, }, }, DependsOn: []v1beta1.DependsOn{ { Ref: corev1.ObjectReference{ APIVersion: "core.oam.dev/v1beta1", Kind: "Initializer", Name: "init-depended", Namespace: namespace, }, }, }, }, } By("Create Initializer depended") Expect(k8sClient.Create(ctx, initA)).NotTo(HaveOccurred()) By("Create Initializer depending") Expect(k8sClient.Create(ctx, initDependingA)).NotTo(HaveOccurred()) By("Deleting Initializer depended") Expect(k8sClient.Delete(ctx, initA)).Should(HaveOccurred()) }) }) }) var initYaml = ` apiVersion: core.oam.dev/v1beta1 kind: Initializer metadata: annotations: addons.oam.dev/description: Kruise is a Kubernetes extended suite for application automations name: built-in namespace: vela-system spec: appTemplate: spec: components: - name: kruise-repo type: worker properties: image: busybox cmd: ["sleep", "10000"] `