Use service catalog to get services URLs
diff --git a/openstack/client.go b/openstack/client.go
index aaf940b..7d67b15 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -133,10 +133,18 @@
 		v3Client.Endpoint = endpoint
 	}
 
-	token, err := tokens3.Create(v3Client, options, nil).Extract()
+	result := tokens3.Create(v3Client, options, nil)
+
+	token, err := result.ExtractToken()
 	if err != nil {
 		return err
 	}
+
+	catalog, err := result.ExtractServiceCatalog()
+	if err != nil {
+		return err
+	}
+
 	client.TokenID = token.ID
 
 	if options.AllowReauth {
@@ -145,7 +153,7 @@
 		}
 	}
 	client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
-		return V3EndpointURL(v3Client, opts)
+		return V3EndpointURL(catalog, opts)
 	}
 
 	return nil
diff --git a/openstack/endpoint_location.go b/openstack/endpoint_location.go
index 5a311e4..29d02c4 100644
--- a/openstack/endpoint_location.go
+++ b/openstack/endpoint_location.go
@@ -5,9 +5,7 @@
 
 	"github.com/rackspace/gophercloud"
 	tokens2 "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
-	endpoints3 "github.com/rackspace/gophercloud/openstack/identity/v3/endpoints"
-	services3 "github.com/rackspace/gophercloud/openstack/identity/v3/services"
-	"github.com/rackspace/gophercloud/pagination"
+	tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
 )
 
 // V2EndpointURL discovers the endpoint URL for a specific service from a ServiceCatalog acquired
@@ -52,73 +50,42 @@
 	return "", gophercloud.ErrEndpointNotFound
 }
 
-// V3EndpointURL discovers the endpoint URL for a specific service using multiple calls against
-// an identity v3 service endpoint. The specified EndpointOpts are used to identify a unique,
+// V3EndpointURL discovers the endpoint URL for a specific service from a Catalog acquired
+// during the v3 identity service. The specified EndpointOpts are used to identify a unique,
 // unambiguous endpoint to return. It's an error both when multiple endpoints match the provided
 // criteria and when none do. 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 V3EndpointURL(v3Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) {
-	// Discover the service we're interested in.
-	var services = make([]services3.Service, 0, 1)
-	servicePager := services3.List(v3Client, services3.ListOpts{ServiceType: opts.Type})
-	err := servicePager.EachPage(func(page pagination.Page) (bool, error) {
-		part, err := services3.ExtractServices(page)
-		if err != nil {
-			return false, err
-		}
-
-		for _, service := range part {
-			if service.Name == opts.Name {
-				services = append(services, service)
+func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
+	// Extract Endpoints from the catalog entries that match the requested Type, Interface,
+	// 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) {
+			for _, endpoint := range entry.Endpoints {
+				if opts.Availability != gophercloud.AvailabilityAdmin &&
+					opts.Availability != gophercloud.AvailabilityPublic &&
+					opts.Availability != gophercloud.AvailabilityInternal {
+					return "", fmt.Errorf("Unexpected availability in endpoint query: %s", opts.Availability)
+				}
+				if (opts.Availability == gophercloud.Availability(endpoint.Interface)) &&
+					(opts.Region == "" || endpoint.Region == opts.Region) {
+					endpoints = append(endpoints, endpoint)
+				}
 			}
 		}
-
-		return true, nil
-	})
-	if err != nil {
-		return "", err
 	}
 
-	if len(services) == 0 {
-		return "", gophercloud.ErrServiceNotFound
-	}
-	if len(services) > 1 {
-		return "", fmt.Errorf("Discovered %d matching services: %#v", len(services), services)
-	}
-	service := services[0]
-
-	// Enumerate the endpoints available for this service.
-	var endpoints []endpoints3.Endpoint
-	endpointPager := endpoints3.List(v3Client, endpoints3.ListOpts{
-		Availability: opts.Availability,
-		ServiceID:    service.ID,
-	})
-	err = endpointPager.EachPage(func(page pagination.Page) (bool, error) {
-		part, err := endpoints3.ExtractEndpoints(page)
-		if err != nil {
-			return false, err
-		}
-
-		for _, endpoint := range part {
-			if opts.Region == "" || endpoint.Region == opts.Region {
-				endpoints = append(endpoints, endpoint)
-			}
-		}
-
-		return true, nil
-	})
-	if err != nil {
-		return "", err
-	}
-
-	if len(endpoints) == 0 {
-		return "", gophercloud.ErrEndpointNotFound
-	}
+	// Report an error if the options were ambiguous.
 	if len(endpoints) > 1 {
 		return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
 	}
-	endpoint := endpoints[0]
 
-	return gophercloud.NormalizeURL(endpoint.URL), nil
+	// Extract the URL from the matching Endpoint.
+	for _, endpoint := range endpoints {
+		return gophercloud.NormalizeURL(endpoint.URL), nil
+	}
+
+	// Report an error if there were no matching endpoints.
+	return "", gophercloud.ErrEndpointNotFound
 }
