pkg/api: move *_test.go -> pkg/api/testing
This commit is contained in:
169
pkg/api/testing/backward_compatibility_test.go
Normal file
169
pkg/api/testing/backward_compatibility_test.go
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testing/compat"
|
||||
"k8s.io/kubernetes/pkg/api/validation"
|
||||
|
||||
_ "k8s.io/kubernetes/pkg/api/install"
|
||||
)
|
||||
|
||||
func TestCompatibility_v1_PodSecurityContext(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input string
|
||||
expectedKeys map[string]string
|
||||
absentKeys []string
|
||||
}{
|
||||
{
|
||||
name: "hostNetwork = true",
|
||||
input: `
|
||||
{
|
||||
"kind":"Pod",
|
||||
"apiVersion":"v1",
|
||||
"metadata":{"name":"my-pod-name", "namespace":"my-pod-namespace"},
|
||||
"spec": {
|
||||
"hostNetwork": true,
|
||||
"containers":[{
|
||||
"name":"a",
|
||||
"image":"my-container-image"
|
||||
}]
|
||||
}
|
||||
}
|
||||
`,
|
||||
expectedKeys: map[string]string{
|
||||
"spec.hostNetwork": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hostNetwork = false",
|
||||
input: `
|
||||
{
|
||||
"kind":"Pod",
|
||||
"apiVersion":"v1",
|
||||
"metadata":{"name":"my-pod-name", "namespace":"my-pod-namespace"},
|
||||
"spec": {
|
||||
"hostNetwork": false,
|
||||
"containers":[{
|
||||
"name":"a",
|
||||
"image":"my-container-image"
|
||||
}]
|
||||
}
|
||||
}
|
||||
`,
|
||||
absentKeys: []string{
|
||||
"spec.hostNetwork",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hostIPC = true",
|
||||
input: `
|
||||
{
|
||||
"kind":"Pod",
|
||||
"apiVersion":"v1",
|
||||
"metadata":{"name":"my-pod-name", "namespace":"my-pod-namespace"},
|
||||
"spec": {
|
||||
"hostIPC": true,
|
||||
"containers":[{
|
||||
"name":"a",
|
||||
"image":"my-container-image"
|
||||
}]
|
||||
}
|
||||
}
|
||||
`,
|
||||
expectedKeys: map[string]string{
|
||||
"spec.hostIPC": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hostIPC = false",
|
||||
input: `
|
||||
{
|
||||
"kind":"Pod",
|
||||
"apiVersion":"v1",
|
||||
"metadata":{"name":"my-pod-name", "namespace":"my-pod-namespace"},
|
||||
"spec": {
|
||||
"hostIPC": false,
|
||||
"containers":[{
|
||||
"name":"a",
|
||||
"image":"my-container-image"
|
||||
}]
|
||||
}
|
||||
}
|
||||
`,
|
||||
absentKeys: []string{
|
||||
"spec.hostIPC",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hostPID = true",
|
||||
input: `
|
||||
{
|
||||
"kind":"Pod",
|
||||
"apiVersion":"v1",
|
||||
"metadata":{"name":"my-pod-name", "namespace":"my-pod-namespace"},
|
||||
"spec": {
|
||||
"hostPID": true,
|
||||
"containers":[{
|
||||
"name":"a",
|
||||
"image":"my-container-image"
|
||||
}]
|
||||
}
|
||||
}
|
||||
`,
|
||||
expectedKeys: map[string]string{
|
||||
"spec.hostPID": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hostPID = false",
|
||||
input: `
|
||||
{
|
||||
"kind":"Pod",
|
||||
"apiVersion":"v1",
|
||||
"metadata":{"name":"my-pod-name", "namespace":"my-pod-namespace"},
|
||||
"spec": {
|
||||
"hostPID": false,
|
||||
"containers":[{
|
||||
"name":"a",
|
||||
"image":"my-container-image"
|
||||
}]
|
||||
}
|
||||
}
|
||||
`,
|
||||
absentKeys: []string{
|
||||
"spec.hostPID",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
validator := func(obj runtime.Object) field.ErrorList {
|
||||
return validation.ValidatePodSpec(&(obj.(*api.Pod).Spec), field.NewPath("spec"))
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Logf("Testing 1.0.0 backward compatibility for %v", tc.name)
|
||||
compat.TestCompatibility(t, v1.SchemeGroupVersion, []byte(tc.input), validator, tc.expectedKeys, tc.absentKeys)
|
||||
}
|
||||
}
|
116
pkg/api/testing/conversion_test.go
Normal file
116
pkg/api/testing/conversion_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
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 (
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/testing/fuzzer"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
)
|
||||
|
||||
func BenchmarkPodConversion(b *testing.B) {
|
||||
apiObjectFuzzer := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), api.Codecs)
|
||||
items := make([]api.Pod, 4)
|
||||
for i := range items {
|
||||
apiObjectFuzzer.Fuzz(&items[i])
|
||||
items[i].Spec.InitContainers = nil
|
||||
items[i].Status.InitContainerStatuses = nil
|
||||
}
|
||||
|
||||
// add a fixed item
|
||||
items = append(items, benchmarkPod)
|
||||
width := len(items)
|
||||
|
||||
scheme := api.Scheme
|
||||
for i := 0; i < b.N; i++ {
|
||||
pod := &items[i%width]
|
||||
versionedObj, err := scheme.UnsafeConvertToVersion(pod, api.Registry.GroupOrDie(api.GroupName).GroupVersion)
|
||||
if err != nil {
|
||||
b.Fatalf("Conversion error: %v", err)
|
||||
}
|
||||
if _, err = scheme.UnsafeConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion()); err != nil {
|
||||
b.Fatalf("Conversion error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNodeConversion(b *testing.B) {
|
||||
data, err := ioutil.ReadFile("node_example.json")
|
||||
if err != nil {
|
||||
b.Fatalf("Unexpected error while reading file: %v", err)
|
||||
}
|
||||
var node api.Node
|
||||
if err := runtime.DecodeInto(testapi.Default.Codec(), data, &node); err != nil {
|
||||
b.Fatalf("Unexpected error decoding node: %v", err)
|
||||
}
|
||||
|
||||
scheme := api.Scheme
|
||||
var result *api.Node
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
versionedObj, err := scheme.UnsafeConvertToVersion(&node, api.Registry.GroupOrDie(api.GroupName).GroupVersion)
|
||||
if err != nil {
|
||||
b.Fatalf("Conversion error: %v", err)
|
||||
}
|
||||
obj, err := scheme.UnsafeConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion())
|
||||
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", diff.ObjectDiff(node, *result))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReplicationControllerConversion(b *testing.B) {
|
||||
data, err := ioutil.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(testapi.Default.Codec(), data, &replicationController); err != nil {
|
||||
b.Fatalf("Unexpected error decoding node: %v", err)
|
||||
}
|
||||
|
||||
scheme := api.Scheme
|
||||
var result *api.ReplicationController
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
versionedObj, err := scheme.UnsafeConvertToVersion(&replicationController, api.Registry.GroupOrDie(api.GroupName).GroupVersion)
|
||||
if err != nil {
|
||||
b.Fatalf("Conversion error: %v", err)
|
||||
}
|
||||
obj, err := scheme.UnsafeConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion())
|
||||
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)
|
||||
}
|
||||
}
|
86
pkg/api/testing/copy_test.go
Normal file
86
pkg/api/testing/copy_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/gofuzz"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/testing/fuzzer"
|
||||
"k8s.io/apimachinery/pkg/api/testing/roundtrip"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
)
|
||||
|
||||
func TestDeepCopyApiObjects(t *testing.T) {
|
||||
for i := 0; i < *roundtrip.FuzzIters; i++ {
|
||||
for _, version := range []schema.GroupVersion{testapi.Default.InternalGroupVersion(), api.Registry.GroupOrDie(api.GroupName).GroupVersion} {
|
||||
f := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(rand.Int63()), api.Codecs)
|
||||
for kind := range api.Scheme.KnownTypes(version) {
|
||||
doDeepCopyTest(t, version.WithKind(kind), f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doDeepCopyTest(t *testing.T, kind schema.GroupVersionKind, f *fuzz.Fuzzer) {
|
||||
item, err := api.Scheme.New(kind)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create a %v: %s", kind, err)
|
||||
}
|
||||
f.Fuzz(item)
|
||||
itemCopy := item.DeepCopyObject()
|
||||
if !reflect.DeepEqual(item, itemCopy) {
|
||||
t.Errorf("\nexpected: %#v\n\ngot: %#v\n\ndiff: %v", item, itemCopy, diff.ObjectReflectDiff(item, itemCopy))
|
||||
}
|
||||
|
||||
prefuzzData := &bytes.Buffer{}
|
||||
if err := api.Codecs.LegacyCodec(kind.GroupVersion()).Encode(item, prefuzzData); err != nil {
|
||||
t.Errorf("Could not encode a %v: %s", kind, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Refuzz the copy, which should have no effect on the original
|
||||
f.Fuzz(itemCopy)
|
||||
|
||||
postfuzzData := &bytes.Buffer{}
|
||||
if err := api.Codecs.LegacyCodec(kind.GroupVersion()).Encode(item, postfuzzData); err != nil {
|
||||
t.Errorf("Could not encode a %v: %s", kind, err)
|
||||
return
|
||||
}
|
||||
|
||||
if bytes.Compare(prefuzzData.Bytes(), postfuzzData.Bytes()) != 0 {
|
||||
t.Log(diff.StringDiff(prefuzzData.String(), postfuzzData.String()))
|
||||
t.Errorf("Fuzzing copy modified original of %#v", kind)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeepCopySingleType(t *testing.T) {
|
||||
for i := 0; i < *roundtrip.FuzzIters; i++ {
|
||||
for _, version := range []schema.GroupVersion{testapi.Default.InternalGroupVersion(), api.Registry.GroupOrDie(api.GroupName).GroupVersion} {
|
||||
f := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(rand.Int63()), api.Codecs)
|
||||
doDeepCopyTest(t, version.WithKind("Pod"), f)
|
||||
}
|
||||
}
|
||||
}
|
177
pkg/api/testing/deep_copy_test.go
Normal file
177
pkg/api/testing/deep_copy_test.go
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
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 (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
)
|
||||
|
||||
func parseTimeOrDie(ts string) metav1.Time {
|
||||
t, err := time.Parse(time.RFC3339, ts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return metav1.Time{Time: t}
|
||||
}
|
||||
|
||||
var benchmarkPod api.Pod = api.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "etcd-server-e2e-test-wojtekt-master",
|
||||
Namespace: "default",
|
||||
SelfLink: "/api/v1/namespaces/default/pods/etcd-server-e2e-test-wojtekt-master",
|
||||
UID: types.UID("a671734a-e8e5-11e4-8fde-42010af09327"),
|
||||
ResourceVersion: "22",
|
||||
CreationTimestamp: parseTimeOrDie("2015-04-22T11:49:36Z"),
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Volumes: []api.Volume{
|
||||
{
|
||||
Name: "varetcd",
|
||||
VolumeSource: api.VolumeSource{
|
||||
HostPath: &api.HostPathVolumeSource{
|
||||
Path: "/mnt/master-pd/var/etcd",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "etcd-container",
|
||||
Image: "gcr.io/google_containers/etcd:2.0.9",
|
||||
Command: []string{
|
||||
"/usr/local/bin/etcd",
|
||||
"--addr",
|
||||
"127.0.0.1:2379",
|
||||
"--bind-addr",
|
||||
"127.0.0.1:2379",
|
||||
"--data-dir",
|
||||
"/var/etcd/data",
|
||||
},
|
||||
Ports: []api.ContainerPort{
|
||||
{
|
||||
Name: "serverport",
|
||||
HostPort: 2380,
|
||||
ContainerPort: 2380,
|
||||
Protocol: "TCP",
|
||||
},
|
||||
{
|
||||
Name: "clientport",
|
||||
HostPort: 2379,
|
||||
ContainerPort: 2379,
|
||||
Protocol: "TCP",
|
||||
},
|
||||
},
|
||||
VolumeMounts: []api.VolumeMount{
|
||||
{
|
||||
Name: "varetcd",
|
||||
MountPath: "/var/etcd",
|
||||
},
|
||||
},
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: api.PullIfNotPresent,
|
||||
},
|
||||
},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
NodeName: "e2e-test-wojtekt-master",
|
||||
},
|
||||
Status: api.PodStatus{
|
||||
Phase: api.PodRunning,
|
||||
Conditions: []api.PodCondition{
|
||||
{
|
||||
Type: api.PodReady,
|
||||
Status: api.ConditionTrue,
|
||||
},
|
||||
},
|
||||
ContainerStatuses: []api.ContainerStatus{
|
||||
{
|
||||
Name: "etcd-container",
|
||||
State: api.ContainerState{
|
||||
Running: &api.ContainerStateRunning{
|
||||
StartedAt: parseTimeOrDie("2015-04-22T11:49:32Z"),
|
||||
},
|
||||
},
|
||||
Ready: true,
|
||||
RestartCount: 0,
|
||||
Image: "gcr.io/google_containers/etcd:2.0.9",
|
||||
ImageID: "docker://b6b9a86dc06aa1361357ca1b105feba961f6a4145adca6c54e142c0be0fe87b0",
|
||||
ContainerID: "docker://3cbbf818f1addfc252957b4504f56ef2907a313fe6afc47fc75373674255d46d",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func BenchmarkPodCopy(b *testing.B) {
|
||||
var result *api.Pod
|
||||
for i := 0; i < b.N; i++ {
|
||||
result = benchmarkPod.DeepCopy()
|
||||
}
|
||||
if !apiequality.Semantic.DeepEqual(benchmarkPod, *result) {
|
||||
b.Fatalf("Incorrect copy: expected %v, got %v", benchmarkPod, *result)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNodeCopy(b *testing.B) {
|
||||
data, err := ioutil.ReadFile("node_example.json")
|
||||
if err != nil {
|
||||
b.Fatalf("Unexpected error while reading file: %v", err)
|
||||
}
|
||||
var node api.Node
|
||||
if err := runtime.DecodeInto(testapi.Default.Codec(), data, &node); err != nil {
|
||||
b.Fatalf("Unexpected error decoding node: %v", err)
|
||||
}
|
||||
|
||||
var result *api.Node
|
||||
for i := 0; i < b.N; i++ {
|
||||
result = node.DeepCopy()
|
||||
}
|
||||
if !apiequality.Semantic.DeepEqual(node, *result) {
|
||||
b.Fatalf("Incorrect copy: expected %v, got %v", node, *result)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReplicationControllerCopy(b *testing.B) {
|
||||
data, err := ioutil.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(testapi.Default.Codec(), data, &replicationController); err != nil {
|
||||
b.Fatalf("Unexpected error decoding node: %v", err)
|
||||
}
|
||||
|
||||
var result *api.ReplicationController
|
||||
for i := 0; i < b.N; i++ {
|
||||
result = replicationController.DeepCopy()
|
||||
}
|
||||
if !apiequality.Semantic.DeepEqual(replicationController, *result) {
|
||||
b.Fatalf("Incorrect copy: expected %v, got %v", replicationController, *result)
|
||||
}
|
||||
}
|
227
pkg/api/testing/defaulting_test.go
Normal file
227
pkg/api/testing/defaulting_test.go
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
Copyright 2016 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 (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
fuzz "github.com/google/gofuzz"
|
||||
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
roundtrip "k8s.io/apimachinery/pkg/api/testing/roundtrip"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
type orderedGroupVersionKinds []schema.GroupVersionKind
|
||||
|
||||
func (o orderedGroupVersionKinds) Len() int { return len(o) }
|
||||
func (o orderedGroupVersionKinds) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
|
||||
func (o orderedGroupVersionKinds) Less(i, j int) bool {
|
||||
return o[i].String() < o[j].String()
|
||||
}
|
||||
|
||||
// TODO: add a reflexive test that verifies that all SetDefaults functions are registered
|
||||
func TestDefaulting(t *testing.T) {
|
||||
// these are the known types with defaulters - you must add to this list if you add a top level defaulter
|
||||
typesWithDefaulting := map[schema.GroupVersionKind]struct{}{
|
||||
{Group: "", Version: "v1", Kind: "ConfigMap"}: {},
|
||||
{Group: "", Version: "v1", Kind: "ConfigMapList"}: {},
|
||||
{Group: "", Version: "v1", Kind: "Endpoints"}: {},
|
||||
{Group: "", Version: "v1", Kind: "EndpointsList"}: {},
|
||||
{Group: "", Version: "v1", Kind: "Namespace"}: {},
|
||||
{Group: "", Version: "v1", Kind: "NamespaceList"}: {},
|
||||
{Group: "", Version: "v1", Kind: "Node"}: {},
|
||||
{Group: "", Version: "v1", Kind: "NodeList"}: {},
|
||||
{Group: "", Version: "v1", Kind: "PersistentVolume"}: {},
|
||||
{Group: "", Version: "v1", Kind: "PersistentVolumeList"}: {},
|
||||
{Group: "", Version: "v1", Kind: "PersistentVolumeClaim"}: {},
|
||||
{Group: "", Version: "v1", Kind: "PersistentVolumeClaimList"}: {},
|
||||
{Group: "", Version: "v1", Kind: "PodAttachOptions"}: {},
|
||||
{Group: "", Version: "v1", Kind: "PodExecOptions"}: {},
|
||||
{Group: "", Version: "v1", Kind: "Pod"}: {},
|
||||
{Group: "", Version: "v1", Kind: "PodList"}: {},
|
||||
{Group: "", Version: "v1", Kind: "PodTemplate"}: {},
|
||||
{Group: "", Version: "v1", Kind: "PodTemplateList"}: {},
|
||||
{Group: "", Version: "v1", Kind: "ReplicationController"}: {},
|
||||
{Group: "", Version: "v1", Kind: "ReplicationControllerList"}: {},
|
||||
{Group: "", Version: "v1", Kind: "Secret"}: {},
|
||||
{Group: "", Version: "v1", Kind: "SecretList"}: {},
|
||||
{Group: "", Version: "v1", Kind: "Service"}: {},
|
||||
{Group: "", Version: "v1", Kind: "ServiceList"}: {},
|
||||
{Group: "apps", Version: "v1beta1", Kind: "StatefulSet"}: {},
|
||||
{Group: "apps", Version: "v1beta1", Kind: "StatefulSetList"}: {},
|
||||
{Group: "apps", Version: "v1beta2", Kind: "StatefulSet"}: {},
|
||||
{Group: "apps", Version: "v1beta2", Kind: "StatefulSetList"}: {},
|
||||
{Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscaler"}: {},
|
||||
{Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscalerList"}: {},
|
||||
{Group: "autoscaling", Version: "v2beta1", Kind: "HorizontalPodAutoscaler"}: {},
|
||||
{Group: "autoscaling", Version: "v2beta1", Kind: "HorizontalPodAutoscalerList"}: {},
|
||||
{Group: "batch", Version: "v1", Kind: "Job"}: {},
|
||||
{Group: "batch", Version: "v1", Kind: "JobList"}: {},
|
||||
{Group: "batch", Version: "v1beta1", Kind: "CronJob"}: {},
|
||||
{Group: "batch", Version: "v1beta1", Kind: "CronJobList"}: {},
|
||||
{Group: "batch", Version: "v1beta1", Kind: "JobTemplate"}: {},
|
||||
{Group: "batch", Version: "v2alpha1", Kind: "CronJob"}: {},
|
||||
{Group: "batch", Version: "v2alpha1", Kind: "CronJobList"}: {},
|
||||
{Group: "batch", Version: "v2alpha1", Kind: "JobTemplate"}: {},
|
||||
{Group: "certificates.k8s.io", Version: "v1beta1", Kind: "CertificateSigningRequest"}: {},
|
||||
{Group: "certificates.k8s.io", Version: "v1beta1", Kind: "CertificateSigningRequestList"}: {},
|
||||
{Group: "componentconfig", Version: "v1alpha1", Kind: "KubeProxyConfiguration"}: {},
|
||||
{Group: "componentconfig", Version: "v1alpha1", Kind: "KubeSchedulerConfiguration"}: {},
|
||||
{Group: "componentconfig", Version: "v1alpha1", Kind: "KubeletConfiguration"}: {},
|
||||
{Group: "kubeadm.k8s.io", Version: "v1alpha1", Kind: "MasterConfiguration"}: {},
|
||||
// This object contains only int fields which currently breaks the defaulting test because
|
||||
// it's pretty stupid. Once we add non integer fields, we should uncomment this.
|
||||
// {Group: "kubeadm.k8s.io", Version: "v1alpha1", Kind: "NodeConfiguration"}: {},
|
||||
{Group: "extensions", Version: "v1beta1", Kind: "DaemonSet"}: {},
|
||||
{Group: "extensions", Version: "v1beta1", Kind: "DaemonSetList"}: {},
|
||||
{Group: "apps", Version: "v1beta2", Kind: "DaemonSet"}: {},
|
||||
{Group: "apps", Version: "v1beta2", Kind: "DaemonSetList"}: {},
|
||||
{Group: "apps", Version: "v1", Kind: "DaemonSet"}: {},
|
||||
{Group: "apps", Version: "v1", Kind: "DaemonSetList"}: {},
|
||||
{Group: "extensions", Version: "v1beta1", Kind: "Deployment"}: {},
|
||||
{Group: "extensions", Version: "v1beta1", Kind: "DeploymentList"}: {},
|
||||
{Group: "apps", Version: "v1beta1", Kind: "Deployment"}: {},
|
||||
{Group: "apps", Version: "v1beta1", Kind: "DeploymentList"}: {},
|
||||
{Group: "apps", Version: "v1beta2", Kind: "Deployment"}: {},
|
||||
{Group: "apps", Version: "v1beta2", Kind: "DeploymentList"}: {},
|
||||
{Group: "extensions", Version: "v1beta1", Kind: "PodSecurityPolicy"}: {},
|
||||
{Group: "extensions", Version: "v1beta1", Kind: "PodSecurityPolicyList"}: {},
|
||||
{Group: "apps", Version: "v1beta2", Kind: "ReplicaSet"}: {},
|
||||
{Group: "apps", Version: "v1beta2", Kind: "ReplicaSetList"}: {},
|
||||
{Group: "extensions", Version: "v1beta1", Kind: "ReplicaSet"}: {},
|
||||
{Group: "extensions", Version: "v1beta1", Kind: "ReplicaSetList"}: {},
|
||||
{Group: "extensions", Version: "v1beta1", Kind: "NetworkPolicy"}: {},
|
||||
{Group: "extensions", Version: "v1beta1", Kind: "NetworkPolicyList"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "ClusterRoleBinding"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "ClusterRoleBindingList"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "RoleBinding"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "RoleBindingList"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "ClusterRoleBinding"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "ClusterRoleBindingList"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "RoleBinding"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "RoleBindingList"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBindingList"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "RoleBinding"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "RoleBindingList"}: {},
|
||||
{Group: "settings.k8s.io", Version: "v1alpha1", Kind: "PodPreset"}: {},
|
||||
{Group: "settings.k8s.io", Version: "v1alpha1", Kind: "PodPresetList"}: {},
|
||||
{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ExternalAdmissionHookConfiguration"}: {},
|
||||
{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ExternalAdmissionHookConfigurationList"}: {},
|
||||
{Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicy"}: {},
|
||||
{Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicyList"}: {},
|
||||
{Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClass"}: {},
|
||||
{Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClassList"}: {},
|
||||
{Group: "storage.k8s.io", Version: "v1", Kind: "StorageClass"}: {},
|
||||
{Group: "storage.k8s.io", Version: "v1", Kind: "StorageClassList"}: {},
|
||||
}
|
||||
|
||||
f := fuzz.New().NilChance(.5).NumElements(1, 1).RandSource(rand.NewSource(1))
|
||||
f.Funcs(
|
||||
func(s *runtime.RawExtension, c fuzz.Continue) {},
|
||||
func(s *metav1.LabelSelector, c fuzz.Continue) {
|
||||
c.FuzzNoCustom(s)
|
||||
s.MatchExpressions = nil // need to fuzz this specially
|
||||
},
|
||||
func(s *apiv1.ListOptions, c fuzz.Continue) {
|
||||
c.FuzzNoCustom(s)
|
||||
s.LabelSelector = "" // need to fuzz requirement strings specially
|
||||
s.FieldSelector = "" // need to fuzz requirement strings specially
|
||||
},
|
||||
func(s *extensionsv1beta1.ScaleStatus, c fuzz.Continue) {
|
||||
c.FuzzNoCustom(s)
|
||||
s.TargetSelector = "" // need to fuzz requirement strings specially
|
||||
},
|
||||
)
|
||||
|
||||
scheme := api.Scheme
|
||||
var testTypes orderedGroupVersionKinds
|
||||
for gvk := range scheme.AllKnownTypes() {
|
||||
if gvk.Version == runtime.APIVersionInternal {
|
||||
continue
|
||||
}
|
||||
testTypes = append(testTypes, gvk)
|
||||
}
|
||||
sort.Sort(testTypes)
|
||||
|
||||
for _, gvk := range testTypes {
|
||||
_, expectedChanged := typesWithDefaulting[gvk]
|
||||
iter := 0
|
||||
changedOnce := false
|
||||
for {
|
||||
if iter > *roundtrip.FuzzIters {
|
||||
if !expectedChanged || changedOnce {
|
||||
break
|
||||
}
|
||||
if iter > 300 {
|
||||
t.Errorf("expected %s to trigger defaulting due to fuzzing", gvk)
|
||||
break
|
||||
}
|
||||
// if we expected defaulting, continue looping until the fuzzer gives us one
|
||||
// at worst, we will timeout
|
||||
}
|
||||
iter++
|
||||
|
||||
src, err := scheme.New(gvk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Fuzz(src)
|
||||
|
||||
src.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
|
||||
|
||||
original := src.DeepCopyObject()
|
||||
|
||||
// get internal
|
||||
withDefaults := src.DeepCopyObject()
|
||||
scheme.Default(withDefaults.(runtime.Object))
|
||||
|
||||
if !reflect.DeepEqual(original, withDefaults) {
|
||||
changedOnce = true
|
||||
if !expectedChanged {
|
||||
t.Errorf("{Group: \"%s\", Version: \"%s\", Kind: \"%s\"} did not expect defaults to be set - update expected or check defaulter registering: %s", gvk.Group, gvk.Version, gvk.Kind, diff.ObjectReflectDiff(original, withDefaults))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPodDefaulting(b *testing.B) {
|
||||
f := fuzz.New().NilChance(.5).NumElements(1, 1).RandSource(rand.NewSource(1))
|
||||
items := make([]apiv1.Pod, 100)
|
||||
for i := range items {
|
||||
f.Fuzz(&items[i])
|
||||
}
|
||||
|
||||
scheme := api.Scheme
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pod := &items[i%len(items)]
|
||||
|
||||
scheme.Default(pod)
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
20
pkg/api/testing/doc.go
Normal file
20
pkg/api/testing/doc.go
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
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 contains
|
||||
// - all generic API tests which depend on Kubernetes API types
|
||||
// - all cross-Kubernetes-API tests.
|
||||
package testing
|
103
pkg/api/testing/meta_test.go
Normal file
103
pkg/api/testing/meta_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright 2014 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 (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
var _ metav1.Object = &metav1.ObjectMeta{}
|
||||
|
||||
func TestAccessorImplementations(t *testing.T) {
|
||||
for _, gv := range api.Registry.EnabledVersions() {
|
||||
internalGV := schema.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}
|
||||
for _, gv := range []schema.GroupVersion{gv, internalGV} {
|
||||
for kind, knownType := range api.Scheme.KnownTypes(gv) {
|
||||
value := reflect.New(knownType)
|
||||
obj := value.Interface()
|
||||
if _, ok := obj.(runtime.Object); !ok {
|
||||
t.Errorf("%v (%v) does not implement runtime.Object", gv.WithKind(kind), knownType)
|
||||
}
|
||||
lm, isLM := obj.(meta.ListMetaAccessor)
|
||||
om, isOM := obj.(metav1.ObjectMetaAccessor)
|
||||
switch {
|
||||
case isLM && isOM:
|
||||
t.Errorf("%v (%v) implements ListMetaAccessor and ObjectMetaAccessor", gv.WithKind(kind), knownType)
|
||||
continue
|
||||
case isLM:
|
||||
m := lm.GetListMeta()
|
||||
if m == nil {
|
||||
t.Errorf("%v (%v) returns nil ListMeta", gv.WithKind(kind), knownType)
|
||||
continue
|
||||
}
|
||||
m.SetResourceVersion("102030")
|
||||
if m.GetResourceVersion() != "102030" {
|
||||
t.Errorf("%v (%v) did not preserve resource version", gv.WithKind(kind), knownType)
|
||||
continue
|
||||
}
|
||||
m.SetSelfLink("102030")
|
||||
if m.GetSelfLink() != "102030" {
|
||||
t.Errorf("%v (%v) did not preserve self link", gv.WithKind(kind), knownType)
|
||||
continue
|
||||
}
|
||||
case isOM:
|
||||
m := om.GetObjectMeta()
|
||||
if m == nil {
|
||||
t.Errorf("%v (%v) returns nil ObjectMeta", gv.WithKind(kind), knownType)
|
||||
continue
|
||||
}
|
||||
m.SetResourceVersion("102030")
|
||||
if m.GetResourceVersion() != "102030" {
|
||||
t.Errorf("%v (%v) did not preserve resource version", gv.WithKind(kind), knownType)
|
||||
continue
|
||||
}
|
||||
m.SetSelfLink("102030")
|
||||
if m.GetSelfLink() != "102030" {
|
||||
t.Errorf("%v (%v) did not preserve self link", gv.WithKind(kind), knownType)
|
||||
continue
|
||||
}
|
||||
labels := map[string]string{"a": "b"}
|
||||
m.SetLabels(labels)
|
||||
if !reflect.DeepEqual(m.GetLabels(), labels) {
|
||||
t.Errorf("%v (%v) did not preserve labels", gv.WithKind(kind), knownType)
|
||||
continue
|
||||
}
|
||||
default:
|
||||
if _, ok := obj.(metav1.ListMetaAccessor); ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := value.Elem().Type().FieldByName("ObjectMeta"); ok {
|
||||
t.Errorf("%v (%v) has ObjectMeta but does not implement ObjectMetaAccessor", gv.WithKind(kind), knownType)
|
||||
continue
|
||||
}
|
||||
if _, ok := value.Elem().Type().FieldByName("ListMeta"); ok {
|
||||
t.Errorf("%v (%v) has ListMeta but does not implement ListMetaAccessor", gv.WithKind(kind), knownType)
|
||||
continue
|
||||
}
|
||||
t.Logf("%v (%v) does not implement ListMetaAccessor or ObjectMetaAccessor", gv.WithKind(kind), knownType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
pkg/api/testing/node_example.json
Normal file
49
pkg/api/testing/node_example.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"kind": "Node",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "e2e-test-wojtekt-node-etd6",
|
||||
"selfLink": "/api/v1/nodes/e2e-test-wojtekt-node-etd6",
|
||||
"uid": "a7e89222-e8e5-11e4-8fde-42010af09327",
|
||||
"resourceVersion": "379",
|
||||
"creationTimestamp": "2015-04-22T11:49:39Z"
|
||||
},
|
||||
"spec": {
|
||||
"externalID": "15488322946290398375"
|
||||
},
|
||||
"status": {
|
||||
"capacity": {
|
||||
"cpu": "1",
|
||||
"memory": "1745152Ki"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"type": "Ready",
|
||||
"status": "True",
|
||||
"lastHeartbeatTime": "2015-04-22T11:58:17Z",
|
||||
"lastTransitionTime": "2015-04-22T11:49:52Z",
|
||||
"reason": "kubelet is posting ready status"
|
||||
}
|
||||
],
|
||||
"addresses": [
|
||||
{
|
||||
"type": "ExternalIP",
|
||||
"address": "104.197.49.213"
|
||||
},
|
||||
{
|
||||
"type": "LegacyHostIP",
|
||||
"address": "104.197.20.11"
|
||||
}
|
||||
],
|
||||
"nodeInfo": {
|
||||
"machineID": "",
|
||||
"systemUUID": "D59FA3FA-7B5B-7287-5E1A-1D79F13CB577",
|
||||
"bootID": "44a832f3-8cfb-4de5-b7d2-d66030b6cd95",
|
||||
"kernelVersion": "3.16.0-0.bpo.4-amd64",
|
||||
"osImage": "Debian GNU/Linux 7 (wheezy)",
|
||||
"containerRuntimeVersion": "docker://1.5.0",
|
||||
"kubeletVersion": "v0.15.0-484-g0c8ee980d705a3-dirty",
|
||||
"kubeProxyVersion": "v0.15.0-484-g0c8ee980d705a3-dirty"
|
||||
}
|
||||
}
|
||||
}
|
83
pkg/api/testing/replication_controller_example.json
Normal file
83
pkg/api/testing/replication_controller_example.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"kind": "ReplicationController",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "elasticsearch-logging-controller",
|
||||
"namespace": "default",
|
||||
"selfLink": "/api/v1/namespaces/default/replicationcontrollers/elasticsearch-logging-controller",
|
||||
"uid": "aa76f162-e8e5-11e4-8fde-42010af09327",
|
||||
"resourceVersion": "98",
|
||||
"creationTimestamp": "2015-04-22T11:49:43Z",
|
||||
"labels": {
|
||||
"kubernetes.io/cluster-service": "true",
|
||||
"name": "elasticsearch-logging"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"replicas": 1,
|
||||
"selector": {
|
||||
"name": "elasticsearch-logging"
|
||||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"labels": {
|
||||
"kubernetes.io/cluster-service": "true",
|
||||
"name": "elasticsearch-logging"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"volumes": [
|
||||
{
|
||||
"name": "es-persistent-storage",
|
||||
"hostPath": null,
|
||||
"emptyDir": {
|
||||
"medium": ""
|
||||
},
|
||||
"gcePersistentDisk": null,
|
||||
"awsElasticBlockStore": null,
|
||||
"gitRepo": null,
|
||||
"secret": null,
|
||||
"nfs": null,
|
||||
"iscsi": null,
|
||||
"glusterfs": null,
|
||||
"quobyte": null
|
||||
}
|
||||
],
|
||||
"containers": [
|
||||
{
|
||||
"name": "elasticsearch-logging",
|
||||
"image": "gcr.io/google_containers/elasticsearch:1.0",
|
||||
"ports": [
|
||||
{
|
||||
"name": "db",
|
||||
"containerPort": 9200,
|
||||
"protocol": "TCP"
|
||||
},
|
||||
{
|
||||
"name": "transport",
|
||||
"containerPort": 9300,
|
||||
"protocol": "TCP"
|
||||
}
|
||||
],
|
||||
"resources": {},
|
||||
"volumeMounts": [
|
||||
{
|
||||
"name": "es-persistent-storage",
|
||||
"mountPath": "/data"
|
||||
}
|
||||
],
|
||||
"terminationMessagePath": "/dev/termination-log",
|
||||
"imagePullPolicy": "IfNotPresent",
|
||||
"capabilities": {}
|
||||
}
|
||||
],
|
||||
"restartPolicy": "Always",
|
||||
"dnsPolicy": "ClusterFirst"
|
||||
}
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"replicas": 1
|
||||
}
|
||||
}
|
216
pkg/api/testing/serialization_proto_test.go
Normal file
216
pkg/api/testing/serialization_proto_test.go
Normal file
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/testing/fuzzer"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/protobuf"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
_ "k8s.io/kubernetes/pkg/apis/extensions"
|
||||
_ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
||||
)
|
||||
|
||||
func TestUniversalDeserializer(t *testing.T) {
|
||||
expected := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test"}}
|
||||
d := api.Codecs.UniversalDeserializer()
|
||||
for _, mediaType := range []string{"application/json", "application/yaml", "application/vnd.kubernetes.protobuf"} {
|
||||
info, ok := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), mediaType)
|
||||
if !ok {
|
||||
t.Fatal(mediaType)
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
if err := info.Serializer.Encode(expected, buf); err != nil {
|
||||
t.Fatalf("%s: %v", mediaType, err)
|
||||
}
|
||||
obj, _, err := d.Decode(buf.Bytes(), &schema.GroupVersionKind{Kind: "Pod", Version: "v1"}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", mediaType, err)
|
||||
}
|
||||
if !apiequality.Semantic.DeepEqual(expected, obj) {
|
||||
t.Fatalf("%s: %#v", mediaType, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllFieldsHaveTags(t *testing.T) {
|
||||
for gvk, obj := range api.Scheme.AllKnownTypes() {
|
||||
if gvk.Version == runtime.APIVersionInternal {
|
||||
// internal versions are not serialized to protobuf
|
||||
continue
|
||||
}
|
||||
if gvk.Group == "componentconfig" {
|
||||
// component config is not serialized to protobuf
|
||||
continue
|
||||
}
|
||||
if err := fieldsHaveProtobufTags(obj); err != nil {
|
||||
t.Errorf("type %s as gvk %v is missing tags: %v", obj, gvk, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fieldsHaveProtobufTags(obj reflect.Type) error {
|
||||
switch obj.Kind() {
|
||||
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Array:
|
||||
return fieldsHaveProtobufTags(obj.Elem())
|
||||
case reflect.Struct:
|
||||
for i := 0; i < obj.NumField(); i++ {
|
||||
f := obj.Field(i)
|
||||
if f.Name == "TypeMeta" && f.Type.Name() == "TypeMeta" {
|
||||
// TypeMeta is not included in external protobuf because we use an envelope type with TypeMeta
|
||||
continue
|
||||
}
|
||||
if len(f.Tag.Get("json")) > 0 && len(f.Tag.Get("protobuf")) == 0 {
|
||||
return fmt.Errorf("field %s in %s has a 'json' tag but no protobuf tag", f.Name, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestProtobufRoundTrip(t *testing.T) {
|
||||
obj := &v1.Pod{}
|
||||
fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), api.Codecs).Fuzz(obj)
|
||||
// InitContainers are turned into annotations by conversion.
|
||||
obj.Spec.InitContainers = nil
|
||||
obj.Status.InitContainerStatuses = nil
|
||||
data, err := obj.Marshal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out := &v1.Pod{}
|
||||
if err := out.Unmarshal(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !apiequality.Semantic.Equalities.DeepEqual(out, obj) {
|
||||
t.Logf("marshal\n%s", hex.Dump(data))
|
||||
t.Fatalf("Unmarshal is unequal\n%s", diff.ObjectGoPrintDiff(out, obj))
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkEncodeCodec measures the cost of performing a codec encode, which includes
|
||||
// reflection (to clear APIVersion and Kind)
|
||||
func BenchmarkEncodeCodecProtobuf(b *testing.B) {
|
||||
items := benchmarkItems(b)
|
||||
width := len(items)
|
||||
s := protobuf.NewSerializer(nil, nil, "application/arbitrary.content.type")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := runtime.Encode(s, &items[i%width]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
// 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(b)
|
||||
width := len(items)
|
||||
encodable := make([]api.Pod, width)
|
||||
for i := range items {
|
||||
if err := api.Scheme.Convert(&items[i], &encodable[i], nil); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
s := protobuf.NewSerializer(nil, nil, "application/arbitrary.content.type")
|
||||
codec := api.Codecs.EncoderForVersion(s, v1.SchemeGroupVersion)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := runtime.Encode(codec, &encodable[i%width]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkEncodeProtobufGeneratedMarshal(b *testing.B) {
|
||||
items := benchmarkItems(b)
|
||||
width := len(items)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := items[i%width].Marshal(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
// 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(b)
|
||||
width := len(items)
|
||||
s := protobuf.NewSerializer(api.Scheme, api.Scheme, "application/arbitrary.content.type")
|
||||
encoder := api.Codecs.EncoderForVersion(s, v1.SchemeGroupVersion)
|
||||
var encoded [][]byte
|
||||
for i := range items {
|
||||
data, err := runtime.Encode(encoder, &items[i])
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
encoded = append(encoded, data)
|
||||
}
|
||||
|
||||
decoder := api.Codecs.DecoderToVersion(s, api.SchemeGroupVersion)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := runtime.Decode(decoder, encoded[i%width]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
// BenchmarkDecodeJSON provides a baseline for regular JSON decode performance
|
||||
func BenchmarkDecodeIntoProtobuf(b *testing.B) {
|
||||
items := benchmarkItems(b)
|
||||
width := len(items)
|
||||
encoded := make([][]byte, width)
|
||||
for i := range items {
|
||||
data, err := (&items[i]).Marshal()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
encoded[i] = data
|
||||
validate := &v1.Pod{}
|
||||
if err := proto.Unmarshal(data, validate); err != nil {
|
||||
b.Fatalf("Failed to unmarshal %d: %v\n%#v", i, err, items[i])
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
obj := v1.Pod{}
|
||||
if err := proto.Unmarshal(encoded[i%width], &obj); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
560
pkg/api/testing/serialization_test.go
Normal file
560
pkg/api/testing/serialization_test.go
Normal file
@@ -0,0 +1,560 @@
|
||||
/*
|
||||
Copyright 2014 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 (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/api/testing/fuzzer"
|
||||
"k8s.io/apimachinery/pkg/api/testing/roundtrip"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
k8s_v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), api.Codecs).Fuzz(item)
|
||||
|
||||
j, err := meta.TypeAccessor(item)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v for %#v", err, item)
|
||||
}
|
||||
j.SetKind("")
|
||||
j.SetAPIVersion("")
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
// dataAsString returns the given byte array as a string; handles detecting
|
||||
// protocol buffers.
|
||||
func dataAsString(data []byte) string {
|
||||
dataString := string(data)
|
||||
if !strings.HasPrefix(dataString, "{") {
|
||||
dataString = "\n" + hex.Dump(data)
|
||||
proto.NewBuffer(make([]byte, 0, 1024)).DebugPrint("decoded object", data)
|
||||
}
|
||||
return dataString
|
||||
}
|
||||
|
||||
func Convert_v1beta1_ReplicaSet_to_api_ReplicationController(in *v1beta1.ReplicaSet, out *api.ReplicationController, s conversion.Scope) error {
|
||||
intermediate1 := &extensions.ReplicaSet{}
|
||||
if err := k8s_v1beta1.Convert_v1beta1_ReplicaSet_To_extensions_ReplicaSet(in, intermediate1, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
intermediate2 := &v1.ReplicationController{}
|
||||
if err := k8s_api_v1.Convert_extensions_ReplicaSet_to_v1_ReplicationController(intermediate1, intermediate2, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return k8s_api_v1.Convert_v1_ReplicationController_To_api_ReplicationController(intermediate2, out, s)
|
||||
}
|
||||
|
||||
func TestSetControllerConversion(t *testing.T) {
|
||||
if err := api.Scheme.AddConversionFuncs(Convert_v1beta1_ReplicaSet_to_api_ReplicationController); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rs := &extensions.ReplicaSet{}
|
||||
rc := &api.ReplicationController{}
|
||||
|
||||
extGroup := testapi.Extensions
|
||||
defaultGroup := testapi.Default
|
||||
|
||||
fuzzInternalObject(t, extGroup.InternalGroupVersion(), rs, rand.Int63())
|
||||
|
||||
t.Logf("rs._internal.extensions -> rs.v1beta1.extensions")
|
||||
data, err := runtime.Encode(extGroup.Codec(), rs)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected encoding error: %v", err)
|
||||
}
|
||||
|
||||
decoder := api.Codecs.DecoderToVersion(
|
||||
api.Codecs.UniversalDeserializer(),
|
||||
runtime.NewMultiGroupVersioner(
|
||||
*defaultGroup.GroupVersion(),
|
||||
schema.GroupKind{Group: defaultGroup.GroupVersion().Group},
|
||||
schema.GroupKind{Group: extGroup.GroupVersion().Group},
|
||||
),
|
||||
)
|
||||
|
||||
t.Logf("rs.v1beta1.extensions -> rc._internal")
|
||||
if err := runtime.DecodeInto(decoder, data, rc); err != nil {
|
||||
t.Fatalf("unexpected decoding error: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("rc._internal -> rc.v1")
|
||||
data, err = runtime.Encode(defaultGroup.Codec(), rc)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected encoding error: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("rc.v1 -> rs._internal.extensions")
|
||||
if err := runtime.DecodeInto(decoder, data, rs); err != nil {
|
||||
t.Fatalf("unexpected decoding error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSpecificKind round-trips a single specific kind and is intended to help
|
||||
// debug issues that arise while adding a new API type.
|
||||
func TestSpecificKind(t *testing.T) {
|
||||
// Uncomment the following line to enable logging of which conversions
|
||||
// api.Scheme.Log(t)
|
||||
internalGVK := schema.GroupVersionKind{Group: "extensions", Version: runtime.APIVersionInternal, Kind: "DaemonSet"}
|
||||
|
||||
seed := rand.Int63()
|
||||
fuzzer := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), api.Codecs)
|
||||
|
||||
roundtrip.RoundTripSpecificKind(t, internalGVK, api.Scheme, api.Codecs, fuzzer, nil)
|
||||
}
|
||||
|
||||
var nonRoundTrippableTypes = sets.NewString(
|
||||
"ExportOptions",
|
||||
"GetOptions",
|
||||
// WatchEvent does not include kind and version and can only be deserialized
|
||||
// implicitly (if the caller expects the specific object). The watch call defines
|
||||
// the schema by content type, rather than via kind/version included in each
|
||||
// object.
|
||||
"WatchEvent",
|
||||
// ListOptions is now part of the meta group
|
||||
"ListOptions",
|
||||
// Delete options is only read in metav1
|
||||
"DeleteOptions",
|
||||
)
|
||||
|
||||
var commonKinds = []string{"Status", "ListOptions", "DeleteOptions", "ExportOptions"}
|
||||
|
||||
// TestCommonKindsRegistered verifies that all group/versions registered with
|
||||
// the testapi package have the common kinds.
|
||||
func TestCommonKindsRegistered(t *testing.T) {
|
||||
for _, kind := range commonKinds {
|
||||
for _, group := range testapi.Groups {
|
||||
gv := group.GroupVersion()
|
||||
gvk := gv.WithKind(kind)
|
||||
obj, err := api.Scheme.New(gvk)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defaults := gv.WithKind("")
|
||||
var got *schema.GroupVersionKind
|
||||
if obj, got, err = api.Codecs.LegacyCodec().Decode([]byte(`{"kind":"`+kind+`"}`), &defaults, obj); err != nil || gvk != *got {
|
||||
t.Errorf("expected %v: %v %v", gvk, got, err)
|
||||
}
|
||||
data, err := runtime.Encode(api.Codecs.LegacyCodec(*gv), obj)
|
||||
if err != nil {
|
||||
t.Errorf("expected %v: %v\n%s", gvk, err, string(data))
|
||||
continue
|
||||
}
|
||||
if !bytes.Contains(data, []byte(`"kind":"`+kind+`","apiVersion":"`+gv.String()+`"`)) {
|
||||
if kind != "Status" {
|
||||
t.Errorf("expected %v: %v\n%s", gvk, err, string(data))
|
||||
continue
|
||||
}
|
||||
// TODO: this is wrong, but legacy clients expect it
|
||||
if !bytes.Contains(data, []byte(`"kind":"`+kind+`","apiVersion":"v1"`)) {
|
||||
t.Errorf("expected %v: %v\n%s", gvk, err, string(data))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRoundTripTypes applies the round-trip test to all round-trippable Kinds
|
||||
// in all of the API groups registered for test in the testapi package.
|
||||
func TestRoundTripTypes(t *testing.T) {
|
||||
seed := rand.Int63()
|
||||
fuzzer := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), api.Codecs)
|
||||
|
||||
nonRoundTrippableTypes := map[schema.GroupVersionKind]bool{
|
||||
{Group: "componentconfig", Version: runtime.APIVersionInternal, Kind: "KubeProxyConfiguration"}: true,
|
||||
{Group: "componentconfig", Version: runtime.APIVersionInternal, Kind: "KubeSchedulerConfiguration"}: true,
|
||||
}
|
||||
|
||||
roundtrip.RoundTripTypes(t, api.Scheme, api.Codecs, fuzzer, nonRoundTrippableTypes)
|
||||
}
|
||||
|
||||
// TestEncodePtr tests that a pointer to a golang type can be encoded and
|
||||
// decoded without information loss or mutation.
|
||||
func TestEncodePtr(t *testing.T) {
|
||||
grace := int64(30)
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{"name": "foo"},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
SchedulerName: api.DefaultSchedulerName,
|
||||
},
|
||||
}
|
||||
obj := runtime.Object(pod)
|
||||
data, err := runtime.Encode(testapi.Default.Codec(), obj)
|
||||
obj2, err2 := runtime.Decode(testapi.Default.Codec(), data)
|
||||
if err != nil || err2 != nil {
|
||||
t.Fatalf("Failure: '%v' '%v'", err, err2)
|
||||
}
|
||||
if _, ok := obj2.(*api.Pod); !ok {
|
||||
t.Fatalf("Got wrong type")
|
||||
}
|
||||
if !apiequality.Semantic.DeepEqual(obj2, pod) {
|
||||
t.Errorf("\nExpected:\n\n %#v,\n\nGot:\n\n %#vDiff: %v\n\n", pod, obj2, diff.ObjectDiff(obj2, pod))
|
||||
}
|
||||
}
|
||||
|
||||
// TestBadJSONRejection establishes that a JSON object without a kind or with
|
||||
// an unknown kind will not be decoded without error.
|
||||
func TestBadJSONRejection(t *testing.T) {
|
||||
badJSONMissingKind := []byte(`{ }`)
|
||||
if _, err := runtime.Decode(testapi.Default.Codec(), badJSONMissingKind); err == nil {
|
||||
t.Errorf("Did not reject despite lack of kind field: %s", badJSONMissingKind)
|
||||
}
|
||||
badJSONUnknownType := []byte(`{"kind": "bar"}`)
|
||||
if _, err1 := runtime.Decode(testapi.Default.Codec(), badJSONUnknownType); err1 == nil {
|
||||
t.Errorf("Did not reject despite use of unknown type: %s", badJSONUnknownType)
|
||||
}
|
||||
/*badJSONKindMismatch := []byte(`{"kind": "Pod"}`)
|
||||
if err2 := DecodeInto(badJSONKindMismatch, &Node{}); err2 == nil {
|
||||
t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch)
|
||||
}*/
|
||||
}
|
||||
|
||||
// TestUnversionedTypes establishes that the default codec can encode and
|
||||
// decode unversioned objects.
|
||||
func TestUnversionedTypes(t *testing.T) {
|
||||
testcases := []runtime.Object{
|
||||
&metav1.Status{Status: "Failure", Message: "something went wrong"},
|
||||
&metav1.APIVersions{Versions: []string{"A", "B", "C"}},
|
||||
&metav1.APIGroupList{Groups: []metav1.APIGroup{{Name: "mygroup"}}},
|
||||
&metav1.APIGroup{Name: "mygroup"},
|
||||
&metav1.APIResourceList{GroupVersion: "mygroup/myversion"},
|
||||
}
|
||||
|
||||
for _, obj := range testcases {
|
||||
// Make sure the unversioned codec can encode
|
||||
unversionedJSON, err := runtime.Encode(testapi.Default.Codec(), obj)
|
||||
if err != nil {
|
||||
t.Errorf("%v: unexpected error: %v", obj, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Make sure the versioned codec under test can decode
|
||||
versionDecodedObject, err := runtime.Decode(testapi.Default.Codec(), unversionedJSON)
|
||||
if err != nil {
|
||||
t.Errorf("%v: unexpected error: %v", obj, err)
|
||||
continue
|
||||
}
|
||||
// Make sure it decodes correctly
|
||||
if !reflect.DeepEqual(obj, versionDecodedObject) {
|
||||
t.Errorf("%v: expected %#v, got %#v", obj, obj, versionDecodedObject)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), api.Codecs)
|
||||
secret := &api.Secret{}
|
||||
f.Fuzz(secret)
|
||||
secret.Data["binary"] = []byte{0x00, 0x10, 0x30, 0x55, 0xff, 0x00}
|
||||
secret.Data["utf8"] = []byte("a string with \u0345 characters")
|
||||
secret.Data["long"] = bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x00}, 1000)
|
||||
converted, _ := api.Scheme.ConvertToVersion(secret, v1.SchemeGroupVersion)
|
||||
v1secret := converted.(*v1.Secret)
|
||||
for _, info := range api.Codecs.SupportedMediaTypes() {
|
||||
if info.StreamSerializer == nil {
|
||||
continue
|
||||
}
|
||||
s := info.StreamSerializer
|
||||
framer := s.Framer
|
||||
embedded := info.Serializer
|
||||
if embedded == nil {
|
||||
t.Errorf("no embedded serializer for %s", info.MediaType)
|
||||
continue
|
||||
}
|
||||
innerDecode := api.Codecs.DecoderToVersion(embedded, api.SchemeGroupVersion)
|
||||
|
||||
// write a single object through the framer and back out
|
||||
obj := &bytes.Buffer{}
|
||||
if err := s.Encode(v1secret, obj); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out := &bytes.Buffer{}
|
||||
w := framer.NewFrameWriter(out)
|
||||
if n, err := w.Write(obj.Bytes()); err != nil || n != len(obj.Bytes()) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sr := streaming.NewDecoder(framer.NewFrameReader(ioutil.NopCloser(out)), s)
|
||||
resultSecret := &v1.Secret{}
|
||||
res, _, err := sr.Decode(nil, resultSecret)
|
||||
if err != nil {
|
||||
t.Fatalf("%v:\n%s", err, hex.Dump(obj.Bytes()))
|
||||
}
|
||||
resultSecret.Kind = "Secret"
|
||||
resultSecret.APIVersion = "v1"
|
||||
if !apiequality.Semantic.DeepEqual(v1secret, res) {
|
||||
t.Fatalf("objects did not match: %s", diff.ObjectGoPrintDiff(v1secret, res))
|
||||
}
|
||||
|
||||
// write a watch event through the frame writer and read it back in
|
||||
// via the frame reader for this media type
|
||||
obj = &bytes.Buffer{}
|
||||
if err := embedded.Encode(v1secret, obj); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
event := &metav1.WatchEvent{Type: string(watch.Added)}
|
||||
event.Object.Raw = obj.Bytes()
|
||||
obj = &bytes.Buffer{}
|
||||
if err := s.Encode(event, obj); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out = &bytes.Buffer{}
|
||||
w = framer.NewFrameWriter(out)
|
||||
if n, err := w.Write(obj.Bytes()); err != nil || n != len(obj.Bytes()) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sr = streaming.NewDecoder(framer.NewFrameReader(ioutil.NopCloser(out)), s)
|
||||
outEvent := &metav1.WatchEvent{}
|
||||
res, _, err = sr.Decode(nil, outEvent)
|
||||
if err != nil || outEvent.Type != string(watch.Added) {
|
||||
t.Fatalf("%v: %#v", err, outEvent)
|
||||
}
|
||||
if outEvent.Object.Object == nil && outEvent.Object.Raw != nil {
|
||||
outEvent.Object.Object, err = runtime.Decode(innerDecode, outEvent.Object.Raw)
|
||||
if err != nil {
|
||||
t.Fatalf("%v:\n%s", err, hex.Dump(outEvent.Object.Raw))
|
||||
}
|
||||
}
|
||||
|
||||
if !apiequality.Semantic.DeepEqual(secret, outEvent.Object.Object) {
|
||||
t.Fatalf("%s: did not match after frame decoding: %s", info.MediaType, diff.ObjectGoPrintDiff(secret, outEvent.Object.Object))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const benchmarkSeed = 100
|
||||
|
||||
func benchmarkItems(b *testing.B) []v1.Pod {
|
||||
apiObjectFuzzer := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), api.Codecs)
|
||||
items := make([]v1.Pod, 10)
|
||||
for i := range items {
|
||||
var pod api.Pod
|
||||
apiObjectFuzzer.Fuzz(&pod)
|
||||
pod.Spec.InitContainers, pod.Status.InitContainerStatuses = nil, nil
|
||||
out, err := api.Scheme.ConvertToVersion(&pod, v1.SchemeGroupVersion)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
items[i] = *out.(*v1.Pod)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// BenchmarkEncodeCodec measures the cost of performing a codec encode, which includes
|
||||
// reflection (to clear APIVersion and Kind)
|
||||
func BenchmarkEncodeCodec(b *testing.B) {
|
||||
items := benchmarkItems(b)
|
||||
width := len(items)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := runtime.Encode(testapi.Default.Codec(), &items[i%width]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
// BenchmarkEncodeCodecFromInternal measures the cost of performing a codec encode,
|
||||
// including conversions.
|
||||
func BenchmarkEncodeCodecFromInternal(b *testing.B) {
|
||||
items := benchmarkItems(b)
|
||||
width := len(items)
|
||||
encodable := make([]api.Pod, width)
|
||||
for i := range items {
|
||||
if err := api.Scheme.Convert(&items[i], &encodable[i], nil); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := runtime.Encode(testapi.Default.Codec(), &encodable[i%width]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
// BenchmarkEncodeJSONMarshal provides a baseline for regular JSON encode performance
|
||||
func BenchmarkEncodeJSONMarshal(b *testing.B) {
|
||||
items := benchmarkItems(b)
|
||||
width := len(items)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := json.Marshal(&items[i%width]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkDecodeCodec(b *testing.B) {
|
||||
codec := testapi.Default.Codec()
|
||||
items := benchmarkItems(b)
|
||||
width := len(items)
|
||||
encoded := make([][]byte, width)
|
||||
for i := range items {
|
||||
data, err := runtime.Encode(codec, &items[i])
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
encoded[i] = data
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := runtime.Decode(codec, encoded[i%width]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkDecodeIntoExternalCodec(b *testing.B) {
|
||||
codec := testapi.Default.Codec()
|
||||
items := benchmarkItems(b)
|
||||
width := len(items)
|
||||
encoded := make([][]byte, width)
|
||||
for i := range items {
|
||||
data, err := runtime.Encode(codec, &items[i])
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
encoded[i] = data
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
obj := v1.Pod{}
|
||||
if err := runtime.DecodeInto(codec, encoded[i%width], &obj); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkDecodeIntoInternalCodec(b *testing.B) {
|
||||
codec := testapi.Default.Codec()
|
||||
items := benchmarkItems(b)
|
||||
width := len(items)
|
||||
encoded := make([][]byte, width)
|
||||
for i := range items {
|
||||
data, err := runtime.Encode(codec, &items[i])
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
encoded[i] = data
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
obj := api.Pod{}
|
||||
if err := runtime.DecodeInto(codec, encoded[i%width], &obj); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
// BenchmarkDecodeJSON provides a baseline for regular JSON decode performance
|
||||
func BenchmarkDecodeIntoJSON(b *testing.B) {
|
||||
codec := testapi.Default.Codec()
|
||||
items := benchmarkItems(b)
|
||||
width := len(items)
|
||||
encoded := make([][]byte, width)
|
||||
for i := range items {
|
||||
data, err := runtime.Encode(codec, &items[i])
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
encoded[i] = data
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
obj := v1.Pod{}
|
||||
if err := json.Unmarshal(encoded[i%width], &obj); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
// BenchmarkDecodeJSON provides a baseline for JSON decode performance
|
||||
func BenchmarkDecodeIntoJSONCodecGen(b *testing.B) {
|
||||
kcodec := testapi.Default.Codec()
|
||||
items := benchmarkItems(b)
|
||||
width := len(items)
|
||||
encoded := make([][]byte, width)
|
||||
for i := range items {
|
||||
data, err := runtime.Encode(kcodec, &items[i])
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
encoded[i] = data
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
obj := v1.Pod{}
|
||||
if err := jsoniter.ConfigFastest.Unmarshal(encoded[i%width], &obj); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
173
pkg/api/testing/unstructured_test.go
Normal file
173
pkg/api/testing/unstructured_test.go
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
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 (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/gofuzz"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/testing/fuzzer"
|
||||
"k8s.io/apimachinery/pkg/conversion/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
)
|
||||
|
||||
func doRoundTrip(t *testing.T, group testapi.TestGroup, kind string) {
|
||||
// We do fuzzing on the internal version of the object, and only then
|
||||
// convert to the external version. This is because custom fuzzing
|
||||
// function are only supported for internal objects.
|
||||
internalObj, err := api.Scheme.New(group.InternalGroupVersion().WithKind(kind))
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create internal object %v: %v", kind, err)
|
||||
}
|
||||
seed := rand.Int63()
|
||||
fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), api.Codecs).
|
||||
// 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
|
||||
// those disappear.
|
||||
Funcs(
|
||||
func(s *api.PodSpec, c fuzz.Continue) {
|
||||
c.FuzzNoCustom(s)
|
||||
s.InitContainers = nil
|
||||
},
|
||||
func(s *api.PodStatus, c fuzz.Continue) {
|
||||
c.FuzzNoCustom(s)
|
||||
s.InitContainerStatuses = nil
|
||||
},
|
||||
).Fuzz(internalObj)
|
||||
|
||||
item, err := api.Scheme.New(group.GroupVersion().WithKind(kind))
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create external object %v: %v", kind, err)
|
||||
}
|
||||
if err := api.Scheme.Convert(internalObj, item, nil); err != nil {
|
||||
t.Fatalf("Conversion for %v failed: %v", kind, err)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(item)
|
||||
if err != nil {
|
||||
t.Errorf("Error when marshaling object: %v", err)
|
||||
return
|
||||
}
|
||||
unstr := make(map[string]interface{})
|
||||
err = json.Unmarshal(data, &unstr)
|
||||
if err != nil {
|
||||
t.Errorf("Error when unmarshaling to unstructured: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
data, err = json.Marshal(unstr)
|
||||
if err != nil {
|
||||
t.Errorf("Error when marshaling unstructured: %v", err)
|
||||
return
|
||||
}
|
||||
unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
|
||||
err = json.Unmarshal(data, &unmarshalledObj)
|
||||
if err != nil {
|
||||
t.Errorf("Error when unmarshaling to object: %v", err)
|
||||
return
|
||||
}
|
||||
if !apiequality.Semantic.DeepEqual(item, unmarshalledObj) {
|
||||
t.Errorf("Object changed during JSON operations, diff: %v", diff.ObjectReflectDiff(item, unmarshalledObj))
|
||||
return
|
||||
}
|
||||
|
||||
newUnstr, err := unstructured.DefaultConverter.ToUnstructured(item)
|
||||
if err != nil {
|
||||
t.Errorf("ToUnstructured failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
|
||||
err = unstructured.DefaultConverter.FromUnstructured(newUnstr, newObj)
|
||||
if err != nil {
|
||||
t.Errorf("FromUnstructured failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !apiequality.Semantic.DeepEqual(item, newObj) {
|
||||
t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(item, newObj))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
for groupKey, group := range testapi.Groups {
|
||||
for kind := range group.ExternalTypes() {
|
||||
if nonRoundTrippableTypes.Has(kind) {
|
||||
continue
|
||||
}
|
||||
t.Logf("Testing: %v in %v", kind, groupKey)
|
||||
for i := 0; i < 50; i++ {
|
||||
doRoundTrip(t, group, kind)
|
||||
if t.Failed() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkToFromUnstructured(b *testing.B) {
|
||||
items := benchmarkItems(b)
|
||||
size := len(items)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
unstr, err := unstructured.DefaultConverter.ToUnstructured(&items[i%size])
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
obj := v1.Pod{}
|
||||
if err := unstructured.DefaultConverter.FromUnstructured(unstr, &obj); err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkToFromUnstructuredViaJSON(b *testing.B) {
|
||||
items := benchmarkItems(b)
|
||||
size := len(items)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := json.Marshal(&items[i%size])
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
unstr := map[string]interface{}{}
|
||||
if err := json.Unmarshal(data, &unstr); err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
data, err = json.Marshal(unstr)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
obj := v1.Pod{}
|
||||
if err := json.Unmarshal(data, &obj); err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
Reference in New Issue
Block a user