diff --git a/pkg/proxy/apis/config/BUILD b/pkg/proxy/apis/config/BUILD index bc94d8d0620..6b9f48df8bc 100644 --- a/pkg/proxy/apis/config/BUILD +++ b/pkg/proxy/apis/config/BUILD @@ -1,9 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", @@ -40,3 +37,13 @@ filegroup( ], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["register_test.go"], + embed = [":go_default_library"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/component-base/config/testing:go_default_library", + ], +) diff --git a/pkg/proxy/apis/config/register_test.go b/pkg/proxy/apis/config/register_test.go new file mode 100644 index 00000000000..adb131ee003 --- /dev/null +++ b/pkg/proxy/apis/config/register_test.go @@ -0,0 +1,42 @@ +/* +Copyright 2019 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 config + +import ( + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + componentconfigtesting "k8s.io/component-base/config/testing" +) + +func TestComponentConfigSetup(t *testing.T) { + pkginfo := &componentconfigtesting.ComponentConfigPackage{ + ComponentName: "kube-proxy", + GroupName: GroupName, + SchemeGroupVersion: SchemeGroupVersion, + AddToScheme: AddToScheme, + AllowedTags: map[reflect.Type]bool{ + reflect.TypeOf(metav1.TypeMeta{}): true, + reflect.TypeOf(metav1.Duration{}): true, + }, + } + + if err := componentconfigtesting.VerifyInternalTypePackage(pkginfo); err != nil { + t.Errorf("failed TestComponentConfigSetup for kube-proxy: %v", err) + } +} diff --git a/pkg/proxy/apis/config/scheme/BUILD b/pkg/proxy/apis/config/scheme/BUILD index d851e815194..87c3ddef435 100644 --- a/pkg/proxy/apis/config/scheme/BUILD +++ b/pkg/proxy/apis/config/scheme/BUILD @@ -31,9 +31,11 @@ filegroup( go_test( name = "go_default_test", srcs = ["scheme_test.go"], + data = glob(["testdata/**"]), embed = [":go_default_library"], deps = [ "//pkg/proxy/apis/config/fuzzer:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip:go_default_library", + "//staging/src/k8s.io/component-base/config/testing:go_default_library", ], ) diff --git a/pkg/proxy/apis/config/scheme/scheme_test.go b/pkg/proxy/apis/config/scheme/scheme_test.go index 1ad7786ebfa..50ba53e1225 100644 --- a/pkg/proxy/apis/config/scheme/scheme_test.go +++ b/pkg/proxy/apis/config/scheme/scheme_test.go @@ -20,9 +20,18 @@ import ( "testing" "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" + componentconfigtesting "k8s.io/component-base/config/testing" "k8s.io/kubernetes/pkg/proxy/apis/config/fuzzer" ) -func TestRoundTripTypes(t *testing.T) { +func TestRoundTripFuzzing(t *testing.T) { roundtrip.RoundTripTestForScheme(t, Scheme, fuzzer.Funcs) } + +func TestRoundTripYAML(t *testing.T) { + componentconfigtesting.RoundTripTest(t, Scheme, Codecs) +} + +func TestDefaults(t *testing.T) { + componentconfigtesting.DefaultingTest(t, Scheme, Codecs) +} diff --git a/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/after/__internal.yaml b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/after/__internal.yaml new file mode 100755 index 00000000000..c20883b95ac --- /dev/null +++ b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/after/__internal.yaml @@ -0,0 +1,39 @@ +BindAddress: "" +ClientConnection: + AcceptContentTypes: "" + Burst: 0 + ContentType: "" + Kubeconfig: "" + QPS: 0 +ClusterCIDR: "" +ConfigSyncPeriod: 0s +Conntrack: + MaxPerCore: null + Min: null + TCPCloseWaitTimeout: null + TCPEstablishedTimeout: null +EnableProfiling: false +FeatureGates: null +HealthzBindAddress: "" +HostnameOverride: "" +IPTables: + MasqueradeAll: false + MasqueradeBit: null + MinSyncPeriod: 0s + SyncPeriod: 0s +IPVS: + ExcludeCIDRs: null + MinSyncPeriod: 0s + Scheduler: "" + StrictARP: false + SyncPeriod: 0s +MetricsBindAddress: "" +Mode: "" +NodePortAddresses: null +OOMScoreAdj: null +PortRange: "" +UDPIdleTimeout: 0s +Winkernel: + EnableDSR: false + NetworkName: "" + SourceVip: "" diff --git a/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/after/v1alpha1.yaml b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/after/v1alpha1.yaml new file mode 100755 index 00000000000..20a467ec023 --- /dev/null +++ b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/after/v1alpha1.yaml @@ -0,0 +1,40 @@ +apiVersion: kubeproxy.config.k8s.io/v1alpha1 +bindAddress: 0.0.0.0 +clientConnection: + acceptContentTypes: "" + burst: 10 + contentType: application/vnd.kubernetes.protobuf + kubeconfig: "" + qps: 5 +clusterCIDR: "" +configSyncPeriod: 15m0s +conntrack: + maxPerCore: 32768 + min: 131072 + tcpCloseWaitTimeout: 1h0m0s + tcpEstablishedTimeout: 24h0m0s +enableProfiling: false +healthzBindAddress: 0.0.0.0:10256 +hostnameOverride: "" +iptables: + masqueradeAll: false + masqueradeBit: 14 + minSyncPeriod: 0s + syncPeriod: 30s +ipvs: + excludeCIDRs: null + minSyncPeriod: 0s + scheduler: "" + strictARP: false + syncPeriod: 30s +kind: KubeProxyConfiguration +metricsBindAddress: 127.0.0.1:10249 +mode: "" +nodePortAddresses: null +oomScoreAdj: -999 +portRange: "" +udpIdleTimeout: 250ms +winkernel: + enableDSR: false + networkName: "" + sourceVip: "" diff --git a/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/before/__internal.yaml b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/before/__internal.yaml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/before/v1alpha1.yaml b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/before/v1alpha1.yaml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/v1alpha1To__internal/empty.yaml b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/v1alpha1To__internal/empty.yaml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/v1alpha1To__internal/empty.yaml.after_roundtrip b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/v1alpha1To__internal/empty.yaml.after_roundtrip new file mode 100755 index 00000000000..341efcd3235 --- /dev/null +++ b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/v1alpha1To__internal/empty.yaml.after_roundtrip @@ -0,0 +1,39 @@ +BindAddress: 0.0.0.0 +ClientConnection: + AcceptContentTypes: "" + Burst: 10 + ContentType: application/vnd.kubernetes.protobuf + Kubeconfig: "" + QPS: 5 +ClusterCIDR: "" +ConfigSyncPeriod: 15m0s +Conntrack: + MaxPerCore: 32768 + Min: 131072 + TCPCloseWaitTimeout: 1h0m0s + TCPEstablishedTimeout: 24h0m0s +EnableProfiling: false +FeatureGates: {} +HealthzBindAddress: 0.0.0.0:10256 +HostnameOverride: "" +IPTables: + MasqueradeAll: false + MasqueradeBit: 14 + MinSyncPeriod: 0s + SyncPeriod: 30s +IPVS: + ExcludeCIDRs: null + MinSyncPeriod: 0s + Scheduler: "" + StrictARP: false + SyncPeriod: 30s +MetricsBindAddress: 127.0.0.1:10249 +Mode: "" +NodePortAddresses: null +OOMScoreAdj: -999 +PortRange: "" +UDPIdleTimeout: 250ms +Winkernel: + EnableDSR: false + NetworkName: "" + SourceVip: "" diff --git a/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/v1alpha1Tov1alpha1/empty.yaml b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/v1alpha1Tov1alpha1/empty.yaml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/v1alpha1Tov1alpha1/empty.yaml.after_roundtrip b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/v1alpha1Tov1alpha1/empty.yaml.after_roundtrip new file mode 100755 index 00000000000..20a467ec023 --- /dev/null +++ b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/v1alpha1Tov1alpha1/empty.yaml.after_roundtrip @@ -0,0 +1,40 @@ +apiVersion: kubeproxy.config.k8s.io/v1alpha1 +bindAddress: 0.0.0.0 +clientConnection: + acceptContentTypes: "" + burst: 10 + contentType: application/vnd.kubernetes.protobuf + kubeconfig: "" + qps: 5 +clusterCIDR: "" +configSyncPeriod: 15m0s +conntrack: + maxPerCore: 32768 + min: 131072 + tcpCloseWaitTimeout: 1h0m0s + tcpEstablishedTimeout: 24h0m0s +enableProfiling: false +healthzBindAddress: 0.0.0.0:10256 +hostnameOverride: "" +iptables: + masqueradeAll: false + masqueradeBit: 14 + minSyncPeriod: 0s + syncPeriod: 30s +ipvs: + excludeCIDRs: null + minSyncPeriod: 0s + scheduler: "" + strictARP: false + syncPeriod: 30s +kind: KubeProxyConfiguration +metricsBindAddress: 127.0.0.1:10249 +mode: "" +nodePortAddresses: null +oomScoreAdj: -999 +portRange: "" +udpIdleTimeout: 250ms +winkernel: + enableDSR: false + networkName: "" + sourceVip: "" diff --git a/staging/src/k8s.io/component-base/config/BUILD b/staging/src/k8s.io/component-base/config/BUILD index 7301ed294de..25c1c281586 100644 --- a/staging/src/k8s.io/component-base/config/BUILD +++ b/staging/src/k8s.io/component-base/config/BUILD @@ -24,6 +24,7 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", + "//staging/src/k8s.io/component-base/config/testing:all-srcs", "//staging/src/k8s.io/component-base/config/v1alpha1:all-srcs", "//staging/src/k8s.io/component-base/config/validation:all-srcs", ], diff --git a/staging/src/k8s.io/component-base/config/testing/BUILD b/staging/src/k8s.io/component-base/config/testing/BUILD new file mode 100644 index 00000000000..9fa750a1e50 --- /dev/null +++ b/staging/src/k8s.io/component-base/config/testing/BUILD @@ -0,0 +1,44 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "apigroup.go", + "defaulting.go", + "helpers.go", + "roundtrip.go", + ], + importmap = "k8s.io/kubernetes/vendor/k8s.io/component-base/config/testing", + importpath = "k8s.io/component-base/config/testing", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/api/apitesting/naming:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/github.com/google/go-cmp/cmp:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["apigroup_test.go"], + embed = [":go_default_library"], +) diff --git a/staging/src/k8s.io/component-base/config/testing/apigroup.go b/staging/src/k8s.io/component-base/config/testing/apigroup.go new file mode 100644 index 00000000000..2759f6524da --- /dev/null +++ b/staging/src/k8s.io/component-base/config/testing/apigroup.go @@ -0,0 +1,207 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package testing + +import ( + "fmt" + "reflect" + "regexp" + "strings" + + apinamingtest "k8s.io/apimachinery/pkg/api/apitesting/naming" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" +) + +// APIVersionRegexp is the regular expression that matches with valid apiversion +var APIVersionRegexp = regexp.MustCompile(`^v\d+((alpha|beta){1}\d+)?$`) + +// ComponentConfigPackage is used in APIGroup Testing +type ComponentConfigPackage struct { + ComponentName string + GroupName string + SchemeGroupVersion schema.GroupVersion + AddToScheme func(*runtime.Scheme) error + SkipTests sets.String + AllowedTags map[reflect.Type]bool + AllowedNonstandardJSONNames map[reflect.Type]string +} + +type testingFunc func(*runtime.Scheme, *ComponentConfigPackage) error + +const ( + verifyTagNaming = "verifyTagNaming" + verifyGroupNameSuffix = "verifyGroupNameSuffix" + verifyGroupNameMatch = "verifyGroupNameMatch" + verifyCorrectGroupName = "verifyCorrectGroupName" + verifyComponentConfigKindExists = "verifyComponentConfigKindExists" + verifyExternalAPIVersion = "verifyExternalAPIVersion" + verifyInternalAPIVersion = "verifyInternalAPIVersion" +) + +var testingFuncs = map[string]testingFunc{ + verifyTagNaming: verifyTagNamingFunc, + verifyGroupNameSuffix: verifyGroupNameSuffixFunc, + verifyGroupNameMatch: verifyGroupNameMatchFunc, + verifyCorrectGroupName: verifyCorrectGroupNameFunc, + verifyComponentConfigKindExists: verifyComponentConfigKindExistsFunc, +} + +// VerifyExternalTypePackage tests if external component config package is defined correctly +// Test tag naming (json name should match Go name) +// Test that GroupName has the k8s.io suffix +// Test that GroupName == SchemeGroupVersion.GroupName +// Test that the API version follows the right pattern and isn't internal +// Test that addKnownTypes and AddToScheme registers at least one type and doesn't error +// Test that the GroupName is named correctly (based on ComponentName), and there is a {Component}Configuration kind in the scheme +func VerifyExternalTypePackage(pkginfo *ComponentConfigPackage) error { + scheme, err := setup(pkginfo) + if err != nil { + return fmt.Errorf("test setup error: %v", err) + } + extraFns := map[string]testingFunc{ + verifyExternalAPIVersion: verifyExternalAPIVersionFunc, + } + return runFuncs(scheme, pkginfo, extraFns) +} + +// VerifyInternalTypePackage tests if internal component config package is defined correctly +// Test tag naming (no tags allowed) +// Test that GroupName has the k8s.io suffix +// Test that GroupName == SchemeGroupVersion.GroupName +// API version should be internal +// Test that addKnownTypes and AddToScheme registers at least one type and doesn't error +// Test that the GroupName is named correctly (based on ComponentName), and there is a {Component}Configuration kind in the scheme +func VerifyInternalTypePackage(pkginfo *ComponentConfigPackage) error { + scheme, err := setup(pkginfo) + if err != nil { + return fmt.Errorf("test setup error: %v", err) + } + extraFns := map[string]testingFunc{ + verifyInternalAPIVersion: verifyInternalAPIVersionFunc, + } + return runFuncs(scheme, pkginfo, extraFns) +} + +func setup(pkginfo *ComponentConfigPackage) (*runtime.Scheme, error) { + if len(pkginfo.ComponentName) == 0 || + len(pkginfo.GroupName) == 0 || + pkginfo.SchemeGroupVersion.Empty() || + pkginfo.AddToScheme == nil { + return nil, fmt.Errorf("invalid argument: not all parameters were passed correctly to the function") + } + + scheme := runtime.NewScheme() + if err := pkginfo.AddToScheme(scheme); err != nil { + return nil, fmt.Errorf("AddToScheme must not return an error: %v", err) + } + if len(scheme.AllKnownTypes()) == 0 { + return nil, fmt.Errorf("AddToScheme doesn't register any type") + } + return scheme, nil +} + +func runFuncs(scheme *runtime.Scheme, pkginfo *ComponentConfigPackage, extraFns map[string]testingFunc) error { + verifyFns := []testingFunc{} + for name, fn := range testingFuncs { + if pkginfo.SkipTests.Has(name) { + continue + } + verifyFns = append(verifyFns, fn) + } + for name, fn := range extraFns { + if pkginfo.SkipTests.Has(name) { + continue + } + verifyFns = append(verifyFns, fn) + } + errs := []error{} + for _, fn := range verifyFns { + if err := fn(scheme, pkginfo); err != nil { + errs = append(errs, err) + } + } + return errors.NewAggregate(errs) +} + +func verifyTagNamingFunc(scheme *runtime.Scheme, pkginfo *ComponentConfigPackage) error { + return apinamingtest.VerifyTagNaming(scheme, pkginfo.AllowedTags, pkginfo.AllowedNonstandardJSONNames) +} + +func verifyGroupNameSuffixFunc(scheme *runtime.Scheme, _ *ComponentConfigPackage) error { + return apinamingtest.VerifyGroupNames(scheme, sets.NewString()) +} + +func verifyGroupNameMatchFunc(_ *runtime.Scheme, pkginfo *ComponentConfigPackage) error { + if pkginfo.GroupName != pkginfo.SchemeGroupVersion.Group { + return fmt.Errorf("GroupName must be equal to SchemeGroupVersion.Group, GroupName: %v,SchemeGroupVersion.Group: %v", + pkginfo.GroupName, pkginfo.SchemeGroupVersion.Group) + } + return nil +} + +func verifyCorrectGroupNameFunc(_ *runtime.Scheme, pkginfo *ComponentConfigPackage) error { + desiredGroupName := fmt.Sprintf("%s.config.k8s.io", lowercaseWithoutDashes(pkginfo.ComponentName)) + if pkginfo.SchemeGroupVersion.Group != desiredGroupName { + return fmt.Errorf("got GroupName %q, want %q", pkginfo.SchemeGroupVersion.Group, desiredGroupName) + + } + return nil +} + +func verifyComponentConfigKindExistsFunc(scheme *runtime.Scheme, pkginfo *ComponentConfigPackage) error { + expectedKind := fmt.Sprintf("%sConfiguration", dashesToCapitalCase(pkginfo.ComponentName)) + expectedGVK := pkginfo.SchemeGroupVersion.WithKind(expectedKind) + if !scheme.Recognizes(expectedGVK) { + registeredKinds := sets.NewString() + for gvk := range scheme.AllKnownTypes() { + registeredKinds.Insert(gvk.Kind) + } + return fmt.Errorf("Kind %s not registered in the scheme, registered kinds are %v", expectedKind, registeredKinds.List()) + } + return nil +} + +func verifyExternalAPIVersionFunc(_ *runtime.Scheme, pkginfo *ComponentConfigPackage) error { + if !APIVersionRegexp.MatchString(pkginfo.SchemeGroupVersion.Version) { + return fmt.Errorf("invalid API version %q, must match %q", pkginfo.SchemeGroupVersion.Version, APIVersionRegexp.String()) + } + return nil +} + +func verifyInternalAPIVersionFunc(_ *runtime.Scheme, pkginfo *ComponentConfigPackage) error { + if pkginfo.SchemeGroupVersion.Version != runtime.APIVersionInternal { + return fmt.Errorf("internal API version must be %q, got %q", + runtime.APIVersionInternal, pkginfo.SchemeGroupVersion.Version) + } + return nil +} + +func lowercaseWithoutDashes(str string) string { + return strings.Replace(strings.ToLower(str), "-", "", -1) +} + +func dashesToCapitalCase(str string) string { + segments := strings.Split(str, "-") + result := "" + for _, segment := range segments { + result += strings.Title(segment) + } + return result +} diff --git a/staging/src/k8s.io/component-base/config/testing/apigroup_test.go b/staging/src/k8s.io/component-base/config/testing/apigroup_test.go new file mode 100644 index 00000000000..691d733f8f9 --- /dev/null +++ b/staging/src/k8s.io/component-base/config/testing/apigroup_test.go @@ -0,0 +1,73 @@ +/* +Copyright 2019 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" +) + +func TestAPIVersionRegexp(t *testing.T) { + testCases := []struct { + name string + apiversion string + expected bool + }{ + { + name: "v1", + apiversion: "v1", + expected: true, + }, + { + name: "v1alpha1", + apiversion: "v1alpha1", + expected: true, + }, + { + name: "v1beta1", + apiversion: "v1beta1", + expected: true, + }, + { + name: "doesn't start with v", + apiversion: "beta1", + expected: false, + }, + { + name: "doesn't end with digit", + apiversion: "v1alpha", + expected: false, + }, + { + name: "doesn't have digit after v", + apiversion: "valpha1", + expected: false, + }, + { + name: "both alpha beta", + apiversion: "v1alpha1beta1", + expected: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actual := APIVersionRegexp.MatchString(tc.apiversion) + if actual != tc.expected { + t.Errorf("APIVersionRegexp expected %v, got %v", tc.expected, actual) + } + }) + } +} diff --git a/staging/src/k8s.io/component-base/config/testing/defaulting.go b/staging/src/k8s.io/component-base/config/testing/defaulting.go new file mode 100644 index 00000000000..a8ffe263793 --- /dev/null +++ b/staging/src/k8s.io/component-base/config/testing/defaulting.go @@ -0,0 +1,51 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package testing + +import ( + "fmt" + "path/filepath" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" +) + +// DefaultingTest run defaulting tests for given scheme +func DefaultingTest(t *testing.T, scheme *runtime.Scheme, codecs serializer.CodecFactory) { + tc := GetDefaultingTestCases(scheme) + RunTestsOnYAMLData(t, scheme, tc, codecs) +} + +// GetDefaultingTestCases returns defaulting testcases for given scheme +func GetDefaultingTestCases(scheme *runtime.Scheme) []TestCase { + cases := []TestCase{} + for gvk := range scheme.AllKnownTypes() { + beforeDir := fmt.Sprintf("testdata/%s/before", gvk.Kind) + afterDir := fmt.Sprintf("testdata/%s/after", gvk.Kind) + filename := fmt.Sprintf("%s.yaml", gvk.Version) + + cases = append(cases, TestCase{ + name: fmt.Sprintf("default_%s", gvk.Version), + in: filepath.Join(beforeDir, filename), + inGVK: gvk, + out: filepath.Join(afterDir, filename), + outGV: gvk.GroupVersion(), + }) + } + return cases +} diff --git a/staging/src/k8s.io/component-base/config/testing/helpers.go b/staging/src/k8s.io/component-base/config/testing/helpers.go new file mode 100644 index 00000000000..9fa62bcd5e0 --- /dev/null +++ b/staging/src/k8s.io/component-base/config/testing/helpers.go @@ -0,0 +1,96 @@ +/* +Copyright 2019 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" + "io/ioutil" + "os" + "testing" + + "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" +) + +// RunTestsOnYAMLData decodes the yaml file from specified path, encodes the object and matches +// with expected yaml in specified path +func RunTestsOnYAMLData(t *testing.T, scheme *runtime.Scheme, tests []TestCase, codecs serializer.CodecFactory) { + for _, rt := range tests { + t.Run(rt.name, func(t *testing.T) { + obj, err := decodeTestData(rt.in, scheme, rt.inGVK, codecs) + if err != nil { + t.Fatal(err) + } + + const mediaType = runtime.ContentTypeYAML + info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType) + if !ok { + t.Errorf("unable to locate encoder -- %q is not a supported media type", mediaType) + } + + encoder := codecs.EncoderForVersion(info.Serializer, rt.outGV) + + actual, err := runtime.Encode(encoder, obj) + if err != nil { + t.Fatalf("failed to encode object: %v", err) + } + + expected, err := ioutil.ReadFile(rt.out) + if err != nil && !os.IsNotExist(err) { + t.Fatalf("couldn't read test data: %v", err) + } + + needsUpdate := false + if os.IsNotExist(err) { + needsUpdate = true + t.Error("couldn't find test data") + } else { + if !bytes.Equal(expected, actual) { + t.Errorf("Output does not match expected, diff (- want, + got):\n\tin: %s\n\tout: %s\n\tgroupversion: %s\n\tdiff: \n%s\n", + rt.in, rt.out, rt.outGV.String(), cmp.Diff(string(expected), string(actual))) + needsUpdate = true + } + } + if needsUpdate { + const updateEnvVar = "UPDATE_COMPONENTCONFIG_FIXTURE_DATA" + if os.Getenv(updateEnvVar) == "true" { + if err := ioutil.WriteFile(rt.out, actual, 0755); err != nil { + t.Fatal(err) + } + t.Logf("wrote expected test data... verify, commit, and rerun tests") + } else { + t.Logf("if the diff is expected because of a new type or a new field, re-run with %s=true to update the compatibility data", updateEnvVar) + } + } + }) + } +} + +func decodeTestData(path string, scheme *runtime.Scheme, gvk schema.GroupVersionKind, codecs serializer.CodecFactory) (runtime.Object, error) { + content, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + obj, _, err := codecs.DecoderToVersion(codecs.UniversalDecoder(), gvk.GroupVersion()).Decode(content, &gvk, nil) + if err != nil { + return nil, err + } + return obj, nil +} diff --git a/staging/src/k8s.io/component-base/config/testing/roundtrip.go b/staging/src/k8s.io/component-base/config/testing/roundtrip.go new file mode 100644 index 00000000000..4bcdb6cff32 --- /dev/null +++ b/staging/src/k8s.io/component-base/config/testing/roundtrip.go @@ -0,0 +1,92 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package testing + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/sets" +) + +// RoundTripTest runs roundtrip tests for given scheme +func RoundTripTest(t *testing.T, scheme *runtime.Scheme, codecs serializer.CodecFactory) { + tc := GetRoundtripTestCases(scheme, nil) + RunTestsOnYAMLData(t, scheme, tc, codecs) +} + +// TestCase defines a testcase for roundtrip and defaulting tests +type TestCase struct { + name, in, out string + inGVK schema.GroupVersionKind + outGV schema.GroupVersion +} + +// GetRoundtripTestCases returns the testcases for roundtrip testing for given scheme +func GetRoundtripTestCases(scheme *runtime.Scheme, disallowMarshalGroupVersions sets.String) []TestCase { + cases := []TestCase{} + versionsForKind := map[schema.GroupKind][]string{} + for gvk := range scheme.AllKnownTypes() { + versionsForKind[gvk.GroupKind()] = append(versionsForKind[gvk.GroupKind()], gvk.Version) + } + + for gk, versions := range versionsForKind { + for _, vin := range versions { + if vin == runtime.APIVersionInternal { + continue // Don't try to deserialize the internal version + } + for _, vout := range versions { + inGVK := schema.GroupVersionKind{Group: gk.Group, Version: vin, Kind: gk.Kind} + marshalGV := schema.GroupVersion{Group: gk.Group, Version: vout} + if disallowMarshalGroupVersions.Has(marshalGV.String()) { + continue // Don't marshal a gv that is blacklisted + } + testdir := filepath.Join("testdata", gk.Kind, fmt.Sprintf("%sTo%s", vin, vout)) + utilruntime.Must(filepath.Walk(testdir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + if info.Name() == fmt.Sprintf("%sTo%s", vin, vout) { + return nil + } + return filepath.SkipDir + } + if filepath.Ext(info.Name()) != ".yaml" { + return nil + } + cases = append(cases, TestCase{ + name: fmt.Sprintf("%sTo%s", vin, vout), + in: filepath.Join(testdir, info.Name()), + inGVK: inGVK, + out: filepath.Join(testdir, fmt.Sprintf("%s.after_roundtrip", info.Name())), + outGV: marshalGV, + }) + + return nil + })) + } + } + } + return cases +} diff --git a/staging/src/k8s.io/component-base/go.mod b/staging/src/k8s.io/component-base/go.mod index 611c0c952f6..d7916f458a0 100644 --- a/staging/src/k8s.io/component-base/go.mod +++ b/staging/src/k8s.io/component-base/go.mod @@ -6,6 +6,7 @@ go 1.12 require ( github.com/blang/semver v3.5.0+incompatible + github.com/google/go-cmp v0.3.0 github.com/prometheus/client_golang v1.0.0 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 github.com/prometheus/common v0.4.1 diff --git a/staging/src/k8s.io/kube-proxy/config/v1alpha1/BUILD b/staging/src/k8s.io/kube-proxy/config/v1alpha1/BUILD index e6f48d8ae4e..f290c33f0eb 100644 --- a/staging/src/k8s.io/kube-proxy/config/v1alpha1/BUILD +++ b/staging/src/k8s.io/kube-proxy/config/v1alpha1/BUILD @@ -1,4 +1,4 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", @@ -32,3 +32,10 @@ filegroup( tags = ["automanaged"], visibility = ["//visibility:public"], ) + +go_test( + name = "go_default_test", + srcs = ["register_test.go"], + embed = [":go_default_library"], + deps = ["//staging/src/k8s.io/component-base/config/testing:go_default_library"], +) diff --git a/staging/src/k8s.io/kube-proxy/config/v1alpha1/register_test.go b/staging/src/k8s.io/kube-proxy/config/v1alpha1/register_test.go new file mode 100644 index 00000000000..a2d500c91f9 --- /dev/null +++ b/staging/src/k8s.io/kube-proxy/config/v1alpha1/register_test.go @@ -0,0 +1,36 @@ +/* +Copyright 2019 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 v1alpha1 + +import ( + "testing" + + componentconfigtesting "k8s.io/component-base/config/testing" +) + +func TestComponentConfigSetup(t *testing.T) { + pkginfo := &componentconfigtesting.ComponentConfigPackage{ + ComponentName: "kube-proxy", + GroupName: GroupName, + SchemeGroupVersion: SchemeGroupVersion, + AddToScheme: AddToScheme, + } + + if err := componentconfigtesting.VerifyExternalTypePackage(pkginfo); err != nil { + t.Errorf("failed TestComponentConfigSetup: %v", err) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 50f23cb098d..7bd5bf5b759 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1657,6 +1657,7 @@ k8s.io/component-base/cli/flag k8s.io/component-base/cli/globalflag k8s.io/component-base/codec k8s.io/component-base/config +k8s.io/component-base/config/testing k8s.io/component-base/config/v1alpha1 k8s.io/component-base/config/validation k8s.io/component-base/featuregate