diff --git a/pkg/api/conversion_test.go b/pkg/api/conversion_test.go index da651a466c9..acf79eebbed 100644 --- a/pkg/api/conversion_test.go +++ b/pkg/api/conversion_test.go @@ -21,15 +21,16 @@ import ( "math/rand" "testing" + apitesting "k8s.io/apimachinery/pkg/api/testing" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - apitesting "k8s.io/kubernetes/pkg/api/testing" + kapitesting "k8s.io/kubernetes/pkg/api/testing" ) func BenchmarkPodConversion(b *testing.B) { - apiObjectFuzzer := apitesting.FuzzerFor(nil, api.SchemeGroupVersion, rand.NewSource(benchmarkSeed)) + apiObjectFuzzer := apitesting.FuzzerFor(kapitesting.FuzzerFuncs(b), rand.NewSource(benchmarkSeed)) items := make([]api.Pod, 4) for i := range items { apiObjectFuzzer.Fuzz(&items[i]) diff --git a/pkg/api/copy_test.go b/pkg/api/copy_test.go index 5393a6d02ad..56ec756af40 100644 --- a/pkg/api/copy_test.go +++ b/pkg/api/copy_test.go @@ -22,11 +22,12 @@ import ( "reflect" "testing" + apitesting "k8s.io/apimachinery/pkg/api/testing" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - apitesting "k8s.io/kubernetes/pkg/api/testing" + kapitesting "k8s.io/kubernetes/pkg/api/testing" "github.com/google/gofuzz" ) @@ -34,7 +35,7 @@ import ( func TestDeepCopyApiObjects(t *testing.T) { for i := 0; i < *fuzzIters; i++ { for _, version := range []schema.GroupVersion{testapi.Default.InternalGroupVersion(), api.Registry.GroupOrDie(api.GroupName).GroupVersion} { - f := apitesting.FuzzerFor(t, version, rand.NewSource(rand.Int63())) + f := apitesting.FuzzerFor(kapitesting.FuzzerFuncs(t), rand.NewSource(rand.Int63())) for kind := range api.Scheme.KnownTypes(version) { doDeepCopyTest(t, version.WithKind(kind), f) } @@ -83,7 +84,7 @@ func doDeepCopyTest(t *testing.T, kind schema.GroupVersionKind, f *fuzz.Fuzzer) func TestDeepCopySingleType(t *testing.T) { for i := 0; i < *fuzzIters; i++ { for _, version := range []schema.GroupVersion{testapi.Default.InternalGroupVersion(), api.Registry.GroupOrDie(api.GroupName).GroupVersion} { - f := apitesting.FuzzerFor(t, version, rand.NewSource(rand.Int63())) + f := apitesting.FuzzerFor(kapitesting.FuzzerFuncs(t), rand.NewSource(rand.Int63())) doDeepCopyTest(t, version.WithKind("Pod"), f) } } diff --git a/pkg/api/serialization_proto_test.go b/pkg/api/serialization_proto_test.go index 37d14e486ea..f2cd0e1ed9c 100644 --- a/pkg/api/serialization_proto_test.go +++ b/pkg/api/serialization_proto_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/gogo/protobuf/proto" + apitesting "k8s.io/apimachinery/pkg/api/testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -31,7 +32,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - apitesting "k8s.io/kubernetes/pkg/api/testing" + kapitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/api/v1" _ "k8s.io/kubernetes/pkg/apis/extensions" _ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" @@ -75,7 +76,7 @@ func TestUniversalDeserializer(t *testing.T) { func TestProtobufRoundTrip(t *testing.T) { obj := &v1.Pod{} - apitesting.FuzzerFor(t, v1.SchemeGroupVersion, rand.NewSource(benchmarkSeed)).Fuzz(obj) + apitesting.FuzzerFor(kapitesting.FuzzerFuncs(t), rand.NewSource(benchmarkSeed)).Fuzz(obj) // InitContainers are turned into annotations by conversion. obj.Spec.InitContainers = nil obj.Status.InitContainerStatuses = nil @@ -96,7 +97,7 @@ func TestProtobufRoundTrip(t *testing.T) { // BenchmarkEncodeCodec measures the cost of performing a codec encode, which includes // reflection (to clear APIVersion and Kind) func BenchmarkEncodeCodecProtobuf(b *testing.B) { - items := benchmarkItems() + items := benchmarkItems(b) width := len(items) s := protobuf.NewSerializer(nil, nil, "application/arbitrary.content.type") b.ResetTimer() @@ -111,7 +112,7 @@ func BenchmarkEncodeCodecProtobuf(b *testing.B) { // BenchmarkEncodeCodecFromInternalProtobuf measures the cost of performing a codec encode, // including conversions and any type setting. This is a "full" encode. func BenchmarkEncodeCodecFromInternalProtobuf(b *testing.B) { - items := benchmarkItems() + items := benchmarkItems(b) width := len(items) encodable := make([]api.Pod, width) for i := range items { @@ -131,7 +132,7 @@ func BenchmarkEncodeCodecFromInternalProtobuf(b *testing.B) { } func BenchmarkEncodeProtobufGeneratedMarshal(b *testing.B) { - items := benchmarkItems() + items := benchmarkItems(b) width := len(items) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -145,7 +146,7 @@ func BenchmarkEncodeProtobufGeneratedMarshal(b *testing.B) { // BenchmarkDecodeCodecToInternalProtobuf measures the cost of performing a codec decode, // including conversions and any type setting. This is a "full" decode. func BenchmarkDecodeCodecToInternalProtobuf(b *testing.B) { - items := benchmarkItems() + items := benchmarkItems(b) width := len(items) s := protobuf.NewSerializer(api.Scheme, api.Scheme, "application/arbitrary.content.type") encoder := api.Codecs.EncoderForVersion(s, v1.SchemeGroupVersion) @@ -170,7 +171,7 @@ func BenchmarkDecodeCodecToInternalProtobuf(b *testing.B) { // BenchmarkDecodeJSON provides a baseline for regular JSON decode performance func BenchmarkDecodeIntoProtobuf(b *testing.B) { - items := benchmarkItems() + items := benchmarkItems(b) width := len(items) encoded := make([][]byte, width) for i := range items { diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index 29b9d989995..ac55bac72bd 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -33,6 +33,7 @@ import ( "github.com/ugorji/go/codec" "k8s.io/apimachinery/pkg/api/meta" + apitesting "k8s.io/apimachinery/pkg/api/testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" @@ -43,7 +44,7 @@ import ( "k8s.io/apimachinery/pkg/watch" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - apitesting "k8s.io/kubernetes/pkg/api/testing" + kapitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" @@ -63,7 +64,7 @@ var codecsToTest = []func(version schema.GroupVersion, item runtime.Object) (run // fuzzInternalObject fuzzes an arbitrary runtime object using the appropriate // fuzzer registered with the apitesting package. func fuzzInternalObject(t *testing.T, forVersion schema.GroupVersion, item runtime.Object, seed int64) runtime.Object { - apitesting.FuzzerFor(t, forVersion, rand.NewSource(seed)).Fuzz(item) + apitesting.FuzzerFor(kapitesting.FuzzerFuncs(t), rand.NewSource(seed)).Fuzz(item) j, err := meta.TypeAccessor(item) if err != nil { @@ -440,7 +441,7 @@ func TestUnversionedTypes(t *testing.T) { // TestObjectWatchFraming establishes that a watch event can be encoded and // decoded correctly through each of the supported RFC2046 media types. func TestObjectWatchFraming(t *testing.T) { - f := apitesting.FuzzerFor(nil, api.SchemeGroupVersion, rand.NewSource(benchmarkSeed)) + f := apitesting.FuzzerFor(kapitesting.FuzzerFuncs(t), rand.NewSource(benchmarkSeed)) secret := &api.Secret{} f.Fuzz(secret) secret.Data["binary"] = []byte{0x00, 0x10, 0x30, 0x55, 0xff, 0x00} @@ -521,8 +522,8 @@ func TestObjectWatchFraming(t *testing.T) { const benchmarkSeed = 100 -func benchmarkItems() []v1.Pod { - apiObjectFuzzer := apitesting.FuzzerFor(nil, api.SchemeGroupVersion, rand.NewSource(benchmarkSeed)) +func benchmarkItems(b *testing.B) []v1.Pod { + apiObjectFuzzer := apitesting.FuzzerFor(kapitesting.FuzzerFuncs(b), rand.NewSource(benchmarkSeed)) items := make([]v1.Pod, 10) for i := range items { var pod api.Pod @@ -540,7 +541,7 @@ func benchmarkItems() []v1.Pod { // BenchmarkEncodeCodec measures the cost of performing a codec encode, which includes // reflection (to clear APIVersion and Kind) func BenchmarkEncodeCodec(b *testing.B) { - items := benchmarkItems() + items := benchmarkItems(b) width := len(items) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -554,7 +555,7 @@ func BenchmarkEncodeCodec(b *testing.B) { // BenchmarkEncodeCodecFromInternal measures the cost of performing a codec encode, // including conversions. func BenchmarkEncodeCodecFromInternal(b *testing.B) { - items := benchmarkItems() + items := benchmarkItems(b) width := len(items) encodable := make([]api.Pod, width) for i := range items { @@ -573,7 +574,7 @@ func BenchmarkEncodeCodecFromInternal(b *testing.B) { // BenchmarkEncodeJSONMarshal provides a baseline for regular JSON encode performance func BenchmarkEncodeJSONMarshal(b *testing.B) { - items := benchmarkItems() + items := benchmarkItems(b) width := len(items) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -586,7 +587,7 @@ func BenchmarkEncodeJSONMarshal(b *testing.B) { func BenchmarkDecodeCodec(b *testing.B) { codec := testapi.Default.Codec() - items := benchmarkItems() + items := benchmarkItems(b) width := len(items) encoded := make([][]byte, width) for i := range items { @@ -608,7 +609,7 @@ func BenchmarkDecodeCodec(b *testing.B) { func BenchmarkDecodeIntoExternalCodec(b *testing.B) { codec := testapi.Default.Codec() - items := benchmarkItems() + items := benchmarkItems(b) width := len(items) encoded := make([][]byte, width) for i := range items { @@ -631,7 +632,7 @@ func BenchmarkDecodeIntoExternalCodec(b *testing.B) { func BenchmarkDecodeIntoInternalCodec(b *testing.B) { codec := testapi.Default.Codec() - items := benchmarkItems() + items := benchmarkItems(b) width := len(items) encoded := make([][]byte, width) for i := range items { @@ -655,7 +656,7 @@ func BenchmarkDecodeIntoInternalCodec(b *testing.B) { // BenchmarkDecodeJSON provides a baseline for regular JSON decode performance func BenchmarkDecodeIntoJSON(b *testing.B) { codec := testapi.Default.Codec() - items := benchmarkItems() + items := benchmarkItems(b) width := len(items) encoded := make([][]byte, width) for i := range items { @@ -679,7 +680,7 @@ func BenchmarkDecodeIntoJSON(b *testing.B) { // BenchmarkDecodeJSON provides a baseline for codecgen JSON decode performance func BenchmarkDecodeIntoJSONCodecGen(b *testing.B) { kcodec := testapi.Default.Codec() - items := benchmarkItems() + items := benchmarkItems(b) width := len(items) encoded := make([][]byte, width) for i := range items { diff --git a/pkg/api/testing/fuzzer.go b/pkg/api/testing/fuzzer.go index d73506f6c60..d20690eeabc 100644 --- a/pkg/api/testing/fuzzer.go +++ b/pkg/api/testing/fuzzer.go @@ -18,19 +18,17 @@ package testing import ( "fmt" - "math/rand" "reflect" "strconv" - "testing" "github.com/google/gofuzz" + apitesting "k8s.io/apimachinery/pkg/api/testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" @@ -44,51 +42,65 @@ import ( "k8s.io/kubernetes/pkg/util/intstr" ) -// FuzzerFor can randomly populate api objects that are destined for version. -func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz.Fuzzer { - f := fuzz.New().NilChance(.5).NumElements(0, 1) - if src != nil { - f.RandSource(src) - } - f.Funcs( - func(j *int, c fuzz.Continue) { - *j = int(c.Int31()) - }, - func(j **int, c fuzz.Continue) { - if c.RandBool() { - i := int(c.Int31()) - *j = &i +// overrideGenericFuncs override some generic fuzzer funcs from k8s.io/apiserver in order to have more realistic +// values in a Kubernetes context. +func overrideGenericFuncs(t apitesting.TestingCommon) []interface{} { + return []interface{}{ + func(j *runtime.Object, c fuzz.Continue) { + // TODO: uncomment when round trip starts from a versioned object + if true { //c.RandBool() { + *j = &runtime.Unknown{ + // We do not set TypeMeta here because it is not carried through a round trip + Raw: []byte(`{"apiVersion":"unknown.group/unknown","kind":"Something","someKey":"someValue"}`), + ContentType: runtime.ContentTypeJSON, + } } else { - *j = nil + types := []runtime.Object{&api.Pod{}, &api.ReplicationController{}} + t := types[c.Rand.Intn(len(types))] + c.Fuzz(t) + *j = t } }, + func(r *runtime.RawExtension, c fuzz.Continue) { + // Pick an arbitrary type and fuzz it + types := []runtime.Object{&api.Pod{}, &extensions.Deployment{}, &api.Service{}} + obj := types[c.Rand.Intn(len(types))] + c.Fuzz(obj) + + // Find a codec for converting the object to raw bytes. This is necessary for the + // api version and kind to be correctly set be serialization. + var codec runtime.Codec + switch obj.(type) { + case *api.Pod: + codec = testapi.Default.Codec() + case *extensions.Deployment: + codec = testapi.Extensions.Codec() + case *api.Service: + codec = testapi.Default.Codec() + default: + + t.Errorf("Failed to find codec for object type: %T", obj) + return + } + + // Convert the object to raw bytes + bytes, err := runtime.Encode(codec, obj) + if err != nil { + t.Errorf("Failed to encode object: %v", err) + return + } + + // Set the bytes field on the RawExtension + r.Raw = bytes + }, + } +} + +func coreFuncs(t apitesting.TestingCommon) []interface{} { + return []interface{}{ func(q *resource.Quantity, c fuzz.Continue) { *q = *resource.NewQuantity(c.Int63n(1000), resource.DecimalExponent) }, - func(j *runtime.TypeMeta, c fuzz.Continue) { - // We have to customize the randomization of TypeMetas because their - // APIVersion and Kind must remain blank in memory. - j.APIVersion = "" - j.Kind = "" - }, - func(j *metav1.TypeMeta, c fuzz.Continue) { - // We have to customize the randomization of TypeMetas because their - // APIVersion and Kind must remain blank in memory. - j.APIVersion = "" - j.Kind = "" - }, - func(j *metav1.ObjectMeta, c fuzz.Continue) { - j.Name = c.RandString() - j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) - j.SelfLink = c.RandString() - j.UID = types.UID(c.RandString()) - j.GenerateName = c.RandString() - - var sec, nsec int64 - c.Fuzz(&sec) - c.Fuzz(&nsec) - j.CreationTimestamp = metav1.Unix(sec, nsec).Rfc3339Copy() - }, func(j *api.ObjectReference, c fuzz.Continue) { // We have to customize the randomization of TypeMetas because their // APIVersion and Kind must remain blank in memory. @@ -99,10 +111,6 @@ func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) j.FieldPath = c.RandString() }, - func(j *metav1.ListMeta, c fuzz.Continue) { - j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) - j.SelfLink = c.RandString() - }, func(j *api.ListOptions, c fuzz.Continue) { label, _ := labels.Parse("a=b") j.LabelSelector = label @@ -158,48 +166,6 @@ func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz c.FuzzNoCustom(j) // fuzz self without calling this function again //j.TemplateRef = nil // this is required for round trip }, - func(j *extensions.DeploymentStrategy, c fuzz.Continue) { - c.FuzzNoCustom(j) // fuzz self without calling this function again - // Ensure that strategyType is one of valid values. - strategyTypes := []extensions.DeploymentStrategyType{extensions.RecreateDeploymentStrategyType, extensions.RollingUpdateDeploymentStrategyType} - j.Type = strategyTypes[c.Rand.Intn(len(strategyTypes))] - if j.Type != extensions.RollingUpdateDeploymentStrategyType { - j.RollingUpdate = nil - } else { - rollingUpdate := extensions.RollingUpdateDeployment{} - if c.RandBool() { - rollingUpdate.MaxUnavailable = intstr.FromInt(int(c.Rand.Int31())) - rollingUpdate.MaxSurge = intstr.FromInt(int(c.Rand.Int31())) - } else { - rollingUpdate.MaxSurge = intstr.FromString(fmt.Sprintf("%d%%", c.Rand.Int31())) - } - j.RollingUpdate = &rollingUpdate - } - }, - func(j *batch.JobSpec, c fuzz.Continue) { - c.FuzzNoCustom(j) // fuzz self without calling this function again - completions := int32(c.Rand.Int31()) - parallelism := int32(c.Rand.Int31()) - j.Completions = &completions - j.Parallelism = ¶llelism - if c.Rand.Int31()%2 == 0 { - j.ManualSelector = newBool(true) - } else { - j.ManualSelector = nil - } - }, - func(sj *batch.CronJobSpec, c fuzz.Continue) { - c.FuzzNoCustom(sj) - suspend := c.RandBool() - sj.Suspend = &suspend - sds := int64(c.RandUint64()) - sj.StartingDeadlineSeconds = &sds - sj.Schedule = c.RandString() - }, - func(cp *batch.ConcurrencyPolicy, c fuzz.Continue) { - policies := []batch.ConcurrencyPolicy{batch.AllowConcurrent, batch.ForbidConcurrent, batch.ReplaceConcurrent} - *cp = policies[c.Rand.Intn(len(policies))] - }, func(j *api.List, c fuzz.Continue) { c.FuzzNoCustom(j) // fuzz self without calling this function again // TODO: uncomment when round trip starts from a versioned object @@ -207,21 +173,6 @@ func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz j.Items = []runtime.Object{} } }, - func(j *runtime.Object, c fuzz.Continue) { - // TODO: uncomment when round trip starts from a versioned object - if true { //c.RandBool() { - *j = &runtime.Unknown{ - // We do not set TypeMeta here because it is not carried through a round trip - Raw: []byte(`{"apiVersion":"unknown.group/unknown","kind":"Something","someKey":"someValue"}`), - ContentType: runtime.ContentTypeJSON, - } - } else { - types := []runtime.Object{&api.Pod{}, &api.ReplicationController{}} - t := types[c.Rand.Intn(len(types))] - c.Fuzz(t) - *j = t - } - }, func(q *api.ResourceRequirements, c fuzz.Continue) { randomQuantity := func() resource.Quantity { var q resource.Quantity @@ -513,12 +464,28 @@ func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz c.FuzzNoCustom(s) s.Allocatable = s.Capacity }, - func(s *autoscaling.HorizontalPodAutoscalerSpec, c fuzz.Continue) { - c.FuzzNoCustom(s) // fuzz self without calling this function again - minReplicas := int32(c.Rand.Int31()) - s.MinReplicas = &minReplicas - targetCpu := int32(c.RandUint64()) - s.TargetCPUUtilizationPercentage = &targetCpu + } +} + +func extensionFuncs(t apitesting.TestingCommon) []interface{} { + return []interface{}{ + func(j *extensions.DeploymentStrategy, c fuzz.Continue) { + c.FuzzNoCustom(j) // fuzz self without calling this function again + // Ensure that strategyType is one of valid values. + strategyTypes := []extensions.DeploymentStrategyType{extensions.RecreateDeploymentStrategyType, extensions.RollingUpdateDeploymentStrategyType} + j.Type = strategyTypes[c.Rand.Intn(len(strategyTypes))] + if j.Type != extensions.RollingUpdateDeploymentStrategyType { + j.RollingUpdate = nil + } else { + rollingUpdate := extensions.RollingUpdateDeployment{} + if c.RandBool() { + rollingUpdate.MaxUnavailable = intstr.FromInt(int(c.Rand.Int31())) + rollingUpdate.MaxSurge = intstr.FromInt(int(c.Rand.Int31())) + } else { + rollingUpdate.MaxSurge = intstr.FromString(fmt.Sprintf("%d%%", c.Rand.Int31())) + } + j.RollingUpdate = &rollingUpdate + } }, func(psp *extensions.PodSecurityPolicySpec, c fuzz.Continue) { c.FuzzNoCustom(psp) // fuzz self without calling this function again @@ -546,6 +513,52 @@ func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz } } }, + } +} + +func batchFuncs(t apitesting.TestingCommon) []interface{} { + return []interface{}{ + func(j *batch.JobSpec, c fuzz.Continue) { + c.FuzzNoCustom(j) // fuzz self without calling this function again + completions := int32(c.Rand.Int31()) + parallelism := int32(c.Rand.Int31()) + j.Completions = &completions + j.Parallelism = ¶llelism + if c.Rand.Int31()%2 == 0 { + j.ManualSelector = newBool(true) + } else { + j.ManualSelector = nil + } + }, + func(sj *batch.CronJobSpec, c fuzz.Continue) { + c.FuzzNoCustom(sj) + suspend := c.RandBool() + sj.Suspend = &suspend + sds := int64(c.RandUint64()) + sj.StartingDeadlineSeconds = &sds + sj.Schedule = c.RandString() + }, + func(cp *batch.ConcurrencyPolicy, c fuzz.Continue) { + policies := []batch.ConcurrencyPolicy{batch.AllowConcurrent, batch.ForbidConcurrent, batch.ReplaceConcurrent} + *cp = policies[c.Rand.Intn(len(policies))] + }, + } +} + +func autoscalingFuncs(t apitesting.TestingCommon) []interface{} { + return []interface{}{ + func(s *autoscaling.HorizontalPodAutoscalerSpec, c fuzz.Continue) { + c.FuzzNoCustom(s) // fuzz self without calling this function again + minReplicas := int32(c.Rand.Int31()) + s.MinReplicas = &minReplicas + targetCpu := int32(c.RandUint64()) + s.TargetCPUUtilizationPercentage = &targetCpu + }, + } +} + +func rbacFuncs(t apitesting.TestingCommon) []interface{} { + return []interface{}{ func(r *rbac.RoleRef, c fuzz.Continue) { c.FuzzNoCustom(r) // fuzz self without calling this function again @@ -554,37 +567,11 @@ func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz r.APIGroup = rbac.GroupName } }, - func(r *runtime.RawExtension, c fuzz.Continue) { - // Pick an arbitrary type and fuzz it - types := []runtime.Object{&api.Pod{}, &extensions.Deployment{}, &api.Service{}} - obj := types[c.Rand.Intn(len(types))] - c.Fuzz(obj) + } +} - // Find a codec for converting the object to raw bytes. This is necessary for the - // api version and kind to be correctly set be serialization. - var codec runtime.Codec - switch obj.(type) { - case *api.Pod: - codec = testapi.Default.Codec() - case *extensions.Deployment: - codec = testapi.Extensions.Codec() - case *api.Service: - codec = testapi.Default.Codec() - default: - t.Errorf("Failed to find codec for object type: %T", obj) - return - } - - // Convert the object to raw bytes - bytes, err := runtime.Encode(codec, obj) - if err != nil { - t.Errorf("Failed to encode object: %v", err) - return - } - - // Set the bytes field on the RawExtension - r.Raw = bytes - }, +func kubeAdmFuncs(t apitesting.TestingCommon) []interface{} { + return []interface{}{ func(obj *kubeadm.MasterConfiguration, c fuzz.Continue) { c.FuzzNoCustom(obj) obj.KubernetesVersion = "v10" @@ -594,16 +581,40 @@ func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz obj.AuthorizationMode = "foo" obj.Discovery.Token = &kubeadm.TokenDiscovery{} }, + } +} + +func policyFuncs(t apitesting.TestingCommon) []interface{} { + return []interface{}{ func(s *policy.PodDisruptionBudgetStatus, c fuzz.Continue) { c.FuzzNoCustom(s) // fuzz self without calling this function again s.PodDisruptionsAllowed = int32(c.Rand.Intn(2)) }, + } +} + +func certificateFuncs(t apitesting.TestingCommon) []interface{} { + return []interface{}{ func(obj *certificates.CertificateSigningRequestSpec, c fuzz.Continue) { c.FuzzNoCustom(obj) // fuzz self without calling this function again obj.Usages = []certificates.KeyUsage{certificates.UsageKeyEncipherment} }, + } +} + +func FuzzerFuncs(t apitesting.TestingCommon) []interface{} { + return mergeFuncLists(t, + apitesting.GenericFuzzerFuncs(t), + overrideGenericFuncs(t), + coreFuncs(t), + extensionFuncs(t), + batchFuncs(t), + autoscalingFuncs(t), + rbacFuncs(t), + kubeAdmFuncs(t), + policyFuncs(t), + certificateFuncs(t), ) - return f } func newBool(val bool) *bool { @@ -611,3 +622,25 @@ func newBool(val bool) *bool { *p = val return p } + +// mergeFuncLists will merge the given funcLists, overriding early funcs with later ones if there first +// argument has the same type. +func mergeFuncLists(t apitesting.TestingCommon, funcLists ...[]interface{}) []interface{} { + funcMap := map[string]interface{}{} + for _, list := range funcLists { + for _, f := range list { + fT := reflect.TypeOf(f) + if fT.Kind() != reflect.Func || fT.NumIn() != 2 { + t.Errorf("Fuzzer func with invalid type: %v", fT) + continue + } + funcMap[fT.In(0).String()] = f + } + } + + result := []interface{}{} + for _, f := range funcMap { + result = append(result, f) + } + return result +} diff --git a/pkg/api/unstructured_test.go b/pkg/api/unstructured_test.go index 17ea662183b..3d2e37fa466 100644 --- a/pkg/api/unstructured_test.go +++ b/pkg/api/unstructured_test.go @@ -21,9 +21,10 @@ import ( "reflect" "testing" + apitesting "k8s.io/apimachinery/pkg/api/testing" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - apitesting "k8s.io/kubernetes/pkg/api/testing" + kapitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/apimachinery/pkg/util/diff" @@ -41,7 +42,7 @@ func doRoundTrip(t *testing.T, group testapi.TestGroup, kind string) { t.Fatalf("Couldn't create internal object %v: %v", kind, err) } seed := rand.Int63() - apitesting.FuzzerFor(t, group.InternalGroupVersion(), rand.NewSource(seed)). + apitesting.FuzzerFor(kapitesting.FuzzerFuncs(t), rand.NewSource(seed)). // We are explicitly overwriting custom fuzzing functions, to ensure // that InitContainers and their statuses are not generated. This is // because in thise test we are simply doing json operations, in which @@ -155,7 +156,7 @@ func BenchmarkToFromUnstructured(b *testing.B) { */ func BenchmarkToFromUnstructuredViaJSON(b *testing.B) { - items := benchmarkItems() + items := benchmarkItems(b) size := len(items) b.ResetTimer() for i := 0; i < b.N; i++ { diff --git a/pkg/api/validation/schema_test.go b/pkg/api/validation/schema_test.go index 760fb2c380e..736e7312104 100644 --- a/pkg/api/validation/schema_test.go +++ b/pkg/api/validation/schema_test.go @@ -24,11 +24,12 @@ import ( "strings" "testing" + apitesting "k8s.io/apimachinery/pkg/api/testing" "k8s.io/apimachinery/pkg/runtime" k8syaml "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - apitesting "k8s.io/kubernetes/pkg/api/testing" + kapitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" @@ -148,7 +149,7 @@ func TestValidateOk(t *testing.T) { } seed := rand.Int63() - apiObjectFuzzer := apitesting.FuzzerFor(nil, testapi.Default.InternalGroupVersion(), rand.NewSource(seed)) + apiObjectFuzzer := apitesting.FuzzerFor(kapitesting.FuzzerFuncs(t), rand.NewSource(seed)) for i := 0; i < 5; i++ { for _, test := range tests { testObj := test.obj diff --git a/pkg/genericapiserver/endpoints/apiserver_test.go b/pkg/genericapiserver/endpoints/apiserver_test.go index b0869dda9d6..650ca3085ce 100644 --- a/pkg/genericapiserver/endpoints/apiserver_test.go +++ b/pkg/genericapiserver/endpoints/apiserver_test.go @@ -36,6 +36,7 @@ import ( apierrs "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + apitesting "k8s.io/apimachinery/pkg/api/testing" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" @@ -48,7 +49,7 @@ import ( "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/kubernetes/pkg/api" - apitesting "k8s.io/kubernetes/pkg/api/testing" + kapitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/api/v1" genericapifilters "k8s.io/kubernetes/pkg/genericapiserver/endpoints/filters" "k8s.io/kubernetes/pkg/genericapiserver/endpoints/handlers/responsewriters" @@ -3338,7 +3339,7 @@ func readBodyOrDie(r io.Reader) []byte { // BenchmarkUpdateProtobuf measures the cost of processing an update on the server in proto func BenchmarkUpdateProtobuf(b *testing.B) { - items := benchmarkItems() + items := benchmarkItems(b) simpleStorage := &SimpleRESTStorage{} handler := handle(map[string]rest.Storage{"simples": simpleStorage}) @@ -3394,8 +3395,8 @@ func newTestRequestInfoResolver() *request.RequestInfoFactory { const benchmarkSeed = 100 -func benchmarkItems() []api.Pod { - apiObjectFuzzer := apitesting.FuzzerFor(nil, api.SchemeGroupVersion, rand.NewSource(benchmarkSeed)) +func benchmarkItems(b *testing.B) []api.Pod { + apiObjectFuzzer := apitesting.FuzzerFor(kapitesting.FuzzerFuncs(b), rand.NewSource(benchmarkSeed)) items := make([]api.Pod, 3) for i := range items { apiObjectFuzzer.Fuzz(&items[i]) diff --git a/pkg/genericapiserver/endpoints/watch_test.go b/pkg/genericapiserver/endpoints/watch_test.go index 3a4643188dc..d94d287cf81 100644 --- a/pkg/genericapiserver/endpoints/watch_test.go +++ b/pkg/genericapiserver/endpoints/watch_test.go @@ -631,7 +631,7 @@ func TestWatchHTTPTimeout(t *testing.T) { // BenchmarkWatchHTTP measures the cost of serving a watch. func BenchmarkWatchHTTP(b *testing.B) { - items := benchmarkItems() + items := benchmarkItems(b) simpleStorage := &SimpleRESTStorage{} handler := handle(map[string]rest.Storage{"simples": simpleStorage}) @@ -678,7 +678,7 @@ func BenchmarkWatchHTTP(b *testing.B) { // BenchmarkWatchWebsocket measures the cost of serving a watch. func BenchmarkWatchWebsocket(b *testing.B) { - items := benchmarkItems() + items := benchmarkItems(b) simpleStorage := &SimpleRESTStorage{} handler := handle(map[string]rest.Storage{"simples": simpleStorage}) @@ -718,7 +718,7 @@ func BenchmarkWatchWebsocket(b *testing.B) { // BenchmarkWatchProtobuf measures the cost of serving a watch. func BenchmarkWatchProtobuf(b *testing.B) { - items := benchmarkItems() + items := benchmarkItems(b) simpleStorage := &SimpleRESTStorage{} handler := handle(map[string]rest.Storage{"simples": simpleStorage}) diff --git a/staging/src/k8s.io/apimachinery/pkg/api/testing/fuzzer.go b/staging/src/k8s.io/apimachinery/pkg/api/testing/fuzzer.go new file mode 100644 index 00000000000..001f1dfa09c --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/api/testing/fuzzer.go @@ -0,0 +1,133 @@ +/* +Copyright 2017 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 ( + "strconv" + "math/rand" + "testing" + + "github.com/google/gofuzz" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" +) + +func GenericFuzzerFuncs(t TestingCommon) []interface{} { + return []interface{}{ + func(j *int, c fuzz.Continue) { + *j = int(c.Int31()) + }, + func(j **int, c fuzz.Continue) { + if c.RandBool() { + i := int(c.Int31()) + *j = &i + } else { + *j = nil + } + }, + func(j *runtime.TypeMeta, c fuzz.Continue) { + // We have to customize the randomization of TypeMetas because their + // APIVersion and Kind must remain blank in memory. + j.APIVersion = "" + j.Kind = "" + }, + func(j *metav1.TypeMeta, c fuzz.Continue) { + // We have to customize the randomization of TypeMetas because their + // APIVersion and Kind must remain blank in memory. + j.APIVersion = "" + j.Kind = "" + }, + func(j *metav1.ObjectMeta, c fuzz.Continue) { + j.Name = c.RandString() + j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) + j.SelfLink = c.RandString() + j.UID = types.UID(c.RandString()) + j.GenerateName = c.RandString() + + var sec, nsec int64 + c.Fuzz(&sec) + c.Fuzz(&nsec) + j.CreationTimestamp = metav1.Unix(sec, nsec).Rfc3339Copy() + }, + func(j *metav1.ListMeta, c fuzz.Continue) { + j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) + j.SelfLink = c.RandString() + }, + func(j *runtime.Object, c fuzz.Continue) { + // TODO: uncomment when round trip starts from a versioned object + if true { //c.RandBool() { + *j = &runtime.Unknown{ + // We do not set TypeMeta here because it is not carried through a round trip + Raw: []byte(`{"apiVersion":"unknown.group/unknown","kind":"Something","someKey":"someValue"}`), + ContentType: runtime.ContentTypeJSON, + } + } else { + types := []runtime.Object{&metav1.Status{}, &metav1.APIGroup{}} + t := types[c.Rand.Intn(len(types))] + c.Fuzz(t) + *j = t + } + }, + func(r *runtime.RawExtension, c fuzz.Continue) { + // Pick an arbitrary type and fuzz it + types := []runtime.Object{&metav1.Status{}, &metav1.APIGroup{}} + obj := types[c.Rand.Intn(len(types))] + c.Fuzz(obj) + + // Find a codec for converting the object to raw bytes. This is necessary for the + // api version and kind to be correctly set be serialization. + var codec = Codec(metav1.SchemeGroupVersion) + + // Convert the object to raw bytes + bytes, err := runtime.Encode(codec, obj) + if err != nil { + t.Errorf("Failed to encode object: %v", err) + return + } + + // Set the bytes field on the RawExtension + r.Raw = bytes + }, + } +} + +// TestingCommon abstracts testing.T and testing.B +type TestingCommon interface { + Log(args ...interface{}) + Logf(format string, args ...interface{}) + Error(args ...interface{}) + Errorf(format string, args ...interface{}) + Fatal(args ...interface{}) + Fatalf(format string, args ...interface{}) +} + +var ( + _ TestingCommon = &testing.T{} + _ TestingCommon = &testing.B{} +) + +// FuzzerFor can randomly populate api objects that are destined for version. +func FuzzerFor(funcs []interface{}, src rand.Source) *fuzz.Fuzzer { + f := fuzz.New().NilChance(.5).NumElements(0, 1) + if src != nil { + f.RandSource(src) + } + f.Funcs(funcs...) + return f +} diff --git a/staging/src/k8s.io/apimachinery/pkg/api/testing/register.go b/staging/src/k8s.io/apimachinery/pkg/api/testing/register.go new file mode 100644 index 00000000000..2809f68de60 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/api/testing/register.go @@ -0,0 +1,57 @@ +/* +Copyright 2017 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 ( + "os" + "mime" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var scheme = runtime.NewScheme() +var codecs = runtimeserializer.NewCodecFactory(scheme) +var serializer runtime.SerializerInfo + +// Codec returns the codec for the API version to test against, as set by the +// KUBE_TEST_API_TYPE env var. +func Codec(gvs ...schema.GroupVersion) runtime.Codec { + if serializer.Serializer == nil { + return codecs.LegacyCodec(gvs...) + } + return codecs.CodecForVersions(serializer.Serializer, codecs.UniversalDeserializer(), schema.GroupVersions(gvs), nil) +} + +func init() { + metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion) + + if apiMediaType := os.Getenv("KUBE_TEST_API_TYPE"); len(apiMediaType) > 0 { + var ok bool + mediaType, _, err := mime.ParseMediaType(apiMediaType) + if err != nil { + panic(err) + } + serializer, ok = runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType) + if !ok { + panic(fmt.Sprintf("no serializer for %s", apiMediaType)) + } + } +}