sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/topology/cluster/structuredmerge/dryrun_test.go (about)

     1  /*
     2  Copyright 2022 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 structuredmerge
    18  
    19  import (
    20  	"testing"
    21  	"time"
    22  
    23  	. "github.com/onsi/gomega"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    26  
    27  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    28  	"sigs.k8s.io/cluster-api/util/conversion"
    29  )
    30  
    31  func Test_cleanupManagedFieldsAndAnnotation(t *testing.T) {
    32  	rawManagedFieldWithAnnotation := `{"f:metadata":{"f:annotations":{"f:topology.cluster.x-k8s.io/dry-run":{},"f:cluster.x-k8s.io/conversion-data":{}}}}`
    33  	rawManagedFieldWithAnnotationSpecLabels := `{"f:metadata":{"f:annotations":{"f:topology.cluster.x-k8s.io/dry-run":{},"f:cluster.x-k8s.io/conversion-data":{}},"f:labels":{}},"f:spec":{"f:foo":{}}}`
    34  	rawManagedFieldWithSpecLabels := `{"f:metadata":{"f:labels":{}},"f:spec":{"f:foo":{}}}`
    35  
    36  	tests := []struct {
    37  		name    string
    38  		obj     *unstructured.Unstructured
    39  		wantErr bool
    40  		want    *unstructured.Unstructured
    41  	}{
    42  		{
    43  			name:    "no-op",
    44  			obj:     newObjectBuilder().Build(),
    45  			wantErr: false,
    46  		},
    47  		{
    48  			name: "filter out dry-run annotation",
    49  			obj: newObjectBuilder().
    50  				WithAnnotation(clusterv1.TopologyDryRunAnnotation, "").
    51  				Build(),
    52  			wantErr: false,
    53  			want: newObjectBuilder().
    54  				Build(),
    55  		},
    56  		{
    57  			name: "filter out conversion annotation",
    58  			obj: newObjectBuilder().
    59  				WithAnnotation(conversion.DataAnnotation, "").
    60  				Build(),
    61  			wantErr: false,
    62  			want: newObjectBuilder().
    63  				Build(),
    64  		},
    65  		{
    66  			name: "managedFields: should drop managed fields of other manager",
    67  			obj: newObjectBuilder().
    68  				WithManagedFieldsEntry("other", "", metav1.ManagedFieldsOperationApply, []byte(`{}`), nil).
    69  				Build(),
    70  			wantErr: false,
    71  			want: newObjectBuilder().
    72  				Build(),
    73  		},
    74  		{
    75  			name: "managedFields: should drop managed fields of a subresource",
    76  			obj: newObjectBuilder().
    77  				WithManagedFieldsEntry(TopologyManagerName, "status", metav1.ManagedFieldsOperationApply, []byte(`{}`), nil).
    78  				Build(),
    79  			wantErr: false,
    80  			want: newObjectBuilder().
    81  				Build(),
    82  		},
    83  		{
    84  			name: "managedFields: should drop managed fields of another operation",
    85  			obj: newObjectBuilder().
    86  				WithManagedFieldsEntry(TopologyManagerName, "", metav1.ManagedFieldsOperationUpdate, []byte(`{}`), nil).
    87  				Build(),
    88  			wantErr: false,
    89  			want: newObjectBuilder().
    90  				Build(),
    91  		},
    92  		{
    93  			name: "managedFields: cleanup up the managed field entry",
    94  			obj: newObjectBuilder().
    95  				WithManagedFieldsEntry(TopologyManagerName, "", metav1.ManagedFieldsOperationApply, []byte(rawManagedFieldWithAnnotation), nil).
    96  				Build(),
    97  			wantErr: false,
    98  			want: newObjectBuilder().
    99  				WithManagedFieldsEntry(TopologyManagerName, "", metav1.ManagedFieldsOperationApply, []byte(`{}`), nil).
   100  				Build(),
   101  		},
   102  		{
   103  			name: "managedFields: cleanup the managed field entry and preserve other ownership",
   104  			obj: newObjectBuilder().
   105  				WithManagedFieldsEntry(TopologyManagerName, "", metav1.ManagedFieldsOperationApply, []byte(rawManagedFieldWithAnnotationSpecLabels), nil).
   106  				Build(),
   107  			wantErr: false,
   108  			want: newObjectBuilder().
   109  				WithManagedFieldsEntry(TopologyManagerName, "", metav1.ManagedFieldsOperationApply, []byte(rawManagedFieldWithSpecLabels), nil).
   110  				Build(),
   111  		},
   112  		{
   113  			name: "managedFields: remove time",
   114  			obj: newObjectBuilder().
   115  				WithManagedFieldsEntry(TopologyManagerName, "", metav1.ManagedFieldsOperationApply, []byte(`{}`), &metav1.Time{Time: time.Time{}}).
   116  				Build(),
   117  			wantErr: false,
   118  			want: newObjectBuilder().
   119  				WithManagedFieldsEntry(TopologyManagerName, "", metav1.ManagedFieldsOperationApply, []byte(`{}`), nil).
   120  				Build(),
   121  		},
   122  	}
   123  	for _, tt := range tests {
   124  		t.Run(tt.name, func(t *testing.T) {
   125  			g := NewWithT(t)
   126  
   127  			if err := cleanupManagedFieldsAndAnnotation(tt.obj); (err != nil) != tt.wantErr {
   128  				t.Errorf("cleanupManagedFieldsAndAnnotation() error = %v, wantErr %v", err, tt.wantErr)
   129  			}
   130  			if tt.want != nil {
   131  				g.Expect(tt.obj).To(BeEquivalentTo(tt.want))
   132  			}
   133  		})
   134  	}
   135  }
   136  
   137  type objectBuilder struct {
   138  	u *unstructured.Unstructured
   139  }
   140  
   141  func newObjectBuilder() objectBuilder {
   142  	u := &unstructured.Unstructured{Object: map[string]interface{}{}}
   143  	u.SetName("test")
   144  	u.SetNamespace("default")
   145  
   146  	return objectBuilder{
   147  		u: u,
   148  	}
   149  }
   150  
   151  func (b objectBuilder) DeepCopy() objectBuilder {
   152  	return objectBuilder{b.u.DeepCopy()}
   153  }
   154  
   155  func (b objectBuilder) Build() *unstructured.Unstructured {
   156  	// Setting an empty managed field array if no managed field is set so there is
   157  	// no difference between an object which never had a managed field and one from
   158  	// which all managed field entries were removed.
   159  	if b.u.GetManagedFields() == nil {
   160  		b.u.SetManagedFields([]metav1.ManagedFieldsEntry{})
   161  	}
   162  	return b.u.DeepCopy()
   163  }
   164  
   165  func (b objectBuilder) WithAnnotation(k, v string) objectBuilder {
   166  	annotations := b.u.GetAnnotations()
   167  	if annotations == nil {
   168  		annotations = map[string]string{}
   169  	}
   170  	annotations[k] = v
   171  	b.u.SetAnnotations(annotations)
   172  	return b
   173  }
   174  
   175  func (b objectBuilder) WithManagedFieldsEntry(manager, subresource string, operation metav1.ManagedFieldsOperationType, fieldsV1 []byte, time *metav1.Time) objectBuilder {
   176  	managedFields := append(b.u.GetManagedFields(), metav1.ManagedFieldsEntry{
   177  		Manager:     manager,
   178  		Operation:   operation,
   179  		Subresource: subresource,
   180  		FieldsV1:    &metav1.FieldsV1{Raw: fieldsV1},
   181  		Time:        time,
   182  	})
   183  	b.u.SetManagedFields(managedFields)
   184  	return b
   185  }