...

Source file src/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedupdater_test.go

Documentation: k8s.io/apimachinery/pkg/util/managedfields/internal

     1  /*
     2  Copyright 2020 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 internal_test
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"strings"
    23  	"testing"
    24  
    25  	"k8s.io/apimachinery/pkg/api/meta"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/apimachinery/pkg/util/managedfields/internal"
    30  	internaltesting "k8s.io/apimachinery/pkg/util/managedfields/internal/testing"
    31  	"sigs.k8s.io/yaml"
    32  )
    33  
    34  func TestLastAppliedUpdater(t *testing.T) {
    35  	f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment"),
    36  		"",
    37  		func(m internal.Manager) internal.Manager {
    38  			return internal.NewLastAppliedUpdater(m)
    39  		})
    40  
    41  	originalLastApplied := `nonempty`
    42  	appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
    43  	appliedDeployment := []byte(`
    44  apiVersion: apps/v1
    45  kind: Deployment
    46  metadata:
    47    name: my-deployment
    48    annotations:
    49      "kubectl.kubernetes.io/last-applied-configuration": "` + originalLastApplied + `"
    50    labels:
    51      app: my-app
    52  spec:
    53    replicas: 20
    54    selector:
    55      matchLabels:
    56        app: my-app
    57    template:
    58      metadata:
    59        labels:
    60          app: my-app
    61      spec:
    62        containers:
    63        - name: my-c
    64          image: my-image
    65  `)
    66  	if err := yaml.Unmarshal(appliedDeployment, &appliedObj.Object); err != nil {
    67  		t.Errorf("error decoding YAML: %v", err)
    68  	}
    69  
    70  	if err := f.Apply(appliedObj, "NOT-KUBECTL", false); err != nil {
    71  		t.Errorf("error applying object: %v", err)
    72  	}
    73  
    74  	lastApplied, err := getLastApplied(f.Live())
    75  	if err != nil {
    76  		t.Errorf("failed to get last applied: %v", err)
    77  	}
    78  
    79  	if lastApplied != originalLastApplied {
    80  		t.Errorf("expected last applied annotation to be %q and NOT be updated, but got: %q", originalLastApplied, lastApplied)
    81  	}
    82  
    83  	if err := f.Apply(appliedObj, "kubectl", false); err != nil {
    84  		t.Errorf("error applying object: %v", err)
    85  	}
    86  
    87  	lastApplied, err = getLastApplied(f.Live())
    88  	if err != nil {
    89  		t.Errorf("failed to get last applied: %v", err)
    90  	}
    91  
    92  	if lastApplied == originalLastApplied ||
    93  		!strings.Contains(lastApplied, "my-app") ||
    94  		!strings.Contains(lastApplied, "my-image") {
    95  		t.Errorf("expected last applied annotation to be updated, but got: %q", lastApplied)
    96  	}
    97  }
    98  
    99  func TestLargeLastApplied(t *testing.T) {
   100  	tests := []struct {
   101  		name      string
   102  		oldObject *unstructured.Unstructured
   103  		newObject *unstructured.Unstructured
   104  	}{
   105  		{
   106  			name: "old object + new object last-applied annotation is too big",
   107  			oldObject: func() *unstructured.Unstructured {
   108  				u := &unstructured.Unstructured{}
   109  				err := json.Unmarshal([]byte(`
   110  {
   111     "metadata": {
   112        "name": "large-update-test-cm",
   113        "namespace": "default",
   114        "annotations": {
   115           "kubectl.kubernetes.io/last-applied-configuration": "nonempty"
   116        }
   117     },
   118     "apiVersion": "v1",
   119     "kind": "ConfigMap",
   120     "data": {
   121        "k": "v"
   122     }
   123  }`), &u)
   124  				if err != nil {
   125  					panic(err)
   126  				}
   127  				return u
   128  			}(),
   129  			newObject: func() *unstructured.Unstructured {
   130  				u := &unstructured.Unstructured{}
   131  				err := json.Unmarshal([]byte(`
   132  {
   133     "metadata": {
   134        "name": "large-update-test-cm",
   135        "namespace": "default",
   136        "annotations": {
   137           "kubectl.kubernetes.io/last-applied-configuration": "nonempty"
   138        }
   139     },
   140     "apiVersion": "v1",
   141     "kind": "ConfigMap",
   142     "data": {
   143        "k": "v"
   144     }
   145  }`), &u)
   146  				if err != nil {
   147  					panic(err)
   148  				}
   149  				for i := 0; i < 9999; i++ {
   150  					unique := fmt.Sprintf("this-key-is-very-long-so-as-to-create-a-very-large-serialized-fieldset-%v", i)
   151  					unstructured.SetNestedField(u.Object, "A", "data", unique)
   152  				}
   153  				return u
   154  			}(),
   155  		},
   156  		{
   157  			name: "old object + new object annotations + new object last-applied annotation is too big",
   158  			oldObject: func() *unstructured.Unstructured {
   159  				u := &unstructured.Unstructured{}
   160  				err := json.Unmarshal([]byte(`
   161  {
   162     "metadata": {
   163        "name": "large-update-test-cm",
   164        "namespace": "default",
   165        "annotations": {
   166           "kubectl.kubernetes.io/last-applied-configuration": "nonempty"
   167        }
   168     },
   169     "apiVersion": "v1",
   170     "kind": "ConfigMap",
   171     "data": {
   172        "k": "v"
   173     }
   174  }`), &u)
   175  				if err != nil {
   176  					panic(err)
   177  				}
   178  				for i := 0; i < 2000; i++ {
   179  					unique := fmt.Sprintf("this-key-is-very-long-so-as-to-create-a-very-large-serialized-fieldset-%v", i)
   180  					unstructured.SetNestedField(u.Object, "A", "data", unique)
   181  				}
   182  				return u
   183  			}(),
   184  			newObject: func() *unstructured.Unstructured {
   185  				u := &unstructured.Unstructured{}
   186  				err := json.Unmarshal([]byte(`
   187  {
   188     "metadata": {
   189        "name": "large-update-test-cm",
   190        "namespace": "default",
   191        "annotations": {
   192           "kubectl.kubernetes.io/last-applied-configuration": "nonempty"
   193        }
   194     },
   195     "apiVersion": "v1",
   196     "kind": "ConfigMap",
   197     "data": {
   198        "k": "v"
   199     }
   200  }`), &u)
   201  				if err != nil {
   202  					panic(err)
   203  				}
   204  				for i := 0; i < 2000; i++ {
   205  					unique := fmt.Sprintf("this-key-is-very-long-so-as-to-create-a-very-large-serialized-fieldset-%v", i)
   206  					unstructured.SetNestedField(u.Object, "A", "data", unique)
   207  					unstructured.SetNestedField(u.Object, "A", "metadata", "annotations", unique)
   208  				}
   209  				return u
   210  			}(),
   211  		},
   212  	}
   213  	for _, test := range tests {
   214  		t.Run(test.name, func(t *testing.T) {
   215  			f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap"),
   216  				"",
   217  				func(m internal.Manager) internal.Manager {
   218  					return internal.NewLastAppliedUpdater(m)
   219  				})
   220  
   221  			if err := f.Apply(test.oldObject, "kubectl", false); err != nil {
   222  				t.Errorf("Error applying object: %v", err)
   223  			}
   224  
   225  			lastApplied, err := getLastApplied(f.Live())
   226  			if err != nil {
   227  				t.Errorf("Failed to access last applied annotation: %v", err)
   228  			}
   229  			if len(lastApplied) == 0 || lastApplied == "nonempty" {
   230  				t.Errorf("Expected an updated last-applied annotation, but got: %q", lastApplied)
   231  			}
   232  
   233  			if err := f.Apply(test.newObject, "kubectl", false); err != nil {
   234  				t.Errorf("Error applying object: %v", err)
   235  			}
   236  
   237  			accessor := meta.NewAccessor()
   238  			annotations, err := accessor.Annotations(f.Live())
   239  			if err != nil {
   240  				t.Errorf("Failed to access annotations: %v", err)
   241  			}
   242  			if annotations == nil {
   243  				t.Errorf("No annotations on obj: %v", f.Live())
   244  			}
   245  			lastApplied, ok := annotations[internal.LastAppliedConfigAnnotation]
   246  			if ok || len(lastApplied) > 0 {
   247  				t.Errorf("Expected no last applied annotation, but got last applied with length: %d", len(lastApplied))
   248  			}
   249  		})
   250  	}
   251  }
   252  
   253  func getLastApplied(obj runtime.Object) (string, error) {
   254  	accessor := meta.NewAccessor()
   255  	annotations, err := accessor.Annotations(obj)
   256  	if err != nil {
   257  		return "", fmt.Errorf("failed to access annotations: %v", err)
   258  	}
   259  	if annotations == nil {
   260  		return "", fmt.Errorf("no annotations on obj: %v", obj)
   261  	}
   262  
   263  	lastApplied, ok := annotations[internal.LastAppliedConfigAnnotation]
   264  	if !ok {
   265  		return "", fmt.Errorf("expected last applied annotation, but got none for object: %v", obj)
   266  	}
   267  	return lastApplied, nil
   268  }
   269  

View as plain text