k8s.io/kubernetes@v1.29.3/pkg/api/testing/applyconfiguration_test.go (about)

     1  /*
     2  Copyright 2021 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 testing
    18  
    19  import (
    20  	"math/rand"
    21  	"reflect"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	fuzz "github.com/google/gofuzz"
    26  	"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
    27  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    28  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	"k8s.io/apimachinery/pkg/util/json"
    32  	"k8s.io/client-go/applyconfigurations"
    33  	v1mf "k8s.io/client-go/applyconfigurations/core/v1"
    34  
    35  	"k8s.io/kubernetes/pkg/api/legacyscheme"
    36  	api "k8s.io/kubernetes/pkg/apis/core"
    37  )
    38  
    39  // TestUnstructuredRoundTripApplyConfigurations converts each known object type through unstructured
    40  // to the apply configuration for that object type, then converts it back to the object type and
    41  // verifies it is unchanged.
    42  func TestUnstructuredRoundTripApplyConfigurations(t *testing.T) {
    43  	for gvk := range legacyscheme.Scheme.AllKnownTypes() {
    44  		if nonRoundTrippableTypes.Has(gvk.Kind) {
    45  			continue
    46  		}
    47  		if gvk.Version == runtime.APIVersionInternal {
    48  			continue
    49  		}
    50  		if builder := applyconfigurations.ForKind(gvk); builder == nil {
    51  			continue
    52  		}
    53  
    54  		t.Run(gvk.String(), func(t *testing.T) {
    55  			for i := 0; i < 3; i++ {
    56  				item := fuzzObject(t, gvk)
    57  				builder := applyconfigurations.ForKind(gvk)
    58  				unstructuredRoundTripApplyConfiguration(t, item, builder)
    59  				if t.Failed() {
    60  					break
    61  				}
    62  			}
    63  		})
    64  	}
    65  }
    66  
    67  // TestJsonRoundTripApplyConfigurations converts each known object type through JSON to the apply
    68  // configuration for that object type, then converts it back to the object type and verifies it
    69  // is unchanged.
    70  func TestJsonRoundTripApplyConfigurations(t *testing.T) {
    71  	for gvk := range legacyscheme.Scheme.AllKnownTypes() {
    72  		if nonRoundTrippableTypes.Has(gvk.Kind) {
    73  			continue
    74  		}
    75  		if gvk.Version == runtime.APIVersionInternal {
    76  			continue
    77  		}
    78  		if builder := applyconfigurations.ForKind(gvk); builder == nil {
    79  			continue
    80  		}
    81  
    82  		t.Run(gvk.String(), func(t *testing.T) {
    83  			for i := 0; i < 3; i++ {
    84  				item := fuzzObject(t, gvk)
    85  				builder := applyconfigurations.ForKind(gvk)
    86  				jsonRoundTripApplyConfiguration(t, item, builder)
    87  				if t.Failed() {
    88  					break
    89  				}
    90  
    91  			}
    92  		})
    93  	}
    94  }
    95  
    96  func unstructuredRoundTripApplyConfiguration(t *testing.T, item runtime.Object, applyConfig interface{}) {
    97  	u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item)
    98  	if err != nil {
    99  		t.Errorf("ToUnstructured failed: %v", err)
   100  		return
   101  	}
   102  	err = runtime.DefaultUnstructuredConverter.FromUnstructured(u, applyConfig)
   103  	if err != nil {
   104  		t.Errorf("FromUnstructured failed: %v", err)
   105  		return
   106  	}
   107  	rtObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
   108  	u, err = runtime.DefaultUnstructuredConverter.ToUnstructured(applyConfig)
   109  	if err != nil {
   110  		t.Errorf("ToUnstructured failed: %v", err)
   111  		return
   112  	}
   113  	err = runtime.DefaultUnstructuredConverter.FromUnstructured(u, rtObj)
   114  	if err != nil {
   115  		t.Errorf("FromUnstructured failed: %v", err)
   116  		return
   117  	}
   118  	if !apiequality.Semantic.DeepEqual(item, rtObj) {
   119  		t.Errorf("Object changed, diff: %v", cmp.Diff(item, rtObj))
   120  	}
   121  }
   122  
   123  func jsonRoundTripApplyConfiguration(t *testing.T, item runtime.Object, applyConfig interface{}) {
   124  
   125  	objData, err := json.Marshal(item)
   126  	if err != nil {
   127  		t.Errorf("json.Marshal failed: %v", err)
   128  		return
   129  	}
   130  	err = json.Unmarshal(objData, applyConfig)
   131  	if err != nil {
   132  		t.Errorf("applyConfig.UnmarshalJSON failed: %v", err)
   133  		return
   134  	}
   135  	rtObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
   136  	applyData, err := json.Marshal(applyConfig)
   137  	if err != nil {
   138  		t.Errorf("applyConfig.MarshalJSON failed: %v", err)
   139  		return
   140  	}
   141  	err = json.Unmarshal(applyData, rtObj)
   142  	if err != nil {
   143  		t.Errorf("json.Unmarshal failed: %v", err)
   144  		return
   145  	}
   146  	if !apiequality.Semantic.DeepEqual(item, rtObj) {
   147  		t.Errorf("Object changed, diff: %v", cmp.Diff(item, rtObj))
   148  	}
   149  }
   150  
   151  func fuzzObject(t *testing.T, gvk schema.GroupVersionKind) runtime.Object {
   152  	internalVersion := schema.GroupVersion{Group: gvk.Group, Version: runtime.APIVersionInternal}
   153  	externalVersion := gvk.GroupVersion()
   154  	kind := gvk.Kind
   155  
   156  	// We do fuzzing on the internal version of the object, and only then
   157  	// convert to the external version. This is because custom fuzzing
   158  	// function are only supported for internal objects.
   159  	internalObj, err := legacyscheme.Scheme.New(internalVersion.WithKind(kind))
   160  	if err != nil {
   161  		t.Fatalf("Couldn't create internal object %v: %v", kind, err)
   162  	}
   163  	seed := rand.Int63()
   164  	fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), legacyscheme.Codecs).
   165  		Funcs(
   166  			// Ensure that InitContainers and their statuses are not generated. This
   167  			// is because in this test we are simply doing json operations, in which
   168  			// those disappear.
   169  			func(s *api.PodSpec, c fuzz.Continue) {
   170  				c.FuzzNoCustom(s)
   171  				s.InitContainers = nil
   172  			},
   173  			func(s *api.PodStatus, c fuzz.Continue) {
   174  				c.FuzzNoCustom(s)
   175  				s.InitContainerStatuses = nil
   176  			},
   177  			// Apply configuration types do not have managed fields, so we exclude
   178  			// them in our fuzz test cases.
   179  			func(s *v1.ObjectMeta, c fuzz.Continue) {
   180  				c.FuzzNoCustom(s)
   181  				s.ManagedFields = nil
   182  				s.SelfLink = ""
   183  			},
   184  		).Fuzz(internalObj)
   185  
   186  	item, err := legacyscheme.Scheme.New(externalVersion.WithKind(kind))
   187  	if err != nil {
   188  		t.Fatalf("Couldn't create external object %v: %v", kind, err)
   189  	}
   190  	if err := legacyscheme.Scheme.Convert(internalObj, item, nil); err != nil {
   191  		t.Fatalf("Conversion for %v failed: %v", kind, err)
   192  	}
   193  	return item
   194  }
   195  
   196  func BenchmarkApplyConfigurationsFromUnstructured(b *testing.B) {
   197  	items := benchmarkItems(b)
   198  	convertor := runtime.DefaultUnstructuredConverter
   199  	unstr := make([]map[string]interface{}, len(items))
   200  	for i := range items {
   201  		item, err := convertor.ToUnstructured(&items[i])
   202  		if err != nil || item == nil {
   203  			b.Fatalf("unexpected error: %v", err)
   204  		}
   205  		unstr = append(unstr, item)
   206  	}
   207  	size := len(items)
   208  	b.ResetTimer()
   209  	for i := 0; i < b.N; i++ {
   210  		builder := &v1mf.PodApplyConfiguration{}
   211  		if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstr[i%size], builder); err != nil {
   212  			b.Fatalf("unexpected error: %v", err)
   213  		}
   214  	}
   215  	b.StopTimer()
   216  }
   217  
   218  func BenchmarkApplyConfigurationsToUnstructured(b *testing.B) {
   219  	items := benchmarkItems(b)
   220  	convertor := runtime.DefaultUnstructuredConverter
   221  	builders := make([]*v1mf.PodApplyConfiguration, len(items))
   222  	for i := range items {
   223  		item, err := convertor.ToUnstructured(&items[i])
   224  		if err != nil || item == nil {
   225  			b.Fatalf("unexpected error: %v", err)
   226  		}
   227  		builder := &v1mf.PodApplyConfiguration{}
   228  		if err := runtime.DefaultUnstructuredConverter.FromUnstructured(item, builder); err != nil {
   229  			b.Fatalf("unexpected error: %v", err)
   230  		}
   231  		builders[i] = builder
   232  	}
   233  	b.ResetTimer()
   234  	size := len(items)
   235  	for i := 0; i < b.N; i++ {
   236  		builder := builders[i%size]
   237  		if _, err := runtime.DefaultUnstructuredConverter.ToUnstructured(builder); err != nil {
   238  			b.Fatalf("unexpected error: %v", err)
   239  		}
   240  	}
   241  	b.StopTimer()
   242  }