|
17 | 17 | - [Design Details](#design-details)
|
18 | 18 | - [Discriminator Field](#discriminator-field)
|
19 | 19 | - [Go Markers](#go-markers)
|
| 20 | + - [Discriminator Values](#discriminator-values) |
| 21 | + - [Empty Union Members](#empty-union-members) |
| 22 | + - [Examples](#examples) |
20 | 23 | - [OpenAPI](#openapi)
|
21 | 24 | - [Normalization and Validation](#normalization-and-validation)
|
22 | 25 | - [Migrating existing unions](#migrating-existing-unions)
|
23 |
| - - [Open Questions](#open-questions) |
24 | 26 | - [Test Plan](#test-plan)
|
25 | 27 | - [Prerequisite testing updates](#prerequisite-testing-updates)
|
26 | 28 | - [Unit tests](#unit-tests)
|
@@ -133,10 +135,6 @@ functions (e.g. `pkg/apis/<group>/validation/validation.go`).
|
133 | 135 | best match the intent of the client, despite the client potentially having
|
134 | 136 | incomplete information
|
135 | 137 |
|
136 |
| -Another goal is to migrate at least one existing union to the new semantics in |
137 |
| -order to verify that migration is possible. Ideally migrate one of each type of |
138 |
| -existing union (discriminated and non-discriminated). |
139 |
| - |
140 | 138 | ### Non-Goals
|
141 | 139 |
|
142 | 140 | Migrating all existing unions away from their bespoke validation
|
@@ -242,19 +240,76 @@ kubebuilder types):
|
242 | 240 |
|
243 | 241 |
|
244 | 242 | - `// +unionDiscriminator` before a field means that this field is the
|
245 |
| - discriminator for the union. This field MUST be a string. This field MUST be |
| 243 | + discriminator for the union. This field MUST be an enum defined as a string (see section on |
| 244 | + discriminator values). This field MUST be |
246 | 245 | required.
|
247 |
| -- `// +unionOptional` before a discriminator field means that by setting the |
248 |
| - discriminator to an empty string, none of the union fields are persisted |
249 |
| - (meaning at most one member of the union must be set). If not present on the |
250 |
| - discriminator, exactly one of the union members must be set in order to be |
251 |
| - valid. |
252 | 246 | - `// +unionMember=<memberName>,discriminatedBy=<discriminatorName>` before a field means that this
|
253 |
| - field is a member of a union. The `<memberName>` is the name of the field that must be set as the discriminator value. It defaults to the json representation of the field name if not specified. The `<discriminatorName>` is optional if there |
| 247 | + field is a member of a union. The `<memberName>` is the name of the field that will be set as the discriminator value. |
| 248 | + It MUST correspond to one of the valid enum values of the discriminator's enum |
| 249 | + type. It defaults to the `CamelCase` representation of the field name if not specified. The `<discriminatorName>` is optional if there |
254 | 250 | is only union in a struct and required if there are multiple unions per
|
255 |
| - struct. It should be the go representation of the field tagged with |
| 251 | + struct. It should be the `CamelCase` representation of the field tagged with |
256 | 252 | `unionDiscriminator`.
|
257 | 253 |
|
| 254 | +#### Discriminator Values |
| 255 | + |
| 256 | +Here we present a description of how discriminators and their valid values |
| 257 | +should be defined. |
| 258 | + |
| 259 | +As described above, the discriminator field must be a string and required. |
| 260 | +Because, there are only a few specific values that the discriminator can be, we |
| 261 | +propose that all discriminators should be defined as an enum, and should be |
| 262 | +tagged so via the enum go marker `// +enum`. |
| 263 | + |
| 264 | +Required unions will have the number of valid discriminator values equal to the |
| 265 | +number of member fields (see exception below on empty union members). Optional |
| 266 | +unions will have the number of valid discriminator values equal to the number of |
| 267 | +member fields, plus one additional value for when "no member" is desired. By |
| 268 | +convention, this "no member" discriminator value should be the empty string. |
| 269 | + |
| 270 | +We define optional unions as union where "at most" one member field of the union |
| 271 | +must be non-nil (as opposed to a required union, where "exactly" one member |
| 272 | +field of the union must be non-nil). |
| 273 | + |
| 274 | +##### Empty Union Members |
| 275 | + |
| 276 | +In some cases there are more discriminator values than there are member fields |
| 277 | +defined in the struct when that specific member requires no configuration. An |
| 278 | +example is the `DeploymentStrategy` where it has one member field `rollingUpdate`, |
| 279 | +but two valid discriminator values `RollingUpdate` and `Recreate`. By using an |
| 280 | +enum as the discriminator value we are able to define values beyond the member |
| 281 | +fields in order to accommodate this pattern. |
| 282 | + |
| 283 | +#### Examples |
| 284 | + |
| 285 | +Below is an example of how to define a union based on the above design |
| 286 | + |
| 287 | +``` |
| 288 | +// +enum |
| 289 | +type UnionType string |
| 290 | +
|
| 291 | +const ( |
| 292 | + FieldA UnionType = "FieldA" |
| 293 | + FieldB = "FieldC" |
| 294 | + FieldC = "FieldC" |
| 295 | + FieldNone = "" |
| 296 | +) |
| 297 | +
|
| 298 | +type Union struct { |
| 299 | + // +unionDiscriminator |
| 300 | + // +required |
| 301 | + UnionType UnionType |
| 302 | +
|
| 303 | + // +unionMember |
| 304 | + // +optional |
| 305 | + FieldA int |
| 306 | + // +unionMember |
| 307 | + // +optional |
| 308 | + FieldB int |
| 309 | +} |
| 310 | +``` |
| 311 | + |
| 312 | + |
258 | 313 | Note unions can't span across multiple go structures (all the fields that are part of a union has to be together in the
|
259 | 314 | same structure), examples of what is allowed:
|
260 | 315 |
|
@@ -392,53 +447,6 @@ initial migration.
|
392 | 447 | For non-discriminated unions, there are a few relatively straightforward types
|
393 | 448 | that make good candidates for initial migration, such as `ContainerState`
|
394 | 449 |
|
395 |
| -### Open Questions |
396 |
| - |
397 |
| -A few open questions exist with the design that need to be resolved with SIG API |
398 |
| -Machinery before moving forward. |
399 |
| - |
400 |
| -1. What do we do when the discriminator is set (to the previously set value), |
401 |
| - but all union member fields are null? This likely implies that the client cleared |
402 |
| - the union member unintentionally, given that the discriminator value remains. |
403 |
| - Should the server: |
404 |
| - |
405 |
| - a. Error loudly and inform the client that the field pointed to by the |
406 |
| - discriminator is null? |
407 |
| - |
408 |
| - b. Retain the previously set member field, present on the old object, absent |
409 |
| - from the new object, but pointed to by the discriminator? |
410 |
| -2. How should a server behave when it receives a union with multiple fields set |
411 |
| - and the discriminator modified to point to the newly set member field? |
412 |
| - |
413 |
| - a. reject the request, |
414 |
| - |
415 |
| - b. accept the request but don't clear any fields |
416 |
| - |
417 |
| - c. accept the request and clear all the fields except the one chosen by the |
418 |
| - discriminator |
419 |
| -3. What should the value of the discriminator be when no field in the union is |
420 |
| - to be set (i.e for optional unions). |
421 |
| - |
422 |
| - a. mandate that the discriminator is the empty string for all optional unions? |
423 |
| - |
424 |
| - b. make the field specified on a per union basis (defined in the go markers)? |
425 |
| - |
426 |
| - c. mandate that the discriminator be unset (thus make the discriminator an |
427 |
| - optional field for optional unions)? |
428 |
| -4. What should we default the discriminator value of a member field to if not |
429 |
| - specified in the go markers? |
430 |
| - |
431 |
| - a. the json representation of the field name |
432 |
| - |
433 |
| - b. the go representation of the field name |
434 |
| - Should we even give users the ability to define it or just stick to whatever |
435 |
| - we decide the default is? |
436 |
| -5. Given that we want to migrate at least one existing union to use the new |
437 |
| - semantics, which union should we do? Must we upgrade both a discriminated and |
438 |
| - non-discriminated union for alpha or is only upgrading an existing |
439 |
| - discriminated union sufficient? Thoughts on my proposed types of |
440 |
| - `MetricStatus` (discriminated) and `ContainerState` (non-discriminated). |
441 |
| - |
442 | 450 | ### Test Plan
|
443 | 451 |
|
444 | 452 | [x] I/we understand the owners of the involved components may require updates to
|
@@ -502,6 +510,12 @@ A fully documented test matrix exists in a [google
|
502 | 510 | spreadsheet](https://docs.google.com/spreadsheets/d/1dIOOqgrgvMI9b2412eVuRSydEaOxhYOoqk6bUjZOY9c/edit?resourcekey=0-wlOfJTC_EX-qpU680STHMA#gid=3601413) along with a
|
503 | 511 | [guide
|
504 | 512 | doc](https://docs.google.com/document/d/1Wruosjo0ELLl1yxauzpsUjgH2fK9KdgXDmOdJ5sG7Kg/edit?resourcekey=0-8Pwzx6EvsFR7VQoXzCTY4Q) on how to read and understand the test matrix.
|
| 513 | + |
| 514 | +As part of implementing the test matrix we will be able to prove the viability |
| 515 | +of upgrading existing unions by writing tests to mimic using the standardized |
| 516 | +union semantics on existing unions (even if actually upgrading these unions is |
| 517 | +outside the scope of alpha graduation) |
| 518 | + |
505 | 519 | <!--
|
506 | 520 | This question should be filled when targeting a release.
|
507 | 521 | For Alpha, describe what tests will be added to ensure proper quality of the enhancement.
|
@@ -534,8 +548,8 @@ We expect no non-infra related flakes in the last month as a GA graduation crite
|
534 | 548 |
|
535 | 549 | - CRDs can be created with union fields and are properly validated when
|
536 | 550 | created/updated.
|
537 |
| -- At least one existing unions that has discriminators, is properly tagged and validated via |
538 |
| - union validation |
| 551 | +- Prove the viability of upgrading existing unions to the new semantics by |
| 552 | + mimicking existing unions in e2e tests. |
539 | 553 | - Existing unions that don't have discriminators do not break when upgraded.
|
540 | 554 |
|
541 | 555 | ### Upgrade / Downgrade Strategy
|
@@ -991,6 +1005,30 @@ allows the server to better understand the intentions of clients that do not
|
991 | 1005 | have knowledge of all the fields in a union if newer versions of the server add
|
992 | 1006 | new fields to the union.
|
993 | 1007 |
|
| 1008 | +###### "None" Discriminator Values |
| 1009 | + |
| 1010 | +A number of strategies were discussed around how to represent the "none" value |
| 1011 | +of the discriminator (see "Discriminator Values" section above). |
| 1012 | + |
| 1013 | +* One alternative was to mandate the "none" value always be the empty string. |
| 1014 | + The advantage to this is its simplicity and not creating a situation where |
| 1015 | + different API authors define there "none" value differently, so that anyone |
| 1016 | + could immediately know that a discriminator set to "" (empty string), is not |
| 1017 | + selecting any of the member fields. Also, it would allow us to not have to |
| 1018 | + define the set of enum values for each discriminator (as we could just use the |
| 1019 | + name of the member field). The disadvantage is that by not defining the set of |
| 1020 | + enum values, we make it impossible to support the "empty union members" case. |
| 1021 | +* Another alternative was to make the discriminator a pointer to a string and |
| 1022 | + its value nil. The disadvantage here is that this requires more complicated |
| 1023 | + union validation logic (first do a nil check, then check the value) and makes |
| 1024 | + it harder to determine client intent on patches where the discriminator is not |
| 1025 | + set. |
| 1026 | +* A third alternative is to require all unions be defined in their own separate |
| 1027 | + struct. This was rejected because there are many existing unions that define |
| 1028 | + random fields that are not members in the union within the same struct as |
| 1029 | + fields that do make up the union and we hope to be able to migrate at least |
| 1030 | + some of the existing unions to the new semantics. |
| 1031 | + |
994 | 1032 | <!--
|
995 | 1033 | What other approaches did you consider, and why did you rule them out? These do
|
996 | 1034 | not need to be as detailed as the proposal, but should include enough
|
|
0 commit comments