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: Support Sending Merge Key in the Patch by Clients #476

Closed
wants to merge 4 commits into from

Conversation

mengqiy
Copy link
Member

@mengqiy mengqiy commented Mar 23, 2017

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Mar 23, 2017
@pwittrock pwittrock self-requested a review April 11, 2017 15:12
@pwittrock pwittrock self-assigned this Apr 11, 2017
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.
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 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

Copy link
Member Author

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>"`.
Copy link
Member

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?

Copy link
Member Author

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.
Copy link
Member

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?

Copy link
Member

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 :)

Copy link
Member

@pwittrock pwittrock Apr 18, 2017

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.

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 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
Copy link
Member

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

Copy link
Member Author

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.
Copy link
Member

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`.
Copy link
Member

Choose a reason for hiding this comment

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

update to ','

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@pwittrock
Copy link
Member

I think this is ready for a broader review. Would you present at the next sig-cli?

@pwittrock
Copy link
Member

@liggitt Would you be willing to act as a reviewer for this proposal for supporting multiple-fields as a mergeKey?

@mengqiy
Copy link
Member Author

mengqiy commented Apr 19, 2017

I think this is ready for a broader review. Would you present at the next sig-cli?

Sure.


Document what the developer should consider when adding an API with `mergeKey`.

## Version Skew
Copy link
Member

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.

Copy link
Member Author

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.

Ref: kubernetes/kubernetes#44531

Copy link
Member

@liggitt liggitt Apr 20, 2017

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.

Copy link
Contributor

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.

Copy link
Member Author

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?

Copy link
Member

@pwittrock pwittrock Apr 21, 2017

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?

Copy link
Contributor

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.

Copy link
Member

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 ServicePorts 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.

Copy link
Member

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`.
Copy link
Contributor

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

Copy link
Contributor

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 and port

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.
Copy link
Contributor

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

Copy link
Member

Choose a reason for hiding this comment

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

What about ints?

Copy link
Member Author

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
Copy link
Contributor

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`.
Copy link
Contributor

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`.
Copy link
Contributor

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:
Copy link
Contributor

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:
Copy link
Contributor

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:
Copy link
Contributor

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
Copy link
Contributor

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?

Copy link
Member Author

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.

@mengqiy
Copy link
Member Author

mengqiy commented May 2, 2017

Added edge cases.
@pwittrock There is case when deleting maybe you will want to think about it. 5e9717f#diff-7bf73a741414e64cb422a883e2de7f0eR182

@mengqiy
Copy link
Member Author

mengqiy commented May 6, 2017

PTAL

An example patch will look like:
```yaml
list:
- $patchMergeKey:
Copy link
Member

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?

Copy link
Member Author

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.
Copy link
Member

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.

Copy link
Member Author

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

Copy link
Member Author

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.

@pwittrock
Copy link
Member

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

@mengqiy
Copy link
Member Author

mengqiy commented May 10, 2017

Updating:

@liggitt
Copy link
Member

liggitt commented May 10, 2017

cc @kubernetes/sig-cli-proposals @kubernetes/sig-api-machinery-proposals

@k8s-ci-robot
Copy link
Contributor

@liggitt: These labels do not exist in this repository: sig/cli, sig/api-machinery.

In response to this:

cc @kubernetes/sig-cli-proposals @kubernetes/sig-api-machinery-proposals

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.
Copy link
Member

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.

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 we should consider building a more principled form of the patch syntax. We keep making ad-hoc fixes.

Copy link
Contributor

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.

@castrojo
Copy link
Member

This change is Reviewable

@k8s-github-robot
Copy link

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

@k8s-github-robot k8s-github-robot added the kind/design Categorizes issue or PR as related to design. label Feb 6, 2018
@fejta-bot
Copy link

Issues go stale after 90d of inactivity.
Mark the issue as fresh with /remove-lifecycle stale.
Stale issues rot after an additional 30d of inactivity and eventually close.

If this issue is safe to close now please do so with /close.

Send feedback to sig-testing, kubernetes/test-infra and/or fejta.
/lifecycle stale

@k8s-ci-robot k8s-ci-robot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Mar 13, 2018
@bgrant0607
Copy link
Member

@mengqiy @pwittrock @lavalamp I think we should close this.

@lavalamp
Copy link
Member

Agree, we're fixing this class of things with a different mechanism entirely.

@fejta-bot
Copy link

Stale issues rot after 30d of inactivity.
Mark the issue as fresh with /remove-lifecycle rotten.
Rotten issues close after an additional 30d of inactivity.

If this issue is safe to close now please do so with /close.

Send feedback to sig-testing, kubernetes/test-infra and/or fejta.
/lifecycle rotten
/remove-lifecycle stale

@k8s-ci-robot k8s-ci-robot added lifecycle/rotten Denotes an issue or PR that has aged beyond stale and will be auto-closed. and removed lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. labels Apr 13, 2018
@mengqiy
Copy link
Member Author

mengqiy commented Apr 13, 2018

Closing in favor of server-side apply.

@mengqiy mengqiy closed this Apr 13, 2018
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. kind/design Categorizes issue or PR as related to design. lifecycle/rotten Denotes an issue or PR that has aged beyond stale and will be auto-closed. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.