Skip to content

Commit a37b6fc

Browse files
authored
Merge pull request #108138 from liggitt/v1beta1-selector
Revert v1beta1 PodDisruptionBudget selector patchStrategy to pre-1.21 behavior
2 parents d1c45c7 + 33fc0b9 commit a37b6fc

File tree

5 files changed

+149
-10
lines changed

5 files changed

+149
-10
lines changed

api/openapi-spec/swagger.json

+1-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/openapi-spec/v3/apis__policy__v1beta1_openapi.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,7 @@
213213
},
214214
"selector": {
215215
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
216-
"description": "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.",
217-
"x-kubernetes-patch-strategy": "replace"
216+
"description": "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."
218217
}
219218
},
220219
"type": "object"

staging/src/k8s.io/api/policy/v1beta1/generated.proto

-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

staging/src/k8s.io/api/policy/v1beta1/types.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ type PodDisruptionBudgetSpec struct {
3636
// A null selector selects no pods.
3737
// An empty selector ({}) also selects no pods, which differs from standard behavior of selecting all pods.
3838
// In policy/v1, an empty selector will select all pods in the namespace.
39-
// +patchStrategy=replace
4039
// +optional
41-
Selector *metav1.LabelSelector `json:"selector,omitempty" patchStrategy:"replace" protobuf:"bytes,2,opt,name=selector"`
40+
Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"`
4241

4342
// An eviction is allowed if at most "maxUnavailable" pods selected by
4443
// "selector" are unavailable after the eviction, i.e. even in absence of

test/integration/disruption/disruption_test.go

+146-3
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,22 @@ package disruption
1919
import (
2020
"context"
2121
"fmt"
22+
"reflect"
2223
"testing"
2324
"time"
2425

25-
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
26-
27-
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
26+
"github.com/google/go-cmp/cmp"
2827

2928
v1 "k8s.io/api/core/v1"
3029
policyv1 "k8s.io/api/policy/v1"
3130
"k8s.io/api/policy/v1beta1"
31+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3232
apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
33+
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
3334
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3435
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3536
"k8s.io/apimachinery/pkg/runtime/schema"
37+
"k8s.io/apimachinery/pkg/types"
3638
"k8s.io/apimachinery/pkg/util/intstr"
3739
"k8s.io/apimachinery/pkg/util/wait"
3840
cacheddiscovery "k8s.io/client-go/discovery/cached/memory"
@@ -47,6 +49,7 @@ import (
4749
"k8s.io/kubernetes/pkg/controller/disruption"
4850
"k8s.io/kubernetes/test/integration/etcd"
4951
"k8s.io/kubernetes/test/integration/framework"
52+
"k8s.io/utils/pointer"
5053
)
5154

5255
func setup(t *testing.T) (*kubeapiservertesting.TestServer, *disruption.DisruptionController, informers.SharedInformerFactory, clientset.Interface, *apiextensionsclientset.Clientset, dynamic.Interface) {
@@ -493,3 +496,143 @@ func waitToObservePods(t *testing.T, podInformer cache.SharedIndexInformer, podN
493496
t.Fatal(err)
494497
}
495498
}
499+
500+
func TestPatchCompatibility(t *testing.T) {
501+
s, _, _, clientSet, _, _ := setup(t)
502+
defer s.TearDownFn()
503+
504+
testcases := []struct {
505+
name string
506+
version string
507+
startingSelector *metav1.LabelSelector
508+
patchType types.PatchType
509+
patch string
510+
force *bool
511+
fieldManager string
512+
expectSelector *metav1.LabelSelector
513+
}{
514+
{
515+
name: "v1beta1-smp",
516+
version: "v1beta1",
517+
patchType: types.StrategicMergePatchType,
518+
patch: `{"spec":{"selector":{"matchLabels":{"patchmatch":"true"},"matchExpressions":[{"key":"patchexpression","operator":"In","values":["true"]}]}}}`,
519+
// matchLabels portion is merged, matchExpressions portion is replaced (because it's a list with no patchStrategy defined)
520+
expectSelector: &metav1.LabelSelector{
521+
MatchLabels: map[string]string{"basematch": "true", "patchmatch": "true"},
522+
MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "patchexpression", Operator: "In", Values: []string{"true"}}},
523+
},
524+
},
525+
{
526+
name: "v1-smp",
527+
version: "v1",
528+
patchType: types.StrategicMergePatchType,
529+
patch: `{"spec":{"selector":{"matchLabels":{"patchmatch":"true"},"matchExpressions":[{"key":"patchexpression","operator":"In","values":["true"]}]}}}`,
530+
// matchLabels and matchExpressions are both replaced (because selector patchStrategy=replace in v1)
531+
expectSelector: &metav1.LabelSelector{
532+
MatchLabels: map[string]string{"patchmatch": "true"},
533+
MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "patchexpression", Operator: "In", Values: []string{"true"}}},
534+
},
535+
},
536+
537+
{
538+
name: "v1beta1-mergepatch",
539+
version: "v1beta1",
540+
patchType: types.MergePatchType,
541+
patch: `{"spec":{"selector":{"matchLabels":{"patchmatch":"true"},"matchExpressions":[{"key":"patchexpression","operator":"In","values":["true"]}]}}}`,
542+
// matchLabels portion is merged, matchExpressions portion is replaced (because it's a list)
543+
expectSelector: &metav1.LabelSelector{
544+
MatchLabels: map[string]string{"basematch": "true", "patchmatch": "true"},
545+
MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "patchexpression", Operator: "In", Values: []string{"true"}}},
546+
},
547+
},
548+
{
549+
name: "v1-mergepatch",
550+
version: "v1",
551+
patchType: types.MergePatchType,
552+
patch: `{"spec":{"selector":{"matchLabels":{"patchmatch":"true"},"matchExpressions":[{"key":"patchexpression","operator":"In","values":["true"]}]}}}`,
553+
// matchLabels portion is merged, matchExpressions portion is replaced (because it's a list)
554+
expectSelector: &metav1.LabelSelector{
555+
MatchLabels: map[string]string{"basematch": "true", "patchmatch": "true"},
556+
MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "patchexpression", Operator: "In", Values: []string{"true"}}},
557+
},
558+
},
559+
560+
{
561+
name: "v1beta1-apply",
562+
version: "v1beta1",
563+
patchType: types.ApplyPatchType,
564+
patch: `{"apiVersion":"policy/v1beta1","kind":"PodDisruptionBudget","spec":{"selector":{"matchLabels":{"patchmatch":"true"},"matchExpressions":[{"key":"patchexpression","operator":"In","values":["true"]}]}}}`,
565+
force: pointer.Bool(true),
566+
fieldManager: "test",
567+
// entire selector is replaced (because structType=atomic)
568+
expectSelector: &metav1.LabelSelector{
569+
MatchLabels: map[string]string{"patchmatch": "true"},
570+
MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "patchexpression", Operator: "In", Values: []string{"true"}}},
571+
},
572+
},
573+
{
574+
name: "v1-apply",
575+
version: "v1",
576+
patchType: types.ApplyPatchType,
577+
patch: `{"apiVersion":"policy/v1","kind":"PodDisruptionBudget","spec":{"selector":{"matchLabels":{"patchmatch":"true"},"matchExpressions":[{"key":"patchexpression","operator":"In","values":["true"]}]}}}`,
578+
force: pointer.Bool(true),
579+
fieldManager: "test",
580+
// entire selector is replaced (because structType=atomic)
581+
expectSelector: &metav1.LabelSelector{
582+
MatchLabels: map[string]string{"patchmatch": "true"},
583+
MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "patchexpression", Operator: "In", Values: []string{"true"}}},
584+
},
585+
},
586+
}
587+
588+
for _, tc := range testcases {
589+
t.Run(tc.name, func(t *testing.T) {
590+
ns := "default"
591+
maxUnavailable := int32(2)
592+
pdb := &policyv1.PodDisruptionBudget{
593+
ObjectMeta: metav1.ObjectMeta{
594+
Name: "test-pdb",
595+
},
596+
Spec: policyv1.PodDisruptionBudgetSpec{
597+
MaxUnavailable: &intstr.IntOrString{Type: intstr.Int, IntVal: maxUnavailable},
598+
Selector: &metav1.LabelSelector{
599+
MatchLabels: map[string]string{"basematch": "true"},
600+
MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "baseexpression", Operator: "In", Values: []string{"true"}}},
601+
},
602+
},
603+
}
604+
if _, err := clientSet.PolicyV1().PodDisruptionBudgets(ns).Create(context.TODO(), pdb, metav1.CreateOptions{}); err != nil {
605+
t.Fatalf("Error creating PodDisruptionBudget: %v", err)
606+
}
607+
defer func() {
608+
err := clientSet.PolicyV1().PodDisruptionBudgets(ns).Delete(context.TODO(), pdb.Name, metav1.DeleteOptions{})
609+
if err != nil {
610+
t.Fatal(err)
611+
}
612+
}()
613+
614+
var resultSelector *metav1.LabelSelector
615+
switch tc.version {
616+
case "v1":
617+
result, err := clientSet.PolicyV1().PodDisruptionBudgets(ns).Patch(context.TODO(), pdb.Name, tc.patchType, []byte(tc.patch), metav1.PatchOptions{Force: tc.force, FieldManager: tc.fieldManager})
618+
if err != nil {
619+
t.Fatal(err)
620+
}
621+
resultSelector = result.Spec.Selector
622+
case "v1beta1":
623+
result, err := clientSet.PolicyV1beta1().PodDisruptionBudgets(ns).Patch(context.TODO(), pdb.Name, tc.patchType, []byte(tc.patch), metav1.PatchOptions{Force: tc.force, FieldManager: tc.fieldManager})
624+
if err != nil {
625+
t.Fatal(err)
626+
}
627+
resultSelector = result.Spec.Selector
628+
default:
629+
t.Error("unknown version")
630+
}
631+
632+
if !reflect.DeepEqual(resultSelector, tc.expectSelector) {
633+
t.Fatalf("unexpected selector:\n%s", cmp.Diff(tc.expectSelector, resultSelector))
634+
}
635+
})
636+
}
637+
638+
}

0 commit comments

Comments
 (0)