Skip to content

Commit fdce07a

Browse files
authored
Merge pull request #5292 from yongruilin/ratcheting
[KEP-5073] Update KEP with section of validation ratcheting
2 parents 4bfb40a + f9f82d5 commit fdce07a

File tree

1 file changed

+55
-3
lines changed
  • keps/sig-api-machinery/5073-declarative-validation-with-validation-gen

1 file changed

+55
-3
lines changed

keps/sig-api-machinery/5073-declarative-validation-with-validation-gen/README.md

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@
7474
- [Scale-Type Subresources](#scale-type-subresources)
7575
- [Streaming Subresources](#streaming-subresources)
7676
- [Ratcheting](#ratcheting)
77+
- [Core Principles](#core-principles)
78+
- [Default Ratcheting Behavior](#default-ratcheting-behavior)
79+
- [Definition of Semantic Equivalence](#definition-of-semantic-equivalence)
80+
- [Policy on Order-Sensitive List Validation](#policy-on-order-sensitive-list-validation)
81+
- [Ratcheting and Cross-Field Validation](#ratcheting-and-cross-field-validation)
7782
- [Test Plan](#test-plan)
7883
- [Prerequisite testing updates](#prerequisite-testing-updates)
7984
- [Unit tests](#unit-tests)
@@ -1421,10 +1426,57 @@ The streamed data does not require declarative validation, as it is not structur
14211426

14221427
### Ratcheting
14231428

1424-
TODO: Document and explain how:
1429+
As Kubernetes APIs evolve, validation rules change. To minimize disruption for users with existing objects created under older rules, declarative validation will incorporate **Validation Ratcheting**. This mechanism aims to selectively bypass new or changed validation rules during object updates (`UPDATE`, `PATCH`, `APPLY`) for fields that have not been modified from their previously persisted state.
14251430

1426-
- Add general purpose ratcheting to automatically skip validation of unchanged fields
1427-
- Catalog and handle complex cases where strict equality checks are not sufficient (lots of non-trivial cases exist today)
1431+
#### Core Principles
1432+
1433+
The design adheres to the following core principles:
1434+
1435+
1. **Stored data is considered valid:** Any object successfully persisted was once considered valid. Subsequent apiservers must not retroactively invalidate stored objects. (Implication: fixing validation bugs post-release is challenging).
1436+
2. **Unchanged fields do not cause update rejections:** An `UPDATE` operation must not fail validation due to fields that were not modified in that operation. (Rationale: HTTP 4xx errors imply a client request problem).
1437+
3. **Semantic deep-equal is always sufficient to elide re-validation:** Kubernetes API objects adhere to canonical semantic equivalence rules (`equality.Semantic.DeepEqual`). If a deserialized object satisfies that equivalence with its prior state, re-validation can be bypassed.
1438+
* **Subtle:** List elements might individually bypass re-validation, but the list itself might still be re-validated (e.g., if reordered).
1439+
1440+
Ratcheting is the **default behavior** during `UPDATE` operations.
1441+
1442+
#### Default Ratcheting Behavior
1443+
1444+
The default mechanism handles common cases by skipping re-validation if a field hasn't changed based on semantic equivalence.
1445+
1446+
##### Definition of Semantic Equivalence
1447+
1448+
"Semantic equivalence" builds on `k8s.io/apimachinery/pkg/api/equality.Semantic.DeepEqual` (similar to `reflect.DeepEqual` but `nil` and empty slices/maps are equivalent). The table below outlines the behavior:
1449+
1450+
| Value type | Semantic Equivalence | Ratcheting | CRD Comparison (KEP-4008) |
1451+
| :---------------------------------- | :------------------------------------------------------------- | :----------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ |
1452+
| Scalars (int, string, etc.) | direct equality of the value | revalidate the value if changed | same |
1453+
| Pointers | equivalence of the pointee | revalidate the value if changed | same |
1454+
| Struct | all fields are equivalent | revalidate the struct if any field changed | same |
1455+
| Struct fields<br/>`structType=granular` | - | revalidate changed fields | same |
1456+
| Struct fields<br/>`structType=atomic` | - | revalidate changed fields | [Issue #131566](https://github.com/kubernetes/kubernetes/issues/131566) (Alignment needed) |
1457+
| Slices | all elements are equivalent and the is order unchanged | revalidate the slice if any element changed or order changed | `listType=map`: no validate when re-order<br/>`listType=set`: re-validate when re-order<br/>`listType=atomic`: re-validate when re-order |
1458+
| Slice values<br/>`listType=atomic` | - | validate items which are not found (by value) in the old slice | Validate all elements (CRDs ratcheting may be expanded to match in-tree ratcheting) |
1459+
| Slice values<br/>`listType=map` | - | (re)validate items which are not found (by key) in the old slice or are changed | same |
1460+
| Slice values<br/>`listType=set` | - | validate items which are not found (by value) in the old slice | Validate all elements (CRDs ratcheting may be expanded to match in-tree ratcheting) |
1461+
| Maps | all elements are equivalent | revalidate the map if any element changed | same |
1462+
| Map values<br/>`mapType=granular` | - | (re)validate items which are not found (by key) in the old map or are changed | same |
1463+
| Map values<br/>`mapType=atomic` | - | (re)validate items which are not found (by key) in the old map or are changed | [Issue #131566](https://github.com/kubernetes/kubernetes/issues/131566) (Alignment needed) |
1464+
1465+
**Note on Atomic Types:** The behavior for `structType=atomic` and `mapType=atomic` intentionally deviates from strict atomic re-validation. Only the specific sub-fields or key-value pairs *that were actually modified* are re-validated. This prioritizes user experience but requires alignment with CRD behavior (tracked in Issue #131566).
1466+
1467+
#### Policy on Order-Sensitive List Validation
1468+
1469+
The handling of `listtype=map` requires specific clarification. While its name implies order is irrelevant, the validation mechanism for native types re-validates a list if its elements are reordered.
1470+
1471+
To ensure predictable behavior, the following rule applies: **all new declarative validation rules for list-type=map must be order-insensitive**.
1472+
1473+
This requirement is enforced through vigilant code review to reject any proposed rule that violates this principle.
1474+
1475+
#### Ratcheting and Cross-Field Validation
1476+
1477+
A challenge arises if a cross-field validation rule (e.g. `X < Y`) is defined on a common ancestor struct, and an unrelated field (e.g. `Z`) within that same ancestor is modified. This change to `Z` makes the common ancestor “changed” overall, triggering re-validation of the `X < Y` rule. If this rule was recently evolved (e.g., made stricter), it might now fail even if `X` and `Y` themselves are not modified by the user’s update. This could violate the principle “Unchanged fields do not cause update rejections.”
1478+
1479+
For the initial implementation, this behavior will be documented. Furthermore, cross-field validation rules themselves must incorporate internal ratcheting logic. For instance, generated code for dedicated cross-field tags (like `+k8s:unionMember`) will be designed to only act upon changes to the specific fields they govern and were designed to validate.
14281480

14291481
### Test Plan
14301482

0 commit comments

Comments
 (0)