sigs.k8s.io/cluster-api@v1.6.3/internal/util/ssa/managedfields_test.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes 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 ssa contains utils related to ssa.
    18  package ssa
    19  
    20  import (
    21  	"context"
    22  	"encoding/json"
    23  	"testing"
    24  
    25  	. "github.com/onsi/gomega"
    26  	corev1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    30  
    31  	"sigs.k8s.io/cluster-api/internal/contract"
    32  )
    33  
    34  func TestDropManagedFields(t *testing.T) {
    35  	ctx := context.Background()
    36  
    37  	ssaManager := "ssa-manager"
    38  
    39  	fieldV1Map := map[string]interface{}{
    40  		"f:metadata": map[string]interface{}{
    41  			"f:name":        map[string]interface{}{},
    42  			"f:labels":      map[string]interface{}{},
    43  			"f:annotations": map[string]interface{}{},
    44  			"f:finalizers":  map[string]interface{}{},
    45  		},
    46  	}
    47  	fieldV1, err := json.Marshal(fieldV1Map)
    48  	if err != nil {
    49  		panic(err)
    50  	}
    51  
    52  	objWithoutSSAManager := &corev1.ConfigMap{
    53  		ObjectMeta: metav1.ObjectMeta{
    54  			Name:      "cm-1",
    55  			Namespace: "default",
    56  			ManagedFields: []metav1.ManagedFieldsEntry{{
    57  				Manager:    classicManager,
    58  				Operation:  metav1.ManagedFieldsOperationUpdate,
    59  				FieldsType: "FieldsV1",
    60  				FieldsV1:   &metav1.FieldsV1{Raw: fieldV1},
    61  			}},
    62  			Labels: map[string]string{
    63  				"label-1": "value-1",
    64  			},
    65  			Annotations: map[string]string{
    66  				"annotation-1": "value-1",
    67  			},
    68  			Finalizers: []string{"test-finalizer"},
    69  		},
    70  		Data: map[string]string{
    71  			"test-key": "test-value",
    72  		},
    73  	}
    74  
    75  	objectWithSSAManager := &corev1.ConfigMap{
    76  		ObjectMeta: metav1.ObjectMeta{
    77  			Name:      "cm-2",
    78  			Namespace: "default",
    79  			ManagedFields: []metav1.ManagedFieldsEntry{
    80  				{
    81  					Manager:    classicManager,
    82  					Operation:  metav1.ManagedFieldsOperationUpdate,
    83  					FieldsType: "FieldsV1",
    84  					FieldsV1:   &metav1.FieldsV1{Raw: fieldV1},
    85  				},
    86  				{
    87  					Manager:   ssaManager,
    88  					Operation: metav1.ManagedFieldsOperationApply,
    89  				},
    90  			},
    91  			Labels: map[string]string{
    92  				"label-1": "value-1",
    93  			},
    94  			Annotations: map[string]string{
    95  				"annotation-1": "value-1",
    96  			},
    97  			Finalizers: []string{"test-finalizer"},
    98  		},
    99  		Data: map[string]string{
   100  			"test-key": "test-value",
   101  		},
   102  	}
   103  
   104  	tests := []struct {
   105  		name                string
   106  		obj                 client.Object
   107  		wantOwnershipToDrop bool
   108  	}{
   109  		{
   110  			name:                "should drop ownership of fields if there is no entry for ssaManager",
   111  			obj:                 objWithoutSSAManager,
   112  			wantOwnershipToDrop: true,
   113  		},
   114  		{
   115  			name:                "should not drop ownership of fields if there is an entry for ssaManager",
   116  			obj:                 objectWithSSAManager,
   117  			wantOwnershipToDrop: false,
   118  		},
   119  	}
   120  
   121  	for _, tt := range tests {
   122  		t.Run(tt.name, func(t *testing.T) {
   123  			g := NewWithT(t)
   124  			fakeClient := fake.NewClientBuilder().WithObjects(tt.obj).Build()
   125  			labelsAndAnnotationsManagedFieldPaths := []contract.Path{
   126  				{"f:metadata", "f:annotations"},
   127  				{"f:metadata", "f:labels"},
   128  			}
   129  			g.Expect(DropManagedFields(ctx, fakeClient, tt.obj, ssaManager, labelsAndAnnotationsManagedFieldPaths)).To(Succeed())
   130  			if tt.wantOwnershipToDrop {
   131  				g.Expect(tt.obj.GetManagedFields()).ShouldNot(MatchFieldOwnership(
   132  					classicManager,
   133  					metav1.ManagedFieldsOperationUpdate,
   134  					contract.Path{"f:metadata", "f:labels"},
   135  				))
   136  				g.Expect(tt.obj.GetManagedFields()).ShouldNot(MatchFieldOwnership(
   137  					classicManager,
   138  					metav1.ManagedFieldsOperationUpdate,
   139  					contract.Path{"f:metadata", "f:annotations"},
   140  				))
   141  			} else {
   142  				g.Expect(tt.obj.GetManagedFields()).Should(MatchFieldOwnership(
   143  					classicManager,
   144  					metav1.ManagedFieldsOperationUpdate,
   145  					contract.Path{"f:metadata", "f:labels"},
   146  				))
   147  				g.Expect(tt.obj.GetManagedFields()).Should(MatchFieldOwnership(
   148  					classicManager,
   149  					metav1.ManagedFieldsOperationUpdate,
   150  					contract.Path{"f:metadata", "f:annotations"},
   151  				))
   152  			}
   153  			// Verify ownership of other fields is not affected.
   154  			g.Expect(tt.obj.GetManagedFields()).Should(MatchFieldOwnership(
   155  				classicManager,
   156  				metav1.ManagedFieldsOperationUpdate,
   157  				contract.Path{"f:metadata", "f:finalizers"},
   158  			))
   159  		})
   160  	}
   161  }
   162  
   163  func TestCleanUpManagedFieldsForSSAAdoption(t *testing.T) {
   164  	ctx := context.Background()
   165  
   166  	ssaManager := "ssa-manager"
   167  
   168  	objWithoutAnyManager := &corev1.ConfigMap{
   169  		ObjectMeta: metav1.ObjectMeta{
   170  			Name:      "cm-1",
   171  			Namespace: "default",
   172  		},
   173  		Data: map[string]string{
   174  			"test-key": "test-value",
   175  		},
   176  	}
   177  	objWithOnlyClassicManager := &corev1.ConfigMap{
   178  		ObjectMeta: metav1.ObjectMeta{
   179  			Name:      "cm-1",
   180  			Namespace: "default",
   181  			ManagedFields: []metav1.ManagedFieldsEntry{{
   182  				Manager:   classicManager,
   183  				Operation: metav1.ManagedFieldsOperationUpdate,
   184  			}},
   185  		},
   186  		Data: map[string]string{
   187  			"test-key": "test-value",
   188  		},
   189  	}
   190  	objWithOnlySSAManager := &corev1.ConfigMap{
   191  		ObjectMeta: metav1.ObjectMeta{
   192  			Name:      "cm-1",
   193  			Namespace: "default",
   194  			ManagedFields: []metav1.ManagedFieldsEntry{{
   195  				Manager:   ssaManager,
   196  				Operation: metav1.ManagedFieldsOperationApply,
   197  			}},
   198  		},
   199  		Data: map[string]string{
   200  			"test-key": "test-value",
   201  		},
   202  	}
   203  	objWithClassicManagerAndSSAManager := &corev1.ConfigMap{
   204  		ObjectMeta: metav1.ObjectMeta{
   205  			Name:      "cm-1",
   206  			Namespace: "default",
   207  			ManagedFields: []metav1.ManagedFieldsEntry{
   208  				{
   209  					Manager:   classicManager,
   210  					Operation: metav1.ManagedFieldsOperationUpdate,
   211  				},
   212  				{
   213  					Manager:   ssaManager,
   214  					Operation: metav1.ManagedFieldsOperationApply,
   215  				},
   216  			},
   217  		},
   218  		Data: map[string]string{
   219  			"test-key": "test-value",
   220  		},
   221  	}
   222  
   223  	tests := []struct {
   224  		name                       string
   225  		obj                        client.Object
   226  		wantEntryForClassicManager bool
   227  	}{
   228  		{
   229  			name:                       "should add an entry for ssaManager if it does not have one",
   230  			obj:                        objWithoutAnyManager,
   231  			wantEntryForClassicManager: false,
   232  		},
   233  		{
   234  			name:                       "should add an entry for ssaManager and drop entry for classic manager if it exists",
   235  			obj:                        objWithOnlyClassicManager,
   236  			wantEntryForClassicManager: false,
   237  		},
   238  		{
   239  			name:                       "should keep the entry for ssa-manager if it already has one (no-op)",
   240  			obj:                        objWithOnlySSAManager,
   241  			wantEntryForClassicManager: false,
   242  		},
   243  		{
   244  			name:                       "should keep the entry with ssa-manager if it already has one - should not drop other manager entries (no-op)",
   245  			obj:                        objWithClassicManagerAndSSAManager,
   246  			wantEntryForClassicManager: true,
   247  		},
   248  	}
   249  
   250  	for _, tt := range tests {
   251  		t.Run(tt.name, func(t *testing.T) {
   252  			g := NewWithT(t)
   253  			fakeClient := fake.NewClientBuilder().WithObjects(tt.obj).Build()
   254  			g.Expect(CleanUpManagedFieldsForSSAAdoption(ctx, fakeClient, tt.obj, ssaManager)).Should(Succeed())
   255  			g.Expect(tt.obj.GetManagedFields()).Should(
   256  				ContainElement(MatchManagedFieldsEntry(ssaManager, metav1.ManagedFieldsOperationApply)))
   257  			if tt.wantEntryForClassicManager {
   258  				g.Expect(tt.obj.GetManagedFields()).Should(
   259  					ContainElement(MatchManagedFieldsEntry(classicManager, metav1.ManagedFieldsOperationUpdate)))
   260  			} else {
   261  				g.Expect(tt.obj.GetManagedFields()).ShouldNot(
   262  					ContainElement(MatchManagedFieldsEntry(classicManager, metav1.ManagedFieldsOperationUpdate)))
   263  			}
   264  		})
   265  	}
   266  }