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

Proposal: Add Tag Indicating To Replace Union Fields in Patch #278

Merged
merged 1 commit into from
Mar 4, 2017

Conversation

mengqiy
Copy link
Member

@mengqiy mengqiy commented Jan 20, 2017

Move from issue #229.

@kubernetes/sig-api-machinery-misc @kubernetes/sig-cli-misc

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Jan 20, 2017

1) Move the tag to the parent struct's. E.g. add a tag to `Volume` instead of `VolumeSource`. This will limit the flexibility to add more fields in the future.

2) Add some logic when lookup the metadata in go struct to find the tags for inline fields.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to avoid adding more paths that require access to the go struct, and cannot be expressed in the openapi schema

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need to add the existing mergeStrategy for lists as extensions to the open-api schema. Can we just do the same for this at the same time?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

openapi schemas flatten inlined structs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that is ok for the current use cases, we can just put it in the parent right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right... that's option 1 :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup makes sense. I miss interpreted your original comment.


Add a go struct tag/openAPI value telling clients to replace the entire struct when doing a *PATCH*. For the *inline* fields, we have 2 options:

1) Move the tag to the parent struct's. E.g. add a tag to `Volume` instead of `VolumeSource`. This will limit the flexibility to add more fields in the future.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how you can compatibly add more non-union fields to a type you have defined as a union type, so I'm not sure that loss of flexibility is a dealbreaker

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you summarize current usage of union types, along with any that inline peer to other fields (like this name+volumeSource example)? If a significant number fall into the name+union pattern, we could describe a "named union" that gives special significance to the "name" field, and could be understood via the openapi schema.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @ymqytw has a list of these. I know rollout strategy for Deployment is another.

Copy link
Member Author

@mengqiy mengqiy Jan 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a list in the appendix, but it doesn't have enough information you need. I will update it according to your comment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be useful to include in this document all of the union types as of 1.5 and what the tag additions would be for each of them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we maybe live with existing inlined unions and ban it for future use?

i.e., VolumeSource should not have been inlined, but should have been a nested object.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thockin I think that is the proposal here. The compromise would be that each Volume would need to be entirely managed by apply + configuration file or not managed by it at all. e.g. A Volume could not have some fields managed by apply and come fields managed by another writer (e.g. some controller).

@liggitt
Copy link
Member

liggitt commented Jan 20, 2017

cc @jwforres

@mengqiy mengqiy changed the title Add Tag Indicating To Replace Union Fields in Patch Proposal: Add Tag Indicating To Replace Union Fields in Patch Jan 20, 2017
Add logic to support replacing the entire union fields when there is a `replace` directive. The logic will be similar to the logic of merge strategy for lists.
Add lookup logic to support lookup metadata of inline fields.

The change in Strategic Merge Patch package will affect the behavior of how kubectl creating a patch and how API server apply a patch.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a section about backwards / forward compatibility behavior. Are there any issues here? I think we might need to be careful for Volumes where the name may not be present in the old patches that expect merge, and will be lost. I wonder if we can solve this with a new patch strategy in some way.


# 1. Add Union Support to the API Server

The limitation is that it will be impossible or very hard to make it work for inline union types.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true? Can you explain this more?


We would need to add extensions to the openapi spec we publish. This is something we already need to do for the `patchStrategy` and `mergeKey` struct tags.

# Alternatives Considered
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd mention that the proposals below are not mutually exclusive with the proposal above, and could be added at some point in the future.

@pwittrock
Copy link
Member

@bgrant0607 Is this something you would want to review? This seems like a minimally invasive way to get support for unions in apply and I don't think it prevents us from implementing Unions in the future.

@lavalamp FYI, I already briefly talked with you about this, but in case you wanted the design doc as a follow up. The only required api changes would be adding the struct tags and some changes to the patch merge logic.

@bgrant0607
Copy link
Member

@pwittrock Yes, thanks, I'd like to take a look.


## Strategic Merge Patch

Add logic to support replacing the entire union fields when there is a `replace` directive. The logic will be similar to the logic of merge strategy for lists.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would this be implemented? Does the server know to replace the entire object? Or does the client explicitly clear these fields?

@mengqiy
Copy link
Member Author

mengqiy commented Jan 24, 2017

Updated the proposal to address the comments.


### APIs

Add a go struct tag/openAPI value telling clients to replace the entire struct when doing a *PATCH*. For the *inlined* fields, we have 2 options:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the proposal, should we encourage or discourage discriminator fields for oneof cases? We have both, and there will be more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should encourage discriminators:
kubernetes/kubernetes#35345

@bgrant0607 bgrant0607 self-assigned this Jan 27, 2017

*Backwards / Forward Compatibility Behavior using option 1*

