Provide endpoint discovery via closure for v3.
diff --git a/endpoint_search.go b/endpoint_search.go
new file mode 100644
index 0000000..eea89e8
--- /dev/null
+++ b/endpoint_search.go
@@ -0,0 +1,32 @@
+package gophercloud
+
+import "errors"
+
+var (
+	// ErrEndpointNotFound is returned when no available endpoints match the provided EndpointOpts.
+	ErrEndpointNotFound = errors.New("No suitable endpoint could be found in the service catalog.")
+)
+
+// EndpointOpts contains options for finding an endpoint for an Openstack client.
+type EndpointOpts struct {
+
+	// Type is the service type for the client (e.g., "compute", "object-store").
+	// Type is a required field.
+	Type string
+
+	// Name is the service name for the client (e.g., "nova").
+	// Name is not a required field, but it is used if present.
+	// Services can have the same Type but a different Name, which is one example of when both Type and Name are needed.
+	Name string
+
+	// Region is the region in which the service resides.
+	Region string
+
+	// URLType is they type of endpoint to be returned (e.g., "public", "private").
+	// URLType is not required, and defaults to "public".
+	URLType string
+}
+
+// EndpointLocator is a function that describes how to locate a single endpoint from a service catalog for a specific ProviderClient.
+// It should be set during ProviderClient initialization and used to discover related ServiceClients.
+type EndpointLocator func(EndpointOpts) (string, error)
diff --git a/openstack/client.go b/openstack/client.go
index aef7e71..3031fef 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -8,6 +8,8 @@
 
 	"github.com/rackspace/gophercloud"
 	identity2 "github.com/rackspace/gophercloud/openstack/identity/v2"
+	endpoints3 "github.com/rackspace/gophercloud/openstack/identity/v3/endpoints"
+	services3 "github.com/rackspace/gophercloud/openstack/identity/v3/services"
 	tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
 	"github.com/rackspace/gophercloud/openstack/utils"
 )
@@ -75,7 +77,6 @@
 	case v20:
 		v2Client := NewIdentityV2(client)
 		v2Client.Endpoint = endpoint
-		fmt.Printf("Endpoint is: %s\n", endpoint)
 
 		result, err := identity2.Authenticate(v2Client, options)
 		if err != nil {
@@ -86,7 +87,11 @@
 		if err != nil {
 			return err
 		}
+
 		client.TokenID = token.ID
+		client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
+			return v2endpointLocator(v2Client, opts)
+		}
 
 		return nil
 	case v30:
@@ -103,6 +108,9 @@
 		if err != nil {
 			return err
 		}
+		client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
+			return v3endpointLocator(v3Client, opts)
+		}
 
 		return nil
 	default:
@@ -111,6 +119,93 @@
 	}
 }
 
+func v2endpointLocator(v2Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) {
+	return "", gophercloud.ErrEndpointNotFound
+}
+
+func v3endpointLocator(v3Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) {
+	// Transform URLType into an Interface.
+	var endpointInterface = endpoints3.InterfacePublic
+	switch opts.URLType {
+	case "", "public":
+		endpointInterface = endpoints3.InterfacePublic
+	case "internal":
+		endpointInterface = endpoints3.InterfaceInternal
+	case "admin":
+		endpointInterface = endpoints3.InterfaceAdmin
+	default:
+		return "", fmt.Errorf("Unrecognized URLType: %s", opts.URLType)
+	}
+
+	// Discover the service we're interested in.
+	computeResults, err := services3.List(v3Client, services3.ListOpts{ServiceType: opts.Type})
+	if err != nil {
+		return "", err
+	}
+
+	serviceResults, err := gophercloud.AllPages(computeResults)
+	if err != nil {
+		return "", err
+	}
+	allServices := services3.AsServices(serviceResults)
+
+	if opts.Name != "" {
+		filtered := make([]services3.Service, 1)
+		for _, service := range allServices {
+			if service.Name == opts.Name {
+				filtered = append(filtered, service)
+			}
+		}
+		allServices = filtered
+	}
+
+	if len(allServices) == 0 {
+		return "", gophercloud.ErrEndpointNotFound
+	}
+	if len(allServices) > 1 {
+		return "", fmt.Errorf("Discovered %d matching services: %#v", len(allServices), allServices)
+	}
+
+	service := allServices[0]
+
+	// Enumerate the endpoints available for this service.
+	endpointResults, err := endpoints3.List(v3Client, endpoints3.ListOpts{
+		Interface: endpointInterface,
+		ServiceID: service.ID,
+	})
+	if err != nil {
+		return "", err
+	}
+
+	allEndpoints, err := gophercloud.AllPages(endpointResults)
+	if err != nil {
+		return "", err
+	}
+
+	endpoints := endpoints3.AsEndpoints(allEndpoints)
+
+	if opts.Name != "" {
+		filtered := make([]endpoints3.Endpoint, 1)
+		for _, endpoint := range endpoints {
+			if endpoint.Region == opts.Region {
+				filtered = append(filtered, endpoint)
+			}
+		}
+		endpoints = filtered
+	}
+
+	if len(endpoints) == 0 {
+		return "", gophercloud.ErrEndpointNotFound
+	}
+	if len(endpoints) > 1 {
+		return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
+	}
+
+	endpoint := endpoints[0]
+
+	return endpoint.URL, nil
+}
+
 // NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
 func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
 	v2Endpoint := client.IdentityEndpoint + "/v2.0/"
diff --git a/provider_client.go b/provider_client.go
index 7f8b892..3b9aaed 100644
--- a/provider_client.go
+++ b/provider_client.go
@@ -16,6 +16,9 @@
 
 	// TokenID is the most recently valid token issued.
 	TokenID string
+
+	// EndpointLocator describes how this provider discovers the endpoints for its constituent services.
+	EndpointLocator EndpointLocator
 }
 
 // AuthenticatedHeaders returns a map of HTTP headers that are common for all authenticated service requests.