mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-11-06 05:17:08 +00:00
322 lines
11 KiB
Go
322 lines
11 KiB
Go
/*
|
|
Copyright 2015 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package testing
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
|
|
"k8s.io/apimachinery/pkg/api/apitesting/roundtrip"
|
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
|
api "k8s.io/kubernetes/pkg/apis/core"
|
|
)
|
|
|
|
func BenchmarkPodConversion(b *testing.B) {
|
|
apiObjectFuzzer := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), legacyscheme.Codecs)
|
|
items := make([]api.Pod, 4)
|
|
for i := range items {
|
|
apiObjectFuzzer.Fill(&items[i])
|
|
items[i].Spec.InitContainers = nil
|
|
items[i].Status.InitContainerStatuses = nil
|
|
}
|
|
|
|
// add a fixed item
|
|
items = append(items, benchmarkPod)
|
|
width := len(items)
|
|
|
|
scheme := legacyscheme.Scheme
|
|
for i := 0; i < b.N; i++ {
|
|
pod := &items[i%width]
|
|
versionedObj, err := scheme.UnsafeConvertToVersion(pod, schema.GroupVersion{Group: "", Version: "v1"})
|
|
if err != nil {
|
|
b.Fatalf("Conversion error: %v", err)
|
|
}
|
|
if _, err = scheme.UnsafeConvertToVersion(versionedObj, schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}); err != nil {
|
|
b.Fatalf("Conversion error: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkNodeConversion(b *testing.B) {
|
|
data, err := os.ReadFile("node_example.json")
|
|
if err != nil {
|
|
b.Fatalf("Unexpected error while reading file: %v", err)
|
|
}
|
|
var node api.Node
|
|
if err := runtime.DecodeInto(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), data, &node); err != nil {
|
|
b.Fatalf("Unexpected error decoding node: %v", err)
|
|
}
|
|
|
|
scheme := legacyscheme.Scheme
|
|
var result *api.Node
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
versionedObj, err := scheme.UnsafeConvertToVersion(&node, schema.GroupVersion{Group: "", Version: "v1"})
|
|
if err != nil {
|
|
b.Fatalf("Conversion error: %v", err)
|
|
}
|
|
obj, err := scheme.UnsafeConvertToVersion(versionedObj, schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal})
|
|
if err != nil {
|
|
b.Fatalf("Conversion error: %v", err)
|
|
}
|
|
result = obj.(*api.Node)
|
|
}
|
|
b.StopTimer()
|
|
if !apiequality.Semantic.DeepDerivative(node, *result) {
|
|
b.Fatalf("Incorrect conversion: %s", cmp.Diff(node, *result))
|
|
}
|
|
}
|
|
|
|
func BenchmarkReplicationControllerConversion(b *testing.B) {
|
|
data, err := os.ReadFile("replication_controller_example.json")
|
|
if err != nil {
|
|
b.Fatalf("Unexpected error while reading file: %v", err)
|
|
}
|
|
var replicationController api.ReplicationController
|
|
if err := runtime.DecodeInto(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), data, &replicationController); err != nil {
|
|
b.Fatalf("Unexpected error decoding node: %v", err)
|
|
}
|
|
|
|
scheme := legacyscheme.Scheme
|
|
var result *api.ReplicationController
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
versionedObj, err := scheme.UnsafeConvertToVersion(&replicationController, schema.GroupVersion{Group: "", Version: "v1"})
|
|
if err != nil {
|
|
b.Fatalf("Conversion error: %v", err)
|
|
}
|
|
obj, err := scheme.UnsafeConvertToVersion(versionedObj, schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal})
|
|
if err != nil {
|
|
b.Fatalf("Conversion error: %v", err)
|
|
}
|
|
result = obj.(*api.ReplicationController)
|
|
}
|
|
b.StopTimer()
|
|
if !apiequality.Semantic.DeepDerivative(replicationController, *result) {
|
|
b.Fatalf("Incorrect conversion: expected %v, got %v", replicationController, *result)
|
|
}
|
|
}
|
|
|
|
// checkMemoryIdentical recursively checks if two reflect.Values are memory identical
|
|
// and returns a list of differences.
|
|
//
|
|
// NOTE: this does not support recursive types. If the types have recursive fields,
|
|
// this function will not terminate.
|
|
func checkMemoryIdentical(path string, a, b reflect.Value) []string {
|
|
if a.Kind() != b.Kind() {
|
|
return []string{fmt.Sprintf("%s: kind mismatch: %s vs %s", path, a.Kind(), b.Kind())}
|
|
}
|
|
var diffs []string
|
|
switch a.Kind() {
|
|
case reflect.Struct:
|
|
if a.Type().Size() != b.Type().Size() {
|
|
return []string{fmt.Sprintf("%s: struct size mismatch: %d vs %d", path, a.Type().Size(), b.Type().Size())}
|
|
}
|
|
if a.NumField() != b.NumField() {
|
|
return []string{fmt.Sprintf("%s: field count mismatch: %d vs %d", path, a.NumField(), b.NumField())}
|
|
}
|
|
for i := 0; i < a.NumField(); i++ {
|
|
aTypeField := a.Type().Field(i)
|
|
bTypeField := b.Type().Field(i)
|
|
if aTypeField.Name != bTypeField.Name {
|
|
diffs = append(diffs, fmt.Sprintf("%s: field name mismatch: %s vs %s", path, aTypeField.Name, bTypeField.Name))
|
|
}
|
|
if aTypeField.Offset != bTypeField.Offset {
|
|
diffs = append(diffs, fmt.Sprintf("%s.%s: field offset mismatch: %d vs %d", path, aTypeField.Name, aTypeField.Offset, bTypeField.Offset))
|
|
}
|
|
diffs = append(diffs, checkMemoryIdentical(path+"."+aTypeField.Name, a.Field(i), b.Field(i))...)
|
|
}
|
|
case reflect.Pointer, reflect.Map, reflect.Slice:
|
|
if a.IsNil() != b.IsNil() {
|
|
return []string{fmt.Sprintf("%s: nil mismatch: %v vs %v", path, a.IsNil(), b.IsNil())}
|
|
}
|
|
if !a.IsNil() && a.UnsafePointer() != b.UnsafePointer() {
|
|
return []string{fmt.Sprintf("%s: nilable type (%s) was copied", path, a.Kind())}
|
|
}
|
|
case reflect.Interface:
|
|
if a.IsNil() != b.IsNil() {
|
|
return []string{fmt.Sprintf("%s: nil interface mismatch: %v vs %v", path, a.IsNil(), b.IsNil())}
|
|
}
|
|
if !a.IsNil() {
|
|
diffs = append(diffs, checkMemoryIdentical(path, a.Elem(), b.Elem())...)
|
|
}
|
|
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
|
reflect.Float32, reflect.Float64, reflect.String:
|
|
// Assume scalars are copied by value
|
|
default:
|
|
diffs = append(diffs, fmt.Sprintf("%s: unexpected kind: %v", path, a.Kind()))
|
|
}
|
|
return diffs
|
|
}
|
|
|
|
func checkVersionMemoryIdentical(t *testing.T, scheme *runtime.Scheme, fuzzerFiller interface{ Fill(interface{}) }, internalGVK, extGVK schema.GroupVersionKind) []string {
|
|
var diffs []string
|
|
|
|
extIn, err := scheme.New(extGVK)
|
|
if err != nil {
|
|
t.Fatalf("failed to create %v: %v", extGVK, err)
|
|
}
|
|
fuzzerFiller.Fill(extIn)
|
|
intOut, err := scheme.New(internalGVK)
|
|
if err != nil {
|
|
t.Fatalf("failed to create %v: %v", internalGVK, err)
|
|
}
|
|
if err := scheme.Convert(extIn, intOut, nil); err != nil {
|
|
diffs = append(diffs, fmt.Sprintf("%s->internal convert error: %v", extGVK.Version, err))
|
|
} else {
|
|
diffs = append(diffs, checkMemoryIdentical(extGVK.Version+"->internal", reflect.ValueOf(extIn).Elem(), reflect.ValueOf(intOut).Elem())...)
|
|
}
|
|
|
|
intIn, err := scheme.New(internalGVK)
|
|
if err != nil {
|
|
t.Fatalf("failed to create %v: %v", internalGVK, err)
|
|
}
|
|
fuzzerFiller.Fill(intIn)
|
|
extOut, err := scheme.New(extGVK)
|
|
if err != nil {
|
|
t.Fatalf("failed to create %v: %v", extGVK, err)
|
|
}
|
|
if err := scheme.Convert(intIn, extOut, nil); err != nil {
|
|
diffs = append(diffs, fmt.Sprintf("internal->%s convert error: %v", extGVK.Version, err))
|
|
} else {
|
|
diffs = append(diffs, checkMemoryIdentical("internal->"+extGVK.Version, reflect.ValueOf(intIn).Elem(), reflect.ValueOf(extOut).Elem())...)
|
|
}
|
|
|
|
return diffs
|
|
}
|
|
|
|
// TestMemoryIdenticalConversion ensures that all internal API types have memory-identical
|
|
// conversion (including their corresponding List type, if any) with their highest-priority
|
|
// external version, unless explicitly exempted.
|
|
func TestMemoryIdenticalConversion(t *testing.T) {
|
|
exempt := sets.New[string](
|
|
// Remaining APIs to migrate to memory-identical internal types:
|
|
"AdmissionReview.admission.k8s.io",
|
|
"DaemonSet.apps",
|
|
"Deployment.apps",
|
|
"Event.events.k8s.io",
|
|
"HorizontalPodAutoscaler.autoscaling",
|
|
"MutatingWebhookConfiguration.admissionregistration.k8s.io",
|
|
"Node",
|
|
"PodCertificateRequest.certificates.k8s.io",
|
|
"PriorityLevelConfiguration.flowcontrol.apiserver.k8s.io",
|
|
"ReplicaSet.apps",
|
|
"Secret",
|
|
"StatefulSet.apps",
|
|
"ValidatingWebhookConfiguration.admissionregistration.k8s.io",
|
|
|
|
// Legacy unserved groups/types that share internal hub types with newer groups:
|
|
"DaemonSet.extensions",
|
|
"Deployment.extensions",
|
|
"Ingress.extensions",
|
|
"NetworkPolicy.extensions",
|
|
"ReplicaSet.extensions",
|
|
"Scale.apps",
|
|
"Scale.extensions",
|
|
|
|
// These types are not considered memory-identical right now by conversion-gen, although they are,
|
|
// as we need to define Convert_v1_ConditionsAwareDecision_To_authorization_ConditionsAwareDecision (and vice versa)
|
|
// function to make ConditionsAwareDecision possible to vendor.
|
|
// TODO: Remove this when https://github.com/kubernetes/kubernetes/issues/142345 is fixed
|
|
"SubjectAccessReview.authorization.k8s.io",
|
|
"SelfSubjectAccessReview.authorization.k8s.io",
|
|
"LocalSubjectAccessReview.authorization.k8s.io",
|
|
// Embeds admission.AdmissionRequest, whose internal runtime.Object fields are converted from runtime.RawExtension:
|
|
"AuthorizationConditionsReview.authorization.k8s.io",
|
|
|
|
// Generic meta-list type (metainternalversion.List vs metav1.List):
|
|
"List",
|
|
)
|
|
|
|
scheme := legacyscheme.Scheme
|
|
f := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(1), legacyscheme.Codecs).NilChance(0).NumElements(1, 1)
|
|
|
|
allKnown := scheme.AllKnownTypes()
|
|
var internalGVKs []schema.GroupVersionKind
|
|
for gvk := range allKnown {
|
|
if gvk.Version != runtime.APIVersionInternal {
|
|
continue
|
|
}
|
|
if roundtrip.GlobalNonRoundTrippableTypes().Has(gvk.Kind) {
|
|
continue
|
|
}
|
|
if baseKind, isList := strings.CutSuffix(gvk.Kind, "List"); isList && baseKind != "" {
|
|
if _, hasBase := allKnown[gvk.GroupVersion().WithKind(baseKind)]; hasBase {
|
|
// Tested alongside its singular resource Kind below.
|
|
continue
|
|
}
|
|
}
|
|
internalGVKs = append(internalGVKs, gvk)
|
|
}
|
|
sort.Slice(internalGVKs, func(i, j int) bool {
|
|
return internalGVKs[i].GroupKind().String() < internalGVKs[j].GroupKind().String()
|
|
})
|
|
|
|
for _, internalGVK := range internalGVKs {
|
|
var extGVK schema.GroupVersionKind
|
|
for _, gv := range scheme.PrioritizedVersionsForGroup(internalGVK.Group) {
|
|
candidate := gv.WithKind(internalGVK.Kind)
|
|
if _, ok := allKnown[candidate]; ok {
|
|
extGVK = candidate
|
|
break
|
|
}
|
|
}
|
|
if extGVK.Empty() {
|
|
continue
|
|
}
|
|
|
|
gkName := internalGVK.GroupKind().String()
|
|
t.Run(gkName, func(t *testing.T) {
|
|
diffs := checkVersionMemoryIdentical(t, scheme, f, internalGVK, extGVK)
|
|
|
|
internalListGVK := internalGVK.GroupVersion().WithKind(internalGVK.Kind + "List")
|
|
extListGVK := extGVK.GroupVersion().WithKind(extGVK.Kind + "List")
|
|
if _, hasIntList := allKnown[internalListGVK]; hasIntList {
|
|
if _, hasExtList := allKnown[extListGVK]; hasExtList {
|
|
listDiffs := checkVersionMemoryIdentical(t, scheme, f, internalListGVK, extListGVK)
|
|
for _, d := range listDiffs {
|
|
diffs = append(diffs, "List."+d)
|
|
}
|
|
}
|
|
}
|
|
|
|
conforms := len(diffs) == 0
|
|
if exempt.Has(gkName) {
|
|
if conforms {
|
|
t.Errorf("%s: unexpectedly has memory-identical conversion with %s. Remove it from the exempt list.", gkName, extGVK.Version)
|
|
}
|
|
} else if !conforms {
|
|
t.Errorf("%s: does not have memory-identical conversion with %s:\n %s", gkName, extGVK.Version, strings.Join(diffs, "\n "))
|
|
}
|
|
})
|
|
}
|
|
}
|