Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KEP-4008: CRD Validation Ratcheting alpha implementation #118990

Conversation

alexzielenski
Copy link
Contributor

@alexzielenski alexzielenski commented Jun 29, 2023

What type of PR is this?

/kind feature

What this PR does / why we need it:

Implements alpha of KEP-4008

Depends on kubernetes/kube-openapi#406

Which issue(s) this PR fixes:

Fixes #

Special notes for your reviewer:

Does this PR introduce a user-facing change?

Adds new CRDValidationRatcheting alpha feature. During a PATCH or UPDATE Validation Ratcheting discards errors thrown by unchanged portions of the resource from most OpenAPI schema validations. 

Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:

- [KEP]: https://kep.k8s.io/4008

@k8s-ci-robot k8s-ci-robot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note Denotes a PR that will be considered when it comes time to generate release notes. kind/feature Categorizes issue or PR as related to a new feature. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. area/apiserver area/cloudprovider area/code-generation area/dependency Issues or PRs related to dependency changes area/kube-proxy area/kubectl kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. sig/architecture Categorizes an issue or PR as relevant to SIG Architecture. sig/auth Categorizes an issue or PR as relevant to SIG Auth. sig/cli Categorizes an issue or PR as relevant to SIG CLI. sig/cloud-provider Categorizes an issue or PR as relevant to SIG Cloud Provider. sig/cluster-lifecycle Categorizes an issue or PR as relevant to SIG Cluster Lifecycle. sig/instrumentation Categorizes an issue or PR as relevant to SIG Instrumentation. sig/network Categorizes an issue or PR as relevant to SIG Network. sig/node Categorizes an issue or PR as relevant to SIG Node. sig/storage Categorizes an issue or PR as relevant to SIG Storage. and removed do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. labels Jun 29, 2023
@k8s-ci-robot k8s-ci-robot requested review from andrewsykim, aojea and a team June 29, 2023 20:53
@alexzielenski alexzielenski force-pushed the apiserver/apiextensions/crd-validation-ratcheting branch from e962344 to 8ffb1e2 Compare July 18, 2023 18:48
@alexzielenski alexzielenski force-pushed the apiserver/apiextensions/crd-validation-ratcheting branch from 8ffb1e2 to bfb2c6a Compare July 18, 2023 18:49
@liggitt
Copy link
Member

liggitt commented Jul 18, 2023

/lgtm
/approve

for reference, here's the diff of changes

diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/ratcheting.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/ratcheting.go
index 73792e546e5..6565d83eee5 100644
--- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/ratcheting.go
+++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/ratcheting.go
@@ -59,8 +59,8 @@ func (r *RatchetingSchemaValidator) Validate(new interface{}) *validate.Result {
 	return sv.Validate(new)
 }
 
