diff --git a/hack/.golint_failures b/hack/.golint_failures index dd074d20ef6..2bc450c9b66 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -541,7 +541,6 @@ staging/src/k8s.io/apimachinery/pkg/apimachinery/announced staging/src/k8s.io/apimachinery/pkg/apimachinery/registered staging/src/k8s.io/apimachinery/pkg/apis/meta/fuzzer staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion -staging/src/k8s.io/apimachinery/pkg/apis/meta/v1 staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1 diff --git a/pkg/api/v1/BUILD b/pkg/api/v1/BUILD index b8b55580414..8b3b00eb994 100644 --- a/pkg/api/v1/BUILD +++ b/pkg/api/v1/BUILD @@ -46,10 +46,14 @@ go_test( "//pkg/api:go_default_library", "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", + "//pkg/api/testing:go_default_library", "//pkg/apis/extensions:go_default_library", + "//pkg/util/pointer:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/testing/fuzzer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", diff --git a/pkg/api/v1/conversion_test.go b/pkg/api/v1/conversion_test.go index dbb677b4b08..a8022c280f9 100644 --- a/pkg/api/v1/conversion_test.go +++ b/pkg/api/v1/conversion_test.go @@ -18,21 +18,26 @@ package v1_test import ( "encoding/json" + "math/rand" "net/url" "reflect" "testing" "time" "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/api/testing/fuzzer" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/legacyscheme" + kapitesting "k8s.io/kubernetes/pkg/api/testing" k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/apis/extensions" + utilpointer "k8s.io/kubernetes/pkg/util/pointer" // enforce that all types are installed _ "k8s.io/kubernetes/pkg/api/testapi" @@ -231,59 +236,112 @@ func TestResourceListConversion(t *testing.T) { func TestReplicationControllerConversion(t *testing.T) { // If we start with a RC, we should always have round-trip fidelity. - replicas := int32(1) - in := &v1.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - Namespace: "namespace", - }, - Spec: v1.ReplicationControllerSpec{ - Replicas: &replicas, - MinReadySeconds: 32, - Selector: map[string]string{"foo": "bar", "bar": "foo"}, - Template: &v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"foo": "bar", "bar": "foo"}, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "container", - Image: "image", + inputs := []*v1.ReplicationController{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + }, + Spec: v1.ReplicationControllerSpec{ + Replicas: utilpointer.Int32Ptr(1), + MinReadySeconds: 32, + Selector: map[string]string{"foo": "bar", "bar": "foo"}, + Template: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo": "bar", "bar": "foo"}, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container", + Image: "image", + }, }, }, }, }, - }, - Status: v1.ReplicationControllerStatus{ - Replicas: 1, - FullyLabeledReplicas: 2, - ReadyReplicas: 3, - AvailableReplicas: 4, - ObservedGeneration: 5, - Conditions: []v1.ReplicationControllerCondition{ - { - Type: v1.ReplicationControllerReplicaFailure, - Status: v1.ConditionTrue, - LastTransitionTime: metav1.NewTime(time.Unix(123456789, 0)), - Reason: "Reason", - Message: "Message", + Status: v1.ReplicationControllerStatus{ + Replicas: 1, + FullyLabeledReplicas: 2, + ReadyReplicas: 3, + AvailableReplicas: 4, + ObservedGeneration: 5, + Conditions: []v1.ReplicationControllerCondition{ + { + Type: v1.ReplicationControllerReplicaFailure, + Status: v1.ConditionTrue, + LastTransitionTime: metav1.NewTime(time.Unix(123456789, 0)), + Reason: "Reason", + Message: "Message", + }, }, }, }, } - in = roundTrip(t, in).(*v1.ReplicationController) - rs := &extensions.ReplicaSet{} - if err := k8s_api_v1.Convert_v1_ReplicationController_to_extensions_ReplicaSet(in, rs, nil); err != nil { - t.Fatalf("can't convert RC to RS: %v", err) + + // Add some fuzzed RCs. + apiObjectFuzzer := fuzzer.FuzzerFor(kapitesting.FuzzerFuncs, rand.NewSource(152), legacyscheme.Codecs) + for i := 0; i < 100; i++ { + rc := &v1.ReplicationController{} + apiObjectFuzzer.Fuzz(rc) + // Sometimes the fuzzer decides to leave Spec.Template nil. + // We can't support that because Spec.Template is not a pointer in RS, + // so it will round-trip as non-nil but empty. + if rc.Spec.Template == nil { + rc.Spec.Template = &v1.PodTemplateSpec{} + } + // Sometimes the fuzzer decides to insert an empty label key. + // This doesn't round-trip properly because it's invalid. + if rc.Spec.Selector != nil { + delete(rc.Spec.Selector, "") + } + inputs = append(inputs, rc) } - out := &v1.ReplicationController{} - if err := k8s_api_v1.Convert_extensions_ReplicaSet_to_v1_ReplicationController(rs, out, nil); err != nil { - t.Fatalf("can't convert RS to RC: %v", err) + + // Round-trip the input RCs before converting to RS. + for i := range inputs { + inputs[i] = roundTrip(t, inputs[i]).(*v1.ReplicationController) } - if !apiequality.Semantic.DeepEqual(in, out) { - instr, _ := json.MarshalIndent(in, "", " ") - outstr, _ := json.MarshalIndent(out, "", " ") - t.Errorf("RC-RS conversion round-trip failed:\nin:\n%s\nout:\n%s", instr, outstr) + + for _, in := range inputs { + rs := &extensions.ReplicaSet{} + // Use in.DeepCopy() to avoid sharing pointers with `in`. + if err := k8s_api_v1.Convert_v1_ReplicationController_to_extensions_ReplicaSet(in.DeepCopy(), rs, nil); err != nil { + t.Errorf("can't convert RC to RS: %v", err) + continue + } + // Round-trip RS before converting back to RC. + rs = roundTripRS(t, rs) + out := &v1.ReplicationController{} + if err := k8s_api_v1.Convert_extensions_ReplicaSet_to_v1_ReplicationController(rs, out, nil); err != nil { + t.Errorf("can't convert RS to RC: %v", err) + continue + } + if !apiequality.Semantic.DeepEqual(in, out) { + instr, _ := json.MarshalIndent(in, "", " ") + outstr, _ := json.MarshalIndent(out, "", " ") + t.Errorf("RC-RS conversion round-trip failed:\nin:\n%s\nout:\n%s", instr, outstr) + } } } + +func roundTripRS(t *testing.T, rs *extensions.ReplicaSet) *extensions.ReplicaSet { + codec := legacyscheme.Codecs.LegacyCodec(extensionsv1beta1.SchemeGroupVersion) + data, err := runtime.Encode(codec, rs) + if err != nil { + t.Errorf("%v\n %#v", err, rs) + return nil + } + obj2, err := runtime.Decode(codec, data) + if err != nil { + t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), rs) + return nil + } + obj3 := &extensions.ReplicaSet{} + err = legacyscheme.Scheme.Convert(obj2, obj3, nil) + if err != nil { + t.Errorf("%v\nSource: %#v", err, obj2) + return nil + } + return obj3 +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/BUILD b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/BUILD index 7354f5e2fa1..4a96c3f948c 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/BUILD @@ -93,3 +93,13 @@ filegroup( srcs = ["generated.proto"], visibility = ["//visibility:public"], ) + +go_test( + name = "go_default_xtest", + srcs = ["conversion_test.go"], + importpath = "k8s.io/apimachinery/pkg/apis/meta/v1_test", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion_test.go new file mode 100644 index 00000000000..bc591584ef5 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion_test.go @@ -0,0 +1,49 @@ +/* +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 v1_test + +import ( + "testing" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestMapToLabelSelectorRoundTrip(t *testing.T) { + // We should be able to round-trip a map-only selector through LabelSelector. + inputs := []map[string]string{ + nil, + {}, + {"one": "foo"}, + {"one": "foo", "two": "bar"}, + } + for _, in := range inputs { + ls := &v1.LabelSelector{} + if err := v1.Convert_map_to_unversioned_LabelSelector(&in, ls, nil); err != nil { + t.Errorf("Convert_map_to_unversioned_LabelSelector(%#v): %v", in, err) + continue + } + out := map[string]string{} + if err := v1.Convert_unversioned_LabelSelector_to_map(ls, &out, nil); err != nil { + t.Errorf("Convert_unversioned_LabelSelector_to_map(%#v): %v", ls, err) + continue + } + if !apiequality.Semantic.DeepEqual(in, out) { + t.Errorf("map-selector conversion round-trip failed: got %v; want %v", out, in) + } + } +}