We reuse `patchStrategy:"replace"` if possible. Otherwise, we introduce a new struct tag, `patchStrategyForListEntry:"replace"`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we use replace, then automatically set fields within the replaced entry will be overwritten. For defaults, it doesn't matter, because the same defaults would be set again, but any other automatically set fields would be lost, even if updating the same entry of the union.

@bgrant0607
Copy link
Member

@ymqytw Sorry, this is pretty long. Could you please summarize the proposal?

@mengqiy
Copy link
Member Author

mengqiy commented Jan 27, 2017

@bgrant0607 Sorry about that. Will summarize it today.

Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
VolumeSource `json:",inline" protobuf:"bytes,2,opt,name=volumeSource"`
}
We reuse `patchStrategy:"replace"` if possible. This kind of patch will be backward compatible.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would the replace strategy prevent merging of descendants?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

}
We reuse `patchStrategy:"replace"` if possible. This kind of patch will be backward compatible.

Otherwise, we introduce a new struct tag, `patchStrategyForListEntry:"replace"`, for a list of inlined unions. This kind of patch will not be backward compatible.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't the point of a union type to ensure only one of a set of fields is set at that level in the object? both replace tags seem to remove the possibility of merging further down in the tree if the specified member of the union type didn't change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is potential problem. Thanks for pointing this out.
I guess an solution is:

  1. replace the whole struct if the union field changes.
  2. merge, if the union field is still the old one.

WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's what I would expect a union-type tag to accomplish...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liggitt a union-type tag would be a super set of what is proposed here. This is an incremental step in that direction, but requires fewer changes to the API.

This proposal allows us to defer some of the following design questions:

  • Do we validate that only 1 field is set?
  • How do we validate and express discriminators? Do that tags define which field matches a discriminator value?
  • What about patchKey fields? Are these exempt from the union?
  • What about fields that are not pure unions, and 2 fields may be set together where neither is a discriminator, but are mutually exclusive with other fields that may have defaulted values.

@mengqiy
Copy link
Member Author

mengqiy commented Feb 3, 2017

Updated the proposal. PTAL. Thanks.

@pwittrock
Copy link
Member

@bgrant0607 here is a quick summary:

This proposal is to define a tag where, if set, for a given type all keys/fields missing from the request will be cleared when patching the object. The goal is to defer making decisions around validating the correctness of unions, while enabling more correct patch behavior for union types.

Capability Supported By This Proposal Supported By Full Union
Auto clear missing fields on patch X X
Merge union fields on patch X X
Validate only 1 field set on type X
Validate discriminator field matches 1-of field X
Support non-union patchKey X TBD
Support arbitrary combinations of set fields X

This proposal supports any type where all of the desired fields must be set on every patch request. It is agnostic to details such as if the type is a union or something else. This makes things like supporting patchKeys simpler.


### Server Changes

- Validate that neither *PATCH* nor *UPDATE* set multiple values within a Union
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here UPDATE means PUT operation right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I will update it to make it clear.

@mengqiy mengqiy changed the title Proposal: Add Tag Indicating To Replace Union Fields in Patch Proposal: Support Union in API Server Feb 21, 2017
@mengqiy mengqiy changed the title Proposal: Support Union in API Server Proposal: Add Tag Indicating To Replace Union Fields in Patch Feb 21, 2017
@@ -0,0 +1,200 @@
Add Tag Indicating To Replace *Union* Fields in Patch
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear how this relates to Unions.

Add new patchStrategy to clear fields not present in the patch

from the request will be cleared when patching the object. For a field presents in the request,
it will be merged with the live config.

