From 637e28c2575623de5211fbf343a6c95894f64351 Mon Sep 17 00:00:00 2001 From: xunpan Date: Tue, 21 May 2019 02:27:44 -0400 Subject: [PATCH 1/5] add webhook framework code for kubefedconfig --- .../core/v1beta1/validation/validation.go | 5 + .../webhook/kubefedconfig/webhook.go | 122 ++++++++++++++++++ pkg/webhook/webhook.go | 2 + 3 files changed, 129 insertions(+) create mode 100644 pkg/controller/webhook/kubefedconfig/webhook.go diff --git a/pkg/apis/core/v1beta1/validation/validation.go b/pkg/apis/core/v1beta1/validation/validation.go index 945f117940..235e17265d 100644 --- a/pkg/apis/core/v1beta1/validation/validation.go +++ b/pkg/apis/core/v1beta1/validation/validation.go @@ -127,6 +127,11 @@ func validateEnumStrings(fldPath *field.Path, value string, accepted []string) f return field.ErrorList{field.NotSupported(fldPath, value, accepted)} } +func ValidateKubeFedConfig(object *v1beta1.KubeFedConfig) field.ErrorList { + allErrs := field.ErrorList{} + return allErrs +} + func ValidateFederatedTypeConfigStatus(status *v1beta1.FederatedTypeConfigStatus, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} diff --git a/pkg/controller/webhook/kubefedconfig/webhook.go b/pkg/controller/webhook/kubefedconfig/webhook.go new file mode 100644 index 0000000000..3cee26db81 --- /dev/null +++ b/pkg/controller/webhook/kubefedconfig/webhook.go @@ -0,0 +1,122 @@ +/* +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 kubefedconfig + +import ( + "encoding/json" + "net/http" + "strings" + "sync" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "k8s.io/klog" + + "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1" + "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1/validation" + "sigs.k8s.io/kubefed/pkg/controller/webhook" +) + +const ( + resourceName = "KubeFedConfig" + resourcePluralName = "kubefedconfigs" +) + +type KubeFedConfigValidationHook struct { + client dynamic.ResourceInterface + + lock sync.RWMutex + initialized bool +} + +func (a *KubeFedConfigValidationHook) ValidatingResource() (plural schema.GroupVersionResource, singular string) { + return webhook.NewValidatingResource(resourcePluralName), strings.ToLower(resourceName) +} + +func (a *KubeFedConfigValidationHook) Validate(admissionSpec *admissionv1beta1.AdmissionRequest) *admissionv1beta1.AdmissionResponse { + status := &admissionv1beta1.AdmissionResponse{} + + if webhook.Allowed(admissionSpec, resourcePluralName) { + status.Allowed = true + return status + } + + klog.V(4).Infof("Validating AdmissionRequest = %v", admissionSpec) + + admittingObject := &v1beta1.KubeFedConfig{} + err := json.Unmarshal(admissionSpec.Object.Raw, admittingObject) + if err != nil { + status.Allowed = false + status.Result = &metav1.Status{ + Status: metav1.StatusFailure, Code: http.StatusBadRequest, Reason: metav1.StatusReasonBadRequest, + Message: err.Error(), + } + return status + } + + a.lock.RLock() + defer a.lock.RUnlock() + if !a.initialized { + status.Allowed = false + status.Result = &metav1.Status{ + Status: metav1.StatusFailure, Code: http.StatusInternalServerError, Reason: metav1.StatusReasonInternalError, + Message: "not initialized", + } + return status + } + + errs := validation.ValidateKubeFedConfig(admittingObject) + if len(errs) != 0 { + status.Allowed = false + status.Result = &metav1.Status{ + Status: metav1.StatusFailure, Code: http.StatusForbidden, Reason: metav1.StatusReasonForbidden, + Message: errs.ToAggregate().Error(), + } + return status + } + + status.Allowed = true + return status +} + +func (a *KubeFedConfigValidationHook) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error { + a.lock.Lock() + defer a.lock.Unlock() + + a.initialized = true + + shallowClientConfigCopy := *kubeClientConfig + shallowClientConfigCopy.GroupVersion = &schema.GroupVersion{ + Group: v1beta1.SchemeGroupVersion.Group, + Version: v1beta1.SchemeGroupVersion.Version, + } + shallowClientConfigCopy.APIPath = "/apis" + dynamicClient, err := dynamic.NewForConfig(&shallowClientConfigCopy) + if err != nil { + return err + } + a.client = dynamicClient.Resource(schema.GroupVersionResource{ + Group: v1beta1.SchemeGroupVersion.Group, + Version: v1beta1.SchemeGroupVersion.Version, + Resource: resourceName, + }) + + return nil +} diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go index eed957d946..f0fd558326 100644 --- a/pkg/webhook/webhook.go +++ b/pkg/webhook/webhook.go @@ -25,12 +25,14 @@ import ( "sigs.k8s.io/kubefed/pkg/controller/webhook" "sigs.k8s.io/kubefed/pkg/controller/webhook/federatedtypeconfig" + "sigs.k8s.io/kubefed/pkg/controller/webhook/kubefedconfig" ) func NewWebhookCommand(stopChan <-chan struct{}) *cobra.Command { admissionHooks := []apiserver.AdmissionHook{ &federatedtypeconfig.FederatedTypeConfigValidationHook{}, &webhook.KubeFedClusterValidationHook{}, + &kubefedconfig.KubeFedConfigValidationHook{}, } cmd := server.NewCommandStartAdmissionServer(os.Stdout, os.Stderr, stopChan, admissionHooks...) From 4ecfec09bc94513b74c840123d4f679afa7cd4a6 Mon Sep 17 00:00:00 2001 From: xunpan Date: Tue, 21 May 2019 09:51:06 -0400 Subject: [PATCH 2/5] webhook validates kubefedconfig --- .../controllermanager/templates/webhook.yaml | 24 ++++++ .../core/v1beta1/validation/validation.go | 80 +++++++++++++++++-- .../webhook/kubefedconfig/webhook.go | 2 + 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/charts/kubefed/charts/controllermanager/templates/webhook.yaml b/charts/kubefed/charts/controllermanager/templates/webhook.yaml index eeb919007d..eeef0e2c56 100644 --- a/charts/kubefed/charts/controllermanager/templates/webhook.yaml +++ b/charts/kubefed/charts/controllermanager/templates/webhook.yaml @@ -52,6 +52,30 @@ webhooks: - "kubefedclusters" failurePolicy: Fail --- +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: ValidatingWebhookConfiguration +metadata: + name: "kubefedconfigs.core.kubefed.k8s.io" +webhooks: +- name: kubefedconfigs.core.kubefed.k8s.io + clientConfig: + service: + namespace: {{ .Release.Namespace | quote }} + name: kubefed-admission-webhook + path: /apis/admission.core.kubefed.k8s.io/v1beta1/kubefedconfigs + caBundle: {{ b64enc $ca.Cert | quote }} + rules: + - operations: + - "CREATE" + - "UPDATE" + apiGroups: + - "core.kubefed.k8s.io" + apiVersions: + - "v1beta1" + resources: + - "kubefedconfigs" + failurePolicy: Fail +--- apiVersion: v1 kind: Secret metadata: diff --git a/pkg/apis/core/v1beta1/validation/validation.go b/pkg/apis/core/v1beta1/validation/validation.go index 235e17265d..7fdde7d9ca 100644 --- a/pkg/apis/core/v1beta1/validation/validation.go +++ b/pkg/apis/core/v1beta1/validation/validation.go @@ -18,14 +18,18 @@ package validation import ( "strings" + "time" apiextv1b1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apimachineryval "k8s.io/apimachinery/pkg/api/validation" valutil "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/client-go/tools/leaderelection" + "k8s.io/klog" "sigs.k8s.io/kubefed/pkg/apis/core/typeconfig" "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1" + "sigs.k8s.io/kubefed/pkg/features" ) func ValidateFederatedTypeConfig(obj *v1beta1.FederatedTypeConfig, statusSubResource bool) field.ErrorList { @@ -127,11 +131,6 @@ func validateEnumStrings(fldPath *field.Path, value string, accepted []string) f return field.ErrorList{field.NotSupported(fldPath, value, accepted)} } -func ValidateKubeFedConfig(object *v1beta1.KubeFedConfig) field.ErrorList { - allErrs := field.ErrorList{} - return allErrs -} - func ValidateFederatedTypeConfigStatus(status *v1beta1.FederatedTypeConfigStatus, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} @@ -144,6 +143,77 @@ func ValidateFederatedTypeConfigStatus(status *v1beta1.FederatedTypeConfigStatus return allErrs } +func ValidateKubeFedConfig(kubeFedConfig *v1beta1.KubeFedConfig) field.ErrorList { + klog.V(2).Infof("Validating KubeFedConfig %q", kubeFedConfig.Name) + + allErrs := field.ErrorList{} + + spec := kubeFedConfig.Spec + specPath := field.NewPath("spec") + + duration := spec.ControllerDuration + durationPath := specPath.Child("controllerDuration") + allErrs = append(allErrs, validateGreaterThan0(durationPath.Child("availableDelay"), int64(duration.AvailableDelay.Duration))...) + allErrs = append(allErrs, validateGreaterThan0(durationPath.Child("unavailableDelay"), int64(duration.UnavailableDelay.Duration))...) + + elect := spec.LeaderElect + electPath := specPath.Child("leaderElect") + allErrs = append(allErrs, validateGreaterThan0(electPath.Child("leaseDuration"), int64(elect.LeaseDuration.Duration))...) + allErrs = append(allErrs, validateGreaterThan0(electPath.Child("renewDeadline"), int64(elect.RenewDeadline.Duration))...) + allErrs = append(allErrs, validateGreaterThan0(electPath.Child("retryPeriod"), int64(elect.RetryPeriod.Duration))...) + if elect.LeaseDuration.Duration <= elect.RenewDeadline.Duration { + allErrs = append(allErrs, field.Invalid(electPath.Child("leaseDuration"), elect.LeaseDuration, + "leaseDuration must be greater than renewDeadline")) + } + if elect.RenewDeadline.Duration <= time.Duration(float64(elect.RetryPeriod.Duration)*leaderelection.JitterFactor) { + allErrs = append(allErrs, field.Invalid(electPath.Child("renewDeadline"), elect.RenewDeadline, + "renewDeadline must be greater than retryPeriod*JitterFactor")) + } + allErrs = append(allErrs, validateEnumStrings(electPath.Child("resourceLock"), string(elect.ResourceLock), + []string{string(v1beta1.ConfigMapsResourceLock), string(v1beta1.EndpointsResourceLock)})...) + + gates := spec.FeatureGates + gatesPath := specPath.Child("featureGates") + existingNames := make(map[string]bool) + for _, gate := range gates { + _, ok := existingNames[gate.Name] + if ok { + allErrs = append(allErrs, field.Duplicate(gatesPath.Child("name"), gate.Name)) + continue + } + existingNames[gate.Name] = true + + allErrs = append(allErrs, validateEnumStrings(gatesPath.Child("name"), string(gate.Name), + []string{string(features.PushReconciler), string(features.SchedulerPreferences), + string(features.CrossClusterServiceDiscovery), string(features.FederatedIngress)})...) + + allErrs = append(allErrs, validateEnumStrings(gatesPath.Child("configuration"), string(gate.Configuration), + []string{string(v1beta1.ConfigurationEnabled), string(v1beta1.ConfigurationDisabled)})...) + } + + health := spec.ClusterHealthCheck + healthPath := specPath.Child("clusterHealthCheck") + allErrs = append(allErrs, validateGreaterThan0(healthPath.Child("periodSeconds"), health.PeriodSeconds)...) + allErrs = append(allErrs, validateGreaterThan0(healthPath.Child("failureThreshold"), health.FailureThreshold)...) + allErrs = append(allErrs, validateGreaterThan0(healthPath.Child("successThreshold"), health.SuccessThreshold)...) + allErrs = append(allErrs, validateGreaterThan0(healthPath.Child("timeoutSeconds"), health.TimeoutSeconds)...) + + sync := spec.SyncController + syncPath := specPath.Child("syncController") + allErrs = append(allErrs, validateEnumStrings(syncPath.Child("adoptResources"), string(sync.AdoptResources), + []string{string(v1beta1.AdoptResourcesEnabled), string(v1beta1.AdoptResourcesDisabled)})...) + + return allErrs +} + +func validateGreaterThan0(path *field.Path, value int64) field.ErrorList { + errs := field.ErrorList{} + if value <= 0 { + errs = append(errs, field.Invalid(path, value, "should be greater than 0")) + } + return errs +} + func ValidateKubeFedCluster(object *v1beta1.KubeFedCluster) field.ErrorList { allErrs := field.ErrorList{} return allErrs diff --git a/pkg/controller/webhook/kubefedconfig/webhook.go b/pkg/controller/webhook/kubefedconfig/webhook.go index 3cee26db81..f823c82f08 100644 --- a/pkg/controller/webhook/kubefedconfig/webhook.go +++ b/pkg/controller/webhook/kubefedconfig/webhook.go @@ -102,6 +102,8 @@ func (a *KubeFedConfigValidationHook) Initialize(kubeClientConfig *rest.Config, a.initialized = true + klog.V(2).Infof("KubeFedConfig validation webhook is initialized") + shallowClientConfigCopy := *kubeClientConfig shallowClientConfigCopy.GroupVersion = &schema.GroupVersion{ Group: v1beta1.SchemeGroupVersion.Group, From 486b41009bb131018368fbf19d3d05857647d85f Mon Sep 17 00:00:00 2001 From: xunpan Date: Thu, 23 May 2019 01:33:10 -0400 Subject: [PATCH 3/5] add kubefedconfigs in ClusterRole move clusterrole to the corresponding yaml file --- .../templates/clusterrole.yaml | 14 ++++++++++ .../templates/clusterrolebindings.yaml | 26 +++++++++++++++++++ .../templates/rolebindings.yaml | 26 ------------------- .../controllermanager/templates/roles.yaml | 13 ---------- 4 files changed, 40 insertions(+), 39 deletions(-) diff --git a/charts/kubefed/charts/controllermanager/templates/clusterrole.yaml b/charts/kubefed/charts/controllermanager/templates/clusterrole.yaml index 3e737b4a10..fcd5192109 100644 --- a/charts/kubefed/charts/controllermanager/templates/clusterrole.yaml +++ b/charts/kubefed/charts/controllermanager/templates/clusterrole.yaml @@ -65,3 +65,17 @@ rules: - update - patch {{- end }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: system:kubefed:admission-requester +rules: +- apiGroups: + - admission.core.kubefed.k8s.io + resources: + - federatedtypeconfigs + - kubefedclusters + - kubefedconfigs + verbs: + - create diff --git a/charts/kubefed/charts/controllermanager/templates/clusterrolebindings.yaml b/charts/kubefed/charts/controllermanager/templates/clusterrolebindings.yaml index 5bae943601..79976d3cd0 100644 --- a/charts/kubefed/charts/controllermanager/templates/clusterrolebindings.yaml +++ b/charts/kubefed/charts/controllermanager/templates/clusterrolebindings.yaml @@ -13,3 +13,29 @@ subjects: name: kubefed-controller namespace: {{ .Release.Namespace }} {{- end }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: auth-delegator-kubefed-admission-webhook +roleRef: + kind: ClusterRole + apiGroup: rbac.authorization.k8s.io + name: system:auth-delegator +subjects: +- kind: ServiceAccount + name: kubefed-admission-webhook + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: anonymous-kubefed-admission-webhook-auth +roleRef: + kind: ClusterRole + apiGroup: rbac.authorization.k8s.io + name: system:kubefed:admission-requester +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: User + name: system:anonymous diff --git a/charts/kubefed/charts/controllermanager/templates/rolebindings.yaml b/charts/kubefed/charts/controllermanager/templates/rolebindings.yaml index d4c7effbb4..1523afec0c 100644 --- a/charts/kubefed/charts/controllermanager/templates/rolebindings.yaml +++ b/charts/kubefed/charts/controllermanager/templates/rolebindings.yaml @@ -43,19 +43,6 @@ subjects: namespace: {{ .Release.Namespace }} --- apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: auth-delegator-kubefed-admission-webhook -roleRef: - kind: ClusterRole - apiGroup: rbac.authorization.k8s.io - name: system:auth-delegator -subjects: -- kind: ServiceAccount - name: kubefed-admission-webhook - namespace: {{ .Release.Namespace }} ---- -apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: kubefed-admission-webhook-apiextension-viewer @@ -68,16 +55,3 @@ subjects: - kind: ServiceAccount name: kubefed-admission-webhook namespace: {{ .Release.Namespace }} ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: anonymous-kubefed-admission-webhook-auth -roleRef: - kind: ClusterRole - apiGroup: rbac.authorization.k8s.io - name: system:kubefed:admission-requester -subjects: -- apiGroup: rbac.authorization.k8s.io - kind: User - name: system:anonymous diff --git a/charts/kubefed/charts/controllermanager/templates/roles.yaml b/charts/kubefed/charts/controllermanager/templates/roles.yaml index 95a3b512b8..91075f3240 100644 --- a/charts/kubefed/charts/controllermanager/templates/roles.yaml +++ b/charts/kubefed/charts/controllermanager/templates/roles.yaml @@ -99,16 +99,3 @@ rules: - get - watch - list ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: system:kubefed:admission-requester -rules: -- apiGroups: - - admission.core.kubefed.k8s.io - resources: - - federatedtypeconfigs - - kubefedclusters - verbs: - - create From e347356e293b749f6e775b5de2797b104409605a Mon Sep 17 00:00:00 2001 From: xunpan Date: Mon, 3 Jun 2019 09:07:37 -0400 Subject: [PATCH 4/5] add test case for validate KubeFedConfig --- .../app/controller-manager.go | 4 +- config/kubefedconfig.yaml | 1 + .../core/v1beta1/validation/validation.go | 2 + .../v1beta1/validation/validation_test.go | 119 ++++++++++++++++++ pkg/features/features.go | 7 ++ 5 files changed, 131 insertions(+), 2 deletions(-) diff --git a/cmd/controller-manager/app/controller-manager.go b/cmd/controller-manager/app/controller-manager.go index e2d47658df..1789d31615 100644 --- a/cmd/controller-manager/app/controller-manager.go +++ b/cmd/controller-manager/app/controller-manager.go @@ -237,7 +237,7 @@ func setInt64(target *int64, defaultValue int64) { } } -func setDefaultKubeFedConfig(fedConfig *corev1b1.KubeFedConfig) { +func SetDefaultKubeFedConfig(fedConfig *corev1b1.KubeFedConfig) { spec := &fedConfig.Spec if len(spec.Scope) == 0 { @@ -325,7 +325,7 @@ func setOptionsByKubeFedConfig(opts *options.Options) { } } - setDefaultKubeFedConfig(fedConfig) + SetDefaultKubeFedConfig(fedConfig) spec := fedConfig.Spec opts.Scope = spec.Scope diff --git a/config/kubefedconfig.yaml b/config/kubefedconfig.yaml index 509caf2095..adae350f72 100644 --- a/config/kubefedconfig.yaml +++ b/config/kubefedconfig.yaml @@ -4,6 +4,7 @@ metadata: name: kubefed namespace: kube-federation-system spec: + scope: Cluster leaderElect: leaseDuration: 1500ms renewDeadline: 1000ms diff --git a/pkg/apis/core/v1beta1/validation/validation.go b/pkg/apis/core/v1beta1/validation/validation.go index 7fdde7d9ca..5e17f57925 100644 --- a/pkg/apis/core/v1beta1/validation/validation.go +++ b/pkg/apis/core/v1beta1/validation/validation.go @@ -150,6 +150,8 @@ func ValidateKubeFedConfig(kubeFedConfig *v1beta1.KubeFedConfig) field.ErrorList spec := kubeFedConfig.Spec specPath := field.NewPath("spec") + allErrs = append(allErrs, validateEnumStrings(specPath.Child("scope"), string(spec.Scope), + []string{string(apiextv1b1.ClusterScoped), string(apiextv1b1.NamespaceScoped)})...) duration := spec.ControllerDuration durationPath := specPath.Child("controllerDuration") diff --git a/pkg/apis/core/v1beta1/validation/validation_test.go b/pkg/apis/core/v1beta1/validation/validation_test.go index 365aeaf386..a184e0cc3d 100644 --- a/pkg/apis/core/v1beta1/validation/validation_test.go +++ b/pkg/apis/core/v1beta1/validation/validation_test.go @@ -21,11 +21,15 @@ import ( "strings" "testing" + apiextv1b1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" + "sigs.k8s.io/kubefed/cmd/controller-manager/app" "sigs.k8s.io/kubefed/pkg/apis/core/typeconfig" "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1" + "sigs.k8s.io/kubefed/pkg/controller/util" + "sigs.k8s.io/kubefed/pkg/features" "sigs.k8s.io/kubefed/pkg/kubefedctl/enable" "sigs.k8s.io/kubefed/pkg/kubefedctl/options" ) @@ -288,3 +292,118 @@ func federatedTypeConfig(apiResource *metav1.APIResource) *v1beta1.FederatedType } return ftc } + +func TestValidateKubeFedConfig(t *testing.T) { + errs := ValidateKubeFedConfig(validKubeFedConfig()) + if len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + + errorCases := map[string]*v1beta1.KubeFedConfig{} + + validScope := validKubeFedConfig() + validScope.Spec.Scope = "NeitherClusterOrNamespaceScoped" + errorCases["spec.scope: Unsupported value"] = validScope + + validAvailableDelayGreaterThan0 := validKubeFedConfig() + validAvailableDelayGreaterThan0.Spec.ControllerDuration.AvailableDelay.Duration = 0 + errorCases["spec.controllerDuration.availableDelay: Invalid value"] = validAvailableDelayGreaterThan0 + + validUnavailableDelayGreaterThan0 := validKubeFedConfig() + validUnavailableDelayGreaterThan0.Spec.ControllerDuration.UnavailableDelay.Duration = 0 + errorCases["spec.controllerDuration.unavailableDelay: Invalid value"] = validUnavailableDelayGreaterThan0 + + validLeaseDurationGreaterThan0 := validKubeFedConfig() + validLeaseDurationGreaterThan0.Spec.LeaderElect.LeaseDuration.Duration = 0 + errorCases["spec.leaderElect.leaseDuration: Invalid value"] = validLeaseDurationGreaterThan0 + + validRenewDeadlineGreaterThan0 := validKubeFedConfig() + validRenewDeadlineGreaterThan0.Spec.LeaderElect.RenewDeadline.Duration = 0 + errorCases["spec.leaderElect.renewDeadline: Invalid value"] = validRenewDeadlineGreaterThan0 + + // spec.leaderElect.leaderDuration must be greater than renewDeadline + validElectorLeaseDurationGreater := validKubeFedConfig() + validElectorLeaseDurationGreater.Spec.LeaderElect.LeaseDuration.Duration = 1 + validElectorLeaseDurationGreater.Spec.LeaderElect.RenewDeadline.Duration = 2 + errorCases["spec.leaderElect.leaseDuration: Invalid value"] = validElectorLeaseDurationGreater + + validRetryPeriodGreaterThan0 := validKubeFedConfig() + validRetryPeriodGreaterThan0.Spec.LeaderElect.RetryPeriod.Duration = 0 + errorCases["spec.leaderElect.retryPeriod: Invalid value"] = validRetryPeriodGreaterThan0 + + // spec.leaderElect.renewDeadline must be greater than retryPeriod*JitterFactor(1.2) + validElectorDuration := validKubeFedConfig() + validElectorDuration.Spec.LeaderElect.RenewDeadline.Duration = 12 + validElectorDuration.Spec.LeaderElect.RetryPeriod.Duration = 10 + errorCases["spec.leaderElect.renewDeadline: Invalid value"] = validElectorDuration + + validElectorResourceLock := validKubeFedConfig() + validElectorResourceLock.Spec.LeaderElect.ResourceLock = "NeitherConfigmapsOrEndpoints" + errorCases["spec.leaderElect.resourceLock: Unsupported value"] = validElectorResourceLock + + validFeatureGateName := validKubeFedConfig() + validFeatureGateName.Spec.FeatureGates[0].Name = "BadFeatureName" + errorCases["spec.featureGates.name: Unsupported value"] = validFeatureGateName + + validDupFeatureGates := validKubeFedConfig() + dupFeature := v1beta1.FeatureGatesConfig{ + Name: string(features.PushReconciler), + Configuration: v1beta1.ConfigurationEnabled, + } + validDupFeatureGates.Spec.FeatureGates = append(validDupFeatureGates.Spec.FeatureGates, dupFeature) + errorCases["spec.featureGates.name: Duplicate value"] = validDupFeatureGates + + validFeatureGateConf := validKubeFedConfig() + validFeatureGateConf.Spec.FeatureGates[0].Configuration = "NeitherEnableOrDisable" + errorCases["spec.featureGates.configuration: Unsupported value"] = validFeatureGateConf + + validPeriodSecondsGreaterThan0 := validKubeFedConfig() + validPeriodSecondsGreaterThan0.Spec.ClusterHealthCheck.PeriodSeconds = 0 + errorCases["spec.clusterHealthCheck.periodSeconds: Invalid value"] = validPeriodSecondsGreaterThan0 + + validFailureThresholdGreaterThan0 := validKubeFedConfig() + validFailureThresholdGreaterThan0.Spec.ClusterHealthCheck.FailureThreshold = 0 + errorCases["spec.clusterHealthCheck.failureThreshold: Invalid value"] = validFailureThresholdGreaterThan0 + + validSuccessThresholdGreaterThan0 := validKubeFedConfig() + validSuccessThresholdGreaterThan0.Spec.ClusterHealthCheck.SuccessThreshold = 0 + errorCases["spec.clusterHealthCheck.successThreshold: Invalid value"] = validSuccessThresholdGreaterThan0 + + validTimeoutSecondsGreaterThan0 := validKubeFedConfig() + validTimeoutSecondsGreaterThan0.Spec.ClusterHealthCheck.TimeoutSeconds = 0 + errorCases["spec.clusterHealthCheck.timeoutSeconds: Invalid value"] = validTimeoutSecondsGreaterThan0 + + validAdoptResources := validKubeFedConfig() + validAdoptResources.Spec.SyncController.AdoptResources = "NeitherEnableOrDisable" + errorCases["spec.syncController.adoptResources: Unsupported value"] = validAdoptResources + + for k, v := range errorCases { + errs := ValidateKubeFedConfig(v) + if len(errs) == 0 { + t.Errorf("[%s] expected failure", k) + } else if !strings.Contains(errs[0].Error(), k) { + t.Errorf("unexpected error: %q, expected: %q", errs[0].Error(), k) + } + } +} + +func validKubeFedConfig() *v1beta1.KubeFedConfig { + kfc := &v1beta1.KubeFedConfig{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: util.DefaultKubeFedSystemNamespace, + Name: util.KubeFedConfigName, + }, + Spec: v1beta1.KubeFedConfigSpec{ + Scope: apiextv1b1.ClusterScoped, + }, + } + + app.SetDefaultKubeFedConfig(kfc) + + for _, name := range features.FeatureNames { + feature := v1beta1.FeatureGatesConfig{Name: string(name), Configuration: v1beta1.ConfigurationEnabled} + kfc.Spec.FeatureGates = append(kfc.Spec.FeatureGates, feature) + } + + return kfc +} diff --git a/pkg/features/features.go b/pkg/features/features.go index 80e09aaf3a..fa3d903653 100644 --- a/pkg/features/features.go +++ b/pkg/features/features.go @@ -54,6 +54,13 @@ const ( FederatedIngress utilfeature.Feature = "FederatedIngress" ) +var FeatureNames = []utilfeature.Feature{ + PushReconciler, + SchedulerPreferences, + CrossClusterServiceDiscovery, + FederatedIngress, +} + func init() { if err := utilfeature.DefaultFeatureGate.Add(defaultKubeFedFeatureGates); err != nil { klog.Fatalf("Unexpected error: %v", err) From 8a8818101ef10cdc754b9e9827dc360d22d38469 Mon Sep 17 00:00:00 2001 From: xunpan Date: Mon, 3 Jun 2019 09:44:21 -0400 Subject: [PATCH 5/5] change log update --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9324b7ada..801d9a8a86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ # Unreleased +- [#941](https://github.com/kubernetes-sigs/kubefed/issues/941) Adds + admission webhook validations for KubeFedConfig API. - [#909](https://github.com/kubernetes-sigs/kubefed/issues/909) Adds admission webhook validations for FederatedTypeConfig API.