openstack errors
diff --git a/openstack/auth_env.go b/openstack/auth_env.go
index 41e41e4..2309783 100644
--- a/openstack/auth_env.go
+++ b/openstack/auth_env.go
@@ -1,7 +1,6 @@
 package openstack
 
 import (
-	"fmt"
 	"os"
 
 	"github.com/gophercloud/gophercloud"
@@ -9,14 +8,6 @@
 
 var nilOptions = gophercloud.AuthOptions{}
 
-// ErrNoAuthUrl, ErrNoUsername, and ErrNoPassword errors indicate of the required OS_AUTH_URL, OS_USERNAME, or OS_PASSWORD
-// environment variables, respectively, remain undefined.  See the AuthOptions() function for more details.
-var (
-	ErrNoAuthURL  = fmt.Errorf("Environment variable OS_AUTH_URL needs to be set.")
-	ErrNoUsername = fmt.Errorf("Environment variable OS_USERNAME needs to be set.")
-	ErrNoPassword = fmt.Errorf("Environment variable OS_PASSWORD needs to be set.")
-)
-
 // AuthOptionsFromEnv fills out an identity.AuthOptions structure with the settings found on the various OpenStack
 // OS_* environment variables.  The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
 // OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME.  Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must
@@ -32,15 +23,24 @@
 	domainName := os.Getenv("OS_DOMAIN_NAME")
 
 	if authURL == "" {
-		return nilOptions, ErrNoAuthURL
+		err := &ErrNoAuthURL{}
+		err.Function = "openstack.AuthOptionsFromEnv"
+		err.Argument = "authURL"
+		return nilOptions, err
 	}
 
 	if username == "" && userID == "" {
-		return nilOptions, ErrNoUsername
+		err := &ErrNoUsername{}
+		err.Function = "openstack.AuthOptionsFromEnv"
+		err.Argument = "username"
+		return nilOptions, err
 	}
 
 	if password == "" {
-		return nilOptions, ErrNoPassword
+		err := &ErrNoPassword{}
+		err.Function = "openstack.AuthOptionsFromEnv"
+		err.Argument = "password"
+		return nilOptions, err
 	}
 
 	ao := gophercloud.AuthOptions{
diff --git a/openstack/client.go b/openstack/client.go
index c40922a..be08be0 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -3,7 +3,6 @@
 import (
 	"fmt"
 	"net/url"
-	"strings"
 
 	"github.com/gophercloud/gophercloud"
 	tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
@@ -91,7 +90,11 @@
 }
 
 func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
-	v2Client := NewIdentityV2(client)
+	v2Client, err := NewIdentityV2(client, eo)
+	if err != nil {
+		return err
+	}
+
 	if endpoint != "" {
 		v2Client.Endpoint = endpoint
 	}
@@ -129,7 +132,11 @@
 
 func v3auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
 	// Override the generated service endpoint with the one returned by the version endpoint.
-	v3Client := NewIdentityV3(client)
+	v3Client, err := NewIdentityV3(client, eo)
+	if err != nil {
+		return err
+	}
+
 	if endpoint != "" {
 		v3Client.Endpoint = endpoint
 	}
@@ -180,57 +187,23 @@
 }
 
 // NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
-func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
+func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
 	v2Endpoint := client.IdentityBase + "v2.0/"
 
 	return &gophercloud.ServiceClient{
 		ProviderClient: client,
 		Endpoint:       v2Endpoint,
-	}
+	}, nil
 }
 
 // NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
-func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
+func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
 	v3Endpoint := client.IdentityBase + "v3/"
 
 	return &gophercloud.ServiceClient{
 		ProviderClient: client,
 		Endpoint:       v3Endpoint,
-	}
-}
-
-func NewIdentityAdminV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
-	eo.ApplyDefaults("identity")
-	eo.Availability = gophercloud.AvailabilityAdmin
-
-	url, err := client.EndpointLocator(eo)
-	if err != nil {
-		return nil, err
-	}
-
-	// Force using v2 API
-	if strings.Contains(url, "/v3") {
-		url = strings.Replace(url, "/v3", "/v2.0", -1)
-	}
-
-	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
-}
-
-func NewIdentityAdminV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
-	eo.ApplyDefaults("identity")
-	eo.Availability = gophercloud.AvailabilityAdmin
-
-	url, err := client.EndpointLocator(eo)
-	if err != nil {
-		return nil, err
-	}
-
-	// Force using v3 API
-	if strings.Contains(url, "/v2.0") {
-		url = strings.Replace(url, "/v2.0", "/v3", -1)
-	}
-
-	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
+	}, nil
 }
 
 // NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
