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 }