Repository navigation
On Microversions #441
Description
This issue is an attempt to collect and organize different topics related to microversions which have been taking place in other PRs and Issues.
This issue is to discuss high-level implementation decisions, not about how the decision will actually be implemented. Please refrain from discussing the performance benefit of strings.Contains vs regexp.MatchString or how a specific implementation saves n additional function calls.
Background Docs:
- https://developer.openstack.org/api-guide/compute/microversions.html
- http://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html
- https://github.com/openstack/keystoneauth/blob/master/doc/source/using-sessions.rst
External examples:
- https://github.com/openstack/python-novaclient/blob/2e307bb3e8c92c68e4de7829df60055e9b573c40/novaclient/v2/hypervisors.py
- https://github.com/openstack/python-novaclient/blob/2e307bb3e8c92c68e4de7829df60055e9b573c40/novaclient/api_versions.py
- https://github.com/openstack/python-manilaclient/blob/c8bb1a275587e4aafde9850e3133b71f75c0e53b/manilaclient/v2/share_replicas.py
- https://github.com/openstack/python-cinderclient/blob/ce2c1daee5bef64b5c932f4e0ee787694a6309f4/cinderclient/v3/messages.py
Microversion Logs:
- https://docs.openstack.org/nova/latest/reference/api-microversion-history.html
- https://github.com/openstack/cinder/blob/master/cinder/api/openstack/rest_api_version_history.rst
- https://github.com/openstack/manila/blob/master/manila/api/openstack/rest_api_version_history.rst
Topics raised so far:
- To what extent does Gophercloud need to be concerned about microversions?
Using the above hypervisors.py link as an example, if Gophercloud were to match functionality, then microversions will play a significant role. The above link is useful for illustrative purposes because the file is small yet several microversion-specific features are used:
- "guarding" API calls with a min and max microversion
- Altering functionality in the midst of an API call based on the microversion.
In addition, an example has been cited which shows additional Fields being added in new microversions. This may have an effect on Gophercloud's result structures.
But why is there so much microversion logic in the client?
- How will API versions be retrievable via Gophercloud?
Decision: Each service will have a versionless apiversions package.
- How will Gophercloud send a microversion to the API service?
Decision: Support for sending the service-specific version header was added in 5acaa3c.
- What microversion information should the ServiceClient hold?
The ServiceClient currently supports setting and retrieving a single microversion value at client.Microversion.
A question of whether the ServiceClient should also store minimum and maximum microversion of the API service has been raised. python-novaclient only seems to be concerned about a single value: the microversion set in the client. That's a good precedence to go off of, but I'll leave this item open for discussion.
- Should Gophercloud automatically populate the ServiceClient with a requested microversion, the API service's minimum supported microversion, or nothing at all?
Please see the points made in the spec under "Clients should expect the following behavior from the server".
My interpretation of the above points is that the API service should be quite lenient toward the client not specifying a microversion at all. Problems may arise when the API service has not implemented functionality to make those points work. The client may then see unexpected behavior.
- Should Gophercloud prevent a client from calling a method if
client.Microversionis incompatible?
I might be incorrect about this, but it seems as though the only reason for doing this is to provide the user with an error that the API service itself has chosen not to respond with. According to the spec, if the client was allowed to make any API call, the service would respond with a clear error the client's currently set microversion is not compatible as well as which microversion(s) are compatible.
Referring to a statement earlier ("But why is there so much microversion logic in the client?"), having the client handle a large amount of microversion functionality might be to provide a better end-user experience when the API service has chosen to use microversions, but has not implemented the correct 406 response.
Given that I've found examples of preventing/guarding in three different Python clients, either this is standard practice or there's another reason for limiting so many calls with the compatible microversions that I'm not aware of yet.
- Should Gophercloud alter result struct fields based on the microversion?
I think this just made #300 even more difficult.
- Should Gophercloud alter behavior of an API call depending on the microversion?
There's already precedence of Gophercloud implementing extra logic in building some API calls. Altering behavior based on microversion seems no different.
Activity
jrperritt commentedon Jul 31, 2017
@jtopjian Great write-up; thank you for summarizing the issue.
The current
Microversionfield is intended to be populated by the user to represent the microversion their application uses. At most, I'd say maybe aminMicroversionand amaxMicroversionfield (notice they're unexported), but it's unclear to me whether they'd be generally useful.I don't have strong feelings about this, but I think because the service client is long-lived, and in most cases should only need to be created once, perhaps fetching the max and min versions and setting them in the suggested, unexported fields above at creation time would be OK.
I'm not sure how we'd know if a method was incompatible. Would each service (or package?) have a
SupportedMicroversionglobal variable? Or would theServiceClienthave a field for this that gets populated on creation? Would such a variable need to be bumped every time a feature was added to the service?I think these two questions are closely related. I'll need to think about this a bit to see how such a design may look. We already (would like to) modify results based on extensions, but as you say, this makes even that more complicated.
jtopjian commentedon Jul 31, 2017
Topic: What microversion information should the ServiceClient hold?
One benefit of storing min and max would be to do the following:
Where
SetMicroversionis a method that compares the user's input with the discovered min and max.I think this is helpful and useful and I wouldn't oppose it.
Topic: Should Gophercloud automatically populate the ServiceClient with a requested microversion, the API service's minimum supported microversion, or nothing at all?
I think having microversion information available at the time the client is created is certainly helpful and given how many features are now mixed up in microversions, it will save users/developers an extra step of creating a client and then setting a microversion.
However, I'm not sure what to use as a default setting.
python-novaclienthard-codes the default version: https://github.com/openstack/python-novaclient/blob/master/novaclient/__init__.py#L28Since gophercloud does not cut releases, this isn't realistic (nor maintainable).
So, what about automatically setting a sane default value? The spec says a
min_versionandmax_versionshould be published. However, at the moment, Nova is only publishingmin_versionandversion(go figure).I'm leaning on the following priorities:
IMO,
max_versionshould be safe to use if the catalog is publishing it. I might be completely wrong about that, though. With my operator-hat on, it makes me a little nervous. However, ensuring Gophercloud-based apps can easily take advantage of new features, it makes sense.Providing options to client creation might get too embellished. For example, adding something like a
Microversionfield toEndpointOptswhere valid values aremax_version,min_version,version,latest(keyword), or a specific version.Topic: Should Gophercloud prevent a client from calling a method if client.Microversion is incompatible?
Something like that. Take a look at the python client links. Some methods have the following:
This will restrict the method to only being valid when the client's microversion is between 2.0 and 2.32. As a test, I increased the version to a high erroneous number and got:
Where
2.41is the default version hard-coded in the version ofpython-novaclientI'm using.Running
nova --debug ..., I can see that no attempt was ever made to contact the Nova API service. The call was blocked at the client.pospispa commentedon Aug 1, 2017
@jtopjian Thanks a lot for putting all the information together.
Topic: Should Gophercloud prevent a client from calling a method if
client.Microversionis incompatible?Topic: Should Gophercloud alter result struct fields based on the microversion?
Topic: Should Gophercloud alter behavior of an API call depending on the microversion?
For me all the above topics are related.
In my understanding, microversions are means of introducing new features without having to increase the API major version. Increasing major API version is costly so instead of it there is probably a cheaper way: microversion, i.e. increase of API minor version.
How are microversions implemented on the server side? Pieces of code are guarded by specific microversion interval checks like
@api_versions.wraps("2.0", "2.32").What is the result for the client side? Different server API microversions behave in different way. The differences are not big, however, the differences exist.
How does
gophercloudhandle different API major versions?gophercloudhave separate code in separate directories for different API major versions. So thegophercloudcode is guarded by specific API major version.Should
gophercloudalter behaviour depending on microversion? Yes, as the server-side pieces of code are guarded by specific microversion interval the same must be done on the client-side, i.e. ingophercloud.So I answer to all the 3 "Should ..." questions: "YES".
Topic: To what extent does Gophercloud need to be concerned about microversions?
Important: every microversion represents a separate API specification. So in case
gopherclouduser chooses "2.11" microversiongopherclouduser expects thatgophercloudwill behave exactly according to "2.11" specification.I expect
gophercloudto behave exactly according to particular microversion I choose. Sogophercloudmust be "very concerned" about microversions.Topic: What microversion information should the ServiceClient hold?
Information whether
gopherclouduser switched the microversions feature on or off. Currently, I interpretclient.Microversion == ""as microversions feature switched off. I'd like to have the microversions feature switched off by default to keep backwards compatibility.User requested microversion, e.g. "2.17". I assume it's stored in
client.Microversion.Server side
maxMicroversion. In case user choosesclient.Microversion == "latest"it means that the user chose server-sidemaxMicroversion. That's why themaxMicroversionneeds to be stored.Server side
minMicroversion. Currently, I don't see any use of storing server sideminMicroversionexcept checking that the microversion chose by user is within server sideminMicroversion,maxMicroversioninterval. However, I more inclined to store the server sideminMicroversionthan not storing it.Edit:
4. Server side
minMicroversion. In case user chooses client.Microversion == "" it means that the user chose server-sideminMicroversion. That's why theminMicroversionneeds to be stored.Topic: Should Gophercloud automatically populate the ServiceClient with a requested microversion, the API service's minimum supported microversion, or nothing at all?
I'd leave the microversions feature switched off (i.e.
client.Microversion == "") to keep the backwards compatibility.I'd set the server-side
minMicroversionandmaxMicroversionto reasonable defaults without querying the server when the ServiceClient is created. So in case ServiceClient forv2endpoint is created I'd set bothminMicroversionandmaxMicroversionto "2.0".I'd query the server for
minMicroversionandmaxMicroversionas late as the user switches the microversions feature on, e.g. callsclient.SetMicroversion("2.88")method.jtopjian commentedon Aug 1, 2017
Topic: To what extent does Gophercloud need to be concerned about microversions?
That's a really good point.
Topic: What microversion information should the ServiceClient hold?
I don't think this is correct. If the microversion is set to "latest", the word "latest", verbatim, is sent to the server. Meaning, the following header is sent, regardless of what the API service is advertising in the catalog:
There's no need to track
maxMicroversionin order to do this.Topic: Should Gophercloud automatically populate the ServiceClient with a requested microversion, the API service's minimum supported microversion, or nothing at all?
A few notes about this:
min_versionadvertised by the api service. Once an API service has adopted microversions, as soon as it begins advertising amin_version, a client will begin using microversions, whether implicitly or explicitly.This implicitness might present problems if Gophercloud chooses to adopt the guarding method of the Python clients.
For example, a client is created, no microversion is set by default, and a client makes an API call. If Gophercloud chooses to guard API calls, the client would not be able to make otherwise valid calls. The server's
min_versionis set to, say,2.5, but becauseclient.Microversionis set to"", Gophercloud would prevent the client from running anything supporting2.1,2.2, etc.The same holds true for modified result structures and altering API requests.
If Gophercloud does not set a microversion by default, it is deviating from the behavior of other SDKs (which is not always bad, but needs to be thoughtfully considered), and might present problems implementing other microversion features.
With all of that in mind, if the client tracked
minMicroversion, then ifclient.Microversionwas not set, the client could fall back tominMicroversionand be able to handle all of the guarding, modifications, etc.But I don't see a difference between tracking
minMicroversionand just settingclient.Microversiontomin_versionby default.Maybe this is what
python-novaclientet al are doing when they hard-code the default microversion... it could just be that the hard-coded version was the API service'smin_versionat the time of release, but since microversions change very frequently, this becomes out of sync quickly. Example: my current environment has amin_versionof2.1, but the client defaults to2.41. It could be that thepython-novaclientpackage installed is slightly newer than thenova-apipackage installed.pospispa commentedon Aug 2, 2017
Topic: What microversion information should the ServiceClient hold?
I'm sorry I've not described all my reasoning. I shortened it a way too much.
I've already described that every particular microversion represents a separate API specification. So in case of Manila API, there are currently many independent specifications, particularly "2.0", "2.1", "2.2", ... "2.22", ... All of them are pretty similar, but none of them are same.
How will we implement in
gophercloudthese pretty small differences? I assume we will uses different techniques. I assume that one of the techniques will befunc CompatibleMicroversionthat checks if the microversion chosen by the user is same as the specification microversion the code is for. Typically a piece of source code is for specification version "2.7+" so thefunc CompatibleMicroversionwill check whether the user chose microversion>= "2.7".But what if the user chooses microversion "latest"? Should the code for specification version "2.7+" be executed or not? How could we decide? IMO, the only way is to get the server side
maxMicroversionand find out if the server sidemaxMicroversionis>= "2.7". That's why I think it's necessary to hold the server sidemaxMicroversion.The same applies if the user chooses microversion "". Then the server side
minMicroversionis needed for the comparison.I came to the above while I was developing tests for
func CompatibleMicroversion.pospispa commentedon Aug 2, 2017
Topic: Should Gophercloud automatically populate the ServiceClient with a requested microversion, the API service's minimum supported microversion, or nothing at all?
IMO, what you have described is incorrect guarding of API calls. IMO, no microversion must behave in the same way as if the server's
min_versionwas chosen. I came across this while writing tests forfunc CompatibleMicroversion.That's really a good point. I agree with setting
client.Microversiontomin_versionby default.However, we allow
gopherclouduser to set theclient.Microversionvalue, don't we? Will we considerclient.Microversion == ""a valid value?I'd set the default
client.Microversionvalue tomin_versionand didn't allow user to setclient.Microversionto value "". So theclient.Microversionwill become privateclient.microversionand afunc client.SetMicroversionwill have to be added.pospispa commentedon Aug 2, 2017
Topic: What microversion information should the ServiceClient hold?
Topic: Should Gophercloud automatically populate the ServiceClient with a requested microversion, the API service's minimum supported microversion, or nothing at all?
In case I try to implement design ideas we're discussing it gives me more insight into the topic. That's why I tried to write a very early skeleton implementation of these two topics in the below commits:
Even though we do not discuss implementation I believe it may be useful.
jtopjian commentedon Aug 2, 2017
Topic: What microversion information should the ServiceClient hold?
I apologize, but I'm not fully understanding what you're trying to convey.
However, I did a search of the
python-novaclientcode for the word "latest" and came across the unit tests for the API versions parsing:https://github.com/openstack/python-novaclient/blob/22eade1b11c35c48f133ad221a8aa11fafbaab59/novaclient/tests/unit/test_api_versions.py
It's interesting that there are tests for both what the client is advertising and what the server is advertising. I don't have time right now to fully interpret what this means. I have a feeling that
latestis implemented quite differently than how it is described in the spec.Here's how I see
latestused: If I were to do:and tried to run the following:
Then calling
Foowould fail andBarwould work. My reasoning behind that is because I think it's illogical that 2.3 is the latest microversion. If a function is capped with a max value, that should implicitly say that it's a previous microversion, not the latest. Otherwise, we would need to provide caps on all functions.However, from reading the
python-novaclientunit tests, I see that there are tests comparing both a client with min and max as well as a server with min and max. I believe this is more aboutnovaclient's hard-coded min and max values. I haven't had time to fully interpret what that means for Gophercloud.To be frank, what would be much more useful is researching existing microversion implementations and discussing if they are directly transferrable or not. If they are, then they can quickly be added to Gophercloud. If they are not, then they require careful consideration which may take several weeks.
pospispa commentedon Aug 4, 2017
@jtopjian thank you for being frank.
Topic: What microversion information should the ServiceClient hold?
Subtopic: Existing microversion implementations in existing clients.
I had a look at manila client:
This client doesn't support the keyword "latest" as a microversion:
The client has hard-coded client's min and max microversions:
In case the user chooses a microversion (
OS_SHARE_API_VERSION) higher than the client's hard-coded max microversion the manila client uses in such case the client's hard-coded max microversion (seen in Wireshark inX-Openstack-Manila-Api-Version: 2.22\r\n).In case the user chooses a microversion within hard-coded client's min max range:
I see
X-Openstack-Manila-Api-Version: 2.22\r\nin the first request that requests list of versions supported by the server. However, in the following request containsX-Openstack-Manila-Api-Version: 2.10\r\nas requested by the user.In case the user doesn't specify any microversion (
unset OS_SHARE_API_VERSION) the client uses the client's hard-coded max microversion.Note: the server's
min_versionis "2.0" andversionis "2.22".How do I interpret the fact that the client has hard-coded min max microversions? I guestimate that the manila client fully implements specifications "2.0", "2.1", "2.2", ... "2.22".
However, AFAIK,
gopherclouddoes not implement full specification, it implements only a part of a specification depending on contributions. What implications does this have? Should we always carefully consider the upper bound microversion for guarding functions with a microversion range during code reviews?The manila client chooses the microversion used in the
X-Openstack-Manila-Api-Version:OS_SHARE_API_VERSIONis set) it's checked whether it is within the client's hard-coded min max microversion range and afterwards it's checked whether it is within the server's min max microversion range.OS_SHARE_API_VERSIONis not set), i.e. client's max hard-coded microversion is used and it is checked whether it is within the server's min max microversion range.pospispa commentedon Aug 4, 2017
Topic: What microversion information should the ServiceClient hold?
Subtopic: Should
gophercloudhave client's max microversion hard-coded as manila client?Firstly, assume the
gopherclouddoesn't have any client's max microversion hard-coded. This means thatgophercloudstates that it's compliant with all specifications (e.g. all "2.0+") even with specifications that do not exist yet. So in case user requests high enough microversion, e.g. "2.100" and server supports such microversion there is quite a big risk thatgophercloudwill probaly misbehave for requests that had some incompatabilities introduced in "recent" (let's say in "2.90-2.99") microversions. In addition,gopherclouduser doesn't know for which microversionsgophercloudworks correctly and for which microversions doesn't.Secondly, assume the
gophercloudhas a client's max microversion hard-coded. This means thatgophercloudstates range of microversions it supports as a client. However, this limitsgophercloudusers to certain maximum microversion.IMO,
gophercloudshall have client's maximum microversion hard-coded.@jtopjian please, what do you think?
pospispa commentedon Aug 4, 2017
Topic: What microversion information should the ServiceClient hold?
Subtopic: Existing microversion implementations in existing clients.
Python nova client has client's min max microversions hardcoded.
I tried whether python nova client supports the "latest" keyword:
Nova client documentation says:
So the keyword "latest" is not supported, only "X.latest" is supported.
When I used
OS_COMPUTE_API_VERSION="2.latest"there is no "latest" inX-OpenStack-Nova-API-Version: 2.37\r\n, but the client's max microversion is used (note: server responded that it supports microversions from "2.1" till "2.38").That's why I think "X.latest" in the
OS_COMPUTE_API_VERSIONnova client environment variable has completely different meaning than the "latest" inX-OpenStack-Nova-API-Versionin the specification.IMO, "X.latest" in the
OS_COMPUTE_API_VERSIONnova client environment variable is an instruction for the nova client to calculatemax(client's_max_microversion, server's_max_microversion)and use the resulting particular number in theX-OpenStack-Nova-API-Versionheader.While the "latest" in the
X-OpenStack-Nova-API-Versionheader is an insturction for the server to useserver's_max_microversion.Python cinder client has also client's min max microversions hardcoded.
I also tried whether cinder client supports the "latest" keyword:
The documentation says:
So cinder client does not support the "latest" keyword.
I check manila client for
X.latestformat:But manila client doesn't support this format.
To sum it up, none of the python clients (nova, cinder, manila) supports the "latest" keyword. None of them uses the "latest" keyword in the
X-OpenStack-XXXX-API-Versionheader. So even though the specification allows the "latest" keyword in theX-OpenStack-XXXX-API-Versionheader none of the clients uses this possibility.Only the python nova client uses
X.latest, but the word "latest" is used for different purpose in completely different context.jtopjian commentedon Aug 6, 2017
@pospispa I've been tied up working on a few other outstanding issues. I haven't had time to fully read your comments, but I will do so soon.
I wanted to add a few notes here while they're fresh in my head:
The
python-keystoneauthlibrary recently added support for microversion and service discovery. While those two topics have two different goals, they're very closely related. There's some really good, detailed documentation here.What's described in that document is also currently implemented in the
keystoneauthcode, which is good, because it might help define things that seem odd in the spec. One thing to keep in mind is that microversions are very new to OpenStack. A lot of the services haven't adopted microversions, and those that have may not have adopted all features defined in the spec (ie: thelatestkeyword).Topic: Should Gophercloud automatically populate the ServiceClient with a requested microversion, the API service's minimum supported microversion, or nothing at all?
I think it makes sense to pull this information during the endpoint query being done here. I just came upon an issue where Keystone is now publishing a versionless endpoint in the catalog. You must query the version discovery URL in order to get the versioned endpoint. If that is now required, then it only makes sense to grab microversion information at the same time.
The
keystoneauthdoc details several endpoint options which could go in Gophercloud'sEndpointOpts. I haven't investigated enough to determine if it makes sense to add all of them, though.Topic: What microversion information should the ServiceClient hold?
I really don't like the idea of hard-coding something like this, but you make very good points. I don't want to say either yes or no yet, but I recognize it as a possibility.
The more I dig into microversions, the more I do not like them.
pospispa commentedon Aug 7, 2017
@jtopjian thank you for posting the link.
I'm looking at the
keystoneauth.pospispa commentedon Aug 8, 2017
@jtopjian so you prefer to implement the microversions in the
gophercloudlibrary along the lines of thekeystoneauthimplementation.I agree with this.
I assume that you prefer to currently implement only a part of the features the
keystoneauthprovides to it's users.Topic: Should Gophercloud automatically populate the ServiceClient with a requested microversion, the API service's minimum supported microversion, or nothing at all?
IMO, this may be difficult because both
func V2EndpointURLandfunc V3EndpointURLreturn(string, error)so it's difficult to extend the data type these functions return with additional microversion information.IMO, the information can be pulled also in
func initClientOptsBut anyway, we agree that it make sense to pull the microversion information during endpoint query/
gophercloud.ServiceClientcreation.pospispa commentedon Aug 11, 2017
Topic: Should Gophercloud automatically populate the ServiceClient with a requested microversion, the API service's minimum supported microversion, or nothing at all?
@jtopjian I tried to find more information on unversioned endpoints. The only thing I found is "Service Catalog Standardization".
Unfortunately, I don't know what's the status of the "Service Catalog Standardization". Whether it's already implemented.
My understanding is that catalog can contain both unversioned and versioned endpoints. Both unversioned and versioned endpoints can be used without microversions, however, I believe it's better to firstly query the endpoint for microversion ranges. The versioned endpoint will very probably return only one microversion range while the unversioned endpoint will probably return more than one microversion range (
CURRENT,SUPPORTED).62 remaining items
jtopjian commentedon Nov 8, 2017
I wanted to give a quick update on this. Unfortunately it's "a good solution still hasn't been identified".
Apologies if I'm speaking out of turn on @jrperritt's behalf, but he did mention that he's tried a few ideas, but they didn't pan out. So he hasn't been ignoring this issue. :)
Sometimes it's quite healthy to take a break from a difficult problem -- it's often that you'll see an entirely new way of solving it. So if anyone finds themselves in that position, definitely post your thoughts.
We do plan to continue working on a solution for this, but it might take a little longer. Having a lack of time to devote to this issue is the main reason why it hasn't been solved yet.
flashvoid commentedon May 10, 2018
Hi all, i was wondering if anyone interested in reviving this discussion. Features keep coming out and everyone who depends on gophercloud (read terraform) are lagging behind.
I went through the discussion as much as i could comprehend and it seems to me that problem comes from the fact that enabling microversion may introduce new fields in request/response structs.
I would like to propose a possible solution to replace
RequestOptwith an interfaceMotivation is that
ProviderClient.Requestdoesn't actually need to have an access to the structure and having an interface here would allow for feature specificRequestOptimplementations which would inject appropriate microversion headers and fields. This would cover for an extra request fields when microversion is enabled.When microversion introduces new response field, feature implementation would include
FeatureResponseAccessor(r http.Response) FeatureStructfunction as a stand-alonefunction or as a method on
RequestOptinterface.So use case would look like
jtopjian commentedon May 10, 2018
Hi @flashvoid!
Yes 😄
Personally I exhausted all ideas I had and decided to take a break. There's been more than enough non-microversion activity happening in Gophercloud that has kept me from coming back to this.
Thanks for reading through everything. :)
You're correct in your analysis. The added difficulty is that we don't want to implement something that is going to require a lot of added work every time a new microversion comes out. Gophercloud is maintained entirely by volunteers so having to manage new sets of structs for each release is unmaintainable.
Using Terraform as an example is also applicable here. Whatever solution is implemented, it would be desirable to not have to have new resources for each microversion (or release).
This is already supported, but let me know if you're thinking about using the interface differently than the following:
Any time you see a function that takes an
OptsBuilderas a parameter, it's possible to extend the request structure. Again, using Terraform as an example, you can see that done here:https://github.com/terraform-providers/terraform-provider-openstack/blob/master/openstack/types.go#L163-L173
This adds a field called
ValueSpecsto the request.(Terraform-specific side note:
ValueSpecsis a special field which can add any key/value pair to the request body. Most Terraform resources support this as a way of adding vendor-specific fields. However, it can be used as a backdoor to add any field. This isn't largely advertised since we don't want to see it abused, but if a certain Terrafom resource is missing support for a certain field and the resource supportsValueSpecs, you can inject the field this way as a workaround. The caveat is that this only works for creating resources and not maintaining resources)In addition, there are also several Gophercloud packages that implement API extensions. These are used to extend base structs:
https://github.com/gophercloud/gophercloud/blob/master/openstack/compute/v2/extensions/availabilityzones/doc.go
So in term of adding fields to requests, that part of the puzzle is the easiest.
This is one of the difficult parts. The concern I have here is the user experience and ensuring the dev/user can easily distinguish (or separate) new fields and original fields.
Yes - this 😄
The final piece of the puzzle is handling fields which are no longer supported and might cause an error to be triggered if they were sent (even if they had no value).
What you've proposed sounds very intriguing. Would you be able to do a more detailed proof-of-concept based on an actual microversion feature? I would be more than happy to review.
j-griffith commentedon May 10, 2018
I looked at this a bit more this morning and honestly I think that finding a "perfect" solution isn't going to happen. This proposal combines a number of things we've discussed here and IMO works pretty well. It's my opinion that the design of micro versions in OpenStack are going to require some compromise on the consumers part. The only part about this proposal I'm not a huge fan of is the query step, I'd rather get max supported version on init and use that rather than calling out to verify before each call.
I'm still very unsure on how we actually implement/communicate all of this when there are for example 57 versions in Cinder. Which brings us back to cherry-picking what we implement.
One last alternative I've though of is a "dumbed down client". The idea here being that we don't try and define all of these details; instead we offer an option that provides just a basic foundation:
reqOptions would be the standard RESTy things including header opts etc. Might even make sense to break that into two things; one being endpoint/header opts, and the second being actual requestOpts.
Note that we can pretty much already do this just using provider_client.RequestOpts, a few helpers would make it much easier but I think it's pretty doable.
The point here being that I'm not sure it's realistic to keep up with the mv's across OpenStack at this point so maybe instead of trying to keep up and defining strict interfaces we instead provide a flexible mechanism to exploit the OpenStack API's including micro-versions both now and as they continue to be added. In other words enable the use of Gophercloud as a simple language binding if desired.
So by default we'd provide the base version like we do today with all the conveniences and arg checking etc (ie training wheels), and for those that want to venture ahead provide the basics and let them implement whatever mv feature they'd like.
j-griffith commentedon May 10, 2018
BTW, here's an example of a pattern I've been using that seems to work fairly "ok". I added an additional NewBlockStorage function to setup micro versions when needed, other than that I just use the raw types and extend as necessary. The following example implements volumes.create with group_id option for example:
https://gist.github.com/j-griffith/0dee9a3c871a3e6988d11be530bbd9bb
flashvoid commentedon May 11, 2018
So, after reviewing
CreateOptsBuilderandextensionsi agree it is very similar to what i was going to propose.There are few probably_minor advantages to passing options as an interface right into
client.Request, as opposed to rendering options intomap[string]interface{}and than passing that.Pro:
BuildRequestBodyRequestOptsinterface for everyone as opposed to having one interface per each opts struct (e.g.firewall.CreateOptsBuilder). (please check a demo)Con:
client.Requestfunctions which affects existing code.So, proposed demo doesn't really add much to existing capabilities in terms of microversions. My reasoning was along the lines that code won't write itself, normally, so supporting features guarded by microversion would require writing some code every time. The best that can be done here is to template that code as much as possible so that anyone wanting to contribute an extension would have to satisfy only one small interface.
I'm happy to do more proof-of-concept code and eventually contribute to refactoring if you find this a reasonable thing to do.
jtopjian commentedon May 11, 2018
I've spent the day recalling where I left with this and it was something along the lines of:
CreateOpts/UpdateOpts: Implement microversion tags in the fields like so:
And then implement a
Validatemethod or function which is called during theToXCreateMap.Validatewill check the client's microversion (which was either explicitly set by the user or (in the future) automatically detected).Whether or not
Validatethrows an error or silently ignores invalid fields is up for debate.The result structs would look identical:
Playing with tags in the results is the first thing I experimented with in the beginning of this discussion and learned there is no easy way to outright omit fields. But upon revisiting this, I thought about implementing something like Terraform's
d.Get()system:AFAIK it's impossible to return
fooas a string, so it needs to be casted. So that's a tradeoff.Somehow
volumewould need to know about the microversion, too, so theGetmethod might be more like:or maybe even
The benefit of the above is that
GetFieldwould not have to be implemented in each resource.One benefit to all of the above is that all microversioned fields would be alongside the base fields. No need to extend. This is a big help for maintenance.
At the time, this felt like it would be a satisfactory solution, but I never got beyond thinking about it in my head. Gophercloud began receiving a steady stream of PRs (which is awesome) and I haven't been able to come back to this.
@j-griffith
I agree.
This is totally possible and something I want to also get back on track with. I was doing some work previously with the
apiversionspackages in hopes of eventually having endpoint/version autodiscovery working.This is something I think about regularly. In theory, a small Go REST client that provided basic JSON interaction would work, but I feel like it would be a disservice to using Go in general. In addition, Gophercloud is definitely opinionated but I've learned to appreciate its design. It's prevented numerous bugs as well as uncovered bugs in OpenStack itself. I feel like moving away from that is, in a way, giving up from fully understanding the implementation and ensuring API interaction is done well.
However, with that said, it's still a valid feature and I would not be opposed to investigating this.
@flashvoid
Thanks for writing up the demo. I haven't looked at it in detail (I was writing this comment as it came in) yet. But to quickly address your "pros":
reflectshould always be avoided when possible andparams.gois most definitely not easy to understand upfront, but it I've come to appreciate it very much in one of those "if it didn't exist, it would be badly missed" type ways.When I first started working with Gophercloud, I thought having each resource support this amount of flexibility was overkill. But having fielded Terraform questions for 3 (!) years now, I've learned that there are so many custom modified OpenStack clouds out in the wild and this per-resource flexibility is used.
I agree with this. I'm not a fan of Gophercloud's extension system but I understand why we do it. Some day I hope to create some convenience code that presents common resources with all of their extensions (for example, a
Serverwith everything 99% of the OpenStack clouds have defined as aServer).With all of the above said, I am not opposed to making strong design changes to Gophercloud if it helps Gophercloud be able to keep up with the fast pace of microversions. But for non-technical reasons, these large changes might take a long time to fully vet and review -- it really comes down to a time constraint.
And one more note, we do have https://github.com/gophercloud/utils which is a much more flexible code repository. Gophercloud is opinionated and rigid and deals solely with basic API communication.
utilsis meant as a layer between the app and Gophercloud. Maybe a microversion layer of some sort would work better there?j-griffith commentedon May 11, 2018
@jtopjian
True, and you make a very good point that one of the values of Gophercloud is in fact that it handles the "hard parts".
Now that's an interesting idea as sort of an "incubator" of sorts almost? Honestly most microversions for me with Cinder thus far have mostly been just "convenience" with the exception of some of the newer API's like attachments. Those are still relatively easy though because there aren't excessive versions of that, it's just it's introduction and some bug fixes along the way.
My thoughts on this today were that if there's something I REALLY need and it's not in Gophercloud I can extend my own request type in my consumer code as well as customize a response if needed (I know it's kinda lame), but the other thing is that maybe we use something like utils as a prelim for this sort of thing? Like a map of request/response types based on MV's?
As far as things "in" Gophercloud going forward I think the hardest part is the "what", implementing every mv isn't reasonable in my opinion; maybe a max version for each OpenStack Release? Then if folks need an intermediate version they can use another method to customize?
Anyway, bottom line from my perspective is that I DO think that much of the value of Gophercloud is in it's "strict" nature. My solutions thus far are just "hacks" for the very few things that I really need.
jtopjian commentedon May 12, 2018
It's not lame at all. The ability to do this is a core feature of Gophercloud.
As a maintainer of Gophercloud, monitoring this kind of use is important. If enough devs are doing the same extensions outside of Gophercloud, it's an indication that Gophercloud isn't meeting a core need. I think microversions fit into this area.
I think what you're talking about here is slightly different than the idea about the "dumb" client, but this kind of client would probably also meet this need.
I think providing support for a "dumb" client is absolutely valid. I believe the Python SDK(s) provide this type of feature, so at a minimum it's valid if only to reach feature parity. An immediate benefit would be to help devs move forward with certain requests and APIs currently difficult to support in Gophercloud (ie: microversions).
I think
gophercloud/utilsmight be the best place to house this client, though maybe it would end up going in core.Although the client sounds simple in theory, I'd like for it to take advantage of other parts of Gophercloud core like paging and such (if possible).
Agreed and I think it was generally agreed upon somewhere in this thread that we would base things on an OpenStack release.
But with that said, the idea I outlined above would provide the ability to support fields for every microversion. My hope is that the only barrier to entry for support is someone submitting a PR for the field to be added to the struct -- no extra methods, functions, or package extensions.
(edit: of course there might be iterations within a release that make changes to the same field a few times, possibly adding and later removing it. In this situation, basing field additions/deletions on a release is the sane thing to do)
As far as immediate next steps for me, I have some object storage work I need to finish up. After that I will work on both microversion support and the "dumb" client feature.
Finally, I will be in Vancouver, so if anyone would like to discuss this in person, let me know :)
pospispa commentedon May 12, 2018
Let me try to sum it up. Structs will look like:
Functions will automatically compare the microversion used in
clientwithmv_minandmv_maxand behave accordingly. This will be the main approach used in approx. 95% of cases. There might be corner cases when a specific OpenStack microversion introduces specific behaviour. In such case a function will have to be modified for such corner case.@jtopjian is that correct?
jtopjian commentedon May 12, 2018
@pospispa Correct -- at least that's what I'm hoping 😄
jtopjian commentedon Feb 6, 2019
We've agreed upon and merged the following document:
https://github.com/gophercloud/gophercloud/blob/master/docs/MICROVERSIONS.md
The guidelines in this document are not written in stone and might change over time. But they're a good foundation to start with.
I'm going to close this issue. I would like to thank everyone for the input they've given here.
Acc Tests: Updating LBaaS v2 Member test (gophercloud#441)
Refresh networks in host info post reconciliation (gophercloud#441)