This approach could be used to fix the issues with `kubectl apply` (e.g. [#34292](https://github.com/kubernetes/kubernetes/issues/34292))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go into more detail about this use case for addressing unions and kubectl apply. Start what problems this solves with patch - use case + example.

Use cases:

  • As a user patching a map, I want keys mutually exclusive with those that I am providing to automatically be cleared.
  • As a user running kubectl apply, when I update a field in my configuration file, I want mutually exclusive fields never specified in my configuration to be cleared by apply

Examples:

  • General Example: Keys in a Union are mutually exclusive. Clear unspecified union values in a Union that contains a discriminator.
  • Specific Example: When patching a Deployment .spec.strategy, clear .spec.strategy.rollingUpdate if it is not provided in the patch so that changing .spec.strategy.type will not fail.
  • General Example: Keys in a Union are mutually exclusive. Clear unspecified union values in a Union that does not contain a discriminator.
  • Specific Example: When patching a Pod .spec.volume, clear all volume fields except the one specified in the patch.

```go
type ContainerStatus struct {
...
// Add patchStrategy:"replaceKey"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replaceKeys

type DeploymentSpec struct {
...
// Add patchStrategy:"replaceKey/<discriminator-name>"
Strategy DeploymentStrategy `json:"strategy,omitempty" protobuf:"bytes,4,opt,name=strategy" patchStrategy:"replaceKey/type"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the discriminator impact the merge logic?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT of making the discriminator a separate tag. discriminator:type

Copy link
Member Author

@mengqiy mengqiy Feb 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it need additional logic to support discriminator.

If we use a separate tag discriminator:type, do we need patchStrategy:replaceKeys in the same struct?

type PodSpec struct {
...
// Add another value "replaceKey" to patchStrategy
Volumes []Volume `json:"volumes,omitempty" patchStrategy:"merge|replaceKey" patchMergeKey:"name" protobuf:"bytes,1,rep,name=volumes"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we automatically recognize the mergeKey, and don't clear it in this case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Patching a list will lookup the metadata first. It should not be cleared.

type PodSpec struct {
...
// Add another value "replaceKey" to patchStrategy
Volumes []Volume `json:"volumes,omitempty" patchStrategy:"merge|replaceKey" patchMergeKey:"name" protobuf:"bytes,1,rep,name=volumes"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the result of setting merge|replaceKeys vs just replaceKeys?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to use replaceKeys in this case, then it will has additional meaning for list of non-primitives, merging list+ union.
I'm leaning to use merge|replaceKeys to keep the meaning of each tag clear.

- Merge the union if the union fields are unchanged
- Replace the union if the selected field has changed.


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens with version skew - client server? Will things work correctly? What sort of patch will be generated by the client? How will it be merged by the server?


Update OpenAPI schema.

### Strategic Merge Patch
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plz clarify where this code is executed. Is it in the client or the server?

### Strategic Merge Patch

We need to add additional logic to support:
- Generate the correct patch respecting the new tag, `patchStrategy:"replaceKey"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need to make sure the client always sends a patch that includes all of the keys that it wants to keep. Is this a change in how the client creates patches? I think the client will need to explicitly specify the mergeStrategy to keep backwards compatibility with clients that may not send all of the keys.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need to make sure the client always sends a patch that includes all of the keys that it wants to keep. Is this a change in how the client creates patches?

Yes

I think the client will need to explicitly specify the mergeStrategy to keep backwards compatibility with clients that may not send all of the keys.

Make sense to me. If so, I think we will need both the struct tag and a new patch directive to tell the API server how to do

| Support non-union patchKey | X | TBD |
| Support arbitrary combinations of set fields | X | |

## Proposed Changes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify the behavior for each of the new tags and old tags. e.g. mergeKeys must always be present to perform the merge, and will be preserved.


Example of non-inlined non-discriminated union:
```go
type ContainerStatus struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include for each of these examples the actual contents of the: live object + merge patch + result. Also include examples with apply file configuration + live object + result.

@mengqiy mengqiy force-pushed the add_discriminators branch 4 times, most recently from 5775889 to 088df11 Compare March 1, 2017 22:45
present to perform the merge on the list of non-primitive types, and will be preserved.

2) `patchStrategy`:
It indicates how to generate and merge a patch for lists. It could be `mrege` or `replace`. It is optional for lists.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type: mrege => merge


`patchStrategy: replaceKeys`:

We introduce a new value `replaceKeys` for `patchStrategy`. It indicates the all the fields that
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It indicates that all fields needing to be preserved must be present in the patch.

type DeploymentSpec struct {
...
// Add discriminator:"<discriminator-name>"
Strategy DeploymentStrategy `json:"strategy,omitempty" protobuf:"bytes,4,opt,name=strategy" discriminator:"type"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this just become replaceKeys also?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.
Sorry, forget to update this.


3) Inlined union with `patchMergeKey` only.
This case is special, because `Volumes` already has a tag `patchStrategy:"merge"`.
We change the tag to `patchStrategy:"merge|replaceKeys"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this also be just replaceKeys?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge means we should merge the list.
replaceKeys mean we should replace keys for each entry in the list,

@pwittrock pwittrock merged commit 708b2c3 into kubernetes:master Mar 4, 2017
@mengqiy mengqiy deleted the add_discriminators branch March 4, 2017 00:31
ruebenramirez pushed a commit to ruebenramirez/community that referenced this pull request Apr 22, 2017
Proposal: Add Tag Indicating To Replace Union Fields in Patch
MadhavJivrajani pushed a commit to MadhavJivrajani/community that referenced this pull request Nov 30, 2021
Proposal: Add Tag Indicating To Replace Union Fields in Patch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cncf-cla: yes Indicates the PR's author has signed the CNCF CLA.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants