Move NormalizeURL to the root package.
diff --git a/openstack/client.go b/openstack/client.go
index f3638be..a2a9e91 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -3,7 +3,6 @@
 import (
 	"fmt"
 	"net/url"
-	"strings"
 
 	"github.com/rackspace/gophercloud"
 	tokens2 "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
@@ -32,8 +31,8 @@
 	u.Path, u.RawQuery, u.Fragment = "", "", ""
 	base := u.String()
 
-	endpoint = normalizeURL(endpoint)
-	base = normalizeURL(base)
+	endpoint = gophercloud.NormalizeURL(endpoint)
+	base = gophercloud.NormalizeURL(base)
 
 	if hadPath {
 		return &gophercloud.ProviderClient{
@@ -244,15 +243,7 @@
 	}
 	endpoint := endpoints[0]
 
-	return normalizeURL(endpoint.URL), nil
-}
-
-// normalizeURL ensures that each endpoint URL has a closing `/`, as expected by ServiceClient.
-func normalizeURL(url string) string {
-	if !strings.HasSuffix(url, "/") {
-		return url + "/"
-	}
-	return url
+	return gophercloud.NormalizeURL(endpoint.URL), nil
 }
 
 // NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
diff --git a/openstack/identity/v2/tokens/results.go b/openstack/identity/v2/tokens/results.go
index e88b2c7..5c95506 100644
--- a/openstack/identity/v2/tokens/results.go
+++ b/openstack/identity/v2/tokens/results.go
@@ -1,6 +1,7 @@
 package tokens
 
 import (
+	"fmt"
 	"time"
 
 	"github.com/mitchellh/mapstructure"
@@ -131,3 +132,45 @@
 func createErr(err error) CreateResult {
 	return CreateResult{gophercloud.CommonResult{Err: err}}
 }
+
+// LocateEndpointURL discovers the endpoint URL for a specific service from a ServiceCatalog acquired
+// from a Create request. The specified EndpointOpts are used to identify a unique, unambiguous
+// endpoint to return. The minimum that can be specified is a Type, but you will also often need
+// to specify a Name and/or a Region depending on what's available on your OpenStack deployment.
+func LocateEndpointURL(catalog *ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
+	// Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided.
+	var endpoints = make([]Endpoint, 0, 1)
+	for _, entry := range catalog.Entries {
+		if (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)
+				}
+			}
+		}
+	}
+
+	// Report an error if the options were ambiguous.
+	if len(endpoints) == 0 {
+		return "", gophercloud.ErrEndpointNotFound
+	}
+	if len(endpoints) > 1 {
+		return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
+	}
+
+	// Extract the appropriate URL from the matching Endpoint.
+	for _, endpoint := range endpoints {
+		switch opts.Availability {
+		case gophercloud.AvailabilityPublic:
+			return gophercloud.NormalizeURL(endpoint.PublicURL), nil
+		case gophercloud.AvailabilityInternal:
+			return gophercloud.NormalizeURL(endpoint.InternalURL), nil
+		case gophercloud.AvailabilityAdmin:
+			return gophercloud.NormalizeURL(endpoint.AdminURL), nil
+		default:
+			return "", fmt.Errorf("Unexpected availability in endpoint query: %s", opts.Availability)
+		}
+	}
+
+	return "", gophercloud.ErrEndpointNotFound
+}
diff --git a/util.go b/util.go
index c424759..1715458 100644
--- a/util.go
+++ b/util.go
@@ -2,6 +2,7 @@
 
 import (
 	"fmt"
+	"strings"
 	"time"
 )
 
@@ -20,3 +21,11 @@
 	}
 	return fmt.Errorf("Time out in WaitFor.")
 }
+
+// NormalizeURL ensures that each endpoint URL has a closing `/`, as expected by ServiceClient.
+func NormalizeURL(url string) string {
+	if !strings.HasSuffix(url, "/") {
+		return url + "/"
+	}
+	return url
+}