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  }