Skip to content

Commit 834d047

Browse files
authored
Merge pull request #5607 from liggitt/enum-change
Describe process for allowing a new enum value in an existing field
2 parents d8b242f + 2e9f1ea commit 834d047

File tree

1 file changed

+170
-6
lines changed

1 file changed

+170
-6
lines changed

contributors/devel/sig-architecture/api_changes.md

Lines changed: 170 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ found at [API Conventions](api-conventions.md).
1919
- [Edit types.go](#edit-typesgo-1)
2020
- [Edit validation.go](#edit-validationgo)
2121
- [Edit version conversions](#edit-version-conversions)
22-
- [Generate protobuf objects](#generate-protobuf-objects)
23-
- [Edit json (un)marshaling code](#edit-json-unmarshaling-code)
22+
- [Generate Code](#generate-code)
23+
- [Generate protobuf objects](#generate-protobuf-objects)
24+
- [Generate Clientset](#generate-clientset)
25+
- [Generate Listers](#generate-listers)
26+
- [Generate Informers](#generate-informers)
27+
- [Edit json (un)marshaling code](#edit-json-unmarshaling-code)
2428
- [Making a new API Version](#making-a-new-api-version)
2529
- [Making a new API Group](#making-a-new-api-group)
2630
- [Update the fuzzer](#update-the-fuzzer)
@@ -30,6 +34,9 @@ found at [API Conventions](api-conventions.md).
3034
- [Examples and docs](#examples-and-docs)
3135
- [Alpha, Beta, and Stable Versions](#alpha-beta-and-stable-versions)
3236
- [Adding Unstable Features to Stable Versions](#adding-unstable-features-to-stable-versions)
37+
- [New field in existing API version](#new-field-in-existing-api-version)
38+
- [New enum value in existing field](#new-enum-value-in-existing-field)
39+
- [New alpha API version](#new-alpha-api-version)
3340

3441
## So you want to change the API?
3542

@@ -316,9 +323,10 @@ worked before the change.
316323
values of a given field will not be able to handle the new values. However, removing a
317324
value from an enumerated set *can* be a compatible change, if handled properly (treat the
318325
removed value as deprecated but allowed). For enumeration-like fields that expect to add
319-
new values in the future, such as `reason` fields, please document that expectation clearly
320-
in the API field descriptions. Clients should treat such sets of values as potentially
321-
open-ended.
326+
new values in the future, such as `reason` fields, document that expectation clearly
327+
in the API field description in the first release the field is made available,
328+
and describe how clients should treat an unknown value. Clients should treat such
329+
sets of values as potentially open-ended.
322330

323331
* For [Unions](api-conventions.md#unions), sets of fields where at most one should
324332
be set, it is acceptable to add a new option to the union if the [appropriate
@@ -880,7 +888,10 @@ users are only able or willing to accept a released version of Kubernetes. In
880888
that case, the developer has a few options, both of which require staging work
881889
over several releases.
882890

883-
#### Alpha field in existing API version
891+
The mechanism used depends on whether a new field is being added,
892+
or a new value is being permitted in an existing field.
893+
894+
#### New field in existing API version
884895

885896
Previously, annotations were used for experimental alpha features, but are no longer recommended for several reasons:
886897

@@ -1016,6 +1027,159 @@ In future Kubernetes versions:
10161027
}
10171028
```
10181029

1030+
#### New enum value in existing field
1031+
1032+
A developer is considering adding a new allowed enum value of `"OnlyOnTuesday"`
1033+
to the following existing enum field:
1034+
1035+
```go
1036+
type Frobber struct {
1037+
// restartPolicy may be set to "Always" or "Never".
1038+
// Additional policies may be defined in the future.
1039+
// Clients should expect to handle additional values,
1040+
// and treat unrecognized values in this field as "Never".
1041+
RestartPolicy string `json:"policy"
1042+
}
1043+
```
1044+
1045+
Older versions of expected API clients must be able handle the new value in a safe way:
1046+
1047+
* If the enum field drives behavior of a single component, ensure all versions of that component
1048+
that will encounter API objects containing the new value handle it properly or fail safe.
1049+
For example, a new allowed value in a `Pod` enum field consumed by the kubelet must be handled
1050+
safely by kubelets up to two versions older than the first API server release that allowed the new value.
1051+
* If an API drives behavior that is implemented by external clients (like `Ingress` or `NetworkPolicy`),
1052+
the enum field must explicitly indicate that additional values may be allowed in the future,
1053+
and define how unrecognized values must be handled by clients. If this was not done in the first release
1054+
containing the enum field, it is not safe to add new values that can break existing clients.
1055+
1056+
If expected API clients safely handle the new enum value, the next requirement is to begin allowing it
1057+
in a way that does not break validation of that object by a previous API server.
1058+
This requires at least two releases to accomplish safely:
1059+
1060+
Release 1:
1061+
1062+
* Only allow the new enum value when updating existing objects that already contain the new enum value
1063+
* Disallow it in other cases (creation, and update of objects that do not already contain the new enum value)
1064+
* Verify that known clients handle the new value as expected, honoring the new value or using previously defined "unknown value" behavior,
1065+
(depending on whether the associated feature gate is enabled or not)
1066+
1067+
1068+
Release 2:
1069+
1070+
* Allow the new enum value in create and update scenarios
1071+
1072+
This ensures a cluster with multiple servers at skewed releases (which happens during a rolling upgrade),
1073+
will not allow data to be persisted which the previous release of the API server would choke on.
1074+
1075+
Typically, a feature gate is used to do this rollout, starting in alpha and disabled by default in release 1,
1076+
and graduating to beta and enabled by default in release 2.
1077+
1078+
1. Add a feature gate to the API server to control enablement of the new enum value (and associated function):
1079+
1080+
In [staging/src/k8s.io/apiserver/pkg/features/kube_features.go](https://git.k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/features/kube_features.go):
1081+
1082+
```go
1083+
// owner: @you
1084+
// alpha: v1.11
1085+
//
1086+
// Allow OnTuesday restart policy in frobbers.
1087+
FrobberRestartPolicyOnTuesday utilfeature.Feature = "FrobberRestartPolicyOnTuesday"
1088+
1089+
var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureSpec{
1090+
...
1091+
FrobberRestartPolicyOnTuesday: {Default: false, PreRelease: utilfeature.Alpha},
1092+
}
1093+
```
1094+
1095+
2. Update the documentation on the API type:
1096+
1097+
* include details about the alpha-level in the field description
1098+
1099+
```go
1100+
type Frobber struct {
1101+
// restartPolicy may be set to "Always" or "Never" (or "OnTuesday" if the alpha "FrobberRestartPolicyOnTuesday" feature is enabled).
1102+
// Additional policies may be defined in the future.
1103+
// Unrecognized policies should be treated as "Never".
1104+
RestartPolicy string `json:"policy"
1105+
}
1106+
```
1107+
1108+
3. When validating the object, determine whether the new enum value should be allowed.
1109+
This prevents new usage of the new value when the feature is disabled, while ensuring existing data is preserved.
1110+
Ensuring existing data is preserved is needed so that when the feature is enabled by default in a future version *n*
1111+
and data is unconditionally allowed to be persisted in the field, an *n-1* API server
1112+
(with the feature still disabled by default) will not choke on validation.
1113+
The recommended place to do this is in the REST storage strategy's Validate/ValidateUpdate methods:
1114+
1115+
```go
1116+
func (frobberStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
1117+
frobber := obj.(*api.Frobber)
1118+
return validation.ValidateFrobber(frobber, validationOptionsForFrobber(frobber, nil))
1119+
}
1120+
1121+
func (frobberStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {
1122+
newFrobber := obj.(*api.Frobber)
1123+
oldFrobber := old.(*api.Frobber)
1124+
return validation.ValidateFrobberUpdate(newFrobber, oldFrobber, validationOptionsForFrobber(newFrobber, oldFrobber))
1125+
}
1126+
1127+
func validationOptionsForFrobber(newFrobber, oldFrobber *api.Frobber) validation.FrobberValidationOptions {
1128+
opts := validation.FrobberValidationOptions{
1129+
// allow if the feature is enabled
1130+
AllowRestartPolicyOnTuesday: utilfeature.DefaultFeatureGate.Enabled(features.FrobberRestartPolicyOnTuesday)
1131+
}
1132+
1133+
if oldFrobber == nil {
1134+
// if there's no old object, use the options based solely on feature enablement
1135+
return opts
1136+
}
1137+
1138+
if oldFrobber.RestartPolicy == api.RestartPolicyOnTuesday {
1139+
// if the old object already used the enum value, continue to allow it in the new object
1140+
opts.AllowRestartPolicyOnTuesday = true
1141+
}
1142+
return opts
1143+
}
1144+
```
1145+
1146+
4. In validation, validate the enum value based on the passed-in options:
1147+
1148+
```go
1149+
func ValidateFrobber(f *api.Frobber, opts FrobberValidationOptions) field.ErrorList {
1150+
...
1151+
validRestartPolicies := sets.NewString(RestartPolicyAlways, RestartPolicyNever)
1152+
if opts.AllowRestartPolicyOnTuesday {
1153+
validRestartPolicies.Insert(RestartPolicyOnTuesday)
1154+
}
1155+
1156+
if f.RestartPolicy == RestartPolicyOnTuesday && !opts.AllowRestartPolicyOnTuesday {
1157+
allErrs = append(allErrs, field.Invalid(field.NewPath("restartPolicy"), f.RestartPolicy, "only allowed if the FrobberRestartPolicyOnTuesday feature is enabled"))
1158+
} else if !validRestartPolicies.Has(f.RestartPolicy) {
1159+
allErrs = append(allErrs, field.NotSupported(field.NewPath("restartPolicy"), f.RestartPolicy, validRestartPolicies.List()))
1160+
}
1161+
...
1162+
}
1163+
```
1164+
1165+
5. After at least one release, the feature can be promoted to beta or GA and enabled by default.
1166+
1167+
In [staging/src/k8s.io/apiserver/pkg/features/kube_features.go](https://git.k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/features/kube_features.go):
1168+
1169+
```go
1170+
// owner: @you
1171+
// alpha: v1.11
1172+
// beta: v1.12
1173+
//
1174+
// Allow OnTuesday restart policy in frobbers.
1175+
FrobberRestartPolicyOnTuesday utilfeature.Feature = "FrobberRestartPolicyOnTuesday"
1176+
1177+
var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureSpec{
1178+
...
1179+
FrobberRestartPolicyOnTuesday: {Default: true, PreRelease: utilfeature.Beta},
1180+
}
1181+
```
1182+
10191183
#### New alpha API version
10201184

10211185
Another option is to introduce a new type with an new `alpha` or `beta` version

0 commit comments

Comments
 (0)