Promote PDBs to GA
This commit is contained in:
parent
8a8c267e58
commit
21fba79d45
@ -262,6 +262,7 @@ var apiVersionPriorities = map[schema.GroupVersion]priority{
|
||||
{Group: "networking.k8s.io", Version: "v1"}: {group: 17200, version: 15},
|
||||
{Group: "networking.k8s.io", Version: "v1beta1"}: {group: 17200, version: 9},
|
||||
{Group: "extensions", Version: "v1beta1"}: {group: 17150, version: 1}, // prioritize below networking.k8s.io, which contains the GA version of Ingress, the only resource remaining in extensions/v1beta1
|
||||
{Group: "policy", Version: "v1"}: {group: 17100, version: 15},
|
||||
{Group: "policy", Version: "v1beta1"}: {group: 17100, version: 9},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1"}: {group: 17000, version: 15},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1beta1"}: {group: 17000, version: 12},
|
||||
|
@ -25,7 +25,6 @@ import (
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/scale"
|
||||
@ -34,16 +33,6 @@ import (
|
||||
)
|
||||
|
||||
func startDisruptionController(ctx ControllerContext) (http.Handler, bool, error) {
|
||||
var group = "policy"
|
||||
var version = "v1beta1"
|
||||
var resource = "poddisruptionbudgets"
|
||||
|
||||
if !ctx.AvailableResources[schema.GroupVersionResource{Group: group, Version: version, Resource: resource}] {
|
||||
klog.Infof(
|
||||
"Refusing to start disruption because resource %q in group %q is not available.",
|
||||
resource, group+"/"+version)
|
||||
return nil, false, nil
|
||||
}
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.PodDisruptionBudget) {
|
||||
klog.Infof("Refusing to start disruption because the PodDisruptionBudget feature is disabled")
|
||||
return nil, false, nil
|
||||
@ -59,7 +48,7 @@ func startDisruptionController(ctx ControllerContext) (http.Handler, bool, error
|
||||
|
||||
go disruption.NewDisruptionController(
|
||||
ctx.InformerFactory.Core().V1().Pods(),
|
||||
ctx.InformerFactory.Policy().V1beta1().PodDisruptionBudgets(),
|
||||
ctx.InformerFactory.Policy().V1().PodDisruptionBudgets(),
|
||||
ctx.InformerFactory.Core().V1().ReplicationControllers(),
|
||||
ctx.InformerFactory.Apps().V1().ReplicaSets(),
|
||||
ctx.InformerFactory.Apps().V1().Deployments(),
|
||||
|
@ -28,6 +28,7 @@
|
||||
"k8s.io/api/node/v1alpha1": "nodev1alpha1",
|
||||
"k8s.io/api/node/v1beta1": "nodev1beta1",
|
||||
"k8s.io/api/node/v1": "nodev1",
|
||||
"k8s.io/api/policy/v1": "policyv1",
|
||||
"k8s.io/api/policy/v1beta1": "policyv1beta1",
|
||||
"k8s.io/api/rbac/v1": "rbacv1",
|
||||
"k8s.io/api/rbac/v1alpha1": "rbacv1alpha1",
|
||||
|
@ -95,6 +95,7 @@ networking.k8s.io/v1beta1 \
|
||||
node.k8s.io/v1 \
|
||||
node.k8s.io/v1alpha1 \
|
||||
node.k8s.io/v1beta1 \
|
||||
policy/v1 \
|
||||
policy/v1beta1 \
|
||||
rbac.authorization.k8s.io/v1 \
|
||||
rbac.authorization.k8s.io/v1beta1 \
|
||||
|
51
pkg/apis/policy/helper.go
Normal file
51
pkg/apis/policy/helper.go
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2021 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 policy
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
PDBV1beta1Label = "pdb.kubernetes.io/deprecated-v1beta1-empty-selector-match"
|
||||
)
|
||||
|
||||
var (
|
||||
NonV1beta1MatchAllSelector = &metav1.LabelSelector{}
|
||||
NonV1beta1MatchNoneSelector = &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{{Key: PDBV1beta1Label, Operator: metav1.LabelSelectorOpExists}},
|
||||
}
|
||||
|
||||
V1beta1MatchNoneSelector = &metav1.LabelSelector{}
|
||||
V1beta1MatchAllSelector = &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{{Key: PDBV1beta1Label, Operator: metav1.LabelSelectorOpDoesNotExist}},
|
||||
}
|
||||
)
|
||||
|
||||
func StripPDBV1beta1Label(selector *metav1.LabelSelector) {
|
||||
if selector == nil {
|
||||
return
|
||||
}
|
||||
|
||||
trimmedMatchExpressions := selector.MatchExpressions[:0]
|
||||
for _, exp := range selector.MatchExpressions {
|
||||
if exp.Key != PDBV1beta1Label {
|
||||
trimmedMatchExpressions = append(trimmedMatchExpressions, exp)
|
||||
}
|
||||
}
|
||||
selector.MatchExpressions = trimmedMatchExpressions
|
||||
}
|
@ -23,6 +23,7 @@ import (
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
"k8s.io/kubernetes/pkg/apis/policy/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/policy/v1beta1"
|
||||
)
|
||||
|
||||
@ -34,5 +35,7 @@ func init() {
|
||||
func Install(scheme *runtime.Scheme) {
|
||||
utilruntime.Must(policy.AddToScheme(scheme))
|
||||
utilruntime.Must(v1beta1.AddToScheme(scheme))
|
||||
utilruntime.Must(scheme.SetVersionPriority(v1beta1.SchemeGroupVersion))
|
||||
utilruntime.Must(v1.AddToScheme(scheme))
|
||||
// TODO (mortent): priority should change after 1.21. See https://github.com/kubernetes/kubernetes/pull/95718#discussion_r520969477
|
||||
utilruntime.Must(scheme.SetVersionPriority(v1beta1.SchemeGroupVersion, v1.SchemeGroupVersion))
|
||||
}
|
||||
|
42
pkg/apis/policy/v1/conversion.go
Normal file
42
pkg/apis/policy/v1/conversion.go
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
Copyright 2021 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 v1
|
||||
|
||||
import (
|
||||
"k8s.io/api/policy/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
)
|
||||
|
||||
func Convert_v1_PodDisruptionBudget_To_policy_PodDisruptionBudget(in *v1.PodDisruptionBudget, out *policy.PodDisruptionBudget, s conversion.Scope) error {
|
||||
if err := autoConvert_v1_PodDisruptionBudget_To_policy_PodDisruptionBudget(in, out, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case apiequality.Semantic.DeepEqual(in.Spec.Selector, policy.NonV1beta1MatchNoneSelector):
|
||||
// no-op, preserve
|
||||
case apiequality.Semantic.DeepEqual(in.Spec.Selector, policy.NonV1beta1MatchAllSelector):
|
||||
// no-op, preserve
|
||||
default:
|
||||
// otherwise, make sure the label intended to be used in a match-all or match-none selector
|
||||
// never gets combined with user-specified fields
|
||||
policy.StripPDBV1beta1Label(out.Spec.Selector)
|
||||
}
|
||||
return nil
|
||||
}
|
127
pkg/apis/policy/v1/conversion_test.go
Normal file
127
pkg/apis/policy/v1/conversion_test.go
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
Copyright 2021 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 v1
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"k8s.io/api/policy/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
)
|
||||
|
||||
func TestConversion(t *testing.T) {
|
||||
testcases := []struct {
|
||||
Name string
|
||||
In runtime.Object
|
||||
Out runtime.Object
|
||||
ExpectOut runtime.Object
|
||||
ExpectErr string
|
||||
}{
|
||||
{
|
||||
Name: "v1 to internal with match none selector",
|
||||
In: &v1.PodDisruptionBudget{
|
||||
Spec: v1.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "pdb.kubernetes.io/deprecated-v1beta1-empty-selector-match",
|
||||
Operator: metav1.LabelSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Out: &policy.PodDisruptionBudget{},
|
||||
ExpectOut: &policy.PodDisruptionBudget{
|
||||
Spec: policy.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "pdb.kubernetes.io/deprecated-v1beta1-empty-selector-match",
|
||||
Operator: metav1.LabelSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "v1 to internal with nil selector",
|
||||
In: &v1.PodDisruptionBudget{},
|
||||
Out: &policy.PodDisruptionBudget{},
|
||||
ExpectOut: &policy.PodDisruptionBudget{},
|
||||
},
|
||||
{
|
||||
Name: "v1 to internal with match all selector",
|
||||
In: &v1.PodDisruptionBudget{
|
||||
Spec: v1.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "pdb.kubernetes.io/deprecated-v1beta1-empty-selector-match",
|
||||
Operator: metav1.LabelSelectorOpDoesNotExist,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Out: &policy.PodDisruptionBudget{},
|
||||
ExpectOut: &policy.PodDisruptionBudget{
|
||||
Spec: policy.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
if err := policy.AddToScheme(scheme); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := AddToScheme(scheme); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
err := scheme.Convert(tc.In, tc.Out, nil)
|
||||
if err != nil {
|
||||
if len(tc.ExpectErr) == 0 {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.ExpectErr) {
|
||||
t.Fatalf("expected error %s, got %v", tc.ExpectErr, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(tc.ExpectErr) > 0 {
|
||||
t.Fatalf("expected error %s, got none", tc.ExpectErr)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.Out, tc.ExpectOut) {
|
||||
t.Fatalf("unexpected result:\n %s", cmp.Diff(tc.ExpectOut, tc.Out))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
24
pkg/apis/policy/v1/doc.go
Normal file
24
pkg/apis/policy/v1/doc.go
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright 2021 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.
|
||||
*/
|
||||
|
||||
// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/policy
|
||||
// +k8s:conversion-gen-external-types=k8s.io/api/policy/v1
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/policy/v1
|
||||
|
||||
// Package policy is for any kind of policy object. Currently, this only
|
||||
// includes policyv1.PodDisruptionBudget
|
||||
package v1 // import "k8s.io/kubernetes/pkg/apis/policy/v1"
|
45
pkg/apis/policy/v1/register.go
Normal file
45
pkg/apis/policy/v1/register.go
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright 2021 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 v1
|
||||
|
||||
import (
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package
|
||||
const GroupName = "policy"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
var (
|
||||
localSchemeBuilder = &policyv1.SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We only register manually written functions here. The registration of the
|
||||
// generated functions takes place in the generated files. The separation
|
||||
// makes the code compile even when the generated files are missing.
|
||||
localSchemeBuilder.Register(RegisterDefaults)
|
||||
}
|
66
pkg/apis/policy/v1beta1/conversion.go
Normal file
66
pkg/apis/policy/v1beta1/conversion.go
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2021 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 v1beta1
|
||||
|
||||
import (
|
||||
"k8s.io/api/policy/v1beta1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
)
|
||||
|
||||
func Convert_v1beta1_PodDisruptionBudget_To_policy_PodDisruptionBudget(in *v1beta1.PodDisruptionBudget, out *policy.PodDisruptionBudget, s conversion.Scope) error {
|
||||
if err := autoConvert_v1beta1_PodDisruptionBudget_To_policy_PodDisruptionBudget(in, out, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case apiequality.Semantic.DeepEqual(in.Spec.Selector, policy.V1beta1MatchNoneSelector):
|
||||
// If the v1beta1 version has a non-nil but empty selector, it should be
|
||||
// selecting no pods, even when used with the internal or v1 api. We
|
||||
// add a selector that is non-empty but will never match any pods.
|
||||
out.Spec.Selector = policy.NonV1beta1MatchNoneSelector.DeepCopy()
|
||||
case apiequality.Semantic.DeepEqual(in.Spec.Selector, policy.V1beta1MatchAllSelector):
|
||||
// If the v1beta1 version has our v1beta1-specific "match-all" selector,
|
||||
// swap that out for a simpler empty "match-all" selector for v1
|
||||
out.Spec.Selector = policy.NonV1beta1MatchAllSelector.DeepCopy()
|
||||
default:
|
||||
// otherwise, make sure the label intended to be used in a match-all or match-none selector
|
||||
// never gets combined with user-specified fields
|
||||
policy.StripPDBV1beta1Label(out.Spec.Selector)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_policy_PodDisruptionBudget_To_v1beta1_PodDisruptionBudget(in *policy.PodDisruptionBudget, out *v1beta1.PodDisruptionBudget, s conversion.Scope) error {
|
||||
if err := autoConvert_policy_PodDisruptionBudget_To_v1beta1_PodDisruptionBudget(in, out, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case apiequality.Semantic.DeepEqual(in.Spec.Selector, policy.NonV1beta1MatchNoneSelector):
|
||||
// If the internal version has our v1beta1-specific "match-none" selector,
|
||||
// swap that out for a simpler empty "match-none" selector for v1beta1
|
||||
out.Spec.Selector = policy.V1beta1MatchNoneSelector.DeepCopy()
|
||||
case apiequality.Semantic.DeepEqual(in.Spec.Selector, policy.NonV1beta1MatchAllSelector):
|
||||
// If the internal version has a non-nil but empty selector, we want it to
|
||||
// select all pods. We make sure this happens even with the v1beta1 api by
|
||||
// adding a non-empty selector that selects all pods.
|
||||
out.Spec.Selector = policy.V1beta1MatchAllSelector.DeepCopy()
|
||||
}
|
||||
return nil
|
||||
}
|
197
pkg/apis/policy/v1beta1/conversion_test.go
Normal file
197
pkg/apis/policy/v1beta1/conversion_test.go
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
Copyright 2021 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 v1beta1
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"k8s.io/api/policy/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
)
|
||||
|
||||
func TestConversion(t *testing.T) {
|
||||
testcases := []struct {
|
||||
Name string
|
||||
In runtime.Object
|
||||
Out runtime.Object
|
||||
ExpectOut runtime.Object
|
||||
ExpectErr string
|
||||
}{
|
||||
{
|
||||
Name: "v1beta1 to internal with empty selector",
|
||||
In: &v1beta1.PodDisruptionBudget{
|
||||
Spec: v1beta1.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{},
|
||||
},
|
||||
},
|
||||
Out: &policy.PodDisruptionBudget{},
|
||||
ExpectOut: &policy.PodDisruptionBudget{
|
||||
Spec: policy.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "pdb.kubernetes.io/deprecated-v1beta1-empty-selector-match",
|
||||
Operator: metav1.LabelSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "v1beta1 to internal with nil selector",
|
||||
In: &v1beta1.PodDisruptionBudget{},
|
||||
Out: &policy.PodDisruptionBudget{},
|
||||
ExpectOut: &policy.PodDisruptionBudget{},
|
||||
},
|
||||
{
|
||||
Name: "v1 to internal with existing selector",
|
||||
In: &v1beta1.PodDisruptionBudget{
|
||||
Spec: v1beta1.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Out: &policy.PodDisruptionBudget{},
|
||||
ExpectOut: &policy.PodDisruptionBudget{
|
||||
Spec: policy.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "v1beta1 to internal with existing pdb selector",
|
||||
In: &v1beta1.PodDisruptionBudget{
|
||||
Spec: v1beta1.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "pdb.kubernetes.io/deprecated-v1beta1-empty-selector-match",
|
||||
Operator: metav1.LabelSelectorOpDoesNotExist,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Out: &policy.PodDisruptionBudget{},
|
||||
ExpectOut: &policy.PodDisruptionBudget{
|
||||
Spec: policy.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "internal to v1beta1 with empty selector",
|
||||
In: &policy.PodDisruptionBudget{
|
||||
Spec: policy.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{},
|
||||
},
|
||||
},
|
||||
Out: &v1beta1.PodDisruptionBudget{},
|
||||
ExpectOut: &v1beta1.PodDisruptionBudget{
|
||||
Spec: v1beta1.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "pdb.kubernetes.io/deprecated-v1beta1-empty-selector-match",
|
||||
Operator: metav1.LabelSelectorOpDoesNotExist,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "internal to v1beta1 with nil selector",
|
||||
In: &policy.PodDisruptionBudget{},
|
||||
Out: &v1beta1.PodDisruptionBudget{},
|
||||
ExpectOut: &v1beta1.PodDisruptionBudget{},
|
||||
},
|
||||
{
|
||||
Name: "internal to v1beta1 with existing selector",
|
||||
In: &policy.PodDisruptionBudget{
|
||||
Spec: policy.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Out: &v1beta1.PodDisruptionBudget{},
|
||||
ExpectOut: &v1beta1.PodDisruptionBudget{
|
||||
Spec: v1beta1.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
if err := policy.AddToScheme(scheme); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := AddToScheme(scheme); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
err := scheme.Convert(tc.In, tc.Out, nil)
|
||||
if err != nil {
|
||||
if len(tc.ExpectErr) == 0 {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.ExpectErr) {
|
||||
t.Fatalf("expected error %s, got %v", tc.ExpectErr, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(tc.ExpectErr) > 0 {
|
||||
t.Fatalf("expected error %s, got none", tc.ExpectErr)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.Out, tc.ExpectOut) {
|
||||
t.Fatalf("unexpected result:\n %s", cmp.Diff(tc.ExpectOut, tc.Out))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ import (
|
||||
apps "k8s.io/api/apps/v1beta1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
policy "k8s.io/api/policy/v1beta1"
|
||||
policy "k8s.io/api/policy/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
@ -38,13 +38,13 @@ import (
|
||||
"k8s.io/client-go/discovery"
|
||||
appsv1informers "k8s.io/client-go/informers/apps/v1"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
policyinformers "k8s.io/client-go/informers/policy/v1beta1"
|
||||
policyinformers "k8s.io/client-go/informers/policy/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
appsv1listers "k8s.io/client-go/listers/apps/v1"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
policylisters "k8s.io/client-go/listers/policy/v1beta1"
|
||||
policylisters "k8s.io/client-go/listers/policy/v1"
|
||||
scaleclient "k8s.io/client-go/scale"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
@ -506,9 +506,6 @@ func (dc *DisruptionController) getPodsForPdb(pdb *policy.PodDisruptionBudget) (
|
||||
if err != nil {
|
||||
return []*v1.Pod{}, err
|
||||
}
|
||||
if sel.Empty() {
|
||||
return []*v1.Pod{}, nil
|
||||
}
|
||||
pods, err := dc.podLister.Pods(pdb.Namespace).List(sel)
|
||||
if err != nil {
|
||||
return []*v1.Pod{}, err
|
||||
@ -835,6 +832,6 @@ func (dc *DisruptionController) updatePdbStatus(pdb *policy.PodDisruptionBudget,
|
||||
func (dc *DisruptionController) writePdbStatus(pdb *policy.PodDisruptionBudget) error {
|
||||
// If this update fails, don't retry it. Allow the failure to get handled &
|
||||
// retried in `processNextWorkItem()`.
|
||||
_, err := dc.kubeClient.PolicyV1beta1().PodDisruptionBudgets(pdb.Namespace).UpdateStatus(context.TODO(), pdb, metav1.UpdateOptions{})
|
||||
_, err := dc.kubeClient.PolicyV1().PodDisruptionBudgets(pdb.Namespace).UpdateStatus(context.TODO(), pdb, metav1.UpdateOptions{})
|
||||
return err
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ import (
|
||||
apps "k8s.io/api/apps/v1"
|
||||
autoscalingapi "k8s.io/api/autoscaling/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
policy "k8s.io/api/policy/v1beta1"
|
||||
policy "k8s.io/api/policy/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
@ -151,7 +151,7 @@ func newFakeDisruptionController() (*disruptionController, *pdbStates) {
|
||||
|
||||
dc := NewDisruptionController(
|
||||
informerFactory.Core().V1().Pods(),
|
||||
informerFactory.Policy().V1beta1().PodDisruptionBudgets(),
|
||||
informerFactory.Policy().V1().PodDisruptionBudgets(),
|
||||
informerFactory.Core().V1().ReplicationControllers(),
|
||||
informerFactory.Apps().V1().ReplicaSets(),
|
||||
informerFactory.Apps().V1().Deployments(),
|
||||
@ -175,7 +175,7 @@ func newFakeDisruptionController() (*disruptionController, *pdbStates) {
|
||||
return &disruptionController{
|
||||
dc,
|
||||
informerFactory.Core().V1().Pods().Informer().GetStore(),
|
||||
informerFactory.Policy().V1beta1().PodDisruptionBudgets().Informer().GetStore(),
|
||||
informerFactory.Policy().V1().PodDisruptionBudgets().Informer().GetStore(),
|
||||
informerFactory.Core().V1().ReplicationControllers().Informer().GetStore(),
|
||||
informerFactory.Apps().V1().ReplicaSets().Informer().GetStore(),
|
||||
informerFactory.Apps().V1().Deployments().Informer().GetStore(),
|
||||
@ -402,7 +402,7 @@ func add(t *testing.T, store cache.Store, obj interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Create one with no selector. Verify it matches 0 pods.
|
||||
// Create one with no selector. Verify it matches all pods
|
||||
func TestNoSelector(t *testing.T) {
|
||||
dc, ps := newFakeDisruptionController()
|
||||
|
||||
@ -416,7 +416,7 @@ func TestNoSelector(t *testing.T) {
|
||||
|
||||
add(t, dc.podStore, pod)
|
||||
dc.sync(pdbName)
|
||||
ps.VerifyPdbStatus(t, pdbName, 0, 0, 3, 0, map[string]metav1.Time{})
|
||||
ps.VerifyPdbStatus(t, pdbName, 0, 1, 3, 1, map[string]metav1.Time{})
|
||||
}
|
||||
|
||||
// Verify that available/expected counts go up as we add pods, then verify that
|
||||
@ -1151,7 +1151,7 @@ func TestUpdatePDBStatusRetries(t *testing.T) {
|
||||
|
||||
// Create a PDB and 3 pods that match it.
|
||||
pdb, pdbKey := newMinAvailablePodDisruptionBudget(t, intstr.FromInt(1))
|
||||
pdb, err := dc.coreClient.PolicyV1beta1().PodDisruptionBudgets(pdb.Namespace).Create(context.TODO(), pdb, metav1.CreateOptions{})
|
||||
pdb, err := dc.coreClient.PolicyV1().PodDisruptionBudgets(pdb.Namespace).Create(context.TODO(), pdb, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create PDB: %v", err)
|
||||
}
|
||||
@ -1186,7 +1186,7 @@ func TestUpdatePDBStatusRetries(t *testing.T) {
|
||||
// These GVRs are copied from the generated fake code because they are not exported.
|
||||
var (
|
||||
podsResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
||||
poddisruptionbudgetsResource = schema.GroupVersionResource{Group: "policy", Version: "v1beta1", Resource: "poddisruptionbudgets"}
|
||||
poddisruptionbudgetsResource = schema.GroupVersionResource{Group: "policy", Version: "v1", Resource: "poddisruptionbudgets"}
|
||||
)
|
||||
|
||||
// Bypass the coreClient.Fake and write directly to the ObjectTracker, because
|
||||
@ -1248,7 +1248,7 @@ func TestUpdatePDBStatusRetries(t *testing.T) {
|
||||
|
||||
// (C) Whether or not sync() returned an error, the PDB status should reflect
|
||||
// the evictions that took place.
|
||||
finalPDB, err := dc.coreClient.PolicyV1beta1().PodDisruptionBudgets("default").Get(context.TODO(), pdb.Name, metav1.GetOptions{})
|
||||
finalPDB, err := dc.coreClient.PolicyV1().PodDisruptionBudgets("default").Get(context.TODO(), pdb.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get PDB: %v", err)
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ import (
|
||||
nodev1 "k8s.io/api/node/v1"
|
||||
nodev1alpha1 "k8s.io/api/node/v1alpha1"
|
||||
nodev1beta1 "k8s.io/api/node/v1beta1"
|
||||
policyapiv1 "k8s.io/api/policy/v1"
|
||||
policyapiv1beta1 "k8s.io/api/policy/v1beta1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
rbacv1alpha1 "k8s.io/api/rbac/v1alpha1"
|
||||
@ -688,6 +689,7 @@ func DefaultAPIResourceConfigSource() *serverstorage.ResourceConfig {
|
||||
networkingapiv1beta1.SchemeGroupVersion,
|
||||
nodev1.SchemeGroupVersion,
|
||||
nodev1beta1.SchemeGroupVersion,
|
||||
policyapiv1.SchemeGroupVersion,
|
||||
policyapiv1beta1.SchemeGroupVersion,
|
||||
rbacv1.SchemeGroupVersion,
|
||||
rbacv1beta1.SchemeGroupVersion,
|
||||
|
@ -77,6 +77,7 @@ var GVRToStorageVersionHash = map[string]string{
|
||||
"networking.k8s.io/v1/ingressclasses": "l/iqIbDgFyQ=",
|
||||
"node.k8s.io/v1/runtimeclasses": "WQTu1GL3T2Q=",
|
||||
"node.k8s.io/v1beta1/runtimeclasses": "WQTu1GL3T2Q=",
|
||||
"policy/v1/poddisruptionbudgets": "6BGBu0kpHtk=",
|
||||
"policy/v1beta1/poddisruptionbudgets": "6BGBu0kpHtk=",
|
||||
"policy/v1beta1/podsecuritypolicies": "khBLobUXkqA=",
|
||||
"rbac.authorization.k8s.io/v1/clusterrolebindings": "48tpQ8gZHFc=",
|
||||
|
@ -805,7 +805,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
WindowsEndpointSliceProxying: {Default: true, PreRelease: featuregate.Beta},
|
||||
StartupProbe: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23
|
||||
AllowInsecureBackendProxy: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23
|
||||
PodDisruptionBudget: {Default: true, PreRelease: featuregate.Beta},
|
||||
PodDisruptionBudget: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
|
||||
CronJobControllerV2: {Default: true, PreRelease: featuregate.Beta},
|
||||
DaemonSetUpdateSurge: {Default: false, PreRelease: featuregate.Alpha},
|
||||
ServiceTopology: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
policyv1beta1 "k8s.io/api/policy/v1beta1"
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
@ -31,7 +31,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1beta1"
|
||||
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1"
|
||||
"k8s.io/client-go/util/retry"
|
||||
pdbhelper "k8s.io/component-helpers/apps/poddisruptionbudget"
|
||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||
@ -314,7 +314,7 @@ func createTooManyRequestsError(name string) error {
|
||||
}
|
||||
|
||||
// checkAndDecrement checks if the provided PodDisruptionBudget allows any disruption.
|
||||
func (r *EvictionREST) checkAndDecrement(namespace string, podName string, pdb policyv1beta1.PodDisruptionBudget, dryRun bool) error {
|
||||
func (r *EvictionREST) checkAndDecrement(namespace string, podName string, pdb policyv1.PodDisruptionBudget, dryRun bool) error {
|
||||
if pdb.Status.ObservedGeneration < pdb.Generation {
|
||||
|
||||
return createTooManyRequestsError(pdb.Name)
|
||||
@ -358,17 +358,13 @@ func (r *EvictionREST) checkAndDecrement(namespace string, podName string, pdb p
|
||||
}
|
||||
|
||||
// getPodDisruptionBudgets returns any PDBs that match the pod or err if there's an error.
|
||||
func (r *EvictionREST) getPodDisruptionBudgets(ctx context.Context, pod *api.Pod) ([]policyv1beta1.PodDisruptionBudget, error) {
|
||||
if len(pod.Labels) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *EvictionREST) getPodDisruptionBudgets(ctx context.Context, pod *api.Pod) ([]policyv1.PodDisruptionBudget, error) {
|
||||
pdbList, err := r.podDisruptionBudgetClient.PodDisruptionBudgets(pod.Namespace).List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pdbs []policyv1beta1.PodDisruptionBudget
|
||||
var pdbs []policyv1.PodDisruptionBudget
|
||||
for _, pdb := range pdbList.Items {
|
||||
if pdb.Namespace != pod.Namespace {
|
||||
continue
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
policyv1beta1 "k8s.io/api/policy/v1beta1"
|
||||
policyv1"k8s.io/api/policy/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
@ -52,10 +52,10 @@ func TestEviction(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "matching pdbs with no disruptions allowed, pod running",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
}},
|
||||
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t1", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
|
||||
expectError: true,
|
||||
@ -64,10 +64,10 @@ func TestEviction(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "matching pdbs with no disruptions allowed, pod pending",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
}},
|
||||
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t2", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
|
||||
expectError: false,
|
||||
@ -77,10 +77,10 @@ func TestEviction(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "matching pdbs with no disruptions allowed, pod succeeded",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
}},
|
||||
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t3", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
|
||||
expectError: false,
|
||||
@ -90,10 +90,10 @@ func TestEviction(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "matching pdbs with no disruptions allowed, pod failed",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
}},
|
||||
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t4", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
|
||||
expectError: false,
|
||||
@ -103,10 +103,10 @@ func TestEviction(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "matching pdbs with disruptions allowed",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 1},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 1},
|
||||
}},
|
||||
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t5", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
|
||||
expectDeleted: true,
|
||||
@ -114,10 +114,10 @@ func TestEviction(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "non-matching pdbs",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"b": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"b": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
}},
|
||||
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t6", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
|
||||
expectDeleted: true,
|
||||
@ -125,10 +125,10 @@ func TestEviction(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "matching pdbs with disruptions allowed but bad name in Url",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 1},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 1},
|
||||
}},
|
||||
badNameInURL: true,
|
||||
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t7", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
|
||||
@ -161,7 +161,7 @@ func TestEviction(t *testing.T) {
|
||||
}
|
||||
|
||||
client := fake.NewSimpleClientset(tc.pdbs...)
|
||||
evictionRest := newEvictionStorage(storage.Store, client.PolicyV1beta1())
|
||||
evictionRest := newEvictionStorage(storage.Store, client.PolicyV1())
|
||||
|
||||
name := pod.Name
|
||||
if tc.badNameInURL {
|
||||
@ -225,10 +225,10 @@ func TestEvictionIngorePDB(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "pdbs No disruptions allowed, pod pending, first delete conflict, pod still pending, pod deleted successfully",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
}},
|
||||
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t1", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
|
||||
expectError: false,
|
||||
@ -241,10 +241,10 @@ func TestEvictionIngorePDB(t *testing.T) {
|
||||
// pod should not be deleted.
|
||||
{
|
||||
name: "pdbs No disruptions allowed, pod pending, first delete conflict, pod becomes running, continueToPDBs",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
}},
|
||||
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t2", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
|
||||
expectError: true,
|
||||
@ -254,10 +254,10 @@ func TestEvictionIngorePDB(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "pdbs disruptions allowed, pod pending, first delete conflict, pod becomes running, continueToPDBs",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 1},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 1},
|
||||
}},
|
||||
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t3", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
|
||||
expectError: false,
|
||||
@ -267,10 +267,10 @@ func TestEvictionIngorePDB(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "pod pending, always conflict on delete",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
}},
|
||||
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t4", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
|
||||
expectError: true,
|
||||
@ -280,10 +280,10 @@ func TestEvictionIngorePDB(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "pod pending, always conflict on delete, user provided ResourceVersion constraint",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
}},
|
||||
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t5", Namespace: "default"}, DeleteOptions: metav1.NewRVDeletionPrecondition("userProvided")},
|
||||
expectError: true,
|
||||
@ -293,10 +293,10 @@ func TestEvictionIngorePDB(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "matching pdbs with no disruptions allowed, pod terminating",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
|
||||
}},
|
||||
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t6", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(300)},
|
||||
expectError: false,
|
||||
@ -306,10 +306,10 @@ func TestEvictionIngorePDB(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "matching pdbs with no disruptions allowed, pod running, pod healthy, unhealthy pod not ours",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{
|
||||
// This simulates 3 pods desired, our pod healthy, unhealthy pod is not ours.
|
||||
DisruptionsAllowed: 0,
|
||||
CurrentHealthy: 2,
|
||||
@ -329,10 +329,10 @@ func TestEvictionIngorePDB(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "matching pdbs with no disruptions allowed, pod running, pod unhealthy, unhealthy pod ours",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{
|
||||
// This simulates 3 pods desired, our pod unhealthy
|
||||
DisruptionsAllowed: 0,
|
||||
CurrentHealthy: 2,
|
||||
@ -353,10 +353,10 @@ func TestEvictionIngorePDB(t *testing.T) {
|
||||
{
|
||||
// This case should return the 529 retry error.
|
||||
name: "matching pdbs with no disruptions allowed, pod running, pod unhealthy, unhealthy pod ours, resource version conflict",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{
|
||||
// This simulates 3 pods desired, our pod unhealthy
|
||||
DisruptionsAllowed: 0,
|
||||
CurrentHealthy: 2,
|
||||
@ -377,10 +377,10 @@ func TestEvictionIngorePDB(t *testing.T) {
|
||||
{
|
||||
// This case should return the 529 retry error.
|
||||
name: "matching pdbs with no disruptions allowed, pod running, pod unhealthy, unhealthy pod ours, other error on delete",
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{
|
||||
// This simulates 3 pods desired, our pod unhealthy
|
||||
DisruptionsAllowed: 0,
|
||||
CurrentHealthy: 2,
|
||||
@ -428,7 +428,7 @@ func TestEvictionIngorePDB(t *testing.T) {
|
||||
}
|
||||
|
||||
client := fake.NewSimpleClientset(tc.pdbs...)
|
||||
evictionRest := newEvictionStorage(ms, client.PolicyV1beta1())
|
||||
evictionRest := newEvictionStorage(ms, client.PolicyV1())
|
||||
|
||||
name := pod.Name
|
||||
ms.pod = pod
|
||||
@ -473,10 +473,10 @@ func TestEvictionDryRun(t *testing.T) {
|
||||
name: "with pdbs",
|
||||
evictionOptions: &metav1.DeleteOptions{DryRun: []string{"All"}},
|
||||
requestOptions: &metav1.CreateOptions{DryRun: []string{"All"}},
|
||||
pdbs: []runtime.Object{&policyv1beta1.PodDisruptionBudget{
|
||||
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{DisruptionsAllowed: 1},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 1},
|
||||
}},
|
||||
},
|
||||
}
|
||||
@ -496,7 +496,7 @@ func TestEvictionDryRun(t *testing.T) {
|
||||
}
|
||||
|
||||
client := fake.NewSimpleClientset(tc.pdbs...)
|
||||
evictionRest := newEvictionStorage(storage.Store, client.PolicyV1beta1())
|
||||
evictionRest := newEvictionStorage(storage.Store, client.PolicyV1())
|
||||
eviction := &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"}, DeleteOptions: tc.evictionOptions}
|
||||
_, err := evictionRest.Create(testContext, pod.Name, eviction, nil, tc.requestOptions)
|
||||
if err != nil {
|
||||
@ -509,47 +509,47 @@ func TestEvictionDryRun(t *testing.T) {
|
||||
func TestEvictionPDBStatus(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
pdb *policyv1beta1.PodDisruptionBudget
|
||||
pdb *policyv1.PodDisruptionBudget
|
||||
expectedDisruptionsAllowed int32
|
||||
expectedReason string
|
||||
}{
|
||||
{
|
||||
name: "pdb status is updated after eviction",
|
||||
pdb: &policyv1beta1.PodDisruptionBudget{
|
||||
pdb: &policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{
|
||||
DisruptionsAllowed: 1,
|
||||
Conditions: []metav1.Condition{
|
||||
{
|
||||
Type: policyv1beta1.DisruptionAllowedCondition,
|
||||
Reason: policyv1beta1.SufficientPodsReason,
|
||||
Type: policyv1.DisruptionAllowedCondition,
|
||||
Reason: policyv1.SufficientPodsReason,
|
||||
Status: metav1.ConditionTrue,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDisruptionsAllowed: 0,
|
||||
expectedReason: policyv1beta1.InsufficientPodsReason,
|
||||
expectedReason: policyv1.InsufficientPodsReason,
|
||||
},
|
||||
{
|
||||
name: "condition reason is only updated if AllowedDisruptions becomes 0",
|
||||
pdb: &policyv1beta1.PodDisruptionBudget{
|
||||
pdb: &policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||
Spec: policyv1beta1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1beta1.PodDisruptionBudgetStatus{
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
|
||||
Status: policyv1.PodDisruptionBudgetStatus{
|
||||
DisruptionsAllowed: 3,
|
||||
Conditions: []metav1.Condition{
|
||||
{
|
||||
Type: policyv1beta1.DisruptionAllowedCondition,
|
||||
Reason: policyv1beta1.SufficientPodsReason,
|
||||
Type: policyv1.DisruptionAllowedCondition,
|
||||
Reason: policyv1.SufficientPodsReason,
|
||||
Status: metav1.ConditionTrue,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDisruptionsAllowed: 2,
|
||||
expectedReason: policyv1beta1.SufficientPodsReason,
|
||||
expectedReason: policyv1.SufficientPodsReason,
|
||||
},
|
||||
}
|
||||
|
||||
@ -578,14 +578,14 @@ func TestEvictionPDBStatus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
evictionRest := newEvictionStorage(storage.Store, client.PolicyV1beta1())
|
||||
evictionRest := newEvictionStorage(storage.Store, client.PolicyV1())
|
||||
eviction := &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "foo-1", Namespace: "default"}, DeleteOptions: &metav1.DeleteOptions{}}
|
||||
_, err := evictionRest.Create(testContext, "foo-1", eviction, nil, &metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to run eviction: %v", err)
|
||||
}
|
||||
|
||||
existingPDB, err := client.PolicyV1beta1().PodDisruptionBudgets(metav1.NamespaceDefault).Get(context.TODO(), tc.pdb.Name, metav1.GetOptions{})
|
||||
existingPDB, err := client.PolicyV1().PodDisruptionBudgets(metav1.NamespaceDefault).Get(context.TODO(), tc.pdb.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("%#v", err)
|
||||
return
|
||||
@ -595,7 +595,7 @@ func TestEvictionPDBStatus(t *testing.T) {
|
||||
t.Errorf("expected DisruptionsAllowed to be %d, but got %d", want, got)
|
||||
}
|
||||
|
||||
cond := apimeta.FindStatusCondition(existingPDB.Status.Conditions, policyv1beta1.DisruptionAllowedCondition)
|
||||
cond := apimeta.FindStatusCondition(existingPDB.Status.Conditions, policyv1.DisruptionAllowedCondition)
|
||||
if want, got := tc.expectedReason, cond.Reason; want != got {
|
||||
t.Errorf("expected Reason to be %q, but got %q", want, got)
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ import (
|
||||
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1beta1"
|
||||
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1"
|
||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
|
@ -35,7 +35,7 @@ import (
|
||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||
"k8s.io/apiserver/pkg/storage/etcd3"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1beta1"
|
||||
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
@ -110,14 +110,10 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generi
|
||||
NegotiatedSerializer: legacyscheme.Codecs,
|
||||
}
|
||||
|
||||
var podDisruptionClient policyclient.PodDisruptionBudgetsGetter
|
||||
if policyGroupVersion := (schema.GroupVersion{Group: "policy", Version: "v1beta1"}); legacyscheme.Scheme.IsVersionRegistered(policyGroupVersion) {
|
||||
var err error
|
||||
podDisruptionClient, err = policyclient.NewForConfig(c.LoopbackClientConfig)
|
||||
podDisruptionClient, err := policyclient.NewForConfig(c.LoopbackClientConfig)
|
||||
if err != nil {
|
||||
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, err
|
||||
}
|
||||
}
|
||||
restStorage := LegacyRESTStorage{}
|
||||
|
||||
podTemplateStorage, err := podtemplatestore.NewREST(restOptionsGetter)
|
||||
|
@ -114,8 +114,8 @@ func (podDisruptionBudgetStatusStrategy) ValidateUpdate(ctx context.Context, obj
|
||||
var apiVersion schema.GroupVersion
|
||||
if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
|
||||
apiVersion = schema.GroupVersion{
|
||||
Group: requestInfo.APIVersion,
|
||||
Version: requestInfo.APIGroup,
|
||||
Group: requestInfo.APIGroup,
|
||||
Version: requestInfo.APIVersion,
|
||||
}
|
||||
}
|
||||
return validation.ValidatePodDisruptionBudgetStatusUpdate(obj.(*policy.PodDisruptionBudget).Status,
|
||||
|
@ -148,3 +148,65 @@ func TestPodDisruptionBudgetStatusStrategy(t *testing.T) {
|
||||
t.Errorf("Unexpected error %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodDisruptionBudgetStatusValidationByApiVersion(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
apiVersion string
|
||||
validation bool
|
||||
}{
|
||||
"policy/v1beta1 should not do update validation": {
|
||||
apiVersion: "v1beta1",
|
||||
validation: false,
|
||||
},
|
||||
"policy/v1 should do update validation": {
|
||||
apiVersion: "v1",
|
||||
validation: true,
|
||||
},
|
||||
"policy/some-version should do update validation": {
|
||||
apiVersion: "some-version",
|
||||
validation: true,
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(),
|
||||
&genericapirequest.RequestInfo{
|
||||
APIGroup: "policy",
|
||||
APIVersion: tc.apiVersion,
|
||||
})
|
||||
|
||||
oldMaxUnavailable := intstr.FromInt(2)
|
||||
newMaxUnavailable := intstr.FromInt(3)
|
||||
oldPdb := &policy.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "10"},
|
||||
Spec: policy.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
|
||||
MaxUnavailable: &oldMaxUnavailable,
|
||||
},
|
||||
Status: policy.PodDisruptionBudgetStatus{
|
||||
DisruptionsAllowed: 1,
|
||||
},
|
||||
}
|
||||
newPdb := &policy.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "9"},
|
||||
Spec: policy.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
|
||||
MinAvailable: &newMaxUnavailable,
|
||||
},
|
||||
Status: policy.PodDisruptionBudgetStatus{
|
||||
DisruptionsAllowed: -1, // This is not allowed, so should trigger validation error.
|
||||
},
|
||||
}
|
||||
|
||||
errs := StatusStrategy.ValidateUpdate(ctx, newPdb, oldPdb)
|
||||
hasErrors := len(errs) > 0
|
||||
if !tc.validation && hasErrors {
|
||||
t.Errorf("Validation failed when no validation should happen")
|
||||
}
|
||||
if tc.validation && !hasErrors {
|
||||
t.Errorf("Expected validation errors but didn't get any")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package rest
|
||||
|
||||
import (
|
||||
policyapiv1 "k8s.io/api/policy/v1"
|
||||
policyapiv1beta1 "k8s.io/api/policy/v1beta1"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
@ -42,6 +43,14 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag
|
||||
apiGroupInfo.VersionedResourcesStorageMap[policyapiv1beta1.SchemeGroupVersion.Version] = storageMap
|
||||
}
|
||||
}
|
||||
|
||||
if apiResourceConfigSource.VersionEnabled(policyapiv1.SchemeGroupVersion) {
|
||||
if storageMap, err := p.v1Storage(apiResourceConfigSource, restOptionsGetter); err != nil {
|
||||
return genericapiserver.APIGroupInfo{}, false, err
|
||||
} else {
|
||||
apiGroupInfo.VersionedResourcesStorageMap[policyapiv1.SchemeGroupVersion.Version] = storageMap
|
||||
}
|
||||
}
|
||||
return apiGroupInfo, true, nil
|
||||
}
|
||||
|
||||
@ -64,6 +73,19 @@ func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorag
|
||||
return storage, err
|
||||
}
|
||||
|
||||
func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) {
|
||||
storage := map[string]rest.Storage{}
|
||||
|
||||
poddisruptionbudgetStorage, poddisruptionbudgetStatusStorage, err := poddisruptionbudgetstore.NewREST(restOptionsGetter)
|
||||
if err != nil {
|
||||
return storage, err
|
||||
}
|
||||
storage["poddisruptionbudgets"] = poddisruptionbudgetStorage
|
||||
storage["poddisruptionbudgets/status"] = poddisruptionbudgetStatusStorage
|
||||
|
||||
return storage, err
|
||||
}
|
||||
|
||||
func (p RESTStorageProvider) GroupName() string {
|
||||
return policy.GroupName
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
policy "k8s.io/api/policy/v1beta1"
|
||||
policy "k8s.io/api/policy/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@ -37,7 +37,7 @@ import (
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
policylisters "k8s.io/client-go/listers/policy/v1beta1"
|
||||
policylisters "k8s.io/client-go/listers/policy/v1"
|
||||
corev1helpers "k8s.io/component-helpers/scheduling/corev1"
|
||||
extenderv1 "k8s.io/kube-scheduler/extender/v1"
|
||||
kubefeatures "k8s.io/kubernetes/pkg/features"
|
||||
@ -799,7 +799,7 @@ func filterPodsWithPDBViolation(podInfos []*framework.PodInfo, pdbs []*policy.Po
|
||||
|
||||
func getPDBLister(informerFactory informers.SharedInformerFactory) policylisters.PodDisruptionBudgetLister {
|
||||
if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.PodDisruptionBudget) {
|
||||
return informerFactory.Policy().V1beta1().PodDisruptionBudgets().Lister()
|
||||
return informerFactory.Policy().V1().PodDisruptionBudgets().Lister()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ import (
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
policy "k8s.io/api/policy/v1beta1"
|
||||
policy "k8s.io/api/policy/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
24
staging/src/k8s.io/api/policy/v1/doc.go
Normal file
24
staging/src/k8s.io/api/policy/v1/doc.go
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright 2021 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.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:protobuf-gen=package
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
// Package policy is for any kind of policy object. Suitable examples, even if
|
||||
// they aren't all here, are PodDisruptionBudget, PodSecurityPolicy,
|
||||
// NetworkPolicy, etc.
|
||||
package v1 // import "k8s.io/api/policy/v1"
|
51
staging/src/k8s.io/api/policy/v1/register.go
Normal file
51
staging/src/k8s.io/api/policy/v1/register.go
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2021 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 v1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package
|
||||
const GroupName = "policy"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// Adds the list of known types to the given scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&PodDisruptionBudget{},
|
||||
&PodDisruptionBudgetList{},
|
||||
)
|
||||
// Add the watch version that applies
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
}
|
150
staging/src/k8s.io/api/policy/v1/types.go
Normal file
150
staging/src/k8s.io/api/policy/v1/types.go
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
Copyright 2021 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 v1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
// PodDisruptionBudgetSpec is a description of a PodDisruptionBudget.
|
||||
type PodDisruptionBudgetSpec struct {
|
||||
// An eviction is allowed if at least "minAvailable" pods selected by
|
||||
// "selector" will still be available after the eviction, i.e. even in the
|
||||
// absence of the evicted pod. So for example you can prevent all voluntary
|
||||
// evictions by specifying "100%".
|
||||
// +optional
|
||||
MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty" protobuf:"bytes,1,opt,name=minAvailable"`
|
||||
|
||||
// Label query over pods whose evictions are managed by the disruption
|
||||
// budget.
|
||||
// A null selector will match no pods, while an empty ({}) selector will select
|
||||
// all pods within the namespace.
|
||||
// +patchStrategy=replace
|
||||
// +optional
|
||||
Selector *metav1.LabelSelector `json:"selector,omitempty" patchStrategy:"replace" protobuf:"bytes,2,opt,name=selector"`
|
||||
|
||||
// An eviction is allowed if at most "maxUnavailable" pods selected by
|
||||
// "selector" are unavailable after the eviction, i.e. even in absence of
|
||||
// the evicted pod. For example, one can prevent all voluntary evictions
|
||||
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
|
||||
// +optional
|
||||
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty" protobuf:"bytes,3,opt,name=maxUnavailable"`
|
||||
}
|
||||
|
||||
// PodDisruptionBudgetStatus represents information about the status of a
|
||||
// PodDisruptionBudget. Status may trail the actual state of a system.
|
||||
type PodDisruptionBudgetStatus struct {
|
||||
// Most recent generation observed when updating this PDB status. DisruptionsAllowed and other
|
||||
// status information is valid only if observedGeneration equals to PDB's object generation.
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"`
|
||||
|
||||
// DisruptedPods contains information about pods whose eviction was
|
||||
// processed by the API server eviction subresource handler but has not
|
||||
// yet been observed by the PodDisruptionBudget controller.
|
||||
// A pod will be in this map from the time when the API server processed the
|
||||
// eviction request to the time when the pod is seen by PDB controller
|
||||
// as having been marked for deletion (or after a timeout). The key in the map is the name of the pod
|
||||
// and the value is the time when the API server processed the eviction request. If
|
||||
// the deletion didn't occur and a pod is still there it will be removed from
|
||||
// the list automatically by PodDisruptionBudget controller after some time.
|
||||
// If everything goes smooth this map should be empty for the most of the time.
|
||||
// Large number of entries in the map may indicate problems with pod deletions.
|
||||
// +optional
|
||||
DisruptedPods map[string]metav1.Time `json:"disruptedPods,omitempty" protobuf:"bytes,2,rep,name=disruptedPods"`
|
||||
|
||||
// Number of pod disruptions that are currently allowed.
|
||||
DisruptionsAllowed int32 `json:"disruptionsAllowed" protobuf:"varint,3,opt,name=disruptionsAllowed"`
|
||||
|
||||
// current number of healthy pods
|
||||
CurrentHealthy int32 `json:"currentHealthy" protobuf:"varint,4,opt,name=currentHealthy"`
|
||||
|
||||
// minimum desired number of healthy pods
|
||||
DesiredHealthy int32 `json:"desiredHealthy" protobuf:"varint,5,opt,name=desiredHealthy"`
|
||||
|
||||
// total number of pods counted by this disruption budget
|
||||
ExpectedPods int32 `json:"expectedPods" protobuf:"varint,6,opt,name=expectedPods"`
|
||||
|
||||
// Conditions contain conditions for PDB. The disruption controller sets the
|
||||
// DisruptionAllowed condition. The following are known values for the reason field
|
||||
// (additional reasons could be added in the future):
|
||||
// - SyncFailed: The controller encountered an error and wasn't able to compute
|
||||
// the number of allowed disruptions. Therefore no disruptions are
|
||||
// allowed and the status of the condition will be False.
|
||||
// - InsufficientPods: The number of pods are either at or below the number
|
||||
// required by the PodDisruptionBudget. No disruptions are
|
||||
// allowed and the status of the condition will be False.
|
||||
// - SufficientPods: There are more pods than required by the PodDisruptionBudget.
|
||||
// The condition will be True, and the number of allowed
|
||||
// disruptions are provided by the disruptionsAllowed property.
|
||||
//
|
||||
// +optional
|
||||
// +patchMergeKey=type
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,7,rep,name=conditions"`
|
||||
}
|
||||
|
||||
const (
|
||||
// DisruptionAllowedCondition is a condition set by the disruption controller
|
||||
// that signal whether any of the pods covered by the PDB can be disrupted.
|
||||
DisruptionAllowedCondition = "DisruptionAllowed"
|
||||
|
||||
// SyncFailedReason is set on the DisruptionAllowed condition if reconcile
|
||||
// of the PDB failed and therefore disruption of pods are not allowed.
|
||||
SyncFailedReason = "SyncFailed"
|
||||
// SufficientPodsReason is set on the DisruptionAllowed condition if there are
|
||||
// more pods covered by the PDB than required and at least one can be disrupted.
|
||||
SufficientPodsReason = "SufficientPods"
|
||||
// InsufficientPodsReason is set on the DisruptionAllowed condition if the number
|
||||
// of pods are equal to or fewer than required by the PDB.
|
||||
InsufficientPodsReason = "InsufficientPods"
|
||||
)
|
||||
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// PodDisruptionBudget is an object to define the max disruption that can be caused to a collection of pods
|
||||
type PodDisruptionBudget struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// Standard object's metadata.
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// Specification of the desired behavior of the PodDisruptionBudget.
|
||||
// +optional
|
||||
Spec PodDisruptionBudgetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
|
||||
// Most recently observed status of the PodDisruptionBudget.
|
||||
// +optional
|
||||
Status PodDisruptionBudgetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// PodDisruptionBudgetList is a collection of PodDisruptionBudgets.
|
||||
type PodDisruptionBudgetList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// Standard object's metadata.
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
// +optional
|
||||
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
// Items is a list of PodDisruptionBudgets
|
||||
Items []PodDisruptionBudget `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||
}
|
@ -33,8 +33,12 @@ type PodDisruptionBudgetSpec struct {
|
||||
|
||||
// Label query over pods whose evictions are managed by the disruption
|
||||
// budget.
|
||||
// A null selector selects no pods.
|
||||
// An empty selector ({}) also selects no pods, which differs from standard behavior of selecting all pods.
|
||||
// In policy/v1, an empty selector will select all pods in the namespace.
|
||||
// +patchStrategy=replace
|
||||
// +optional
|
||||
Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"`
|
||||
Selector *metav1.LabelSelector `json:"selector,omitempty" patchStrategy:"replace" protobuf:"bytes,2,opt,name=selector"`
|
||||
|
||||
// An eviction is allowed if at most "maxUnavailable" pods selected by
|
||||
// "selector" are unavailable after the eviction, i.e. even in absence of
|
||||
@ -118,7 +122,9 @@ const (
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +k8s:prerelease-lifecycle-gen:introduced=1.5
|
||||
// +k8s:prerelease-lifecycle-gen:deprecated=1.22
|
||||
// +k8s:prerelease-lifecycle-gen:deprecated=1.21
|
||||
// +k8s:prerelease-lifecycle-gen:removed=1.25
|
||||
// +k8s:prerelease-lifecycle-gen:replacement=policy,v1,PodDisruptionBudget
|
||||
|
||||
// PodDisruptionBudget is an object to define the max disruption that can be caused to a collection of pods
|
||||
type PodDisruptionBudget struct {
|
||||
@ -136,7 +142,9 @@ type PodDisruptionBudget struct {
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +k8s:prerelease-lifecycle-gen:introduced=1.5
|
||||
// +k8s:prerelease-lifecycle-gen:deprecated=1.22
|
||||
// +k8s:prerelease-lifecycle-gen:deprecated=1.21
|
||||
// +k8s:prerelease-lifecycle-gen:removed=1.25
|
||||
// +k8s:prerelease-lifecycle-gen:replacement=policy,v1,PodDisruptionBudgetList
|
||||
|
||||
// PodDisruptionBudgetList is a collection of PodDisruptionBudgets.
|
||||
type PodDisruptionBudgetList struct {
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
admissionregv1 "k8s.io/api/admissionregistration/v1"
|
||||
@ -110,6 +111,7 @@ var groups = []runtime.SchemeBuilder{
|
||||
nodev1.SchemeBuilder,
|
||||
nodev1alpha1.SchemeBuilder,
|
||||
nodev1beta1.SchemeBuilder,
|
||||
policyv1.SchemeBuilder,
|
||||
policyv1beta1.SchemeBuilder,
|
||||
rbacv1alpha1.SchemeBuilder,
|
||||
rbacv1beta1.SchemeBuilder,
|
||||
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2021 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 v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
policy "k8s.io/api/policy/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// PodDisruptionBudgetListerExpansion allows custom methods to be added to
|
||||
// PodDisruptionBudgetLister.
|
||||
type PodDisruptionBudgetListerExpansion interface {
|
||||
GetPodPodDisruptionBudgets(pod *v1.Pod) ([]*policy.PodDisruptionBudget, error)
|
||||
}
|
||||
|
||||
// PodDisruptionBudgetNamespaceListerExpansion allows custom methods to be added to
|
||||
// PodDisruptionBudgetNamespaceLister.
|
||||
type PodDisruptionBudgetNamespaceListerExpansion interface{}
|
||||
|
||||
// GetPodPodDisruptionBudgets returns a list of PodDisruptionBudgets matching a pod.
|
||||
func (s *podDisruptionBudgetLister) GetPodPodDisruptionBudgets(pod *v1.Pod) ([]*policy.PodDisruptionBudget, error) {
|
||||
var selector labels.Selector
|
||||
|
||||
list, err := s.PodDisruptionBudgets(pod.Namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pdbList []*policy.PodDisruptionBudget
|
||||
for i := range list {
|
||||
pdb := list[i]
|
||||
selector, err = metav1.LabelSelectorAsSelector(pdb.Spec.Selector)
|
||||
if err != nil {
|
||||
klog.Warningf("invalid selector: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Unlike the v1beta version, here we let an empty selector match everything.
|
||||
if !selector.Matches(labels.Set(pod.Labels)) {
|
||||
continue
|
||||
}
|
||||
pdbList = append(pdbList, pdb)
|
||||
}
|
||||
|
||||
if len(pdbList) == 0 {
|
||||
return nil, fmt.Errorf("could not find PodDisruptionBudget for pod %s in namespace %s with labels: %v", pod.Name, pod.Namespace, pod.Labels)
|
||||
}
|
||||
|
||||
return pdbList, nil
|
||||
}
|
@ -40,10 +40,6 @@ type PodDisruptionBudgetNamespaceListerExpansion interface{}
|
||||
func (s *podDisruptionBudgetLister) GetPodPodDisruptionBudgets(pod *v1.Pod) ([]*policy.PodDisruptionBudget, error) {
|
||||
var selector labels.Selector
|
||||
|
||||
if len(pod.Labels) == 0 {
|
||||
return nil, fmt.Errorf("no PodDisruptionBudgets found for pod %v because it has no labels", pod.Name)
|
||||
}
|
||||
|
||||
list, err := s.PodDisruptionBudgets(pod.Namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||
package poddisruptionbudget
|
||||
|
||||
import (
|
||||
policy "k8s.io/api/policy/v1beta1"
|
||||
policy "k8s.io/api/policy/v1"
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
@ -38,6 +38,7 @@ import (
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
imagepolicyv1alpha1 "k8s.io/api/imagepolicy/v1alpha1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
policyv1beta1 "k8s.io/api/policy/v1beta1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
rbacv1alpha1 "k8s.io/api/rbac/v1alpha1"
|
||||
@ -74,7 +75,7 @@ func init() {
|
||||
utilruntime.Must(Scheme.SetVersionPriority(extensionsv1beta1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(imagepolicyv1alpha1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(networkingv1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(policyv1beta1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(policyv1beta1.SchemeGroupVersion, policyv1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(rbacv1.SchemeGroupVersion, rbacv1beta1.SchemeGroupVersion, rbacv1alpha1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(schedulingv1alpha1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(storagev1.SchemeGroupVersion, storagev1beta1.SchemeGroupVersion))
|
||||
|
@ -54,6 +54,7 @@ var statusData = map[schema.GroupVersionResource]string{
|
||||
gvr("batch", "v1", "cronjobs"): `{"status": {"lastScheduleTime": null}}`,
|
||||
gvr("batch", "v1beta1", "cronjobs"): `{"status": {"lastScheduleTime": null}}`,
|
||||
gvr("storage.k8s.io", "v1", "volumeattachments"): `{"status": {"attached": true}}`,
|
||||
gvr("policy", "v1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 5}}`,
|
||||
gvr("policy", "v1beta1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 5}}`,
|
||||
gvr("certificates.k8s.io", "v1beta1", "certificatesigningrequests"): `{"status": {"conditions": [{"type": "MyStatus"}]}}`,
|
||||
gvr("certificates.k8s.io", "v1", "certificatesigningrequests"): `{"status": {"conditions": [{"type": "MyStatus", "status": "True"}]}}`,
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
"k8s.io/api/policy/v1beta1"
|
||||
apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -81,7 +82,7 @@ func setup(t *testing.T) (*kubeapiservertesting.TestServer, *disruption.Disrupti
|
||||
|
||||
pdbc := disruption.NewDisruptionController(
|
||||
informers.Core().V1().Pods(),
|
||||
informers.Policy().V1beta1().PodDisruptionBudgets(),
|
||||
informers.Policy().V1().PodDisruptionBudgets(),
|
||||
informers.Core().V1().ReplicationControllers(),
|
||||
informers.Apps().V1().ReplicaSets(),
|
||||
informers.Apps().V1().Deployments(),
|
||||
@ -113,7 +114,6 @@ func TestPDBWithScaleSubresource(t *testing.T) {
|
||||
|
||||
replicas := 4
|
||||
maxUnavailable := int32(2)
|
||||
podLabelValue := "test-crd"
|
||||
|
||||
resource := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
@ -134,40 +134,42 @@ func TestPDBWithScaleSubresource(t *testing.T) {
|
||||
}
|
||||
|
||||
trueValue := true
|
||||
ownerRef := metav1.OwnerReference{
|
||||
ownerRefs := []metav1.OwnerReference{
|
||||
{
|
||||
Name: resource.GetName(),
|
||||
Kind: crdDefinition.Spec.Names.Kind,
|
||||
APIVersion: crdDefinition.Spec.Group + "/" + crdDefinition.Spec.Versions[0].Name,
|
||||
UID: createdResource.GetUID(),
|
||||
Controller: &trueValue,
|
||||
},
|
||||
}
|
||||
for i := 0; i < replicas; i++ {
|
||||
createPod(t, fmt.Sprintf("pod-%d", i), nsName, podLabelValue, clientSet, ownerRef)
|
||||
createPod(t, fmt.Sprintf("pod-%d", i), nsName, map[string]string{"app": "test-crd"}, clientSet, ownerRefs)
|
||||
}
|
||||
|
||||
waitToObservePods(t, informers.Core().V1().Pods().Informer(), 4, v1.PodRunning)
|
||||
|
||||
pdb := &v1beta1.PodDisruptionBudget{
|
||||
pdb := &policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-pdb",
|
||||
},
|
||||
Spec: v1beta1.PodDisruptionBudgetSpec{
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{
|
||||
MaxUnavailable: &intstr.IntOrString{
|
||||
Type: intstr.Int,
|
||||
IntVal: maxUnavailable,
|
||||
},
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"app": podLabelValue},
|
||||
MatchLabels: map[string]string{"app": "test-crd"},
|
||||
},
|
||||
},
|
||||
}
|
||||
if _, err := clientSet.PolicyV1beta1().PodDisruptionBudgets(nsName).Create(context.TODO(), pdb, metav1.CreateOptions{}); err != nil {
|
||||
if _, err := clientSet.PolicyV1().PodDisruptionBudgets(nsName).Create(context.TODO(), pdb, metav1.CreateOptions{}); err != nil {
|
||||
t.Errorf("Error creating PodDisruptionBudget: %v", err)
|
||||
}
|
||||
|
||||
waitPDBStable(t, clientSet, 4, nsName, pdb.Name)
|
||||
|
||||
newPdb, err := clientSet.PolicyV1beta1().PodDisruptionBudgets(nsName).Get(context.TODO(), pdb.Name, metav1.GetOptions{})
|
||||
newPdb, err := clientSet.PolicyV1().PodDisruptionBudgets(nsName).Get(context.TODO(), pdb.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("Error getting PodDisruptionBudget: %v", err)
|
||||
}
|
||||
@ -183,15 +185,211 @@ func TestPDBWithScaleSubresource(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func createPod(t *testing.T, name, namespace, labelValue string, clientSet clientset.Interface, ownerRef metav1.OwnerReference) {
|
||||
func TestEmptySelector(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
createPDBFunc func(clientSet clientset.Interface, name, nsName string, minAvailable intstr.IntOrString) error
|
||||
expectedCurrentHealthy int32
|
||||
}{
|
||||
{
|
||||
name: "v1beta1 should not target any pods",
|
||||
createPDBFunc: func(clientSet clientset.Interface, name, nsName string, minAvailable intstr.IntOrString) error {
|
||||
pdb := &v1beta1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: v1beta1.PodDisruptionBudgetSpec{
|
||||
MinAvailable: &minAvailable,
|
||||
Selector: &metav1.LabelSelector{},
|
||||
},
|
||||
}
|
||||
_, err := clientSet.PolicyV1beta1().PodDisruptionBudgets(nsName).Create(context.TODO(), pdb, metav1.CreateOptions{})
|
||||
return err
|
||||
},
|
||||
expectedCurrentHealthy: 0,
|
||||
},
|
||||
{
|
||||
name: "v1 should target all pods",
|
||||
createPDBFunc: func(clientSet clientset.Interface, name, nsName string, minAvailable intstr.IntOrString) error {
|
||||
pdb := &policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{
|
||||
MinAvailable: &minAvailable,
|
||||
Selector: &metav1.LabelSelector{},
|
||||
},
|
||||
}
|
||||
_, err := clientSet.PolicyV1().PodDisruptionBudgets(nsName).Create(context.TODO(), pdb, metav1.CreateOptions{})
|
||||
return err
|
||||
},
|
||||
expectedCurrentHealthy: 4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s, pdbc, informers, clientSet, _, _ := setup(t)
|
||||
defer s.TearDownFn()
|
||||
|
||||
nsName := fmt.Sprintf("pdb-empty-selector-%d", i)
|
||||
createNs(t, nsName, clientSet)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
informers.Start(stopCh)
|
||||
go pdbc.Run(stopCh)
|
||||
defer close(stopCh)
|
||||
|
||||
replicas := 4
|
||||
minAvailable := intstr.FromInt(2)
|
||||
|
||||
for j := 0; j < replicas; j++ {
|
||||
createPod(t, fmt.Sprintf("pod-%d", j), nsName, map[string]string{"app": "test-crd"},
|
||||
clientSet, []metav1.OwnerReference{})
|
||||
}
|
||||
|
||||
waitToObservePods(t, informers.Core().V1().Pods().Informer(), 4, v1.PodRunning)
|
||||
|
||||
pdbName := "test-pdb"
|
||||
if err := tc.createPDBFunc(clientSet, pdbName, nsName, minAvailable); err != nil {
|
||||
t.Errorf("Error creating PodDisruptionBudget: %v", err)
|
||||
}
|
||||
|
||||
waitPDBStable(t, clientSet, tc.expectedCurrentHealthy, nsName, pdbName)
|
||||
|
||||
newPdb, err := clientSet.PolicyV1().PodDisruptionBudgets(nsName).Get(context.TODO(), pdbName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("Error getting PodDisruptionBudget: %v", err)
|
||||
}
|
||||
|
||||
if expected, found := tc.expectedCurrentHealthy, newPdb.Status.CurrentHealthy; expected != found {
|
||||
t.Errorf("Expected %d, but found %d", expected, found)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectorsForPodsWithoutLabels(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
createPDBFunc func(clientSet clientset.Interface, name, nsName string, minAvailable intstr.IntOrString) error
|
||||
expectedCurrentHealthy int32
|
||||
}{
|
||||
{
|
||||
name: "pods with no labels can be targeted by v1 PDBs with empty selector",
|
||||
createPDBFunc: func(clientSet clientset.Interface, name, nsName string, minAvailable intstr.IntOrString) error {
|
||||
pdb := &policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{
|
||||
MinAvailable: &minAvailable,
|
||||
Selector: &metav1.LabelSelector{},
|
||||
},
|
||||
}
|
||||
_, err := clientSet.PolicyV1().PodDisruptionBudgets(nsName).Create(context.TODO(), pdb, metav1.CreateOptions{})
|
||||
return err
|
||||
},
|
||||
expectedCurrentHealthy: 1,
|
||||
},
|
||||
{
|
||||
name: "pods with no labels can be targeted by v1 PDBs with DoesNotExist selector",
|
||||
createPDBFunc: func(clientSet clientset.Interface, name, nsName string, minAvailable intstr.IntOrString) error {
|
||||
pdb := &policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{
|
||||
MinAvailable: &minAvailable,
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "DoesNotExist",
|
||||
Operator: metav1.LabelSelectorOpDoesNotExist,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := clientSet.PolicyV1().PodDisruptionBudgets(nsName).Create(context.TODO(), pdb, metav1.CreateOptions{})
|
||||
return err
|
||||
},
|
||||
expectedCurrentHealthy: 1,
|
||||
},
|
||||
{
|
||||
name: "pods with no labels can be targeted by v1beta1 PDBs with DoesNotExist selector",
|
||||
createPDBFunc: func(clientSet clientset.Interface, name, nsName string, minAvailable intstr.IntOrString) error {
|
||||
pdb := &v1beta1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: v1beta1.PodDisruptionBudgetSpec{
|
||||
MinAvailable: &minAvailable,
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "DoesNotExist",
|
||||
Operator: metav1.LabelSelectorOpDoesNotExist,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := clientSet.PolicyV1beta1().PodDisruptionBudgets(nsName).Create(context.TODO(), pdb, metav1.CreateOptions{})
|
||||
return err
|
||||
},
|
||||
expectedCurrentHealthy: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s, pdbc, informers, clientSet, _, _ := setup(t)
|
||||
defer s.TearDownFn()
|
||||
|
||||
nsName := fmt.Sprintf("pdb-selectors-%d", i)
|
||||
createNs(t, nsName, clientSet)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
informers.Start(stopCh)
|
||||
go pdbc.Run(stopCh)
|
||||
defer close(stopCh)
|
||||
|
||||
minAvailable := intstr.FromInt(1)
|
||||
|
||||
// Create the PDB first and wait for it to settle.
|
||||
pdbName := "test-pdb"
|
||||
if err := tc.createPDBFunc(clientSet, pdbName, nsName, minAvailable); err != nil {
|
||||
t.Errorf("Error creating PodDisruptionBudget: %v", err)
|
||||
}
|
||||
waitPDBStable(t, clientSet, 0, nsName, pdbName)
|
||||
|
||||
// Create a pod and wait for it be reach the running phase.
|
||||
createPod(t,"pod", nsName, map[string]string{}, clientSet, []metav1.OwnerReference{})
|
||||
waitToObservePods(t, informers.Core().V1().Pods().Informer(), 1, v1.PodRunning)
|
||||
|
||||
// Then verify that the added pod are picked up by the disruption controller.
|
||||
waitPDBStable(t, clientSet, 1, nsName, pdbName)
|
||||
|
||||
newPdb, err := clientSet.PolicyV1().PodDisruptionBudgets(nsName).Get(context.TODO(), pdbName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("Error getting PodDisruptionBudget: %v", err)
|
||||
}
|
||||
|
||||
if expected, found := tc.expectedCurrentHealthy, newPdb.Status.CurrentHealthy; expected != found {
|
||||
t.Errorf("Expected %d, but found %d", expected, found)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createPod(t *testing.T, name, namespace string, labels map[string]string, clientSet clientset.Interface, ownerRefs []metav1.OwnerReference) {
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{"app": labelValue},
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
ownerRef,
|
||||
},
|
||||
Labels: labels,
|
||||
OwnerReferences: ownerRefs,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
@ -271,7 +469,7 @@ func waitPDBStable(t *testing.T, clientSet clientset.Interface, podNum int32, ns
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if pdb.Status.CurrentHealthy != podNum {
|
||||
if pdb.Status.ObservedGeneration == 0 || pdb.Status.CurrentHealthy != podNum {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
|
@ -265,6 +265,14 @@ func GetEtcdStorageDataForNamespace(namespace string) map[schema.GroupVersionRes
|
||||
},
|
||||
// --
|
||||
|
||||
// k8s.io/kubernetes/pkg/apis/policy/v1
|
||||
gvr("policy", "v1", "poddisruptionbudgets"): {
|
||||
Stub: `{"metadata": {"name": "pdbv1"}, "spec": {"selector": {"matchLabels": {"anokkey": "anokvalue"}}}}`,
|
||||
ExpectedEtcdPath: "/registry/poddisruptionbudgets/" + namespace + "/pdbv1",
|
||||
ExpectedGVK: gvkP("policy", "v1beta1", "PodDisruptionBudget"),
|
||||
},
|
||||
// --
|
||||
|
||||
// k8s.io/kubernetes/pkg/apis/policy/v1beta1
|
||||
gvr("policy", "v1beta1", "poddisruptionbudgets"): {
|
||||
Stub: `{"metadata": {"name": "pdb1"}, "spec": {"selector": {"matchLabels": {"anokkey": "anokvalue"}}}}`,
|
||||
|
@ -19,7 +19,6 @@ package evictions
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"sync"
|
||||
@ -28,7 +27,8 @@ import (
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/policy/v1beta1"
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
policyv1beta1 "k8s.io/api/policy/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
@ -95,7 +95,7 @@ func TestConcurrentEvictionRequests(t *testing.T) {
|
||||
waitToObservePods(t, informers.Core().V1().Pods().Informer(), numOfEvictions, v1.PodRunning)
|
||||
|
||||
pdb := newPDB()
|
||||
if _, err := clientSet.PolicyV1beta1().PodDisruptionBudgets(ns.Name).Create(context.TODO(), pdb, metav1.CreateOptions{}); err != nil {
|
||||
if _, err := clientSet.PolicyV1().PodDisruptionBudgets(ns.Name).Create(context.TODO(), pdb, metav1.CreateOptions{}); err != nil {
|
||||
t.Errorf("Failed to create PodDisruptionBudget: %v", err)
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ func TestConcurrentEvictionRequests(t *testing.T) {
|
||||
go func(id int, errCh chan error) {
|
||||
defer wg.Done()
|
||||
podName := fmt.Sprintf(podNameFormat, id)
|
||||
eviction := newEviction(ns.Name, podName, deleteOption)
|
||||
eviction := newV1beta1Eviction(ns.Name, podName, deleteOption)
|
||||
|
||||
err := wait.PollImmediate(5*time.Second, 60*time.Second, func() (bool, error) {
|
||||
e := clientSet.PolicyV1beta1().Evictions(ns.Name).Evict(context.TODO(), eviction)
|
||||
@ -208,7 +208,7 @@ func TestTerminalPodEviction(t *testing.T) {
|
||||
waitToObservePods(t, informers.Core().V1().Pods().Informer(), 1, v1.PodSucceeded)
|
||||
|
||||
pdb := newPDB()
|
||||
if _, err := clientSet.PolicyV1beta1().PodDisruptionBudgets(ns.Name).Create(context.TODO(), pdb, metav1.CreateOptions{}); err != nil {
|
||||
if _, err := clientSet.PolicyV1().PodDisruptionBudgets(ns.Name).Create(context.TODO(), pdb, metav1.CreateOptions{}); err != nil {
|
||||
t.Errorf("Failed to create PodDisruptionBudget: %v", err)
|
||||
}
|
||||
|
||||
@ -219,7 +219,7 @@ func TestTerminalPodEviction(t *testing.T) {
|
||||
t.Fatalf("Error while listing pod disruption budget")
|
||||
}
|
||||
oldPdb := pdbList.Items[0]
|
||||
eviction := newEviction(ns.Name, pod.Name, deleteOption)
|
||||
eviction := newV1beta1Eviction(ns.Name, pod.Name, deleteOption)
|
||||
err = wait.PollImmediate(5*time.Second, 60*time.Second, func() (bool, error) {
|
||||
e := clientSet.PolicyV1beta1().Evictions(ns.Name).Evict(context.TODO(), eviction)
|
||||
switch {
|
||||
@ -292,12 +292,12 @@ func addPodConditionReady(pod *v1.Pod) {
|
||||
}
|
||||
}
|
||||
|
||||
func newPDB() *v1beta1.PodDisruptionBudget {
|
||||
return &v1beta1.PodDisruptionBudget{
|
||||
func newPDB() *policyv1.PodDisruptionBudget {
|
||||
return &policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-pdb",
|
||||
},
|
||||
Spec: v1beta1.PodDisruptionBudgetSpec{
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{
|
||||
MinAvailable: &intstr.IntOrString{
|
||||
Type: intstr.Int,
|
||||
IntVal: 0,
|
||||
@ -309,8 +309,8 @@ func newPDB() *v1beta1.PodDisruptionBudget {
|
||||
}
|
||||
}
|
||||
|
||||
func newEviction(ns, evictionName string, deleteOption metav1.DeleteOptions) *v1beta1.Eviction {
|
||||
return &v1beta1.Eviction{
|
||||
func newV1beta1Eviction(ns, evictionName string, deleteOption metav1.DeleteOptions) *policyv1beta1.Eviction {
|
||||
return &policyv1beta1.Eviction{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "Policy/v1beta1",
|
||||
Kind: "Eviction",
|
||||
@ -348,7 +348,7 @@ func rmSetup(t *testing.T) (*httptest.Server, framework.CloseFunc, *disruption.D
|
||||
|
||||
rm := disruption.NewDisruptionController(
|
||||
informers.Core().V1().Pods(),
|
||||
informers.Policy().V1beta1().PodDisruptionBudgets(),
|
||||
informers.Policy().V1().PodDisruptionBudgets(),
|
||||
informers.Core().V1().ReplicationControllers(),
|
||||
informers.Apps().V1().ReplicaSets(),
|
||||
informers.Apps().V1().Deployments(),
|
||||
|
@ -62,7 +62,7 @@ func initDisruptionController(t *testing.T, testCtx *testutils.TestContext) *dis
|
||||
|
||||
dc := disruption.NewDisruptionController(
|
||||
informers.Core().V1().Pods(),
|
||||
informers.Policy().V1beta1().PodDisruptionBudgets(),
|
||||
informers.Policy().V1().PodDisruptionBudgets(),
|
||||
informers.Core().V1().ReplicationControllers(),
|
||||
informers.Apps().V1().ReplicaSets(),
|
||||
informers.Apps().V1().Deployments(),
|
||||
|
Loading…
Reference in New Issue
Block a user