Skip to content

Commit fdb527e

Browse files
authored
Merge pull request #3209 from shiftstack/proper-service-discovery
Configure and respect service type aliases
2 parents 24c9641 + 5a3e9f6 commit fdb527e

6 files changed

Lines changed: 83 additions & 18 deletions

File tree

endpoint_search.go

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package gophercloud
22

3+
import "slices"
4+
35
// Availability indicates to whom a specific service endpoint is accessible:
46
// the internet at large, internal networks only, or only to administrators.
57
// Different identity services use different terminology for these. Identity v2
@@ -22,6 +24,31 @@ const (
2224
AvailabilityInternal Availability = "internal"
2325
)
2426

27+
// ServiceTypeAliases contains a mapping of service types to any aliases, as
28+
// defined by the OpenStack Service Types Authority. Only service types that
29+
// we support are included.
30+
var ServiceTypeAliases = map[string][]string{
31+
"application-container": {"container"},
32+
"baremetal": {"bare-metal"},
33+
"baremetal-introspection": {},
34+
"block-storage": {"block-store", "volume", "volumev2", "volumev3"},
35+
"compute": {},
36+
"container-infrastructure-management": {"container-infrastructure", "container-infra"},
37+
"database": {},
38+
"dns": {},
39+
"identity": {},
40+
"image": {},
41+
"key-manager": {},
42+
"load-balancer": {},
43+
"message": {"messaging"},
44+
"networking": {},
45+
"object-store": {},
46+
"orchestration": {},
47+
"placement": {},
48+
"shared-file-system": {"sharev2", "share"},
49+
"workflow": {"workflowv2"},
50+
}
51+
2552
// EndpointOpts specifies search criteria used by queries against an
2653
// OpenStack service catalog. The options must contain enough information to
2754
// unambiguously identify one, and only one, endpoint within the catalog.
@@ -30,15 +57,23 @@ const (
3057
// package, like "openstack.NewComputeV2()".
3158
type EndpointOpts struct {
3259
// Type [required] is the service type for the client (e.g., "compute",
33-
// "object-store"). Generally, this will be supplied by the service client
34-
// function, but a user-given value will be honored if provided.
60+
// "object-store"), as defined by the OpenStack Service Types Authority.
61+
// This will generally be supplied by the service client function, but a
62+
// user-given value will be honored if provided.
3563
Type string
3664

3765
// Name [optional] is the service name for the client (e.g., "nova") as it
3866
// appears in the service catalog. Services can have the same Type but a
3967
// different Name, which is why both Type and Name are sometimes needed.
4068
Name string
4169

70+
// Aliases [optional] is the set of aliases of the service type (e.g.
71+
// "volumev2"/"volumev3", "volume" and "block-store" for the
72+
// "block-storage" service type), as defined by the OpenStack Service Types
73+
// Authority. As with Type, this will generally be supplied by the service
74+
// client function, but a user-given value will be honored if provided.
75+
Aliases []string
76+
4277
// Region [required] is the geographic region in which the endpoint resides,
4378
// generally specifying which datacenter should house your resources.
4479
// Required only for services that span multiple regions.
@@ -73,4 +108,26 @@ func (eo *EndpointOpts) ApplyDefaults(t string) {
73108
if eo.Availability == "" {
74109
eo.Availability = AvailabilityPublic
75110
}
111+
if len(eo.Aliases) == 0 {
112+
if aliases, ok := ServiceTypeAliases[eo.Type]; ok {
113+
// happy path: user requested a service type by its official name
114+
eo.Aliases = aliases
115+
} else {
116+
// unhappy path: user requested a service type by its alias or an
117+
// invalid/unsupported service type
118+
// TODO(stephenfin): This should probably be an error in v3
119+
for t, aliases := range ServiceTypeAliases {
120+
if slices.Contains(aliases, eo.Type) {
121+
// we intentionally override the service type, even if it
122+
// was explicitly requested by the user
123+
eo.Type = t
124+
eo.Aliases = aliases
125+
}
126+
}
127+
}
128+
}
129+
}
130+
131+
func (eo *EndpointOpts) Types() []string {
132+
return append([]string{eo.Type}, eo.Aliases...)
76133
}

openstack/blockstorage/noauth/requests.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ func initClientOpts(client *gophercloud.ProviderClient, eo EndpointOpts, clientT
5151

5252
// NewBlockStorageNoAuthV2 creates a ServiceClient that may be used to access "noauth" v2 block storage service.
5353
func NewBlockStorageNoAuthV2(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophercloud.ServiceClient, error) {
54-
return initClientOpts(client, eo, "volumev2")
54+
return initClientOpts(client, eo, "block-storage")
5555
}
5656

5757
// NewBlockStorageNoAuthV3 creates a ServiceClient that may be used to access "noauth" v3 block storage service.
5858
func NewBlockStorageNoAuthV3(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophercloud.ServiceClient, error) {
59-
return initClientOpts(client, eo, "volumev3")
59+
return initClientOpts(client, eo, "block-storage")
6060
}

openstack/client.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp
344344
}, nil
345345
}
346346

347+
// TODO(stephenfin): Allow passing aliases to all New${SERVICE}V${VERSION} methods in v3
347348
func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) {
348349
sc := new(gophercloud.ServiceClient)
349350
eo.ApplyDefaults(clientType)
@@ -393,6 +394,7 @@ func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpt
393394
return sc, err
394395
}
395396

