pkg/api/testing: split fuzzer between apimachinery and kube

This commit is contained in:
Dr. Stefan Schimanski 2017-01-24 20:34:00 +01:00 committed by Dr. Stefan Schimanski
parent a12c661773
commit 6b6b6c747e
11 changed files with 407 additions and 177 deletions

View File

@ -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])

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 = &parallelism
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 = &parallelism
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
}

View File

@ -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++ {

View File

@ -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

View File

@ -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])

View File

@ -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})

View File

@ -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
}

View File

@ -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))
}
}
}