diff --git a/openstack/endpoint_location.go b/openstack/endpoint_location.go index 254cc2d4a..184c3994f 100644 --- a/openstack/endpoint_location.go +++ b/openstack/endpoint_location.go @@ -1,11 +1,26 @@ package openstack import ( + "errors" + "regexp" + "github.com/huaweicloud/golangsdk" tokens2 "github.com/huaweicloud/golangsdk/openstack/identity/v2/tokens" tokens3 "github.com/huaweicloud/golangsdk/openstack/identity/v3/tokens" ) +// a regular patten for searching servicename in an existing endpoint +var replaceEndpointUrl = regexp.MustCompile(`https://.+?\.`) + +// a regular patten for searching service name and location +var repWithoutLocal = regexp.MustCompile(`https://.+?\..+?\.`) + +// service have same endpoint address in different location, refer to https://developer.huaweicloud.com/endpoint +var allRegionInOneEndpoint = map[string]struct{}{ + "cdn": struct{}{}, + "dns": struct{}{}, +} + /* V2EndpointURL discovers the endpoint URL for a specific service from a ServiceCatalog acquired during the v2 identity service. @@ -20,7 +35,7 @@ func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts golangsdk.EndpointOpts) // Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided. var endpoints = make([]tokens2.Endpoint, 0, 1) for _, entry := range catalog.Entries { - if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) { + if (opts.Type == "" || entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) { for _, endpoint := range entry.Endpoints { if opts.Region == "" || endpoint.Region == opts.Region { endpoints = append(endpoints, endpoint) @@ -30,10 +45,18 @@ func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts golangsdk.EndpointOpts) } // Report an error if the options were ambiguous. - if len(endpoints) > 1 { - err := &ErrMultipleMatchingEndpointsV2{} - err.Endpoints = endpoints - return "", err + if opts.Type != "" { + if len(endpoints) > 1 { + err := &ErrMultipleMatchingEndpointsV2{} + err.Endpoints = endpoints + return "", err + } else if len(endpoints) < 1 { + return buildUrlIfNotFoundV2(catalog, opts) + } + } else { + if len(endpoints) < 1 { + return "", &golangsdk.ErrEndpointNotFound{} + } } // Extract the appropriate URL from the matching Endpoint. @@ -73,7 +96,7 @@ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts golangsdk.EndpointOpts) // Name if provided, and Region if provided. var endpoints = make([]tokens3.Endpoint, 0, 1) for _, entry := range catalog.Entries { - if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) { + if (opts.Type == "" || entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) { for _, endpoint := range entry.Endpoints { if opts.Availability != golangsdk.AvailabilityAdmin && opts.Availability != golangsdk.AvailabilityPublic && @@ -92,8 +115,18 @@ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts golangsdk.EndpointOpts) } // Report an error if the options were ambiguous. - if len(endpoints) > 1 { - return "", ErrMultipleMatchingEndpointsV3{Endpoints: endpoints} + if opts.Type != "" { + if len(endpoints) > 1 { + return "", ErrMultipleMatchingEndpointsV3{Endpoints: endpoints} + } else if len(endpoints) < 1 { + return buildUrlIfNotFoundV3(catalog, opts) + } + } else { + if len(endpoints) > 1 { + return endpoints[0].URL, nil + } else if len(endpoints) < 1 { + return "", &golangsdk.ErrEndpointNotFound{} + } } // Extract the URL from the matching Endpoint. @@ -105,3 +138,42 @@ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts golangsdk.EndpointOpts) err := &golangsdk.ErrEndpointNotFound{} return "", err } + +/* + buildUrlIfNotFound builds an endpoint if it is not found in identity service response +*/ +func buildUrlIfNotFoundV2(catalog *tokens2.ServiceCatalog, opts golangsdk.EndpointOpts) (string, error) { + + tmpOpts := opts + tmpOpts.Type = "" + + existingUrl, err := V2EndpointURL(catalog, tmpOpts) + return generateEndpointUrlWithExisting(existingUrl, opts, err) +} + +/* + buildUrlIfNotFound builds an endpoint if it is not found in identity service response +*/ +func buildUrlIfNotFoundV3(catalog *tokens3.ServiceCatalog, opts golangsdk.EndpointOpts) (string, error) { + + tmpOpts := opts + tmpOpts.Type = "" + + existingUrl, err := V3EndpointURL(catalog, tmpOpts) + return generateEndpointUrlWithExisting(existingUrl, opts, err) +} + +// internal method for extract a valid endpoint address +func generateEndpointUrlWithExisting(existingUrl string, opts golangsdk.EndpointOpts, err error) (string, error) { + if err != nil || existingUrl == "" { + return "", errors.New("No suitable endpoint could be found in the service catalog.") + } + + existingUrl = golangsdk.NormalizeURL(existingUrl) + if _, ok := allRegionInOneEndpoint[opts.Type]; ok { + existingUrl = repWithoutLocal.ReplaceAllString(existingUrl, ("https://" + opts.Type + ".")) + } else { + existingUrl = replaceEndpointUrl.ReplaceAllString(existingUrl, ("https://" + opts.Type + ".")) + } + return existingUrl, nil +} diff --git a/openstack/testing/endpoint_location_test.go b/openstack/testing/endpoint_location_test.go index 7d743b5b9..42debe94b 100644 --- a/openstack/testing/endpoint_location_test.go +++ b/openstack/testing/endpoint_location_test.go @@ -80,8 +80,28 @@ func TestV2EndpointExact(t *testing.T) { } } +func TestV2EndpointExactNonExist(t *testing.T) { + expectedURLs := map[golangsdk.Availability]string{ + golangsdk.AvailabilityPublic: "https://none.correct.com/", + golangsdk.AvailabilityAdmin: "https://none.correct.com/", + golangsdk.AvailabilityInternal: "https://none.correct.com/", + } + + for availability, expected := range expectedURLs { + actual, err := openstack.V2EndpointURL(&catalog2, golangsdk.EndpointOpts{ + Type: "none", + Name: "same", + Region: "same", + Availability: availability, + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, expected, actual) + } +} + func TestV2EndpointNone(t *testing.T) { - _, actual := openstack.V2EndpointURL(&catalog2, golangsdk.EndpointOpts{ + _, actual := openstack.V2EndpointURL(&tokens2.ServiceCatalog{ + Entries: []tokens2.CatalogEntry{}}, golangsdk.EndpointOpts{ Type: "nope", Availability: golangsdk.AvailabilityPublic, }) @@ -200,8 +220,28 @@ func TestV3EndpointExact(t *testing.T) { } } +func TestV3EndpointExactNonExist(t *testing.T) { + expectedURLs := map[golangsdk.Availability]string{ + golangsdk.AvailabilityPublic: "https://none.correct.com/", + golangsdk.AvailabilityAdmin: "https://none.correct.com/", + golangsdk.AvailabilityInternal: "https://none.correct.com/", + } + + for availability, expected := range expectedURLs { + actual, err := openstack.V3EndpointURL(&catalog3, golangsdk.EndpointOpts{ + Type: "none", + Name: "same", + Region: "same", + Availability: availability, + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, expected, actual) + } +} + func TestV3EndpointNone(t *testing.T) { - _, actual := openstack.V3EndpointURL(&catalog3, golangsdk.EndpointOpts{ + _, actual := openstack.V3EndpointURL(&tokens3.ServiceCatalog{ + Entries: []tokens3.CatalogEntry{}}, golangsdk.EndpointOpts{ Type: "nope", Availability: golangsdk.AvailabilityPublic, })