-func (r *RatchetingSchemaValidator) ValidateUpdate(old, new interface{}) *validate.Result {
-	return newRatchetingValueValidator(old, new, r.schemaArgs).Validate()
+func (r *RatchetingSchemaValidator) ValidateUpdate(new, old interface{}) *validate.Result {
+	return newRatchetingValueValidator(new, old, r.schemaArgs).Validate()
 }
 
 // ratchetingValueValidator represents an invocation of SchemaValidator.ValidateUpdate
@@ -108,7 +108,7 @@ type ratchetingValueValidator struct {
 	children map[interface{}]*ratchetingValueValidator
 }
 
-func newRatchetingValueValidator(oldValue, newValue interface{}, args schemaArgs) *ratchetingValueValidator {
+func newRatchetingValueValidator(newValue, oldValue interface{}, args schemaArgs) *ratchetingValueValidator {
 	return &ratchetingValueValidator{
 		oldValue:   oldValue,
 		value:      newValue,
@@ -192,7 +192,7 @@ func (r *ratchetingValueValidator) SubPropertyValidator(field string, schema *sp
 	}
 
 	return inlineValidator(func(new interface{}) *validate.Result {
-		childNode := newRatchetingValueValidator(oldValueForField, new, schemaArgs{
+		childNode := newRatchetingValueValidator(new, oldValueForField, schemaArgs{
 			schema:       schema,
 			root:         rootSchema,
 			path:         root,
@@ -221,7 +221,7 @@ func (r *ratchetingValueValidator) SubIndexValidator(index int, schema *spec.Sch
 	}
 
 	return inlineValidator(func(new interface{}) *validate.Result {
-		childNode := newRatchetingValueValidator(oldValueForIndex, new, schemaArgs{
+		childNode := newRatchetingValueValidator(new, oldValueForIndex, schemaArgs{
 			schema:       schema,
 			root:         rootSchema,
 			path:         root,
diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation.go
index b08d9491463..e0042356ac0 100644
--- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation.go
+++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation.go
@@ -33,22 +33,31 @@ import (
 
 type SchemaValidator interface {
 	SchemaCreateValidator
-	ValidateUpdate(old, new interface{}) *validate.Result
+	ValidateUpdate(new, old interface{}) *validate.Result
 }
 
 type SchemaCreateValidator interface {
 	Validate(value interface{}) *validate.Result
 }
 
-type schemaValidator struct {
+// basicSchemaValidator wraps a kube-openapi SchemaCreateValidator to
+// support ValidateUpdate. It implements ValidateUpdate by simply validating
+// the new value via kube-openapi, ignoring the old value.
+type basicSchemaValidator struct {
 	*validate.SchemaValidator
 }
 
-func (s schemaValidator) ValidateUpdate(old, new interface{}) *validate.Result {
+func (s basicSchemaValidator) ValidateUpdate(new, old interface{}) *validate.Result {
 	return s.Validate(new)
 }
 
 // NewSchemaValidator creates an openapi schema validator for the given CRD validation.
+//
+// If feature `CRDValidationRatcheting` is disabled, this returns validator which
+// validates all `Update`s and `Create`s as a `Create` - without considering old value.
+//
+// If feature `CRDValidationRatcheting` is enabled - the validator returned
+// will support ratcheting unchanged correlatable fields across an update.
 func NewSchemaValidator(customResourceValidation *apiextensions.JSONSchemaProps) (SchemaValidator, *spec.Schema, error) {
 	// Convert CRD schema to openapi schema
 	openapiSchema := &spec.Schema{}
@@ -62,21 +71,24 @@ func NewSchemaValidator(customResourceValidation *apiextensions.JSONSchemaProps)
 	if utilfeature.DefaultFeatureGate.Enabled(features.CRDValidationRatcheting) {
 		return NewRatchetingSchemaValidator(openapiSchema, nil, "", strfmt.Default), openapiSchema, nil
 	}
-	return schemaValidator{validate.NewSchemaValidator(openapiSchema, nil, "", strfmt.Default)}, openapiSchema, nil
+	return basicSchemaValidator{validate.NewSchemaValidator(openapiSchema, nil, "", strfmt.Default)}, openapiSchema, nil
 }
 
-// ValidateCustomResourceUodate validates the transition of Custom Resource from
+// ValidateCustomResourceUpdate validates the transition of Custom Resource from
 // `old` to `new` against the schema in the CustomResourceDefinition.
 // Both customResource and old represent a JSON data structures.
 //
 // If feature `CRDValidationRatcheting` is disabled, this behaves identically to
-// ValidateCustomResource(nil, customResource).
-func ValidateCustomResourceUpdate(fldPath *field.Path, old, customResource interface{}, validator SchemaValidator) field.ErrorList {
-	if validator == nil {
+// ValidateCustomResource(customResource).
+func ValidateCustomResourceUpdate(fldPath *field.Path, customResource, old interface{}, validator SchemaValidator) field.ErrorList {
+	// Additional feature gate check for sanity
+	if !utilfeature.DefaultFeatureGate.Enabled(features.CRDValidationRatcheting) {
+		return ValidateCustomResource(nil, customResource, validator)
+	} else if validator == nil {
 		return nil
 	}
 
-	result := validator.ValidateUpdate(old, customResource)
+	result := validator.ValidateUpdate(customResource, old)
 	if result.IsValid() {
 		return nil
 	}
diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go
index 3d17cc9f556..a158aa3b6dc 100644
--- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go
+++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go
@@ -89,7 +89,7 @@ func (a customResourceValidator) ValidateUpdate(ctx context.Context, obj, old ru
 	var allErrs field.ErrorList
 
 	allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))...)
-	allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(nil, oldU.UnstructuredContent(), u.UnstructuredContent(), a.schemaValidator)...)
+	allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(nil, u.UnstructuredContent(), oldU.UnstructuredContent(), a.schemaValidator)...)
 	allErrs = append(allErrs, a.ValidateScaleSpec(ctx, u, scale)...)
 	allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
 
@@ -126,7 +126,7 @@ func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj,
 	var allErrs field.ErrorList
 
 	allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))...)
-	allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(nil, oldU, u.UnstructuredContent(), a.schemaValidator)...)
+	allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(nil, u.UnstructuredContent(), oldU, a.schemaValidator)...)
 	allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
 
 	return allErrs

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jul 18, 2023
@k8s-ci-robot
Copy link
Contributor

LGTM label has been added.

Git tree hash: d1999e7538e5d78dacbcaee9823e0cfc40c06658

@k8s-ci-robot k8s-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 18, 2023
@liggitt
Copy link
Member

liggitt commented Jul 18, 2023

noticed @apelisse had a comment on one of the scenarios at #118990 (comment)

can unhold once that is resolved and @apelisse lgtms

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 18, 2023
@apelisse
Copy link
Member

/lgtm
/approve
/hold cancel

@k8s-ci-robot k8s-ci-robot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 18, 2023
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: alexzielenski, apelisse, liggitt, sttts

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot merged commit c684de5 into kubernetes:master Jul 18, 2023
13 of 14 checks passed
SIG Node PR Triage automation moved this from WIP to Done Jul 18, 2023
@sftim
Copy link
Contributor

sftim commented Jul 19, 2023

The changelog for this PR is light on the detail of what that feature (not feature gate, actual feature) does or means.

@sttts
Copy link
Contributor

sttts commented Jul 19, 2023

@alexzielenski ^

@alexzielenski
Copy link
Contributor Author

Updated release note, PTAL

@alexzielenski alexzielenski changed the title CRD Validation Ratcheting alpha implementation KEP-4008: CRD Validation Ratcheting alpha implementation Oct 24, 2023
sushanth0910 added a commit to sushanth0910/kubernetes that referenced this pull request Feb 28, 2024
…rver/apiextensions/crd-validation-ratcheting"

This reverts commit c684de5, reversing
changes made to 31d662e.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. area/apiserver area/cloudprovider area/code-generation area/dependency Issues or PRs related to dependency changes area/kube-proxy area/kubectl cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API kind/feature Categorizes issue or PR as related to a new feature. lgtm "Looks good to me", indicates that a PR is ready to be merged. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. sig/architecture Categorizes an issue or PR as relevant to SIG Architecture. sig/auth Categorizes an issue or PR as relevant to SIG Auth. sig/cli Categorizes an issue or PR as relevant to SIG CLI. sig/cloud-provider Categorizes an issue or PR as relevant to SIG Cloud Provider. sig/cluster-lifecycle Categorizes an issue or PR as relevant to SIG Cluster Lifecycle. sig/instrumentation Categorizes an issue or PR as relevant to SIG Instrumentation. sig/network Categorizes an issue or PR as relevant to SIG Network. sig/node Categorizes an issue or PR as relevant to SIG Node. sig/storage Categorizes an issue or PR as relevant to SIG Storage. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. triage/accepted Indicates an issue or PR is ready to be actively worked on.
Projects
Status: API review completed, 1.28
Archived in project
Archived in project
Development

Successfully merging this pull request may close these issues.

None yet