diff --git a/openstack/endpoint_location_test.go b/openstack/endpoint_location_test.go
index 4e0569a..8e65918 100644
--- a/openstack/endpoint_location_test.go
+++ b/openstack/endpoint_location_test.go
@@ -1,15 +1,13 @@
 package openstack
 
 import (
-	"fmt"
-	"net/http"
 	"strings"
 	"testing"
 
 	"github.com/rackspace/gophercloud"
 	tokens2 "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
+	tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 // Service catalog fixtures take too much vertical space!
@@ -107,119 +105,124 @@
 		Region:       "same",
 		Availability: "wat",
 	})
-	th.CheckEquals(t, err.Error(), "Unexpected availability in endpoint query: wat")
+	th.CheckEquals(t, "Unexpected availability in endpoint query: wat", err.Error())
 }
 
-func setupV3Responses(t *testing.T) {
-	// Mock the service query.
-	th.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "GET")
-		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-
-		w.Header().Add("Content-Type", "application/json")
-		fmt.Fprintf(w, `
-			{
-				"links": {
-					"next": null,
-					"previous": null
+var catalog3 = tokens3.ServiceCatalog{
+	Entries: []tokens3.CatalogEntry{
+		tokens3.CatalogEntry{
+			Type: "same",
+			Name: "same",
+			Endpoints: []tokens3.Endpoint{
+				tokens3.Endpoint{
+					ID:        "1",
+					Region:    "same",
+					Interface: "public",
+					URL:       "https://public.correct.com/",
 				},
-				"services": [
-					{
-						"description": "Correct",
-						"id": "1234",
-						"name": "same",
-						"type": "same"
-					},
-					{
-						"description": "Bad Name",
-						"id": "9876",
-						"name": "different",
-						"type": "same"
-					}
-				]
-			}
-		`)
-	})
-
-	// Mock the endpoint query.
-	th.Mux.HandleFunc("/endpoints", func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "GET")
-		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		th.TestFormValues(t, r, map[string]string{
-			"service_id": "1234",
-			"interface":  "public",
-		})
-
-		w.Header().Add("Content-Type", "application/json")
-		fmt.Fprintf(w, `
-			{
-				"endpoints": [
-					{
-						"id": "12",
-						"interface": "public",
-						"name": "the-right-one",
-						"region": "same",
-						"service_id": "1234",
-						"url": "https://correct:9000/"
-					},
-					{
-						"id": "14",
-						"interface": "public",
-						"name": "bad-region",
-						"region": "different",
-						"service_id": "1234",
-						"url": "https://bad-region:9001/"
-					}
-				],
-				"links": {
-					"next": null,
-					"previous": null
-				}
-			}
-    `)
-	})
+				tokens3.Endpoint{
+					ID:        "2",
+					Region:    "same",
+					Interface: "admin",
+					URL:       "https://admin.correct.com/",
+				},
+				tokens3.Endpoint{
+					ID:        "3",
+					Region:    "same",
+					Interface: "internal",
+					URL:       "https://internal.correct.com/",
+				},
+				tokens3.Endpoint{
+					ID:        "4",
+					Region:    "different",
+					Interface: "public",
+					URL:       "https://badregion.com/",
+				},
+			},
+		},
+		tokens3.CatalogEntry{
+			Type: "same",
+			Name: "different",
+			Endpoints: []tokens3.Endpoint{
+				tokens3.Endpoint{
+					ID:        "5",
+					Region:    "same",
+					Interface: "public",
+					URL:       "https://badname.com/",
+				},
+				tokens3.Endpoint{
+					ID:        "6",
+					Region:    "different",
+					Interface: "public",
+					URL:       "https://badname.com/+badregion",
+				},
+			},
+		},
+		tokens3.CatalogEntry{
+			Type: "different",
+			Name: "different",
+			Endpoints: []tokens3.Endpoint{
+				tokens3.Endpoint{
+					ID:        "7",
+					Region:    "same",
+					Interface: "public",
+					URL:       "https://badtype.com/+badname",
+				},
+				tokens3.Endpoint{
+					ID:        "8",
+					Region:    "different",
+					Interface: "public",
+					URL:       "https://badtype.com/+badregion+badname",
+				},
+			},
+		},
+	},
 }
 
 func TestV3EndpointExact(t *testing.T) {
-	th.SetupHTTP()
-	defer th.TeardownHTTP()
-	setupV3Responses(t)
+	expectedURLs := map[gophercloud.Availability]string{
+		gophercloud.AvailabilityPublic:   "https://public.correct.com/",
+		gophercloud.AvailabilityAdmin:    "https://admin.correct.com/",
+		gophercloud.AvailabilityInternal: "https://internal.correct.com/",
+	}
 
-	actual, err := V3EndpointURL(fake.ServiceClient(), gophercloud.EndpointOpts{
+	for availability, expected := range expectedURLs {
+		actual, err := V3EndpointURL(&catalog3, gophercloud.EndpointOpts{
+			Type:         "same",
+			Name:         "same",
+			Region:       "same",
+			Availability: availability,
+		})
+		th.AssertNoErr(t, err)
+		th.CheckEquals(t, expected, actual)
+	}
+}
+
+func TestV3EndpointNone(t *testing.T) {
+	_, err := V3EndpointURL(&catalog3, gophercloud.EndpointOpts{
+		Type:         "nope",
+		Availability: gophercloud.AvailabilityPublic,
+	})
+	th.CheckEquals(t, gophercloud.ErrEndpointNotFound, err)
+}
+
+func TestV3EndpointMultiple(t *testing.T) {
+	_, err := V3EndpointURL(&catalog3, gophercloud.EndpointOpts{
+		Type:         "same",
+		Region:       "same",
+		Availability: gophercloud.AvailabilityPublic,
+	})
+	if !strings.HasPrefix(err.Error(), "Discovered 2 matching endpoints:") {
+		t.Errorf("Received unexpected error: %v", err)
+	}
+}
+
+func TestV3EndpointBadAvailability(t *testing.T) {
+	_, err := V3EndpointURL(&catalog3, gophercloud.EndpointOpts{
 		Type:         "same",
 		Name:         "same",
 		Region:       "same",
-		Availability: gophercloud.AvailabilityPublic,
+		Availability: "wat",
 	})
-	th.AssertNoErr(t, err)
-	th.CheckEquals(t, actual, "https://correct:9000/")
-}
-
-func TestV3EndpointNoService(t *testing.T) {
-	th.SetupHTTP()
-	defer th.TeardownHTTP()
-
-	th.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "GET")
-		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-
-		w.Header().Add("Content-Type", "application/json")
-		fmt.Fprintf(w, `
-      {
-        "links": {
-          "next": null,
-          "previous": null
-        },
-        "services": []
-      }
-    `)
-	})
-
-	_, err := V3EndpointURL(fake.ServiceClient(), gophercloud.EndpointOpts{
-		Type:         "nope",
-		Name:         "same",
-		Region:       "same",
-		Availability: gophercloud.AvailabilityPublic,
-	})
-	th.CheckEquals(t, gophercloud.ErrServiceNotFound, err)
+	th.CheckEquals(t, "Unexpected availability in endpoint query: wat", err.Error())
 }