Add microversion utilities#2791
Conversation
a1c043b to
1502c65
Compare
openstack/utils/choose_version.go
Outdated
| } | ||
|
|
||
| // GetSupportedMicroversions returns the minimum and maximum microversion that is supported by the ServiceClient Endpoint. | ||
| func GetSupportedMicroversions(client gophercloud.ServiceClient) (string, string, error) { |
There was a problem hiding this comment.
(Thinking aloud) maybe we could return a wrapper structure with something like
type SupportedMicroversions struct {
MaxMajor int
MaxMinor int
MinMajor int
MinMinor int
}Then some of these functions become methods on it, like
func (vs SupportedMicroversions) Require(version string) error
func (vs SupportedMicroversions) IsSupported(version string) bool
func (vs SupportedMicroversions) Choose(versions []string) stringetc
There was a problem hiding this comment.
Also: you want to pass the client as a pointer, not by value.
There was a problem hiding this comment.
Would the idea be to hold on to this wrapper and pass it around? I'm worried it would become hard to use. If I need to check/require microversions in many different places in a code base it would probably be cumbersome. Alternatively, I would end up calling GetSupportedMicroversions all over the place.
There was a problem hiding this comment.
I only thing it will be more convenient to use methods on the wrapper rather than passing two strings around and parsing them each time. There is also a risk of people using the wrong order (what goes first, min or max)?
There was a problem hiding this comment.
Ah good point! I was still in my head thinking about how I did it in CAPO, where the min and max is stored in the ComputeClient and then these functions become methods on that. Maybe I can find some way to integrate this better with the ServiceClient 🤔
| // ChooseVersion queries the base endpoint of an API to choose the most recent non-experimental alternative from a service's | ||
| // published versions. | ||
| // It returns the highest-Priority Version among the alternatives that are provided, as well as its corresponding endpoint. | ||
| // ChooseVersion queries the base endpoint of an API to choose the identity service version. |
There was a problem hiding this comment.
You're sure this is limited to "identity service", i.e. Keystone?
There was a problem hiding this comment.
It has a reference to IdentityBase down the code, dunno why.
There was a problem hiding this comment.
Yes, I got fooled by the doc string into believing this could be used for Nova. I was very confused about why it never could find a good version to use until @mdbooth pointed out that it is taking the ProviderClient as input, not the ServiceClient. It is indeed only checking the identity endpoint and picks a version for Keystone.
There was a problem hiding this comment.
Given that gophercloud is working on a major release, maybe it's time to change that?
There was a problem hiding this comment.
Yeah that would be nice!
There was a problem hiding this comment.
It would also be really good to have this in the v1 branch though, if possible. We have a bunch of potential near term use cases for it, one of which is described in kubernetes-sigs/cluster-api-provider-openstack#1715 (comment).
There was a problem hiding this comment.
@mdbooth I thought you did not want this kind of negotiation of the version? I have reworked the CAPO PR for that reason to set an explicit version in all cases instead of using a function like this. In which cases do you think we should use this?
There was a problem hiding this comment.
I probably got confused and thought I was commenting on the PR in general :)
You are correct, I don't want to do negotiation in CAPO: we either get a specific version or we don't execute the code. There may be cases where some code might run with one of a small number of specific microversions (e.g. GET server) but we should never, e.g. request microversion N and get microversion N+1.
That said, it might be useful to be able to specify a microversion limit for testing so we can test old microversions against a newer server. The microversion requested would still be at the point of use, though, and we would still either get it or not get it.
However, CAPO hopefully isn't the only consumer of this code. Negotiation might be useful elsewhere, e.g. a CLI utility.
1502c65 to
01c9b43
Compare
01c9b43 to
7da6734
Compare
|
|
||
| return supportedMicroversions, nil | ||
| } | ||
|
|
There was a problem hiding this comment.
nit: I'd also love to see
func (ms SupportedMicroversions) Choose(options []string) string7da6734 to
1219bf6
Compare
|
Is it possible to retrigger the failed test? It looks unrelated to me but I could be wrong. |
This adds GetSupportedMicroversions, MicroversionSupported, RequireMicroversion and ParseMicroversion utility functions for working with microversions. The doc string for ChooseVersion is also updated to clarify how it works.
1219bf6 to
ad897c8
Compare
dtantsur
left a comment
There was a problem hiding this comment.
Works with Ironic in my testing, thank you!
|
Thank you for testing! |
EmilienM
left a comment
There was a problem hiding this comment.
Nice work, I'll wait for others to approve or request changes.
|
I want to look at this this afternoon; please go ahead and merge if I don’t |
I still believe I can get to that today 🤞 |
| type SupportedMicroversions struct { | ||
| MaxMajor int | ||
| MaxMinor int | ||
| MinMajor int | ||
| MinMinor int | ||
| } |
There was a problem hiding this comment.
The internal properties of this struct (in particular: the fact that they're split into exactly major and minor) look to me like implementation details. Please consider un-exporting the properties (and expose getters and setters as necessary) to reduce the API surface of our compatibility promise.
There was a problem hiding this comment.
"the fact that they're split into exactly major and minor" is the hard fact that is embedded into the definition of microversions. I'm fine with hiding private details as long as I can still access them via accessors (not just the string representations - actual numbers).
There was a problem hiding this comment.
| } | ||
|
|
||
| // GetSupportedMicroversions returns the minimum and maximum microversion that is supported by the ServiceClient Endpoint. | ||
| func GetSupportedMicroversions(client *gophercloud.ServiceClient) (SupportedMicroversions, error) { |
There was a problem hiding this comment.
The returned type (SupportedMicroversions) contains a minimum and a maximum version. Are we sure that this is sufficient data to know what version is supported?
For example: consider the hypothetical situation in which service X on OSP Stegosaurus exposes microversions from 1.0 to 1.2 and from 2.0 to 2.3. A security issue is found that is so bad, it needs backport. And it also requires a slight change in the API. So after the fix, service X on OSP T-Rex publishes microversions 1.3 and 2.4. Knowing minimum 1.0 and maximum 2.4 is not a guarantee that 1.3 is available (unless you run more elaborate checks).
There was a problem hiding this comment.
Your example is too hypothetical :) First, it assumes having two major versions on one endpoint, which is not how things tend to be done. Second, it assumes two major versions with microversions which is a thing that is quite unlikely to happen because the very idea of microversions implies that you can do breaking changes without creating major versions. We do ponder Ironic v2 from time to time, but that's more of a wishful thinking (file it under "rewrite Ironic in Go").
There was a problem hiding this comment.
The way I see it, the assumption is to think that my example won’t happen. Which may or may not be a safe assumption.
There was a problem hiding this comment.
It's a question of whether you want to try covering 100% of potential future cases. It's not a terrible idea if you do, but that also mean you will do much more work than you need to. With rust-openstack, I took the approach of "I'll think about it when it happens" (fwiw, openstacksdk does the same), which has its own pros and cons.
There was a problem hiding this comment.
From what I can see, this (min and max) is the only information we can get from a single endpoint so there is nothing more we can do.
| type response struct { | ||
| Version valueResp `json:"version"` | ||
| Versions []valueResp `json:"versions"` | ||
| } |
There was a problem hiding this comment.
I wonder if we can assume that all services will either use version, versions, or no versions. What if we have service-specific logic instead? Have you considered having service-specific logic, maybe exposed in the ServiceClient through an interface?
There was a problem hiding this comment.
In theory - no. In practice, everyone just copied what Nova did :)
There was a problem hiding this comment.
As a data point, I've implemented a similar thing in rust-osauth long ago, and nobody complained so far: https://github.com/dtantsur/rust-osauth/blob/master/src/protocol.rs#L31-L39
There was a problem hiding this comment.
How is Swift versioning its API?
There was a problem hiding this comment.
I don't think it actually does. Of the core services, I think only Nova, Keystone and Cinder (and Ironic if you count it core) use microversioning.
|
|
||
| // RequireMicroversion checks that the required microversion is supported and | ||
| // returns a ServiceClient with the microversion set. | ||
| func RequireMicroversion(client gophercloud.ServiceClient, required string) (gophercloud.ServiceClient, error) { |
There was a problem hiding this comment.
I like this function signature. You give me a version, and I tell you if it's OK. This requires many less assumptions. Can we make it so that this is the only new exported symbol, and unexport all the rest?
There was a problem hiding this comment.
Please don't. GopherCloud is already bare bones enough to justify another high-level client built upon it.
There was a problem hiding this comment.
I may be failing to see your intended use case. Do you want to share the one you have one in mind?
The use case I’m most inclined to think: a client has two, max three behaviours coded for a specific action, each corresponding to a specific API microversion. The client would ask for its preferred version, then if not available it would test the fallback behaviour, and so on. In this case the client knows what exact microversion it needs and asks Goohercloud whether it is available. Do you have a case where a client needs something different, like a range?
There was a problem hiding this comment.
Largely, I want to know in advance what features I can and cannot use, so that I don't need to delay this decision until much later in the code. Our code is organized in the way that the controller layer does not know about the Ironic implementation details, and we'd rather keep it that way.
|
Thank you for this addition. I think that the functionality is useful. |
|
A more general comment: the most Gophercloud-y solution for this problem would probably be to have a I wonder if this "generic" solution that aims at encapsulating the calls and solving negotiation in one swipe, isn't going to feel like a trap going forward. |
I didn’t mean my review to be blocking; converting to comments
|
I will not have the opportunity to come back to this PR in the next week; @EmilienM please go ahead and do as you see fit |
Thank you for the comment! I appreciate the insights and would ask that the maintainers decide on what would be the preferred way forward here. I can see pros and cons witch both approaches. I want to also highlight this function in the utils repo: https://github.com/gophercloud/utils/blob/80377eca5d56d1f07d0ebdd4c2a5ae000a66a90e/openstack/clientconfig/requests.go#L739 |
Could be, but currently Gophercloud is handling Microversions as a standard feature (which they are in a sense), e.g. they handling is hardcoded in the ServiceClient: Line 116 in 69b57d9 |
|
@lentzi90 @EmilienM @pierreprinetti so folks, what's the path forward here? I can leave with a reduced scope if that allows this change to move forward. |
|
I feel like this is a great iteration moving forward. From Pierre:
I agree but I also think this would be doable in a future version. I want to unblock these folks for now. |
|
I'll merge this for now, @pierreprinetti if you have strong concerns we can always revert if needed; also could we please track the proposal of "a versions package in each relevant service, with a List function that makes the call to the service and returns a typed response and the caller would then adjust their ServiceClient microversion accordingly, with or without a helper function in Gophercloud" (from Pierre). |
This adds GetSupportedMicroversions and MicroversionSupported utility functions for working with microversions.
The doc string for ChooseVersion is also updated to clarify how it works.
Prior to a PR being reviewed, there needs to be a Github issue that the PR
addresses. Replace the brackets and text below with that issue number.
Fixes #2791
Links to the line numbers/files in the OpenStack source code that support the
code in this PR:
TODO. I am not sure what is needed here