343 lines
9.1 KiB
Go
343 lines
9.1 KiB
Go
/*
|
|
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)
|
|
}
|
|
}
|
|
}
|