k8s.io/kubernetes@v1.29.3/pkg/api/testing/serialization_proto_test.go (about) 1 /* 2 Copyright 2015 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 "bytes" 21 "encoding/hex" 22 "fmt" 23 "math/rand" 24 "reflect" 25 "testing" 26 27 "github.com/gogo/protobuf/proto" 28 "github.com/google/go-cmp/cmp" 29 30 v1 "k8s.io/api/core/v1" 31 "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" 32 apiequality "k8s.io/apimachinery/pkg/api/equality" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/runtime/schema" 36 "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" 37 "k8s.io/kubernetes/pkg/api/legacyscheme" 38 api "k8s.io/kubernetes/pkg/apis/core" 39 _ "k8s.io/kubernetes/pkg/apis/extensions" 40 _ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" 41 ) 42 43 func TestUniversalDeserializer(t *testing.T) { 44 expected := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test"}, TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"}} 45 d := legacyscheme.Codecs.UniversalDeserializer() 46 for _, mediaType := range []string{"application/json", "application/yaml", "application/vnd.kubernetes.protobuf"} { 47 info, ok := runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), mediaType) 48 if !ok { 49 t.Fatal(mediaType) 50 } 51 buf := &bytes.Buffer{} 52 if err := info.Serializer.Encode(expected, buf); err != nil { 53 t.Fatalf("%s: %v", mediaType, err) 54 } 55 obj, _, err := d.Decode(buf.Bytes(), &schema.GroupVersionKind{Kind: "Pod", Version: "v1"}, nil) 56 if err != nil { 57 t.Fatalf("%s: %v", mediaType, err) 58 } 59 if !apiequality.Semantic.DeepEqual(expected, obj) { 60 t.Fatalf("%s: %#v", mediaType, obj) 61 } 62 } 63 } 64 65 func TestAllFieldsHaveTags(t *testing.T) { 66 for gvk, obj := range legacyscheme.Scheme.AllKnownTypes() { 67 if gvk.Version == runtime.APIVersionInternal { 68 // internal versions are not serialized to protobuf 69 continue 70 } 71 if gvk.Group == "componentconfig" { 72 // component config is not serialized to protobuf 73 continue 74 } 75 if err := fieldsHaveProtobufTags(obj); err != nil { 76 t.Errorf("type %s as gvk %v is missing tags: %v", obj, gvk, err) 77 } 78 } 79 } 80 81 func fieldsHaveProtobufTags(obj reflect.Type) error { 82 switch obj.Kind() { 83 case reflect.Slice, reflect.Map, reflect.Pointer, reflect.Array: 84 return fieldsHaveProtobufTags(obj.Elem()) 85 case reflect.Struct: 86 for i := 0; i < obj.NumField(); i++ { 87 f := obj.Field(i) 88 if f.Name == "TypeMeta" && f.Type.Name() == "TypeMeta" { 89 // TypeMeta is not included in external protobuf because we use an envelope type with TypeMeta 90 continue 91 } 92 if len(f.Tag.Get("json")) > 0 && len(f.Tag.Get("protobuf")) == 0 { 93 return fmt.Errorf("field %s in %s has a 'json' tag but no protobuf tag", f.Name, obj) 94 } 95 } 96 } 97 return nil 98 } 99 100 func TestProtobufRoundTrip(t *testing.T) { 101 obj := &v1.Pod{} 102 fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), legacyscheme.Codecs).Fuzz(obj) 103 // InitContainers are turned into annotations by conversion. 104 obj.Spec.InitContainers = nil 105 obj.Status.InitContainerStatuses = nil 106 data, err := obj.Marshal() 107 if err != nil { 108 t.Fatal(err) 109 } 110 out := &v1.Pod{} 111 if err := out.Unmarshal(data); err != nil { 112 t.Fatal(err) 113 } 114 if !apiequality.Semantic.Equalities.DeepEqual(out, obj) { 115 t.Logf("marshal\n%s", hex.Dump(data)) 116 t.Fatalf("Unmarshal is unequal\n%s", cmp.Diff(out, obj)) 117 } 118 } 119 120 // BenchmarkEncodeCodec measures the cost of performing a codec encode, which includes 121 // reflection (to clear APIVersion and Kind) 122 func BenchmarkEncodeCodecProtobuf(b *testing.B) { 123 items := benchmarkItems(b) 124 width := len(items) 125 s := protobuf.NewSerializer(nil, nil) 126 b.ResetTimer() 127 for i := 0; i < b.N; i++ { 128 if _, err := runtime.Encode(s, &items[i%width]); err != nil { 129 b.Fatal(err) 130 } 131 } 132 b.StopTimer() 133 } 134 135 // BenchmarkEncodeCodecFromInternalProtobuf measures the cost of performing a codec encode, 136 // including conversions and any type setting. This is a "full" encode. 137 func BenchmarkEncodeCodecFromInternalProtobuf(b *testing.B) { 138 items := benchmarkItems(b) 139 width := len(items) 140 encodable := make([]api.Pod, width) 141 for i := range items { 142 if err := legacyscheme.Scheme.Convert(&items[i], &encodable[i], nil); err != nil { 143 b.Fatal(err) 144 } 145 } 146 s := protobuf.NewSerializer(nil, nil) 147 codec := legacyscheme.Codecs.EncoderForVersion(s, v1.SchemeGroupVersion) 148 b.ResetTimer() 149 for i := 0; i < b.N; i++ { 150 if _, err := runtime.Encode(codec, &encodable[i%width]); err != nil { 151 b.Fatal(err) 152 } 153 } 154 b.StopTimer() 155 } 156 157 func BenchmarkEncodeProtobufGeneratedMarshal(b *testing.B) { 158 items := benchmarkItems(b) 159 width := len(items) 160 b.ResetTimer() 161 for i := 0; i < b.N; i++ { 162 if _, err := items[i%width].Marshal(); err != nil { 163 b.Fatal(err) 164 } 165 } 166 b.StopTimer() 167 } 168 169 func BenchmarkEncodeProtobufGeneratedMarshalList10(b *testing.B) { 170 item := benchmarkItemsList(b, 10) 171 b.ResetTimer() 172 for i := 0; i < b.N; i++ { 173 if _, err := item.Marshal(); err != nil { 174 b.Fatal(err) 175 } 176 } 177 b.StopTimer() 178 } 179 180 func BenchmarkEncodeProtobufGeneratedMarshalList100(b *testing.B) { 181 item := benchmarkItemsList(b, 100) 182 b.ResetTimer() 183 for i := 0; i < b.N; i++ { 184 if _, err := item.Marshal(); err != nil { 185 b.Fatal(err) 186 } 187 } 188 b.StopTimer() 189 } 190 191 func BenchmarkEncodeProtobufGeneratedMarshalList1000(b *testing.B) { 192 item := benchmarkItemsList(b, 1000) 193 b.ResetTimer() 194 for i := 0; i < b.N; i++ { 195 if _, err := item.Marshal(); err != nil { 196 b.Fatal(err) 197 } 198 } 199 b.StopTimer() 200 } 201 202 // BenchmarkDecodeCodecToInternalProtobuf measures the cost of performing a codec decode, 203 // including conversions and any type setting. This is a "full" decode. 204 func BenchmarkDecodeCodecToInternalProtobuf(b *testing.B) { 205 items := benchmarkItems(b) 206 width := len(items) 207 s := protobuf.NewSerializer(legacyscheme.Scheme, legacyscheme.Scheme) 208 encoder := legacyscheme.Codecs.EncoderForVersion(s, v1.SchemeGroupVersion) 209 var encoded [][]byte 210 for i := range items { 211 data, err := runtime.Encode(encoder, &items[i]) 212 if err != nil { 213 b.Fatal(err) 214 } 215 encoded = append(encoded, data) 216 } 217 218 decoder := legacyscheme.Codecs.DecoderToVersion(s, api.SchemeGroupVersion) 219 b.ResetTimer() 220 for i := 0; i < b.N; i++ { 221 if _, err := runtime.Decode(decoder, encoded[i%width]); err != nil { 222 b.Fatal(err) 223 } 224 } 225 b.StopTimer() 226 } 227 228 // BenchmarkDecodeJSON provides a baseline for regular JSON decode performance 229 func BenchmarkDecodeIntoProtobuf(b *testing.B) { 230 items := benchmarkItems(b) 231 width := len(items) 232 encoded := make([][]byte, width) 233 for i := range items { 234 data, err := (&items[i]).Marshal() 235 if err != nil { 236 b.Fatal(err) 237 } 238 encoded[i] = data 239 validate := &v1.Pod{} 240 if err := proto.Unmarshal(data, validate); err != nil { 241 b.Fatalf("Failed to unmarshal %d: %v\n%#v", i, err, items[i]) 242 } 243 } 244 245 for i := 0; i < b.N; i++ { 246 obj := v1.Pod{} 247 if err := proto.Unmarshal(encoded[i%width], &obj); err != nil { 248 b.Fatal(err) 249 } 250 } 251 }