From 0334a34e4af9b56ffa4d8fe17514c931c69db84b Mon Sep 17 00:00:00 2001 From: noqcks Date: Mon, 30 Jul 2018 16:22:22 -0400 Subject: [PATCH] Add validation for kube-scheduler adding validation for componentconfig adding validation to cmd kube-scheduler Add support for ipv6 in IsValidSocketAddr function updating copyright date in componentconfig/validation/validation.go updating copyright date in componentconfig/validation/validation_test.go adding validation for cli options adding BUILD files updating validate function to return []errors in cmd/kube-scheduler ok, really returning []error this time adding comments for exported componentconfig Validation functions silly me, not checking structs along the way :'( refactor to avoid else statement moving policy nil check up one function rejigging some deprecated cmd validations stumbling my way around validation slowly but surely updating according to review from @bsalamat - not validating leader election config unless leader election is enabled - leader election time values cannot be zero - removing validation for KubeConfigFile - removing validation for scheduler policy leader elect options should be non-negative adding test cases for renewDeadline and leaseDuration being zero fixing logic in componentconfig validation :sweat_smile: removing KubeConfigFile reference from tests as it was removed in master https://github.com/kubernetes/kubernetes/commit/2ff9bd6699ccdfb1f46c4aeda01518990ff71eda removing bogus space after var assignment adding more tests for componentconfig based on feedback making updates to validation because types were moved on master update bazel build adding validation for staging/apimachinery adding validation for staging/apiserver adding fieldPaths for staging validations moving staging validations out of componentconfig updating test case scenario for staging/apimachinery ./hack/update-bazel.sh moving kube-scheduler validations from componentconfig ./hack/update-bazel.sh removing non-negative check for QPS resourceLock required adding HardPodAffinitySymmetricWeight 0-100 range to cmd flag help section --- cmd/kube-scheduler/app/options/BUILD | 3 + cmd/kube-scheduler/app/options/deprecated.go | 12 +- .../app/options/deprecated_test.go | 55 +++++++ cmd/kube-scheduler/app/options/options.go | 4 + pkg/scheduler/apis/config/BUILD | 1 + pkg/scheduler/apis/config/validation/BUILD | 41 +++++ .../apis/config/validation/validation.go | 61 ++++++++ .../apis/config/validation/validation_test.go | 140 ++++++++++++++++++ .../k8s.io/apimachinery/pkg/apis/config/BUILD | 1 + .../pkg/apis/config/validation/BUILD | 37 +++++ .../pkg/apis/config/validation/validation.go | 31 ++++ .../apis/config/validation/validation_test.go | 66 +++++++++ .../pkg/util/validation/validation.go | 16 ++ .../pkg/util/validation/validation_test.go | 28 ++++ .../k8s.io/apiserver/pkg/apis/config/BUILD | 1 + .../pkg/apis/config/validation/BUILD | 38 +++++ .../pkg/apis/config/validation/validation.go | 46 ++++++ .../apis/config/validation/validation_test.go | 112 ++++++++++++++ 18 files changed, 688 insertions(+), 5 deletions(-) create mode 100644 cmd/kube-scheduler/app/options/deprecated_test.go create mode 100644 pkg/scheduler/apis/config/validation/BUILD create mode 100644 pkg/scheduler/apis/config/validation/validation.go create mode 100644 pkg/scheduler/apis/config/validation/validation_test.go create mode 100644 staging/src/k8s.io/apimachinery/pkg/apis/config/validation/BUILD create mode 100644 staging/src/k8s.io/apimachinery/pkg/apis/config/validation/validation.go create mode 100644 staging/src/k8s.io/apimachinery/pkg/apis/config/validation/validation_test.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/config/validation/BUILD create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/config/validation/validation.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/config/validation/validation_test.go diff --git a/cmd/kube-scheduler/app/options/BUILD b/cmd/kube-scheduler/app/options/BUILD index d4dbea230c..ebe68f0834 100644 --- a/cmd/kube-scheduler/app/options/BUILD +++ b/cmd/kube-scheduler/app/options/BUILD @@ -17,6 +17,7 @@ go_library( "//pkg/scheduler/apis/config:go_default_library", "//pkg/scheduler/apis/config/scheme:go_default_library", "//pkg/scheduler/apis/config/v1alpha1:go_default_library", + "//pkg/scheduler/apis/config/validation:go_default_library", "//pkg/scheduler/factory:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/config:go_default_library", @@ -24,6 +25,7 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/uuid:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//staging/src/k8s.io/apiserver/pkg/server/options:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//staging/src/k8s.io/client-go/informers:go_default_library", @@ -58,6 +60,7 @@ filegroup( go_test( name = "go_default_test", srcs = [ + "deprecated_test.go", "insecure_serving_test.go", "options_test.go", ], diff --git a/cmd/kube-scheduler/app/options/deprecated.go b/cmd/kube-scheduler/app/options/deprecated.go index 99fbeff6ab..756bc3368b 100644 --- a/cmd/kube-scheduler/app/options/deprecated.go +++ b/cmd/kube-scheduler/app/options/deprecated.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/spf13/pflag" + "k8s.io/apimachinery/pkg/util/validation/field" kubeschedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config" "k8s.io/kubernetes/pkg/scheduler/factory" ) @@ -43,7 +44,6 @@ func (o *DeprecatedOptions) AddFlags(fs *pflag.FlagSet, cfg *kubeschedulerconfig } // TODO: unify deprecation mechanism, string prefix or MarkDeprecated (the latter hides the flag. We also don't want that). - fs.StringVar(&o.AlgorithmProvider, "algorithm-provider", o.AlgorithmProvider, "DEPRECATED: the scheduling algorithm provider to use, one of: "+factory.ListAlgorithmProviders()) fs.StringVar(&o.PolicyConfigFile, "policy-config-file", o.PolicyConfigFile, "DEPRECATED: file with scheduler policy configuration. This file is used if policy ConfigMap is not provided or --use-legacy-policy-config=true") usage := fmt.Sprintf("DEPRECATED: name of the ConfigMap object that contains scheduler's policy configuration. It must exist in the system namespace before scheduler initialization if --use-legacy-policy-config=false. The config must be provided as the value of an element in 'Data' map with the key='%v'", kubeschedulerconfig.SchedulerPolicyConfigMapKey) @@ -63,7 +63,7 @@ func (o *DeprecatedOptions) AddFlags(fs *pflag.FlagSet, cfg *kubeschedulerconfig fs.Int32Var(&cfg.HardPodAffinitySymmetricWeight, "hard-pod-affinity-symmetric-weight", cfg.HardPodAffinitySymmetricWeight, "RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule corresponding "+ - "to every RequiredDuringScheduling affinity rule. --hard-pod-affinity-symmetric-weight represents the weight of implicit PreferredDuringScheduling affinity rule.") + "to every RequiredDuringScheduling affinity rule. --hard-pod-affinity-symmetric-weight represents the weight of implicit PreferredDuringScheduling affinity rule. Must be in the range 0-100.") fs.MarkDeprecated("hard-pod-affinity-symmetric-weight", "This option was moved to the policy configuration file") fs.StringVar(&cfg.FailureDomains, "failure-domains", cfg.FailureDomains, "Indicate the \"all topologies\" set for an empty topologyKey when it's used for PreferredDuringScheduling pod anti-affinity.") fs.MarkDeprecated("failure-domains", "Doesn't have any effect. Will be removed in future version.") @@ -71,10 +71,12 @@ func (o *DeprecatedOptions) AddFlags(fs *pflag.FlagSet, cfg *kubeschedulerconfig // Validate validates the deprecated scheduler options. func (o *DeprecatedOptions) Validate() []error { - if o == nil { - return nil + var errs []error + + if o.UseLegacyPolicyConfig && len(o.PolicyConfigFile) == 0 { + errs = append(errs, field.Required(field.NewPath("policyConfigFile"), "required when --use-legacy-policy-config is true")) } - return nil + return errs } // ApplyTo sets cfg.AlgorithmSource from flags passed on the command line in the following precedence order: diff --git a/cmd/kube-scheduler/app/options/deprecated_test.go b/cmd/kube-scheduler/app/options/deprecated_test.go new file mode 100644 index 0000000000..15f781edfe --- /dev/null +++ b/cmd/kube-scheduler/app/options/deprecated_test.go @@ -0,0 +1,55 @@ +/* +Copyright 2018 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 options + +import ( + "testing" +) + +func TestValidateDeprecatedKubeSchedulerConfiguration(t *testing.T) { + scenarios := map[string]struct { + expectedToFail bool + config *DeprecatedOptions + }{ + "good": { + expectedToFail: false, + config: &DeprecatedOptions{ + PolicyConfigFile: "/some/file", + UseLegacyPolicyConfig: true, + AlgorithmProvider: "", + }, + }, + "bad-policy-config-file-null": { + expectedToFail: true, + config: &DeprecatedOptions{ + PolicyConfigFile: "", + UseLegacyPolicyConfig: true, + AlgorithmProvider: "", + }, + }, + } + + for name, scenario := range scenarios { + errs := scenario.config.Validate() + if len(errs) == 0 && scenario.expectedToFail { + t.Errorf("Unexpected success for scenario: %s", name) + } + if len(errs) > 0 && !scenario.expectedToFail { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) + } + } +} diff --git a/cmd/kube-scheduler/app/options/options.go b/cmd/kube-scheduler/app/options/options.go index 15cbe66cee..20768465ec 100644 --- a/cmd/kube-scheduler/app/options/options.go +++ b/cmd/kube-scheduler/app/options/options.go @@ -47,6 +47,7 @@ import ( "k8s.io/kubernetes/pkg/client/leaderelectionconfig" kubeschedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config" kubeschedulerscheme "k8s.io/kubernetes/pkg/scheduler/apis/config/scheme" + "k8s.io/kubernetes/pkg/scheduler/apis/config/validation" "k8s.io/kubernetes/pkg/scheduler/factory" ) @@ -185,6 +186,9 @@ func (o *Options) ApplyTo(c *schedulerappconfig.Config) error { func (o *Options) Validate() []error { var errs []error + if err := validation.ValidateKubeSchedulerConfiguration(&o.ComponentConfig).ToAggregate(); err != nil { + errs = append(errs, err.Errors()...) + } errs = append(errs, o.SecureServing.Validate()...) errs = append(errs, o.CombinedInsecureServing.Validate()...) errs = append(errs, o.Authentication.Validate()...) diff --git a/pkg/scheduler/apis/config/BUILD b/pkg/scheduler/apis/config/BUILD index cfeee9de9d..f5b97f3946 100644 --- a/pkg/scheduler/apis/config/BUILD +++ b/pkg/scheduler/apis/config/BUILD @@ -32,6 +32,7 @@ filegroup( ":package-srcs", "//pkg/scheduler/apis/config/scheme:all-srcs", "//pkg/scheduler/apis/config/v1alpha1:all-srcs", + "//pkg/scheduler/apis/config/validation:all-srcs", ], tags = ["automanaged"], visibility = ["//visibility:public"], diff --git a/pkg/scheduler/apis/config/validation/BUILD b/pkg/scheduler/apis/config/validation/BUILD new file mode 100644 index 0000000000..8af91c1804 --- /dev/null +++ b/pkg/scheduler/apis/config/validation/BUILD @@ -0,0 +1,41 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["validation.go"], + importpath = "k8s.io/kubernetes/pkg/scheduler/apis/config/validation", + visibility = ["//visibility:public"], + deps = [ + "//pkg/scheduler/apis/config:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/config/validation:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/apis/config/validation:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["validation_test.go"], + embed = [":go_default_library"], + deps = [ + "//pkg/scheduler/apis/config:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/config:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/apis/config: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"], +) diff --git a/pkg/scheduler/apis/config/validation/validation.go b/pkg/scheduler/apis/config/validation/validation.go new file mode 100644 index 0000000000..679a448148 --- /dev/null +++ b/pkg/scheduler/apis/config/validation/validation.go @@ -0,0 +1,61 @@ +/* +Copyright 2018 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 validation + +import ( + apimachinery "k8s.io/apimachinery/pkg/apis/config/validation" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" + apiserver "k8s.io/apiserver/pkg/apis/config/validation" + "k8s.io/kubernetes/pkg/scheduler/apis/config" +) + +// ValidateKubeSchedulerConfiguration ensures validation of the KubeSchedulerConfiguration struct +func ValidateKubeSchedulerConfiguration(cc *config.KubeSchedulerConfiguration) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, apimachinery.ValidateClientConnectionConfiguration(&cc.ClientConnection, field.NewPath("clientConnection"))...) + allErrs = append(allErrs, ValidateKubeSchedulerLeaderElectionConfiguration(&cc.LeaderElection, field.NewPath("leaderElection"))...) + if len(cc.SchedulerName) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("schedulerName"), "")) + } + for _, msg := range validation.IsValidSocketAddr(cc.HealthzBindAddress) { + allErrs = append(allErrs, field.Invalid(field.NewPath("healthzBindAddress"), cc.HealthzBindAddress, msg)) + } + for _, msg := range validation.IsValidSocketAddr(cc.MetricsBindAddress) { + allErrs = append(allErrs, field.Invalid(field.NewPath("metricsBindAddress"), cc.MetricsBindAddress, msg)) + } + if cc.HardPodAffinitySymmetricWeight < 0 || cc.HardPodAffinitySymmetricWeight > 100 { + allErrs = append(allErrs, field.Invalid(field.NewPath("hardPodAffinitySymmetricWeight"), cc.HardPodAffinitySymmetricWeight, "not in valid range 0-100")) + } + return allErrs +} + +// ValidateKubeSchedulerLeaderElectionConfiguration ensures validation of the KubeSchedulerLeaderElectionConfiguration struct +func ValidateKubeSchedulerLeaderElectionConfiguration(cc *config.KubeSchedulerLeaderElectionConfiguration, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if !cc.LeaderElectionConfiguration.LeaderElect { + return allErrs + } + allErrs = append(allErrs, apiserver.ValidateLeaderElectionConfiguration(&cc.LeaderElectionConfiguration, field.NewPath("leaderElectionConfiguration"))...) + if len(cc.LockObjectNamespace) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("lockObjectNamespace"), "")) + } + if len(cc.LockObjectName) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("lockObjectName"), "")) + } + return allErrs +} diff --git a/pkg/scheduler/apis/config/validation/validation_test.go b/pkg/scheduler/apis/config/validation/validation_test.go new file mode 100644 index 0000000000..4800741c32 --- /dev/null +++ b/pkg/scheduler/apis/config/validation/validation_test.go @@ -0,0 +1,140 @@ +/* +Copyright 2018 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 validation + +import ( + apimachinery "k8s.io/apimachinery/pkg/apis/config" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apiserver "k8s.io/apiserver/pkg/apis/config" + "k8s.io/kubernetes/pkg/scheduler/apis/config" + "testing" + "time" +) + +func TestValidateKubeSchedulerConfiguration(t *testing.T) { + validConfig := &config.KubeSchedulerConfiguration{ + SchedulerName: "me", + HealthzBindAddress: "0.0.0.0:10254", + MetricsBindAddress: "0.0.0.0:10254", + HardPodAffinitySymmetricWeight: 80, + ClientConnection: apimachinery.ClientConnectionConfiguration{ + AcceptContentTypes: "application/json", + ContentType: "application/json", + QPS: 10, + Burst: 10, + }, + AlgorithmSource: config.SchedulerAlgorithmSource{ + Policy: &config.SchedulerPolicySource{ + ConfigMap: &config.SchedulerPolicyConfigMapSource{ + Namespace: "name", + Name: "name", + }, + }, + }, + LeaderElection: config.KubeSchedulerLeaderElectionConfiguration{ + LockObjectNamespace: "name", + LockObjectName: "name", + LeaderElectionConfiguration: apiserver.LeaderElectionConfiguration{ + ResourceLock: "configmap", + LeaderElect: true, + LeaseDuration: metav1.Duration{Duration: 30 * time.Second}, + RenewDeadline: metav1.Duration{Duration: 15 * time.Second}, + RetryPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + }, + } + + HardPodAffinitySymmetricWeightGt100 := validConfig.DeepCopy() + HardPodAffinitySymmetricWeightGt100.HardPodAffinitySymmetricWeight = 120 + + HardPodAffinitySymmetricWeightLt0 := validConfig.DeepCopy() + HardPodAffinitySymmetricWeightLt0.HardPodAffinitySymmetricWeight = -1 + + lockObjectNameNotSet := validConfig.DeepCopy() + lockObjectNameNotSet.LeaderElection.LockObjectName = "" + + lockObjectNamespaceNotSet := validConfig.DeepCopy() + lockObjectNamespaceNotSet.LeaderElection.LockObjectNamespace = "" + + metricsBindAddrHostInvalid := validConfig.DeepCopy() + metricsBindAddrHostInvalid.MetricsBindAddress = "0.0.0.0.0:9090" + + metricsBindAddrPortInvalid := validConfig.DeepCopy() + metricsBindAddrPortInvalid.MetricsBindAddress = "0.0.0.0:909090" + + healthzBindAddrHostInvalid := validConfig.DeepCopy() + healthzBindAddrHostInvalid.HealthzBindAddress = "0.0.0.0.0:9090" + + healthzBindAddrPortInvalid := validConfig.DeepCopy() + healthzBindAddrPortInvalid.HealthzBindAddress = "0.0.0.0:909090" + + enableContentProfilingSetWithoutEnableProfiling := validConfig.DeepCopy() + enableContentProfilingSetWithoutEnableProfiling.EnableProfiling = false + enableContentProfilingSetWithoutEnableProfiling.EnableContentionProfiling = true + + scenarios := map[string]struct { + expectedToFail bool + config *config.KubeSchedulerConfiguration + }{ + "good": { + expectedToFail: false, + config: validConfig, + }, + "bad-lock-object-names-not-set": { + expectedToFail: true, + config: lockObjectNameNotSet, + }, + "bad-lock-object-namespace-not-set": { + expectedToFail: true, + config: lockObjectNamespaceNotSet, + }, + "bad-healthz-port-invalid": { + expectedToFail: true, + config: healthzBindAddrPortInvalid, + }, + "bad-healthz-host-invalid": { + expectedToFail: true, + config: healthzBindAddrHostInvalid, + }, + "bad-metrics-port-invalid": { + expectedToFail: true, + config: metricsBindAddrPortInvalid, + }, + "bad-metrics-host-invalid": { + expectedToFail: true, + config: metricsBindAddrHostInvalid, + }, + "bad-hard-pod-affinity-symmetric-weight-lt-0": { + expectedToFail: true, + config: HardPodAffinitySymmetricWeightGt100, + }, + "bad-hard-pod-affinity-symmetric-weight-gt-100": { + expectedToFail: true, + config: HardPodAffinitySymmetricWeightLt0, + }, + } + + for name, scenario := range scenarios { + errs := ValidateKubeSchedulerConfiguration(scenario.config) + if len(errs) == 0 && scenario.expectedToFail { + t.Errorf("Unexpected success for scenario: %s", name) + } + if len(errs) > 0 && !scenario.expectedToFail { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) + } + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/config/BUILD b/staging/src/k8s.io/apimachinery/pkg/apis/config/BUILD index 9a8c653360..45016d21ac 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/config/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apis/config/BUILD @@ -24,6 +24,7 @@ filegroup( srcs = [ ":package-srcs", "//staging/src/k8s.io/apimachinery/pkg/apis/config/v1alpha1:all-srcs", + "//staging/src/k8s.io/apimachinery/pkg/apis/config/validation:all-srcs", ], tags = ["automanaged"], visibility = ["//visibility:public"], diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/config/validation/BUILD b/staging/src/k8s.io/apimachinery/pkg/apis/config/validation/BUILD new file mode 100644 index 0000000000..f84ca74498 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/config/validation/BUILD @@ -0,0 +1,37 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["validation.go"], + importmap = "k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/config/validation", + importpath = "k8s.io/apimachinery/pkg/apis/config/validation", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/apis/config:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["validation_test.go"], + embed = [":go_default_library"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/apis/config:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/validation/field: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"], +) diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/config/validation/validation.go b/staging/src/k8s.io/apimachinery/pkg/apis/config/validation/validation.go new file mode 100644 index 0000000000..dba3767744 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/config/validation/validation.go @@ -0,0 +1,31 @@ +/* +Copyright 2018 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 validation + +import ( + "k8s.io/apimachinery/pkg/apis/config" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// ValidateClientConnectionConfiguration ensures validation of the ClientConnectionConfiguration struct +func ValidateClientConnectionConfiguration(cc *config.ClientConnectionConfiguration, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if cc.Burst < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("burst"), cc.Burst, "must be non-negative")) + } + return allErrs +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/config/validation/validation_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/config/validation/validation_test.go new file mode 100644 index 0000000000..dfa0376740 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/config/validation/validation_test.go @@ -0,0 +1,66 @@ +/* +Copyright 2018 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 validation + +import ( + "k8s.io/apimachinery/pkg/apis/config" + "k8s.io/apimachinery/pkg/util/validation/field" + "testing" +) + +func TestValidateClientConnectionConfiguration(t *testing.T) { + validConfig := &config.ClientConnectionConfiguration{ + AcceptContentTypes: "application/json", + ContentType: "application/json", + QPS: 10, + Burst: 10, + } + + qpsLessThanZero := validConfig.DeepCopy() + qpsLessThanZero.QPS = -1 + + burstLessThanZero := validConfig.DeepCopy() + burstLessThanZero.Burst = -1 + + scenarios := map[string]struct { + expectedToFail bool + config *config.ClientConnectionConfiguration + }{ + "good": { + expectedToFail: false, + config: validConfig, + }, + "good-qps-less-than-zero": { + expectedToFail: false, + config: qpsLessThanZero, + }, + "bad-burst-less-then-zero": { + expectedToFail: true, + config: burstLessThanZero, + }, + } + + for name, scenario := range scenarios { + errs := ValidateClientConnectionConfiguration(scenario.config, field.NewPath("clientConnectionConfiguration")) + if len(errs) == 0 && scenario.expectedToFail { + t.Errorf("Unexpected success for scenario: %s", name) + } + if len(errs) > 0 && !scenario.expectedToFail { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) + } + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go index 7da6a17d99..e0d1715420 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go @@ -21,6 +21,7 @@ import ( "math" "net" "regexp" + "strconv" "strings" "k8s.io/apimachinery/pkg/util/validation/field" @@ -389,3 +390,18 @@ func hasChDirPrefix(value string) []string { } return errs } + +// IsSocketAddr checks that a string conforms is a valid socket address +// as defined in RFC 789. (e.g 0.0.0.0:10254 or [::]:10254)) +func IsValidSocketAddr(value string) []string { + var errs []string + ip, port, err := net.SplitHostPort(value) + if err != nil { + return append(errs, "must be a valid socket address format, (e.g. 0.0.0.0:10254 or [::]:10254)") + return errs + } + portInt, _ := strconv.Atoi(port) + errs = append(errs, IsValidPortNum(portInt)...) + errs = append(errs, IsValidIP(ip)...) + return errs +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go index 4c628bbc44..b3892e1c9c 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go @@ -511,3 +511,31 @@ func TestIsFullyQualifiedName(t *testing.T) { } } } + +func TestIsValidSocketAddr(t *testing.T) { + goodValues := []string{ + "0.0.0.0:10254", + "127.0.0.1:8888", + "[2001:db8:1f70::999:de8:7648:6e8]:10254", + "[::]:10254", + } + for _, val := range goodValues { + if errs := IsValidSocketAddr(val); len(errs) != 0 { + t.Errorf("expected no errors for %q: %v", val, errs) + } + } + + badValues := []string{ + "0.0.0.0.0:2020", + "0.0.0.0", + "6.6.6.6:909090", + "2001:db8:1f70::999:de8:7648:6e8:87567:102545", + "", + "*", + } + for _, val := range badValues { + if errs := IsValidSocketAddr(val); len(errs) == 0 { + t.Errorf("expected errors for %q", val) + } + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/config/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/config/BUILD index 4c6d33d75b..eadb944d51 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/config/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/apis/config/BUILD @@ -25,6 +25,7 @@ filegroup( srcs = [ ":package-srcs", "//staging/src/k8s.io/apiserver/pkg/apis/config/v1alpha1:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/apis/config/validation:all-srcs", ], tags = ["automanaged"], visibility = ["//visibility:public"], diff --git a/staging/src/k8s.io/apiserver/pkg/apis/config/validation/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/config/validation/BUILD new file mode 100644 index 0000000000..e66a553c3c --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/config/validation/BUILD @@ -0,0 +1,38 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["validation.go"], + importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/apis/config/validation", + importpath = "k8s.io/apiserver/pkg/apis/config/validation", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/apis/config:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["validation_test.go"], + embed = [":go_default_library"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/apis/config: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"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/config/validation/validation.go b/staging/src/k8s.io/apiserver/pkg/apis/config/validation/validation.go new file mode 100644 index 0000000000..00cadf1013 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/config/validation/validation.go @@ -0,0 +1,46 @@ +/* +Copyright 2018 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 validation + +import ( + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/apis/config" +) + +// ValidateLeaderElectionConfiguration ensures validation of the LeaderElectionConfiguration struct +func ValidateLeaderElectionConfiguration(cc *config.LeaderElectionConfiguration, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if !cc.LeaderElect { + return allErrs + } + if cc.LeaseDuration.Duration <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("leaseDuration"), cc.LeaseDuration, "must be greater than zero")) + } + if cc.RenewDeadline.Duration <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("renewDeadline"), cc.LeaseDuration, "must be greater than zero")) + } + if cc.RetryPeriod.Duration <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("retryPeriod"), cc.RetryPeriod, "must be greater than zero")) + } + if cc.LeaseDuration.Duration < cc.RenewDeadline.Duration { + allErrs = append(allErrs, field.Invalid(fldPath.Child("leaseDuration"), cc.RenewDeadline, "LeaseDuration must be greater than RenewDeadline")) + } + if len(cc.ResourceLock) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceLock"), cc.RenewDeadline, "resourceLock is required")) + } + return allErrs +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/config/validation/validation_test.go b/staging/src/k8s.io/apiserver/pkg/apis/config/validation/validation_test.go new file mode 100644 index 0000000000..b55c9fb1ba --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/config/validation/validation_test.go @@ -0,0 +1,112 @@ +/* +Copyright 2018 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 validation + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/apis/config" + "testing" + "time" +) + +func TestValidateLeaderElectionConfiguration(t *testing.T) { + validConfig := &config.LeaderElectionConfiguration{ + ResourceLock: "configmap", + LeaderElect: true, + LeaseDuration: metav1.Duration{Duration: 30 * time.Second}, + RenewDeadline: metav1.Duration{Duration: 15 * time.Second}, + RetryPeriod: metav1.Duration{Duration: 5 * time.Second}, + } + + renewDeadlineExceedsLeaseDuration := validConfig.DeepCopy() + renewDeadlineExceedsLeaseDuration.RenewDeadline = metav1.Duration{Duration: 45 * time.Second} + + renewDeadlineZero := validConfig.DeepCopy() + renewDeadlineZero.RenewDeadline = metav1.Duration{Duration: 0 * time.Second} + + leaseDurationZero := validConfig.DeepCopy() + leaseDurationZero.LeaseDuration = metav1.Duration{Duration: 0 * time.Second} + + negativeValForRetryPeriod := validConfig.DeepCopy() + negativeValForRetryPeriod.RetryPeriod = metav1.Duration{Duration: -45 * time.Second} + + negativeValForLeaseDuration := validConfig.DeepCopy() + negativeValForLeaseDuration.LeaseDuration = metav1.Duration{Duration: -45 * time.Second} + + negativeValForRenewDeadline := validConfig.DeepCopy() + negativeValForRenewDeadline.RenewDeadline = metav1.Duration{Duration: -45 * time.Second} + + LeaderElectButLeaderElectNotEnabled := validConfig.DeepCopy() + LeaderElectButLeaderElectNotEnabled.LeaderElect = false + LeaderElectButLeaderElectNotEnabled.LeaseDuration = metav1.Duration{Duration: -45 * time.Second} + + resourceLockNotDefined := validConfig.DeepCopy() + resourceLockNotDefined.ResourceLock = "" + + scenarios := map[string]struct { + expectedToFail bool + config *config.LeaderElectionConfiguration + }{ + "good": { + expectedToFail: false, + config: validConfig, + }, + "good-dont-check-leader-config-if-not-enabled": { + expectedToFail: false, + config: LeaderElectButLeaderElectNotEnabled, + }, + "bad-renew-deadline-exceeds-lease-duration": { + expectedToFail: true, + config: renewDeadlineExceedsLeaseDuration, + }, + "bad-negative-value-for-retry-period": { + expectedToFail: true, + config: negativeValForRetryPeriod, + }, + "bad-negative-value-for-lease-duration": { + expectedToFail: true, + config: negativeValForLeaseDuration, + }, + "bad-negative-value-for-renew-deadline": { + expectedToFail: true, + config: negativeValForRenewDeadline, + }, + "bad-renew-deadline-zero": { + expectedToFail: true, + config: renewDeadlineZero, + }, + "bad-lease-duration-zero": { + expectedToFail: true, + config: leaseDurationZero, + }, + "bad-resource-lock-not-defined": { + expectedToFail: true, + config: resourceLockNotDefined, + }, + } + + for name, scenario := range scenarios { + errs := ValidateLeaderElectionConfiguration(scenario.config, field.NewPath("leaderElectionConfiguration")) + if len(errs) == 0 && scenario.expectedToFail { + t.Errorf("Unexpected success for scenario: %s", name) + } + if len(errs) > 0 && !scenario.expectedToFail { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) + } + } +}