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  }