Skip to content

Commit 37ec207

Browse files
committed
Infer version from URL where possible
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
1 parent 6e92c22 commit 37ec207

File tree

3 files changed

+154
-40
lines changed

3 files changed

+154
-40
lines changed

openstack/endpoint.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func endpointSupportsVersion(ctx context.Context, client *gophercloud.ProviderCl
6565
return false, err
6666
}
6767

68-
supportedVersions, err := utils.GetServiceVersions(ctx, client, endpointURL)
68+
supportedVersions, err := utils.GetServiceVersions(ctx, client, endpointURL, false)
6969
if err != nil {
7070
return false, err
7171
}

openstack/utils/discovery.go

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"net/url"
78
"regexp"
89
"sort"
910
"strconv"
@@ -121,18 +122,66 @@ func (r *response) UnmarshalJSON(in []byte) error {
121122
return fmt.Errorf("failed to unmarshal versions document: %s", in)
122123
}
123124

125+
func extractVersion(endpointURL string) (int, int, error) {
126+
u, err := url.Parse(endpointURL)
127+
if err != nil {
128+
return 0, 0, err
129+
}
130+
131+
parts := strings.Split(strings.TrimRight(u.Path, "/"), "/")
132+
if len(parts) == 0 {
133+
return 0, 0, fmt.Errorf("expected path with version, got: %s", u.Path)
134+
}
135+
136+
// first, check the nth path element for a version string
137+
if majorVersion, minorVersion, err := ParseVersion(parts[len(parts)-1]); err == nil {
138+
return majorVersion, minorVersion, nil
139+
}
140+
141+
// if there are no more parts, quit
142+
if len(parts) == 1 {
143+
// we don't return the error message directly since it might be misleading: at this point
144+
// we might have a *malformed* version identifier rather than *no* version identifier
145+
return 0, 0, fmt.Errorf("failed to infer version from path: %s", u.Path)
146+
}
147+
148+
// the guidelines say we should use the currently scoped project_id from the token, but we
149+
// don't necessarily have a token yet so we speculatively look at the (n-1)th path element
150+
// (but only that) just as keystoneauth does
151+
//
152+
// https://github.com/openstack/keystoneauth/blob/master/keystoneauth1/discover.py#L1534-L1545
153+
if majorVersion, minorVersion, err := ParseVersion(parts[len(parts)-1]); err == nil {
154+
return majorVersion, minorVersion, err
155+
}
156+
157+
// once again, we don't return the error message directly
158+
return 0, 0, fmt.Errorf("failed to infer version from path: %s", u.Path)
159+
}
160+
124161
// GetServiceVersions returns the versions supported by the ServiceClient Endpoint.
125162
// If the endpoint resolves to an unversioned discovery API, this should return one or more supported versions.
126163
// If the endpoint resolves to a versioned discovery API, this should return exactly one supported version.
127-
func GetServiceVersions(ctx context.Context, client *gophercloud.ProviderClient, endpointURL string) ([]SupportedVersion, error) {
164+
func GetServiceVersions(ctx context.Context, client *gophercloud.ProviderClient, endpointURL string, discoverVersions bool) ([]SupportedVersion, error) {
128165
var supportedVersions []SupportedVersion
166+
var endpointVersion *SupportedVersion
167+
168+
if majorVersion, minorVersion, err := extractVersion(endpointURL); err == nil {
169+
endpointVersion = &SupportedVersion{Major: majorVersion, Minor: minorVersion}
170+
if !discoverVersions {
171+
return append(supportedVersions, *endpointVersion), nil
172+
}
173+
}
129174

130175
var resp response
131176
_, err := client.Request(ctx, "GET", endpointURL, &gophercloud.RequestOpts{
132177
JSONResponse: &resp,
133178
OkCodes: []int{200, 300},
134179
})
135180
if err != nil {
181+
// we weren't able to find a discovery document but we have version information from the URL
182+
if endpointVersion != nil {
183+
return append(supportedVersions, *endpointVersion), nil
184+
}
136185
return supportedVersions, err
137186
}
138187

@@ -187,7 +236,7 @@ func GetServiceVersions(ctx context.Context, client *gophercloud.ProviderClient,
187236
func GetSupportedMicroversions(ctx context.Context, client *gophercloud.ServiceClient) (SupportedMicroversions, error) {
188237
var supportedMicroversions SupportedMicroversions
189238

190-
supportedVersions, err := GetServiceVersions(ctx, client.ProviderClient, client.Endpoint)
239+
supportedVersions, err := GetServiceVersions(ctx, client.ProviderClient, client.Endpoint, true)
191240
if err != nil {
192241
return supportedMicroversions, err
193242
}

openstack/utils/testing/discovery_test.go

Lines changed: 102 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ func TestGetServiceVersions(t *testing.T) {
1818
tests := []struct {
1919
name string
2020
endpoint string
21+
discoverVersions bool
2122
expectedVersions []utils.SupportedVersion
2223
expectedErr string
2324
}{
2425
{
25-
name: "identity unversioned endpoint",
26-
endpoint: fakeServer.Endpoint() + "identity/",
26+
name: "identity unversioned endpoint",
27+
endpoint: fakeServer.Endpoint() + "identity/",
28+
discoverVersions: true,
2729
expectedVersions: []utils.SupportedVersion{
2830
{
2931
Major: 3,
@@ -33,8 +35,10 @@ func TestGetServiceVersions(t *testing.T) {
3335
},
3436
},
3537
{
36-
name: "identity versioned endpoint",
37-
endpoint: fakeServer.Endpoint() + "identity/v3/",
38+
name: "identity unversioned endpoint without discovery",
39+
endpoint: fakeServer.Endpoint() + "identity/",
40+
discoverVersions: false,
41+
// we will still run discovery since we can't extract the version from the URL
3842
expectedVersions: []utils.SupportedVersion{
3943
{
4044
Major: 3,
@@ -44,8 +48,34 @@ func TestGetServiceVersions(t *testing.T) {
4448
},
4549
},
4650
{
47-
name: "compute unversioned endpoint",
48-
endpoint: fakeServer.Endpoint() + "compute/",
51+
name: "identity versioned endpoint",
52+
endpoint: fakeServer.Endpoint() + "identity/v3/",
53+
discoverVersions: true,
54+
expectedVersions: []utils.SupportedVersion{
55+
{
56+
Major: 3,
57+
Minor: 14,
58+
Status: utils.StatusCurrent,
59+
},
60+
},
61+
},
62+
{
63+
name: "identity versioned endpoint without discovery",
64+
endpoint: fakeServer.Endpoint() + "identity/v3/",
65+
discoverVersions: false,
66+
// we will skip discovery since we can extract a version from the URL
67+
expectedVersions: []utils.SupportedVersion{
68+
{
69+
Major: 3,
70+
Minor: 0,
71+
Status: utils.StatusUnknown,
72+
},
73+
},
74+
},
75+
{
76+
name: "compute unversioned endpoint",
77+
endpoint: fakeServer.Endpoint() + "compute/",
78+
discoverVersions: true,
4979
expectedVersions: []utils.SupportedVersion{
5080
{
5181
Major: 2,
@@ -63,8 +93,9 @@ func TestGetServiceVersions(t *testing.T) {
6393
},
6494
},
6595
{
66-
name: "compute legacy endpoint",
67-
endpoint: fakeServer.Endpoint() + "compute/v2/",
96+
name: "compute legacy endpoint",
97+
endpoint: fakeServer.Endpoint() + "compute/v2/",
98+
discoverVersions: true,
6899
expectedVersions: []utils.SupportedVersion{
69100
{
70101
Major: 2,
@@ -74,8 +105,9 @@ func TestGetServiceVersions(t *testing.T) {
74105
},
75106
},
76107
{
77-
name: "compute versioned endpoint",
78-
endpoint: fakeServer.Endpoint() + "compute/v2.1/",
108+
name: "compute versioned endpoint",
109+
endpoint: fakeServer.Endpoint() + "compute/v2.1/",
110+
discoverVersions: true,
79111
expectedVersions: []utils.SupportedVersion{
80112
{
81113
Major: 2,
@@ -88,8 +120,9 @@ func TestGetServiceVersions(t *testing.T) {
88120
},
89121
},
90122
{
91-
name: "container-infra unversioned endpoint",
92-
endpoint: fakeServer.Endpoint() + "container-infra/",
123+
name: "container-infra unversioned endpoint",
124+
endpoint: fakeServer.Endpoint() + "container-infra/",
125+
discoverVersions: true,
93126
expectedVersions: []utils.SupportedVersion{
94127
{
95128
Major: 1,
@@ -102,8 +135,9 @@ func TestGetServiceVersions(t *testing.T) {
102135
},
103136
},
104137
{
105-
name: "container-infra versioned endpoint",
106-
endpoint: fakeServer.Endpoint() + "container-infra/v1/",
138+
name: "container-infra versioned endpoint",
139+
endpoint: fakeServer.Endpoint() + "container-infra/v1/",
140+
discoverVersions: true,
107141
expectedVersions: []utils.SupportedVersion{
108142
{
109143
Major: 1,
@@ -113,8 +147,9 @@ func TestGetServiceVersions(t *testing.T) {
113147
},
114148
},
115149
{
116-
name: "orchestration unversioned endpoint",
117-
endpoint: fakeServer.Endpoint() + "heat-api/",
150+
name: "orchestration unversioned endpoint",
151+
endpoint: fakeServer.Endpoint() + "heat-api/",
152+
discoverVersions: true,
118153
expectedVersions: []utils.SupportedVersion{
119154
{
120155
Major: 1,
@@ -124,14 +159,21 @@ func TestGetServiceVersions(t *testing.T) {
124159
},
125160
},
126161
{
127-
// FIXME(stephenfin): We should missing version documents
128-
name: "orchestration versioned endpoint",
129-
endpoint: fakeServer.Endpoint() + "heat-api/v1/",
130-
expectedErr: "Expected HTTP response code",
162+
name: "orchestration versioned endpoint",
163+
endpoint: fakeServer.Endpoint() + "heat-api/v1/",
164+
discoverVersions: true,
165+
expectedVersions: []utils.SupportedVersion{
166+
{
167+
Major: 1,
168+
Minor: 0,
169+
Status: utils.StatusUnknown,
170+
},
171+
},
131172
},
132173
{
133-
name: "workflow unversioned endpoint",
134-
endpoint: fakeServer.Endpoint() + "workflow/",
174+
name: "workflow unversioned endpoint",
175+
endpoint: fakeServer.Endpoint() + "workflow/",
176+
discoverVersions: true,
135177
expectedVersions: []utils.SupportedVersion{
136178
{
137179
Major: 2,
@@ -141,14 +183,21 @@ func TestGetServiceVersions(t *testing.T) {
141183
},
142184
},
143185
{
144-
// FIXME(stephenfin): We should handle invalid version documents
145-
name: "workflow versioned endpoint",
146-
endpoint: fakeServer.Endpoint() + "workflow/v2/",
147-
expectedErr: "failed to unmarshal versions document",
186+
name: "workflow versioned endpoint",
187+
endpoint: fakeServer.Endpoint() + "workflow/v2/",
188+
discoverVersions: true,
189+
expectedVersions: []utils.SupportedVersion{
190+
{
191+
Major: 2,
192+
Minor: 0,
193+
Status: utils.StatusUnknown,
194+
},
195+
},
148196
},
149197
{
150-
name: "baremetal unversioned endpoint",
151-
endpoint: fakeServer.Endpoint() + "baremetal/",
198+
name: "baremetal unversioned endpoint",
199+
endpoint: fakeServer.Endpoint() + "baremetal/",
200+
discoverVersions: true,
152201
expectedVersions: []utils.SupportedVersion{
153202
{
154203
Major: 1,
@@ -161,8 +210,9 @@ func TestGetServiceVersions(t *testing.T) {
161210
},
162211
},
163212
{
164-
name: "baremetal versioned endpoint",
165-
endpoint: fakeServer.Endpoint() + "baremetal/v1/",
213+
name: "baremetal versioned endpoint",
214+
endpoint: fakeServer.Endpoint() + "baremetal/v1/",
215+
discoverVersions: true,
166216
expectedVersions: []utils.SupportedVersion{
167217
{
168218
Major: 1,
@@ -175,8 +225,24 @@ func TestGetServiceVersions(t *testing.T) {
175225
},
176226
},
177227
{
178-
name: "fictional multi-version endpoint",
179-
endpoint: fakeServer.Endpoint() + "multi-version/v1.2/",
228+
name: "baremetal versioned endpoint",
229+
endpoint: fakeServer.Endpoint() + "baremetal/v1/",
230+
discoverVersions: true,
231+
expectedVersions: []utils.SupportedVersion{
232+
{
233+
Major: 1,
234+
Minor: 0,
235+
Status: utils.StatusCurrent,
236+
SupportedMicroversions: utils.SupportedMicroversions{
237+
MaxMajor: 1, MaxMinor: 87, MinMajor: 1, MinMinor: 1,
238+
},
239+
},
240+
},
241+
},
242+
{
243+
name: "fictional multi-version endpoint",
244+
endpoint: fakeServer.Endpoint() + "multi-version/v1.2/",
245+
discoverVersions: true,
180246
expectedVersions: []utils.SupportedVersion{
181247
{
182248
Major: 1,
@@ -201,7 +267,7 @@ func TestGetServiceVersions(t *testing.T) {
201267
t.Run(tt.name, func(t *testing.T) {
202268
client := &gophercloud.ProviderClient{}
203269

204-
actualVersions, err := utils.GetServiceVersions(context.TODO(), client, tt.endpoint)
270+
actualVersions, err := utils.GetServiceVersions(context.TODO(), client, tt.endpoint, tt.discoverVersions)
205271

206272
if tt.expectedErr != "" {
207273
th.AssertErr(t, err)
@@ -279,10 +345,10 @@ func TestGetSupportedMicroversions(t *testing.T) {
279345
expectedErr: "not supported",
280346
},
281347
{
282-
// FIXME(stephenfin): We should handle missing version documents
348+
// orchestration does not support microversions and returns error
283349
name: "orchestration versioned endpoint",
284350
endpoint: fakeServer.Endpoint() + "heat-api/v1/",
285-
expectedErr: "Expected HTTP response code",
351+
expectedErr: "not supported",
286352
},
287353
{
288354
// workflow does not support microversions and returns error
@@ -292,10 +358,9 @@ func TestGetSupportedMicroversions(t *testing.T) {
292358
},
293359
{
294360
// workflow does not support microversions and returns error
295-
// FIXME(stephenfin): We should handle invalid version documents
296361
name: "workflow versioned endpoint",
297362
endpoint: fakeServer.Endpoint() + "workflow/v2/",
298-
expectedErr: "failed to unmarshal versions document",
363+
expectedErr: "not supported",
299364
},
300365
{
301366
name: "baremetal unversioned endpoint",

0 commit comments

Comments
 (0)