Skip to content

Commit ba81696

Browse files
committed
Simplify status subresource ratcheting testing
1 parent 091fa29 commit ba81696

File tree

2 files changed

+79
-101
lines changed

2 files changed

+79
-101
lines changed

staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func (a statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Obj
105105
var correlatedObject *common.CorrelatedObject
106106
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CRDValidationRatcheting) {
107107
correlatedObject = common.NewCorrelatedObject(uNew.Object, uOld.Object, &model.Structural{Structural: a.structuralSchema})
108-
options = append(options, validation.WithRatcheting(correlatedObject))
108+
options = append(options, validation.WithRatcheting(correlatedObject.Key("status")))
109109
celOptions = append(celOptions, cel.WithRatcheting(correlatedObject))
110110
}
111111

staging/src/k8s.io/apiextensions-apiserver/test/integration/ratcheting_test.go

Lines changed: 78 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ import (
4545
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
4646
"k8s.io/apimachinery/pkg/runtime"
4747
"k8s.io/apimachinery/pkg/runtime/schema"
48-
utilerrors "k8s.io/apimachinery/pkg/util/errors"
4948
"k8s.io/apimachinery/pkg/util/uuid"
5049
"k8s.io/apimachinery/pkg/util/wait"
5150
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
@@ -82,7 +81,7 @@ type ratchetingTestContext struct {
8281
*testing.T
8382
DynamicClient dynamic.Interface
8483
APIExtensionsClient clientset.Interface
85-
SkipStatus bool
84+
StatusSubresource bool
8685
}
8786

8887
type ratchetingTestOperation interface {
@@ -97,12 +96,6 @@ type expectError struct {
9796
func (e expectError) Do(ctx *ratchetingTestContext) error {
9897
err := e.op.Do(ctx)
9998
if err != nil {
100-
// If there is an error, it should happen when updating the CR
101-
// and also when updating the CR status subresource.
102-
if agg, ok := err.(utilerrors.Aggregate); ok && len(agg.Errors()) != 2 {
103-
return fmt.Errorf("expected 2 errors, got %v", len(agg.Errors()))
104-
}
105-
10699
return nil
107100
}
108101
return errors.New("expected error")
@@ -172,7 +165,12 @@ func (a applyPatchOperation) Do(ctx *ratchetingTestContext) error {
172165

173166
patch := &unstructured.Unstructured{}
174167
if obj, ok := a.patch.(map[string]interface{}); ok {
175-
patch.Object = obj
168+
patch.Object = map[string]interface{}{}
169+
170+
// Copy the map at the top level to avoid modifying the original.
171+
for k, v := range obj {
172+
patch.Object[k] = v
173+
}
176174
} else if str, ok := a.patch.(string); ok {
177175
str = FixTabsOrDie(str)
178176
if err := utilyaml.NewYAMLOrJSONDecoder(strings.NewReader(str), len(str)).Decode(&patch.Object); err != nil {
@@ -182,67 +180,31 @@ func (a applyPatchOperation) Do(ctx *ratchetingTestContext) error {
182180
return fmt.Errorf("invalid patch type: %T", a.patch)
183181
}
184182

183+
if ctx.StatusSubresource {
184+
patch.Object = map[string]interface{}{"status": patch.Object}
185+
}
186+
185187
patch.SetKind(kind)
186188
patch.SetAPIVersion(a.gvr.GroupVersion().String())
187189
patch.SetName(a.name)
188190
patch.SetNamespace("default")
189191

190-
_, err := ctx.DynamicClient.
191-
Resource(a.gvr).
192-
Namespace(patch.GetNamespace()).
193-
Apply(
194-
context.TODO(),
195-
patch.GetName(),
196-
patch,
197-
metav1.ApplyOptions{
198-
FieldManager: "manager",
199-
})
192+
c := ctx.DynamicClient.Resource(a.gvr).Namespace(patch.GetNamespace())
193+
if ctx.StatusSubresource {
194+
if _, err := c.Get(context.TODO(), patch.GetName(), metav1.GetOptions{}); apierrors.IsNotFound(err) {
195+
// ApplyStatus will not automatically create an object, we must make sure it exists before we can
196+
// apply the status to it.
197+
_, err := c.Create(context.TODO(), patch, metav1.CreateOptions{})
198+
if err != nil {
199+
return err
200+
}
201+
}
200202

201-
if ctx.SkipStatus {
203+
_, err := c.ApplyStatus(context.TODO(), patch.GetName(), patch, metav1.ApplyOptions{FieldManager: "manager"})
202204
return err
203205
}
204-
205-
errs := []error{}
206-
207-
if err != nil {
208-
errs = append(errs, err)
209-
}
210-
211-
statusPatch := &unstructured.Unstructured{
212-
Object: map[string]interface{}{
213-
"status": patch.Object,
214-
},
215-
}
216-
217-
statusPatch.SetKind(kind)
218-
statusPatch.SetAPIVersion(a.gvr.GroupVersion().String())
219-
statusPatch.SetName(a.name)
220-
statusPatch.SetNamespace("default")
221-
222-
delete(patch.Object, "metadata")
223-
delete(patch.Object, "apiVersion")
224-
delete(patch.Object, "kind")
225-
226-
_, err = ctx.DynamicClient.
227-
Resource(a.gvr).
228-
Namespace(statusPatch.GetNamespace()).
229-
ApplyStatus(
230-
context.TODO(),
231-
statusPatch.GetName(),
232-
statusPatch,
233-
metav1.ApplyOptions{
234-
FieldManager: "manager",
235-
})
236-
237-
if err != nil {
238-
errs = append(errs, err)
239-
}
240-
241-
if len(errs) > 0 {
242-
return utilerrors.NewAggregate(errs)
243-
}
244-
245-
return nil
206+
_, err := c.Apply(context.TODO(), patch.GetName(), patch, metav1.ApplyOptions{FieldManager: "manager"})
207+
return err
246208
}
247209

248210
func (a applyPatchOperation) Description() string {
@@ -270,6 +232,17 @@ func (u updateMyCRDV1Beta1Schema) Do(ctx *ratchetingTestContext) error {
270232
sch.Properties = map[string]apiextensionsv1.JSONSchemaProps{}
271233
}
272234

235+
if ctx.StatusSubresource {
236+
sch = &apiextensionsv1.JSONSchemaProps{
237+
Type: "object",
238+
Properties: map[string]apiextensionsv1.JSONSchemaProps{
239+
"status": *sch,
240+
},
241+
}
242+
}
243+
244+
// sentinel must be in the root level of the schema.
245+
// Do not include this in the status schema.
273246
uuidString := string(uuid.NewUUID())
274247
sentinelName := "__ratcheting_sentinel_field__"
275248
sch.Properties[sentinelName] = apiextensionsv1.JSONSchemaProps{
@@ -279,21 +252,11 @@ func (u updateMyCRDV1Beta1Schema) Do(ctx *ratchetingTestContext) error {
279252
}},
280253
}
281254

282-
if !ctx.SkipStatus {
283-
// Duplicate the schema as a status schema
284-
statusSchema := sch.DeepCopy()
285-
sch.Properties["status"] = *statusSchema
286-
}
287-
288255
for _, v := range myCRD.Spec.Versions {
289256
if v.Name != myCRDV1Beta1.Version {
290257
continue
291258
}
292259

293-
*v.Subresources = apiextensionsv1.CustomResourceSubresources{
294-
Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
295-
}
296-
297260
v.Schema.OpenAPIV3Schema = sch
298261
}
299262

@@ -315,7 +278,12 @@ func (u updateMyCRDV1Beta1Schema) Do(ctx *ratchetingTestContext) error {
315278
name: "sentinel-resource",
316279
patch: map[string]interface{}{
317280
sentinelName: fmt.Sprintf("invalid-%d", counter),
318-
}}.Do(ctx)
281+
}}.Do(&ratchetingTestContext{
282+
T: ctx.T,
283+
DynamicClient: ctx.DynamicClient,
284+
APIExtensionsClient: ctx.APIExtensionsClient,
285+
StatusSubresource: false, // Do not carry this over, sentinel check is in the root level.
286+
})
319287

320288
if err == nil {
321289
return false, errors.New("expected error when creating sentinel resource")
@@ -344,18 +312,17 @@ type patchMyCRDV1Beta1Schema struct {
344312
}
345313

346314
func (p patchMyCRDV1Beta1Schema) Do(ctx *ratchetingTestContext) error {
347-
var err error
348-
patchJSON, err := json.Marshal(p.patch)
349-
if err != nil {
350-
return err
315+
patch := p.patch
316+
if ctx.StatusSubresource {
317+
patch = map[string]interface{}{
318+
"properties": map[string]interface{}{
319+
"status": patch,
320+
},
321+
}
351322
}
352323

353-
statusPatch := map[string]interface{}{
354-
"properties": map[string]interface{}{
355-
"status": p.patch,
356-
},
357-
}
358-
statusPatchJSON, err := json.Marshal(statusPatch)
324+
var err error
325+
patchJSON, err := json.Marshal(patch)
359326
if err != nil {
360327
return err
361328
}
@@ -380,19 +347,19 @@ func (p patchMyCRDV1Beta1Schema) Do(ctx *ratchetingTestContext) error {
380347
return err
381348
}
382349

383-
mergedStatus, err := jsonpatch.MergePatch(merged, statusPatchJSON)
384-
if err != nil {
385-
return err
386-
}
387-
388350
var parsed apiextensionsv1.JSONSchemaProps
389-
if err := json.Unmarshal(mergedStatus, &parsed); err != nil {
351+
if err := json.Unmarshal(merged, &parsed); err != nil {
390352
return err
391353
}
392354

393355
return updateMyCRDV1Beta1Schema{
394356
newSchema: &parsed,
395-
}.Do(ctx)
357+
}.Do(&ratchetingTestContext{
358+
T: ctx.T,
359+
DynamicClient: ctx.DynamicClient,
360+
APIExtensionsClient: ctx.APIExtensionsClient,
361+
StatusSubresource: false, // We have already handled the status subresource.
362+
})
396363
}
397364

398365
return fmt.Errorf("could not find version %v in CRD %v", myCRDV1Beta1.Version, myCRD.Name)
@@ -478,14 +445,7 @@ func runTests(t *testing.T, cases []ratchetingTestCase) {
478445
continue
479446
}
480447

481-
t.Run(c.Name, func(t *testing.T) {
482-
ctx := &ratchetingTestContext{
483-
T: t,
484-
DynamicClient: dynamicClient,
485-
APIExtensionsClient: apiExtensionClient,
486-
SkipStatus: c.SkipStatus,
487-
}
488-
448+
run := func(t *testing.T, ctx *ratchetingTestContext) {
489449
for i, op := range c.Operations {
490450
t.Logf("Performing Operation: %v", op.Description())
491451
if err := op.Do(ctx); err != nil {
@@ -498,7 +458,26 @@ func runTests(t *testing.T, cases []ratchetingTestCase) {
498458
if err != nil {
499459
t.Fatal(err)
500460
}
461+
}
462+
463+
t.Run(c.Name, func(t *testing.T) {
464+
run(t, &ratchetingTestContext{
465+
T: t,
466+
DynamicClient: dynamicClient,
467+
APIExtensionsClient: apiExtensionClient,
468+
})
501469
})
470+
471+
if !c.SkipStatus {
472+
t.Run("Status: "+c.Name, func(t *testing.T) {
473+
run(t, &ratchetingTestContext{
474+
T: t,
475+
DynamicClient: dynamicClient,
476+
APIExtensionsClient: apiExtensionClient,
477+
StatusSubresource: true,
478+
})
479+
})
480+
}
502481
}
503482
}
504483

@@ -1473,8 +1452,7 @@ func TestRatchetingFunctionality(t *testing.T) {
14731452
Name: "Not_should_not_ratchet",
14741453
},
14751454
{
1476-
Name: "CEL_transition_rules_should_not_ratchet",
1477-
SkipStatus: true, // Adding a broken status transition rule prevents the object from being updated.
1455+
Name: "CEL_transition_rules_should_not_ratchet",
14781456
Operations: []ratchetingTestOperation{
14791457
updateMyCRDV1Beta1Schema{&apiextensionsv1.JSONSchemaProps{
14801458
Type: "object",

0 commit comments

Comments
 (0)