github.com/oam-dev/kubevela@v1.9.11/pkg/utils/apply/apply_resource_test.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package apply 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "strings" 24 25 . "github.com/onsi/ginkgo/v2" 26 . "github.com/onsi/gomega" 27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/runtime/schema" 30 utilfeature "k8s.io/apiserver/pkg/util/feature" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 33 appsv1 "k8s.io/api/apps/v1" 34 corev1 "k8s.io/api/core/v1" 35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 36 "k8s.io/apimachinery/pkg/types" 37 "k8s.io/utils/pointer" 38 39 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 40 "github.com/oam-dev/kubevela/pkg/features" 41 "github.com/oam-dev/kubevela/pkg/oam" 42 oamutil "github.com/oam-dev/kubevela/pkg/oam/util" 43 ) 44 45 var _ = Describe("Test apply", func() { 46 var ( 47 int32_3 = int32(3) 48 int32_5 = int32(5) 49 ctx = context.Background() 50 deploy *appsv1.Deployment 51 deployKey = types.NamespacedName{ 52 Name: "testdeploy", 53 Namespace: ns, 54 } 55 ) 56 57 BeforeEach(func() { 58 deploy = basicTestDeployment() 59 Expect(k8sApplicator.Apply(ctx, deploy)).Should(Succeed()) 60 }) 61 62 AfterEach(func() { 63 Expect(rawClient.Delete(ctx, deploy)).Should(SatisfyAny(Succeed(), &oamutil.NotFoundMatcher{})) 64 }) 65 66 Context("Test apply resources", func() { 67 It("Test apply core resources", func() { 68 deploy = basicTestDeployment() 69 By("Set normal & array field") 70 deploy.Spec.Replicas = &int32_3 71 deploy.Spec.Template.Spec.Volumes = []corev1.Volume{{Name: "test"}} 72 Expect(k8sApplicator.Apply(ctx, deploy)).Should(Succeed()) 73 resultDeploy := basicTestDeployment() 74 Expect(rawClient.Get(ctx, deployKey, resultDeploy)).Should(Succeed()) 75 Expect(*resultDeploy.Spec.Replicas).Should(Equal(int32_3)) 76 Expect(len(resultDeploy.Spec.Template.Spec.Volumes)).Should(Equal(1)) 77 78 deploy = basicTestDeployment() 79 By("Override normal & array field") 80 deploy.Spec.Replicas = &int32_5 81 deploy.Spec.Template.Spec.Volumes = []corev1.Volume{{Name: "test"}, {Name: "test2"}} 82 Expect(k8sApplicator.Apply(ctx, deploy)).Should(Succeed()) 83 resultDeploy = basicTestDeployment() 84 Expect(rawClient.Get(ctx, deployKey, resultDeploy)).Should(Succeed()) 85 Expect(*resultDeploy.Spec.Replicas).Should(Equal(int32_5)) 86 Expect(len(resultDeploy.Spec.Template.Spec.Volumes)).Should(Equal(2)) 87 88 deploy = basicTestDeployment() 89 By("Unset normal & array field") 90 deploy.Spec.Replicas = nil 91 deploy.Spec.Template.Spec.Volumes = nil 92 Expect(k8sApplicator.Apply(ctx, deploy)).Should(Succeed()) 93 resultDeploy = basicTestDeployment() 94 Expect(rawClient.Get(ctx, deployKey, resultDeploy)).Should(Succeed()) 95 By("Unsetted fields shoulde be removed or set to default value") 96 Expect(*resultDeploy.Spec.Replicas).Should(Equal(int32(1))) 97 Expect(len(resultDeploy.Spec.Template.Spec.Volumes)).Should(Equal(0)) 98 99 deployUpdate := basicTestDeployment() 100 deployUpdate.Name = deploy.Name + "-no-update" 101 Expect(k8sApplicator.Apply(ctx, deployUpdate, DisableUpdateAnnotation())).Should(Succeed()) 102 Expect(len(deployUpdate.Annotations[oam.AnnotationLastAppliedConfig])).Should(Equal(0)) 103 104 deployUpdate = basicTestDeployment() 105 deployUpdate.Spec.Replicas = &int32_3 106 deployUpdate.Spec.Template.Spec.Volumes = []corev1.Volume{{Name: "test"}} 107 Expect(k8sApplicator.Apply(ctx, deployUpdate)).Should(Succeed()) 108 resultDeploy = basicTestDeployment() 109 resultDeploy.Name = deploy.Name + "-no-update" 110 Expect(rawClient.Get(ctx, deployKey, resultDeploy)).Should(Succeed()) 111 Expect(*resultDeploy.Spec.Replicas).Should(Equal(int32_3)) 112 Expect(len(resultDeploy.Spec.Template.Spec.Volumes)).Should(Equal(1)) 113 Expect(rawClient.Delete(ctx, deployUpdate)).Should(SatisfyAny(Succeed(), &oamutil.NotFoundMatcher{})) 114 }) 115 116 It("Test multiple appliers", func() { 117 deploy = basicTestDeployment() 118 originalDeploy := deploy.DeepCopy() 119 Expect(k8sApplicator.Apply(ctx, deploy)).Should(Succeed()) 120 121 modifiedDeploy := &appsv1.Deployment{} 122 modifiedDeploy.SetGroupVersionKind(deploy.GroupVersionKind()) 123 Expect(rawClient.Get(ctx, deployKey, modifiedDeploy)).Should(Succeed()) 124 By("Other applier changed the deployment") 125 modifiedDeploy.Spec.MinReadySeconds = 10 126 modifiedDeploy.Spec.ProgressDeadlineSeconds = pointer.Int32(20) 127 modifiedDeploy.Spec.Template.Spec.Volumes = []corev1.Volume{{Name: "test"}} 128 Expect(rawClient.Update(ctx, modifiedDeploy)).Should(Succeed()) 129 130 By("Original applier apply again") 131 Expect(k8sApplicator.Apply(ctx, originalDeploy)).Should(Succeed()) 132 resultDeploy := basicTestDeployment() 133 Expect(rawClient.Get(ctx, deployKey, resultDeploy)).Should(Succeed()) 134 135 By("Check the changes from other applier are not effected") 136 Expect(resultDeploy.Spec.MinReadySeconds).Should(Equal(int32(10))) 137 Expect(*resultDeploy.Spec.ProgressDeadlineSeconds).Should(Equal(int32(20))) 138 Expect(len(resultDeploy.Spec.Template.Spec.Volumes)).Should(Equal(1)) 139 }) 140 141 It("Test apply resource already exists", func() { 142 ctx := context.Background() 143 app := &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "test-app", Namespace: "default"}} 144 By("Test create resource already exists but has no application owner") 145 cm1 := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-resource-exists", Namespace: "default"}} 146 Expect(rawClient.Create(ctx, cm1)).Should(Succeed()) 147 Expect(rawClient.Get(ctx, client.ObjectKeyFromObject(cm1), &corev1.ConfigMap{})).Should(Succeed()) 148 obj1, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cm1) 149 Expect(err).Should(Succeed()) 150 u1 := &unstructured.Unstructured{Object: obj1} 151 u1.SetGroupVersionKind(schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}) 152 Expect(k8sApplicator.Apply(ctx, u1, MustBeControlledByApp(app))).Should(Satisfy(func(err error) bool { 153 return err != nil && strings.Contains(err.Error(), "exists but not managed by any application now") 154 })) 155 Expect(rawClient.Delete(ctx, cm1)).Should(Succeed()) 156 By("Test create resource already exists but owned by other application") 157 cm2 := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-resource-exists-owned-by-others", Namespace: "default"}} 158 oamutil.AddLabels(cm2, map[string]string{ 159 oam.LabelAppName: "other", 160 oam.LabelAppNamespace: "default", 161 }) 162 Expect(rawClient.Create(ctx, cm2)).Should(Succeed()) 163 Expect(rawClient.Get(ctx, client.ObjectKeyFromObject(cm2), &corev1.ConfigMap{})).Should(Succeed()) 164 obj2, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cm2) 165 u2 := &unstructured.Unstructured{Object: obj2} 166 u2.SetGroupVersionKind(schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}) 167 Expect(err).Should(Succeed()) 168 Expect(k8sApplicator.Apply(ctx, u2, MustBeControlledByApp(app))).Should(Satisfy(func(err error) bool { 169 return err != nil && strings.Contains(err.Error(), "is managed by other application") 170 })) 171 Expect(rawClient.Delete(ctx, cm2)).Should(Succeed()) 172 }) 173 174 It("Test apply resources with external modifier", func() { 175 deploy.SetGroupVersionKind(appsv1.SchemeGroupVersion.WithKind("Deployment")) 176 originalDeploy := deploy.DeepCopy() 177 bs, err := json.Marshal(deploy) 178 Expect(err).Should(Succeed()) 179 deploy.SetAnnotations(map[string]string{oam.AnnotationLastAppliedConfig: string(bs)}) 180 modifiedDeploy := deploy.DeepCopy() 181 modifiedDeploy.Spec.Template.Spec.Containers = append(modifiedDeploy.Spec.Template.Spec.Containers, corev1.Container{ 182 Name: "added-by-external-modifier", 183 Image: "busybox", 184 }) 185 Expect(rawClient.Update(ctx, modifiedDeploy)).Should(Succeed()) 186 187 By("Test patch") 188 Expect(utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=false", features.ApplyResourceByReplace))).Should(Succeed()) 189 Expect(rawClient.Get(ctx, client.ObjectKeyFromObject(deploy), deploy)).Should(Succeed()) 190 copy1 := originalDeploy.DeepCopy() 191 copy1.SetResourceVersion(deploy.ResourceVersion) 192 Expect(k8sApplicator.Apply(ctx, copy1)).Should(Succeed()) 193 Expect(rawClient.Get(ctx, client.ObjectKeyFromObject(deploy), deploy)).Should(Succeed()) 194 Expect(len(deploy.Spec.Template.Spec.Containers)).Should(Equal(2)) 195 196 By("Test update") 197 Expect(utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=true", features.ApplyResourceByReplace))).Should(Succeed()) 198 Expect(rawClient.Get(ctx, client.ObjectKeyFromObject(deploy), deploy)).Should(Succeed()) 199 copy2 := originalDeploy.DeepCopy() 200 copy2.SetResourceVersion(deploy.ResourceVersion) 201 Expect(k8sApplicator.Apply(ctx, copy2)).Should(Succeed()) 202 Expect(rawClient.Get(ctx, client.ObjectKeyFromObject(deploy), deploy)).Should(Succeed()) 203 Expect(len(deploy.Spec.Template.Spec.Containers)).Should(Equal(1)) 204 205 Expect(utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=false", features.ApplyResourceByReplace))).Should(Succeed()) 206 }) 207 }) 208 }) 209 210 func basicTestDeployment() *appsv1.Deployment { 211 return &appsv1.Deployment{ 212 TypeMeta: metav1.TypeMeta{ 213 Kind: "Deployment", 214 APIVersion: "apps/v1", 215 }, 216 ObjectMeta: metav1.ObjectMeta{ 217 Name: "testdeploy", 218 Namespace: ns, 219 }, 220 Spec: appsv1.DeploymentSpec{ 221 // Replicas: x // normal field with default value 222 Selector: &metav1.LabelSelector{ 223 MatchLabels: map[string]string{ 224 "app": "nginx", 225 }, 226 }, 227 Template: corev1.PodTemplateSpec{ 228 Spec: corev1.PodSpec{ 229 Containers: []corev1.Container{ // array field 230 { 231 Name: "nginx", 232 Image: "nginx:1.9.4", // normal field without default value 233 }, 234 }, 235 }, 236 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "nginx"}}, 237 }, 238 }, 239 } 240 }