diff --git a/openstack/endpoint_location.go b/openstack/endpoint_location.go
index 1184b34..70f6800 100644
--- a/openstack/endpoint_location.go
+++ b/openstack/endpoint_location.go
@@ -1,8 +1,6 @@
 package openstack
 
 import (
-	"fmt"
-
 	"github.com/gophercloud/gophercloud"
 	tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
 	tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
@@ -29,7 +27,10 @@
 
 	// Report an error if the options were ambiguous.
 	if len(endpoints) > 1 {
-		return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
+		err := &ErrMultipleMatchingEndpointsV2{}
+		err.Endpoints = endpoints
+		err.Function = "openstack.V2EndpointURL"
+		return "", err
 	}
 
 	// Extract the appropriate URL from the matching Endpoint.
@@ -42,12 +43,18 @@
 		case gophercloud.AvailabilityAdmin:
 			return gophercloud.NormalizeURL(endpoint.AdminURL), nil
 		default:
-			return "", fmt.Errorf("Unexpected availability in endpoint query: %s", opts.Availability)
+			err := &ErrInvalidAvailabilityProvided{}
+			err.Function = "openstack.V2EndpointURL"
+			err.Argument = "Availability"
+			err.Value = opts.Availability
+			return "", err
 		}
 	}
 
 	// Report an error if there were no matching endpoints.
-	return "", &gophercloud.ErrEndpointNotFound{}
+	err := &gophercloud.ErrEndpointNotFound{}
+	err.Function = "openstack.V2EndpointURL"
+	return "", err
 }
 
 // V3EndpointURL discovers the endpoint URL for a specific service from a Catalog acquired
@@ -66,7 +73,10 @@
 				if opts.Availability != gophercloud.AvailabilityAdmin &&
 					opts.Availability != gophercloud.AvailabilityPublic &&
 					opts.Availability != gophercloud.AvailabilityInternal {
-					return "", fmt.Errorf("Unexpected availability in endpoint query: %s", opts.Availability)
+					err := &ErrInvalidAvailabilityProvided{}
+					err.Function = "openstack.V3EndpointURL"
+					err.Argument = "Availability"
+					err.Value = opts.Availability
 				}
 				if (opts.Availability == gophercloud.Availability(endpoint.Interface)) &&
 					(opts.Region == "" || endpoint.Region == opts.Region) {
@@ -78,7 +88,10 @@
 
 	// Report an error if the options were ambiguous.
 	if len(endpoints) > 1 {
-		return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
+		err := &ErrMultipleMatchingEndpointsV3{}
+		err.Endpoints = endpoints
+		err.Function = "openstack.V3EndpointURL"
+		return "", err
 	}
 
 	// Extract the URL from the matching Endpoint.
@@ -87,5 +100,7 @@
 	}
 
 	// Report an error if there were no matching endpoints.
-	return "", &gophercloud.ErrEndpointNotFound{}
+	err := &gophercloud.ErrEndpointNotFound{}
+	err.Function = "openstack.V3EndpointURL"
+	return "", err
 }
diff --git a/openstack/errors.go b/openstack/errors.go
new file mode 100644
index 0000000..8467200
--- /dev/null
+++ b/openstack/errors.go
@@ -0,0 +1,71 @@
+package openstack
+
+import (
+	"fmt"
+
+	"github.com/gophercloud/gophercloud"
+	tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
+	tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
+)
+
+// ErrEndpointNotFound is the error when no suitable endpoint can be found
+// in the user's catalog
+type ErrEndpointNotFound struct{ *gophercloud.BaseError }
+
+func (e ErrEndpointNotFound) Error() string {
+	return "No suitable endpoint could be found in the service catalog."
+}
+
+// ErrInvalidAvailabilityProvided is the error when an invalid endpoint
+// availability is provided
+type ErrInvalidAvailabilityProvided struct{ *gophercloud.ErrInvalidInput }
+
+func (e ErrInvalidAvailabilityProvided) Error() string {
+	return "Unexpected availability in endpoint query"
+}
+
+// ErrMultipleMatchingEndpointsV2 is the error when more than one endpoint
+// for the given options is found in the v2 catalog
+type ErrMultipleMatchingEndpointsV2 struct {
+	*gophercloud.BaseError
+	Endpoints []tokens2.Endpoint
+}
+
+func (e *ErrMultipleMatchingEndpointsV2) Error() string {
+	return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints)
+}
+
+// ErrMultipleMatchingEndpointsV3 is the error when more than one endpoint
+// for the given options is found in the v3 catalog
+type ErrMultipleMatchingEndpointsV3 struct {
+	*gophercloud.BaseError
+	Endpoints []tokens3.Endpoint
+}
+
+func (e *ErrMultipleMatchingEndpointsV3) Error() string {
+	return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints)
+}
+
+// ErrNoAuthURL is the error when the OS_AUTH_URL environment variable is not
+// found
+type ErrNoAuthURL struct{ *gophercloud.ErrInvalidInput }
+
+func (e *ErrNoAuthURL) Error() string {
+	return "Environment variable OS_AUTH_URL needs to be set."
+}
+
+// ErrNoUsername is the error when the OS_USERNAME environment variable is not
+// found
+type ErrNoUsername struct{ *gophercloud.ErrInvalidInput }
+
+func (e *ErrNoUsername) Error() string {
+	return "Environment variable OS_USERNAME needs to be set."
+}
+
+// ErrNoPassword is the error when the OS_PASSWORD environment variable is not
+// found
+type ErrNoPassword struct{ *gophercloud.ErrInvalidInput }
+
+func (e *ErrNoPassword) Error() string {
+	return "Environment variable OS_PASSWORD needs to be set."
+}