397+
// TODO(stephenfin): Remove this in v3. We no longer support the V1 Block Storage service.
396398
// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1
397399
// block storage service.
398400
func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
@@ -402,17 +404,17 @@ func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.Endpoi
402404
// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2
403405
// block storage service.
404406
func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
405-
return initClientOpts(client, eo, "volumev2")
407+
return initClientOpts(client, eo, "block-storage")
406408
}
407409

408410
// NewBlockStorageV3 creates a ServiceClient that may be used to access the v3 block storage service.
409411
func NewBlockStorageV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
410-
return initClientOpts(client, eo, "volumev3")
412+
return initClientOpts(client, eo, "block-storage")
411413
}
412414

413415
// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service.
414416
func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
415-
return initClientOpts(client, eo, "sharev2")
417+
return initClientOpts(client, eo, "shared-file-system")
416418
}
417419

418420
// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1
@@ -457,14 +459,14 @@ func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.Endpoi
457459
// NewMessagingV2 creates a ServiceClient that may be used with the v2 messaging
458460
// service.
459461
func NewMessagingV2(client *gophercloud.ProviderClient, clientID string, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
460-
sc, err := initClientOpts(client, eo, "messaging")
462+
sc, err := initClientOpts(client, eo, "message")
461463
sc.MoreHeaders = map[string]string{"Client-ID": clientID}
462464
return sc, err
463465
}
464466

465467
// NewContainerV1 creates a ServiceClient that may be used with v1 container package
466468
func NewContainerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
467-
return initClientOpts(client, eo, "container")
469+
return initClientOpts(client, eo, "application-container")
468470
}
469471

470472
// NewKeyManagerV1 creates a ServiceClient that may be used with the v1 key
@@ -478,12 +480,12 @@ func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.Endpoint
478480
// NewContainerInfraV1 creates a ServiceClient that may be used with the v1 container infra management
479481
// package.
480482
func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
481-
return initClientOpts(client, eo, "container-infra")
483+
return initClientOpts(client, eo, "container-infrastructure-management")
482484
}
483485

484486
// NewWorkflowV2 creates a ServiceClient that may be used with the v2 workflow management package.
485487
func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
486-
return initClientOpts(client, eo, "workflowv2")
488+
return initClientOpts(client, eo, "workflow")
487489
}
488490

489491
// NewPlacementV1 creates a ServiceClient that may be used with the placement package.

openstack/endpoint_location.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package openstack
22

33
import (
4+
"slices"
5+
46
"github.com/gophercloud/gophercloud/v2"
57
tokens2 "github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens"
68
tokens3 "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens"
@@ -20,7 +22,7 @@ func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpt
2022
// Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided.
2123
var endpoints = make([]tokens2.Endpoint, 0, 1)
2224
for _, entry := range catalog.Entries {
23-
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
25+
if (slices.Contains(opts.Types(), entry.Type)) && (opts.Name == "" || entry.Name == opts.Name) {
2426
for _, endpoint := range entry.Endpoints {
2527
if opts.Region == "" || endpoint.Region == opts.Region {
2628
endpoints = append(endpoints, endpoint)
@@ -74,7 +76,7 @@ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpt
7476
// Name if provided, and Region if provided.
7577
var endpoints = make([]tokens3.Endpoint, 0, 1)
7678
for _, entry := range catalog.Entries {
77-
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
79+
if (slices.Contains(opts.Types(), entry.Type)) && (opts.Name == "" || entry.Name == opts.Name) {
7880
for _, endpoint := range entry.Endpoints {
7981
if opts.Availability != gophercloud.AvailabilityAdmin &&
8082
opts.Availability != gophercloud.AvailabilityPublic &&

service_client.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,21 +115,25 @@ func (client *ServiceClient) Head(ctx context.Context, url string, opts *Request
115115
}
116116

117117
func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) {
118+
serviceType := client.Type
119+
118120
switch client.Type {
119121
case "compute":
120122
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
121-
case "sharev2":
123+
case "shared-file-system", "sharev2", "share":
122124
opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion
123-
case "volume":
125+
case "block-storage", "block-store", "volume", "volumev3":
124126
opts.MoreHeaders["X-OpenStack-Volume-API-Version"] = client.Microversion
127+
// cinder should accept block-storage but (as of Dalmatian) does not
128+
serviceType = "volume"
125129
case "baremetal":
126130
opts.MoreHeaders["X-OpenStack-Ironic-API-Version"] = client.Microversion
127131
case "baremetal-introspection":
128132
opts.MoreHeaders["X-OpenStack-Ironic-Inspector-API-Version"] = client.Microversion
129133
}
130134

131135
if client.Type != "" {
132-
opts.MoreHeaders["OpenStack-API-Version"] = client.Type + " " + client.Microversion
136+
opts.MoreHeaders["OpenStack-API-Version"] = serviceType + " " + client.Microversion
133137
}
134138
}
135139

testing/endpoint_search_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import (
1010
func TestApplyDefaultsToEndpointOpts(t *testing.T) {
1111
eo := gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic}
1212
eo.ApplyDefaults("compute")
13-
expected := gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic, Type: "compute"}
13+
expected := gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic, Type: "compute", Aliases: []string{}}
1414
th.CheckDeepEquals(t, expected, eo)
1515

1616
eo = gophercloud.EndpointOpts{Type: "compute"}
1717
eo.ApplyDefaults("object-store")
18-
expected = gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic, Type: "compute"}
18+
expected = gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic, Type: "compute", Aliases: []string{}}
1919
th.CheckDeepEquals(t, expected, eo)
2020
}

0 commit comments

Comments
 (0)