| /* |
| Copyright 2016 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package validation |
| |
| import ( |
| "fmt" |
| "testing" |
| "time" |
| |
| policyv1beta1 "k8s.io/api/policy/v1beta1" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/runtime/schema" |
| "k8s.io/apimachinery/pkg/util/intstr" |
| "k8s.io/apimachinery/pkg/util/validation/field" |
| "k8s.io/kubernetes/pkg/apis/policy" |
| ) |
| |
| func TestValidatePodDisruptionBudgetSpec(t *testing.T) { |
| minAvailable := intstr.FromString("0%") |
| maxUnavailable := intstr.FromString("10%") |
| |
| spec := policy.PodDisruptionBudgetSpec{ |
| MinAvailable: &minAvailable, |
| MaxUnavailable: &maxUnavailable, |
| } |
| errs := ValidatePodDisruptionBudgetSpec(spec, PodDisruptionBudgetValidationOptions{true}, field.NewPath("foo")) |
| if len(errs) == 0 { |
| t.Errorf("unexpected success for %v", spec) |
| } |
| } |
| |
| func TestValidateMinAvailablePodDisruptionBudgetSpec(t *testing.T) { |
| successCases := []intstr.IntOrString{ |
| intstr.FromString("0%"), |
| intstr.FromString("1%"), |
| intstr.FromString("100%"), |
| intstr.FromInt32(0), |
| intstr.FromInt32(1), |
| intstr.FromInt32(100), |
| } |
| for _, c := range successCases { |
| spec := policy.PodDisruptionBudgetSpec{ |
| MinAvailable: &c, |
| } |
| errs := ValidatePodDisruptionBudgetSpec(spec, PodDisruptionBudgetValidationOptions{true}, field.NewPath("foo")) |
| if len(errs) != 0 { |
| t.Errorf("unexpected failure %v for %v", errs, spec) |
| } |
| } |
| |
| failureCases := []intstr.IntOrString{ |
| intstr.FromString("1.1%"), |
| intstr.FromString("nope"), |
| intstr.FromString("-1%"), |
| intstr.FromString("101%"), |
| intstr.FromInt32(-1), |
| } |
| for _, c := range failureCases { |
| spec := policy.PodDisruptionBudgetSpec{ |
| MinAvailable: &c, |
| } |
| errs := ValidatePodDisruptionBudgetSpec(spec, PodDisruptionBudgetValidationOptions{true}, field.NewPath("foo")) |
| if len(errs) == 0 { |
| t.Errorf("unexpected success for %v", spec) |
| } |
| } |
| } |
| |
| func TestValidateMinAvailablePodAndMaxUnavailableDisruptionBudgetSpec(t *testing.T) { |
| c1 := intstr.FromString("10%") |
| c2 := intstr.FromInt32(1) |
| |
| spec := policy.PodDisruptionBudgetSpec{ |
| MinAvailable: &c1, |
| MaxUnavailable: &c2, |
| } |
| errs := ValidatePodDisruptionBudgetSpec(spec, PodDisruptionBudgetValidationOptions{true}, field.NewPath("foo")) |
| if len(errs) == 0 { |
| t.Errorf("unexpected success for %v", spec) |
| } |
| } |
| |
| func TestValidateUnhealthyPodEvictionPolicyDisruptionBudgetSpec(t *testing.T) { |
| c1 := intstr.FromString("10%") |
| alwaysAllowPolicy := policy.AlwaysAllow |
| invalidPolicy := policy.UnhealthyPodEvictionPolicyType("Invalid") |
| |
| testCases := []struct { |
| name string |
| pdbSpec policy.PodDisruptionBudgetSpec |
| expectErr bool |
| }{{ |
| name: "valid nil UnhealthyPodEvictionPolicy", |
| pdbSpec: policy.PodDisruptionBudgetSpec{ |
| MinAvailable: &c1, |
| UnhealthyPodEvictionPolicy: nil, |
| }, |
| expectErr: false, |
| }, { |
| name: "valid UnhealthyPodEvictionPolicy", |
| pdbSpec: policy.PodDisruptionBudgetSpec{ |
| MinAvailable: &c1, |
| UnhealthyPodEvictionPolicy: &alwaysAllowPolicy, |
| }, |
| expectErr: false, |
| }, { |
| name: "empty UnhealthyPodEvictionPolicy", |
| pdbSpec: policy.PodDisruptionBudgetSpec{ |
| MinAvailable: &c1, |
| UnhealthyPodEvictionPolicy: new(policy.UnhealthyPodEvictionPolicyType), |
| }, |
| expectErr: true, |
| }, { |
| name: "invalid UnhealthyPodEvictionPolicy", |
| pdbSpec: policy.PodDisruptionBudgetSpec{ |
| MinAvailable: &c1, |
| UnhealthyPodEvictionPolicy: &invalidPolicy, |
| }, |
| expectErr: true, |
| }} |
| |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| errs := ValidatePodDisruptionBudgetSpec(tc.pdbSpec, PodDisruptionBudgetValidationOptions{true}, field.NewPath("foo")) |
| if len(errs) == 0 && tc.expectErr { |
| t.Errorf("unexpected success for %v", tc.pdbSpec) |
| } |
| if len(errs) != 0 && !tc.expectErr { |
| t.Errorf("unexpected failure for %v", tc.pdbSpec) |
| } |
| }) |
| } |
| } |
| |
| func TestValidatePodDisruptionBudgetStatus(t *testing.T) { |
| const expectNoErrors = false |
| const expectErrors = true |
| testCases := []struct { |
| name string |
| pdbStatus policy.PodDisruptionBudgetStatus |
| expectErrForVersion map[schema.GroupVersion]bool |
| }{{ |
| name: "DisruptionsAllowed: 10", |
| pdbStatus: policy.PodDisruptionBudgetStatus{ |
| DisruptionsAllowed: 10, |
| }, |
| expectErrForVersion: map[schema.GroupVersion]bool{ |
| policy.SchemeGroupVersion: expectNoErrors, |
| policyv1beta1.SchemeGroupVersion: expectNoErrors, |
| }, |
| }, { |
| name: "CurrentHealthy: 5", |
| pdbStatus: policy.PodDisruptionBudgetStatus{ |
| CurrentHealthy: 5, |
| }, |
| expectErrForVersion: map[schema.GroupVersion]bool{ |
| policy.SchemeGroupVersion: expectNoErrors, |
| policyv1beta1.SchemeGroupVersion: expectNoErrors, |
| }, |
| }, { |
| name: "DesiredHealthy: 3", |
| pdbStatus: policy.PodDisruptionBudgetStatus{ |
| DesiredHealthy: 3, |
| }, |
| expectErrForVersion: map[schema.GroupVersion]bool{ |
| policy.SchemeGroupVersion: expectNoErrors, |
| policyv1beta1.SchemeGroupVersion: expectNoErrors, |
| }, |
| }, { |
| name: "ExpectedPods: 2", |
| pdbStatus: policy.PodDisruptionBudgetStatus{ |
| ExpectedPods: 2, |
| }, |
| expectErrForVersion: map[schema.GroupVersion]bool{ |
| policy.SchemeGroupVersion: expectNoErrors, |
| policyv1beta1.SchemeGroupVersion: expectNoErrors, |
| }, |
| }, { |
| name: "DisruptionsAllowed: -10", |
| pdbStatus: policy.PodDisruptionBudgetStatus{ |
| DisruptionsAllowed: -10, |
| }, |
| expectErrForVersion: map[schema.GroupVersion]bool{ |
| policy.SchemeGroupVersion: expectErrors, |
| policyv1beta1.SchemeGroupVersion: expectNoErrors, |
| }, |
| }, { |
| name: "CurrentHealthy: -5", |
| pdbStatus: policy.PodDisruptionBudgetStatus{ |
| CurrentHealthy: -5, |
| }, |
| expectErrForVersion: map[schema.GroupVersion]bool{ |
| policy.SchemeGroupVersion: expectErrors, |
| policyv1beta1.SchemeGroupVersion: expectNoErrors, |
| }, |
| }, { |
| name: "DesiredHealthy: -3", |
| pdbStatus: policy.PodDisruptionBudgetStatus{ |
| DesiredHealthy: -3, |
| }, |
| expectErrForVersion: map[schema.GroupVersion]bool{ |
| policy.SchemeGroupVersion: expectErrors, |
| policyv1beta1.SchemeGroupVersion: expectNoErrors, |
| }, |
| }, { |
| name: "ExpectedPods: -2", |
| pdbStatus: policy.PodDisruptionBudgetStatus{ |
| ExpectedPods: -2, |
| }, |
| expectErrForVersion: map[schema.GroupVersion]bool{ |
| policy.SchemeGroupVersion: expectErrors, |
| policyv1beta1.SchemeGroupVersion: expectNoErrors, |
| }, |
| }, { |
| name: "Conditions valid", |
| pdbStatus: policy.PodDisruptionBudgetStatus{ |
| Conditions: []metav1.Condition{{ |
| Type: policyv1beta1.DisruptionAllowedCondition, |
| Status: metav1.ConditionTrue, |
| LastTransitionTime: metav1.Time{ |
| Time: time.Now().Add(-5 * time.Minute), |
| }, |
| Reason: policyv1beta1.SufficientPodsReason, |
| Message: "message", |
| ObservedGeneration: 3, |
| }}, |
| }, |
| expectErrForVersion: map[schema.GroupVersion]bool{ |
| policy.SchemeGroupVersion: expectNoErrors, |
| policyv1beta1.SchemeGroupVersion: expectNoErrors, |
| }, |
| }, { |
| name: "Conditions not valid", |
| pdbStatus: policy.PodDisruptionBudgetStatus{ |
| Conditions: []metav1.Condition{{ |
| Type: policyv1beta1.DisruptionAllowedCondition, |
| Status: metav1.ConditionTrue, |
| }, { |
| Type: policyv1beta1.DisruptionAllowedCondition, |
| Status: metav1.ConditionFalse, |
| }}, |
| }, |
| expectErrForVersion: map[schema.GroupVersion]bool{ |
| policy.SchemeGroupVersion: expectErrors, |
| policyv1beta1.SchemeGroupVersion: expectErrors, |
| }, |
| }} |
| |
| for _, tc := range testCases { |
| for apiVersion, expectErrors := range tc.expectErrForVersion { |
| t.Run(fmt.Sprintf("apiVersion: %s, %s", apiVersion.String(), tc.name), func(t *testing.T) { |
| errors := ValidatePodDisruptionBudgetStatusUpdate(tc.pdbStatus, policy.PodDisruptionBudgetStatus{}, |
| field.NewPath("status"), apiVersion) |
| errCount := len(errors) |
| |
| if errCount > 0 && !expectErrors { |
| t.Errorf("unexpected failure %v for %v", errors, tc.pdbStatus) |
| } |
| |
| if errCount == 0 && expectErrors { |
| t.Errorf("expected errors but didn't one for %v", tc.pdbStatus) |
| } |
| }) |
| } |
| } |
| } |
| |
| func TestIsValidSysctlPattern(t *testing.T) { |
| valid := []string{ |
| "a.b.c.d", |
| "a", |
| "a_b", |
| "a-b", |
| "abc", |
| "abc.def", |
| "*", |
| "a.*", |
| "*", |
| "abc*", |
| "a.abc*", |
| "a.b.*", |
| "a/b/c/d", |
| "a/*", |
| "a/b/*", |
| "a.b/c*", |
| "a.b/c.d", |
| "a/b.c/d", |
| } |
| invalid := []string{ |
| "", |
| "รค", |
| "a_", |
| "_", |
| "_a", |
| "_a._b", |
| "__", |
| "-", |
| ".", |
| "a.", |
| ".a", |
| "a.b.", |
| "a*.b", |
| "a*b", |
| "*a", |
| "Abc", |
| "/", |
| "a/", |
| "/a", |
| "a*/b", |
| func(n int) string { |
| x := make([]byte, n) |
| for i := range x { |
| x[i] = byte('a') |
| } |
| return string(x) |
| }(256), |
| } |
| for _, s := range valid { |
| if !IsValidSysctlPattern(s) { |
| t.Errorf("%q expected to be a valid sysctl pattern", s) |
| } |
| } |
| for _, s := range invalid { |
| if IsValidSysctlPattern(s) { |
| t.Errorf("%q expected to be an invalid sysctl pattern", s) |
| } |
| } |
| } |