-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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: Support Sending Merge Key in the Patch by Clients #476
Conversation
ae49d7b
to
c650c37
Compare
It must always be present and unique to perform the merge on the list of non-primitive types, | ||
and will be preserved. | ||
|
||
The issue in practice is that sometimes we cannot find a key that always be present and unique. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd replace this with:
The current implementation requires a single field that uniquely identifies each element in a list. For some element Kinds, the identity is defined using multiple fields. An example of this is the x.x.xPort
, which is identified by protocol
and port
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
### API Change | ||
|
||
We will update the struct tags for the API resources whose Merge Key is not guaranteed to be always | ||
present and unique. The keys will be seperated by "|", i.e. `patchMergeKey:"<key1>|<key2>|<key3>"`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why '|' instead of ','? What is more consistent with other tags and comments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After checking types.go, I agree we should use ','.
### Strategic Merge Patch pkg | ||
|
||
Entries are considered as the same if and only if all the keys in the key set are identical. | ||
We allow keys to be missing in the key set as long as it is not empty. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this mean that it is ok for the field to be missing by not empty? Can you give an example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this is answered following :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about
If one or more of the mergeKeys is unspecified, it is interpreted as an empty value for purposes of uniquely identifying the object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If one or more of the mergeKeys is unspecified, it is interpreted as an empty value for purposes of uniquely identifying the object.
IMO we still want to distinguish unspecified and empty
- port: 12345
and
- name: ""
port: 12345
The wording you give seems not support this?
Can you give me another wording?
spec: | ||
type: NodePort | ||
ports: | ||
- protocol: UDP |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment that the name is missing here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
The plan to not break backward compatibility: | ||
|
||
In 1.7, add the logic support for key set, but keep the APIs' tags unchanged. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay
- Need auditing APIs | ||
|
||
### Broken APIs | ||
(1a) `ContainerPort`: Change merge key from `containerPort` to `name|containerPort`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update to ','
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
I think this is ready for a broader review. Would you present at the next sig-cli? |
@liggitt Would you be willing to act as a reviewer for this proposal for supporting multiple-fields as a mergeKey? |
Sure. |
|
||
Document what the developer should consider when adding an API with `mergeKey`. | ||
|
||
## Version Skew |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again, the 1 minor version skew policy is kubectl-specific, not for the API in general. I'm not sure how to evolve mergeKey on existing fields compatibly without making all clients do field-level API discovery which sounds pretty painful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understood.
The usage of OpenAPI is not just for kubectl. It is actually for all clients. We want all clients to use OpenAPI instead of baked-in types in the future. It is also useful and important for TPR and service catalog.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is also useful and important for TPR and service catalog.
For discovering arbitrary resources, sure, but for a client that wants to patch exactly one thing on a known version of a known resource, forcing them to do openapi discovery to see if the way they are supposed to patch that field in that version has changed is a pretty high bar.
For example, if I want to patch a port on a v1 service, I would not expect to have to do openapi discovery. I'd expect the v1 API to remain compatible. If you wanted to change the mergeKey, that seems like the sort of thing you'd need to bump API versions for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, in general. MergeKeys are forever I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but for a client that wants to patch exactly one thing on a known version of a known resource, forcing them to do openapi discovery to see if the way they are supposed to patch that field in that version has changed is a pretty high bar.
@liggitt Do you have specific client(s) in your mind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@thockin FYI
To clarify, this is an issue generally with changing merge keys, not specifically with supporting tuples as merge keys.
A couple thoughts:
- The ship may have sailed on this already, but I would think that the client should send the merge keys and merge strategies as part of the patch request so it could tell the server how the patch request is intended to be merged.
- It maybe possible to introduce additional keys for fields while keeping backwards compatibility. Would need to think through the combinatorics, but I am wondering if some of the merge key tuple is missing from the patch request (e.g. the new part), if the server could fallback on the remaining pieces as the merge key.
- If merge keys really are "forever", and we needed to change the one referenced in this case, would this then require bumping the version for everything that has a PodTemplate in it - e.g. every controller type? The cure may be worse than the disease in this case.
- Given that we don't publish what the merge keys actually are for resource kinds in any user facing documentation besides the
types.go
files (correct me if I am wrong here), are they considered first class pieces of the API, or are they an implementation detail? Does our API definition include metadata about fields that is only defined in code? Do we have any guess as to which clients besides kubectl calculate patch requests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me qualify - mergeKeys are forever if a patch which previously "worked" (in the sense that a client uses it to accomplish a concrete mutation successfully) is broken when we change the mergeKey. If the patch was impossible or broken (envVar is a clear example of "we were totally broken because order is meaningful in env vars by our spec"), then I don't think a mergeKey change counts as a break.
We could consider versioning the patch strategy per type. Or the micro version approach Dan was championing for fields, but with restricted scope - v1.Pod has mergeKey "foo" for field "bar" at version 1, but "baz" for field "bar" at version 2. And we simply default the merge version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the clarification. IMHO the ServicePort
is broken in a similar fashion as envVar - that is - the patch is impossible / broken for things we support in our spec - e.g. having a list containing 2 different ServicePort
s with the same port
value and different protocol
values. However some clients may not be encountering these issues because they don't rely on those capabilities.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking on this more, I am pretty sure we can introduce additional fields for merge keys while retaining backwards compatibility by having the server fallback to use the present merge keys in the event the additional merge key fields are missing from the patch request.
|
||
The current implementation requires a single field that uniquely identifies each element in a list. | ||
For some element Kinds, the identity is defined using multiple fields. | ||
An [example](https://github.com/kubernetes/kubernetes/issues/39188 of this is the service.spec.ports, which is identified by `protocol` and `port`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Link MD is not terminated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd emphasise both here:
identified by both
protocol
andport
The current implementation requires a single field that uniquely identifies each element in a list. | ||
For some element Kinds, the identity is defined using multiple fields. | ||
An [example](https://github.com/kubernetes/kubernetes/issues/39188 of this is the service.spec.ports, which is identified by `protocol` and `port`. | ||
We need to support a key set as Merge Key. A key set must contain at least 1 key. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about:
As a result we need to also support a set of keys as a Merge Key. A key set must be a list of strings at least 1 element long
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about ints?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The key is always a string, because we require all maps to be map[string]interface{}
.
|
||
### API Change | ||
|
||
We will update the struct tags for the API resources whose Merge Key is not guaranteed to be always |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering whether this sentence makes sense. How about:
for API resources that cannot be effectively merged with a single merge key
We will update the struct tags for the API resources whose Merge Key is not guaranteed to be always | ||
present and unique. The keys will be seperated by ",", i.e. `patchMergeKey:"<key1>,<key2>,<key3>"`. | ||
|
||
E.g. `Ports` in `ServiceSpec`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we link to the source code struct?
|
||
### Open API | ||
|
||
Update Open API schema to have a string that contains multiple keys for `patchMergeKey`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this mean, that the type of patchMergeKey
needs to change? Can you give an example of the OpenAPI change?
|
||
Suppose we are using `name` and `port` as merge key as mentioned in section [API Change](#api-change) | ||
|
||
We have live config: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This config currently exists:
nodePort: 30420 | ||
``` | ||
|
||
We want to add another port, and the user's local config file becomes: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user then wants to add a new port. They will send this manifest:
nodePort: 30420 | ||
``` | ||
|
||
The patch we send should be: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The patch manifest that will be sent is:
ports: | ||
- protocol: UDP | ||
port: 30420 | ||
name: udpport |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if the user omits the name in their manifest? Will the existing UDP
port stay with an empty name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The operation will fail because of the validation error. Name is required if there are more than one ports.
6234e8e
to
958bdeb
Compare
f1a6c7b
to
5e9717f
Compare
Added edge cases. |
PTAL |
An example patch will look like: | ||
```yaml | ||
list: | ||
- $patchMergeKey: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in this example, $patchMergeKey
is included in the first item's definition... is that intended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think $patchMergeKey
should be in each entry in the list. This simple example only has one entry.
|
||
When `$patchMergeKey` only has part of all merge keys, | ||
the server will apply the patch to all the matching items. | ||
So an update or a deletion may apply to multiple items in the list. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accepting a $patchMergeKey
that does not uniquely identify elements is what this proposal is trying to fix... I don't think a $patchMergeKey
key that does not uniquely identify items should be accepted.
If it was, how would this interact with the proposed $setElementOrder
directive, which is supposed to contain a list of keys? Keys that identify more than one item in the list mean that directive becomes ambiguous:
server items:
ports:
- port: 8080
protocol: TCP
name: http-tcp
- port: 9090
protocol: TCP
- port: 8080
protocol: UDP
name: http-udp
patch:
$setElementOrder/ports:
- port: 8080
- port: 8080
- port: 9090
ports:
- port: 8080
protocol: UDP
name: http-udp-renamed
It's not clear which 8080 port was ordered where.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think a $patchMergeKey key that does not uniquely identify items should be accepted.
You are right.
$setElementOrder
should always use all of the recommended merge keys that are present, so each entry in $setElementOrder
list can uniquely identify an entry in the live list.
$setElementOrder/ports:
- port: 8080
protocol: TCP
- port: 8080
protocol: UDP
- port: 9090
protocol: TCP
ports:
- port: 8080
protocol: UDP
name: http-udp-renamed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add a section for $setElementOrder
.
The majority of the content in this doc is now about how to change a merge key value. Lets pull the multi-field merge key into a separate design doc and review that separately from the design around to how to change merge keys for fields and how to control the merge key from the client side |
Updating:
|
cc @kubernetes/sig-cli-proposals @kubernetes/sig-api-machinery-proposals |
@liggitt: These labels do not exist in this repository: In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
|
||
## Goal | ||
|
||
Propose a solution of changing the merge keys for existing APIs without breaking backward compatibility. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to take a step back and think about this systematically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should consider building a more principled form of the patch syntax. We keep making ad-hoc fixes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the existing RFCs have their limitations but evolving one of those with the improvements from SMR seems like a stronger path than retrofitting into existing SMR.
This PR hasn't been active in 61 days. It will be closed in 28 days (Jan 11, 2018). cc @fabianofranz @liggitt @mengqiy @monopole @pwittrock You can add 'keep-open' label to prevent this from happening, or add a comment to keep it open another 90 days |
Issues go stale after 90d of inactivity. If this issue is safe to close now please do so with Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
@mengqiy @pwittrock @lavalamp I think we should close this. |
Agree, we're fixing this class of things with a different mechanism entirely. |
Stale issues rot after 30d of inactivity. If this issue is safe to close now please do so with Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
Closing in favor of server-side apply. |
cc: @pwittrock