Identity Acceptance Test Cleanup (#49)

diff --git a/acceptance/openstack/common.go b/acceptance/openstack/common.go
new file mode 100644
index 0000000..ba78cb6
--- /dev/null
+++ b/acceptance/openstack/common.go
@@ -0,0 +1,19 @@
+// Package openstack contains common functions that can be used
+// across all OpenStack components for acceptance testing.
+package openstack
+
+import (
+	"testing"
+
+	"github.com/gophercloud/gophercloud/openstack/common/extensions"
+)
+
+// PrintExtension prints an extension and all of its attributes.
+func PrintExtension(t *testing.T, extension *extensions.Extension) {
+	t.Logf("Name: %s", extension.Name)
+	t.Logf("Namespace: %s", extension.Namespace)
+	t.Logf("Alias: %s", extension.Alias)
+	t.Logf("Description: %s", extension.Description)
+	t.Logf("Updated: %s", extension.Updated)
+	t.Logf("Links: %v", extension.Links)
+}
diff --git a/acceptance/openstack/compute/v2/extension_test.go b/acceptance/openstack/compute/v2/extension_test.go
index 5905cfc..5de715d 100644
--- a/acceptance/openstack/compute/v2/extension_test.go
+++ b/acceptance/openstack/compute/v2/extension_test.go
@@ -6,6 +6,7 @@
 	"testing"
 
 	"github.com/gophercloud/gophercloud/acceptance/clients"
+	"github.com/gophercloud/gophercloud/acceptance/openstack"
 	"github.com/gophercloud/gophercloud/openstack/common/extensions"
 )
 
@@ -26,7 +27,7 @@
 	}
 
 	for _, extension := range allExtensions {
-		printExtension(t, &extension)
+		openstack.PrintExtension(t, &extension)
 	}
 }
 
@@ -41,14 +42,5 @@
 		t.Fatalf("Unable to get extension os-admin-actions: %v", err)
 	}
 
-	printExtension(t, extension)
-}
-
-func printExtension(t *testing.T, extension *extensions.Extension) {
-	t.Logf("Name: %s", extension.Name)
-	t.Logf("Namespace: %s", extension.Namespace)
-	t.Logf("Alias: %s", extension.Alias)
-	t.Logf("Description: %s", extension.Description)
-	t.Logf("Updated: %s", extension.Updated)
-	t.Logf("Links: %v", extension.Links)
+	openstack.PrintExtension(t, extension)
 }
diff --git a/acceptance/openstack/identity/v2/extension_test.go b/acceptance/openstack/identity/v2/extension_test.go
index 23627dc..3e526f1 100644
--- a/acceptance/openstack/identity/v2/extension_test.go
+++ b/acceptance/openstack/identity/v2/extension_test.go
@@ -5,42 +5,42 @@
 import (
 	"testing"
 
-	extensions2 "github.com/gophercloud/gophercloud/openstack/identity/v2/extensions"
-	"github.com/gophercloud/gophercloud/pagination"
-	th "github.com/gophercloud/gophercloud/testhelper"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
+	"github.com/gophercloud/gophercloud/acceptance/openstack"
+	"github.com/gophercloud/gophercloud/openstack/identity/v2/extensions"
 )
 
-func TestEnumerateExtensions(t *testing.T) {
-	service := authenticatedClient(t)
+func TestExtensionsList(t *testing.T) {
+	client, err := clients.NewIdentityV2Client()
+	if err != nil {
+		t.Fatalf("Unable to create an identity client: %v", err)
+	}
 
-	t.Logf("Extensions available on this identity endpoint:")
-	count := 0
-	err := extensions2.List(service).EachPage(func(page pagination.Page) (bool, error) {
-		t.Logf("--- Page %02d ---", count)
+	allPages, err := extensions.List(client).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to list extensions: %v", err)
+	}
 
-		extensions, err := extensions2.ExtractExtensions(page)
-		th.AssertNoErr(t, err)
+	allExtensions, err := extensions.ExtractExtensions(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract extensions: %v", err)
+	}
 
-		for i, ext := range extensions {
-			t.Logf("[%02d] name=[%s] namespace=[%s]", i, ext.Name, ext.Namespace)
-			t.Logf("     alias=[%s] updated=[%s]", ext.Alias, ext.Updated)
-			t.Logf("     description=[%s]", ext.Description)
-		}
-
-		count++
-		return true, nil
-	})
-	th.AssertNoErr(t, err)
+	for _, extension := range allExtensions {
+		openstack.PrintExtension(t, &extension)
+	}
 }
 
-func TestGetExtension(t *testing.T) {
-	service := authenticatedClient(t)
+func TestExtensionsGet(t *testing.T) {
+	client, err := clients.NewIdentityV2Client()
+	if err != nil {
+		t.Fatalf("Unable to create an identity client: %v", err)
+	}
 
-	ext, err := extensions2.Get(service, "OS-KSCRUD").Extract()
-	th.AssertNoErr(t, err)
+	extension, err := extensions.Get(client, "OS-KSCRUD").Extract()
+	if err != nil {
+		t.Fatalf("Unable to get extension OS-KSCRUD: %v", err)
+	}
 
-	th.CheckEquals(t, "OpenStack Keystone User CRUD", ext.Name)
-	th.CheckEquals(t, "http://docs.openstack.org/identity/api/ext/OS-KSCRUD/v1.0", ext.Namespace)
-	th.CheckEquals(t, "OS-KSCRUD", ext.Alias)
-	th.CheckEquals(t, "OpenStack extensions to Keystone v2.0 API enabling User Operations.", ext.Description)
+	openstack.PrintExtension(t, extension)
 }
diff --git a/acceptance/openstack/identity/v2/identity.go b/acceptance/openstack/identity/v2/identity.go
new file mode 100644
index 0000000..f1c6624
--- /dev/null
+++ b/acceptance/openstack/identity/v2/identity.go
@@ -0,0 +1,216 @@
+// Package v2 contains common functions for creating identity-based resources
+// for use in acceptance tests. See the `*_test.go` files for example usages.
+package v2
+
+import (
+	"testing"
+
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/acceptance/tools"
+	"github.com/gophercloud/gophercloud/openstack/identity/v2/extensions/admin/roles"
+	"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
+	"github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
+	"github.com/gophercloud/gophercloud/openstack/identity/v2/users"
+)
+
+// AddUserRole will grant a role to a user in a tenant. An error will be
+// returned if the grant was unsuccessful.
+func AddUserRole(t *testing.T, client *gophercloud.ServiceClient, tenant *tenants.Tenant, user *users.User, role *roles.Role) error {
+	t.Logf("Attempting to grant user %s role %s in tenant %s", user.ID, role.ID, tenant.ID)
+
+	err := roles.AddUser(client, tenant.ID, user.ID, role.ID).ExtractErr()
+	if err != nil {
+		return err
+	}
+
+	t.Logf("Granted user %s role %s in tenant %s", user.ID, role.ID, tenant.ID)
+
+	return nil
+}
+
+// CreateUser will create a user with a random name and adds them to the given
+// tenant. An error will be returned if the user was unable to be created.
+func CreateUser(t *testing.T, client *gophercloud.ServiceClient, tenant *tenants.Tenant) (*users.User, error) {
+	userName := tools.RandomString("user_", 5)
+	userEmail := userName + "@foo.com"
+	t.Logf("Creating user: %s", userName)
+
+	createOpts := users.CreateOpts{
+		Name:     userName,
+		Enabled:  gophercloud.Disabled,
+		TenantID: tenant.ID,
+		Email:    userEmail,
+	}
+
+	user, err := users.Create(client, createOpts).Extract()
+	if err != nil {
+		return user, err
+	}
+
+	return user, nil
+}
+
+// DeleteUser will delete a user. A fatal error will occur if the delete was
+// unsuccessful. This works best when used as a deferred function.
+func DeleteUser(t *testing.T, client *gophercloud.ServiceClient, user *users.User) {
+	t.Logf("Attempting to delete user: %s", user.Name)
+
+	result := users.Delete(client, user.ID)
+	if result.Err != nil {
+		t.Fatalf("Unable to delete user")
+	}
+
+	t.Logf("Deleted user: %s", user.Name)
+}
+
+// DeleteUserRole will revoke a role of a user in a tenant. A fatal error will
+// occur if the revoke was unsuccessful. This works best when used as a
+// deferred function.
+func DeleteUserRole(t *testing.T, client *gophercloud.ServiceClient, tenant *tenants.Tenant, user *users.User, role *roles.Role) {
+	t.Logf("Attempting to remove role %s from user %s in tenant %s", role.ID, user.ID, tenant.ID)
+
+	err := roles.DeleteUser(client, tenant.ID, user.ID, role.ID).ExtractErr()
+	if err != nil {
+		t.Fatalf("Unable to remove role")
+	}
+
+	t.Logf("Removed role %s from user %s in tenant %s", role.ID, user.ID, tenant.ID)
+}
+
+// FindRole finds all roles that the current authenticated client has access
+// to and returns the first one found. An error will be returned if the lookup
+// was unsuccessful.
+func FindRole(t *testing.T, client *gophercloud.ServiceClient) (*roles.Role, error) {
+	var role *roles.Role
+
+	allPages, err := roles.List(client).AllPages()
+	if err != nil {
+		return role, err
+	}
+
+	allRoles, err := roles.ExtractRoles(allPages)
+	if err != nil {
+		return role, err
+	}
+
+	for _, r := range allRoles {
+		role = &r
+		break
+	}
+
+	return role, nil
+}
+
+// FindTenant finds all tenants that the current authenticated client has access
+// to and returns the first one found. An error will be returned if the lookup
+// was unsuccessful.
+func FindTenant(t *testing.T, client *gophercloud.ServiceClient) (*tenants.Tenant, error) {
+	var tenant *tenants.Tenant
+
+	allPages, err := tenants.List(client, nil).AllPages()
+	if err != nil {
+		return tenant, err
+	}
+
+	allTenants, err := tenants.ExtractTenants(allPages)
+	if err != nil {
+		return tenant, err
+	}
+
+	for _, t := range allTenants {
+		tenant = &t
+		break
+	}
+
+	return tenant, nil
+}
+
+// UpdateUser will update an existing user with a new randomly generated name.
+// An error will be returned if the update was unsuccessful.
+func UpdateUser(t *testing.T, client *gophercloud.ServiceClient, user *users.User) (*users.User, error) {
+	userName := tools.RandomString("user_", 5)
+	userEmail := userName + "@foo.com"
+
+	t.Logf("Attempting to update user name from %s to %s", user.Name, userName)
+
+	updateOpts := users.UpdateOpts{
+		Name:  userName,
+		Email: userEmail,
+	}
+
+	newUser, err := users.Update(client, user.ID, updateOpts).Extract()
+	if err != nil {
+		return newUser, err
+	}
+
+	return newUser, nil
+}
+
+// PrintCatalogEntry will print a catalog entry and all of its attributes.
+func PrintCatalogEntry(t *testing.T, catalogEntry *tokens.CatalogEntry) {
+	t.Logf("Name: %s", catalogEntry.Name)
+	t.Logf("Type: %s", catalogEntry.Type)
+
+	t.Log("Endpoints:")
+	for _, endpoint := range catalogEntry.Endpoints {
+		t.Logf("\tTenantID: %s", endpoint.TenantID)
+		t.Logf("\tPublicURL: %s", endpoint.PublicURL)
+		t.Logf("\tInternalURL: %s", endpoint.InternalURL)
+		t.Logf("\tAdminURL: %s", endpoint.AdminURL)
+		t.Logf("\tRegion: %s", endpoint.Region)
+		t.Logf("\tVersionID: %s", endpoint.VersionID)
+		t.Logf("\tVersionInfo: %s", endpoint.VersionInfo)
+		t.Logf("\tVersionList: %s", endpoint.VersionList)
+	}
+}
+
+// PrintRole will print a role and all of its attributes.
+func PrintRole(t *testing.T, role *roles.Role) {
+	t.Logf("ID: %s", role.ID)
+	t.Logf("Name: %v", role.Name)
+	t.Logf("Description: %s", role.Description)
+	t.Logf("ServiceID: %s", role.ServiceID)
+}
+
+// PrintTenant will print a tenant and all of its attributes.
+func PrintTenant(t *testing.T, tenant *tenants.Tenant) {
+	t.Logf("ID: %s", tenant.ID)
+	t.Logf("Name: %s", tenant.Name)
+	t.Logf("Description: %s", tenant.Description)
+	t.Logf("Enabled: %t", tenant.Enabled)
+}
+
+// PrintToken will print a token and all of its attributes.
+func PrintToken(t *testing.T, token *tokens.Token) {
+	t.Logf("ID: %s", token.ID)
+	t.Logf("ExpiresAt: %v", token.ExpiresAt)
+	t.Logf("TenantID: %s", token.Tenant.ID)
+}
+
+// PrintTokenUser will print the user information of a token and all attributes.
+func PrintTokenUser(t *testing.T, user *tokens.User) {
+	t.Logf("ID: %s", user.ID)
+	t.Logf("Name: %s", user.Name)
+	t.Logf("Username: %s", user.UserName)
+
+	t.Log("Roles")
+	for _, role := range user.Roles {
+		t.Logf("\t%s", role)
+	}
+}
+
+// PrintUser will print a user and all of its attributes.
+func PrintUser(t *testing.T, user *users.User) {
+	t.Logf("ID: %s", user.ID)
+	t.Logf("Name: %s", user.Name)
+	t.Logf("Username: %s", user.Username)
+	t.Logf("Enabled: %t", user.Enabled)
+	t.Logf("Email: %s", user.Email)
+	t.Logf("TenantID: %s", user.TenantID)
+}
+
+// PrintUserRole will print the roles that a user has been granted.
+func PrintUserRole(t *testing.T, role *users.Role) {
+	t.Logf("ID: %s", role.ID)
+	t.Logf("Name: %s", role.Name)
+}
diff --git a/acceptance/openstack/identity/v2/identity_test.go b/acceptance/openstack/identity/v2/identity_test.go
deleted file mode 100644
index e0e2c0e..0000000
--- a/acceptance/openstack/identity/v2/identity_test.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// +build acceptance identity
-
-package v2
-
-import (
-	"testing"
-
-	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/openstack"
-	th "github.com/gophercloud/gophercloud/testhelper"
-)
-
-func v2AuthOptions(t *testing.T) gophercloud.AuthOptions {
-	// Obtain credentials from the environment.
-	ao, err := openstack.AuthOptionsFromEnv()
-	th.AssertNoErr(t, err)
-
-	// Trim out unused fields. Prefer authentication by API key to password.
-	ao.UserID, ao.DomainID, ao.DomainName = "", "", ""
-	if ao.APIKey != "" {
-		ao.Password = ""
-	}
-
-	return ao
-}
-
-func createClient(t *testing.T, auth bool) *gophercloud.ServiceClient {
-	ao := v2AuthOptions(t)
-
-	provider, err := openstack.NewClient(ao.IdentityEndpoint)
-	th.AssertNoErr(t, err)
-
-	if auth {
-		err = openstack.AuthenticateV2(provider, ao)
-		th.AssertNoErr(t, err)
-	}
-
-	return openstack.NewIdentityV2(provider)
-}
-
-func unauthenticatedClient(t *testing.T) *gophercloud.ServiceClient {
-	return createClient(t, false)
-}
-
-func authenticatedClient(t *testing.T) *gophercloud.ServiceClient {
-	return createClient(t, true)
-}
diff --git a/acceptance/openstack/identity/v2/role_test.go b/acceptance/openstack/identity/v2/role_test.go
index 3f47858..5589b1b 100644
--- a/acceptance/openstack/identity/v2/role_test.go
+++ b/acceptance/openstack/identity/v2/role_test.go
@@ -5,54 +5,72 @@
 import (
 	"testing"
 
-	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/openstack/identity/v2/extensions/admin/roles"
-	"github.com/gophercloud/gophercloud/pagination"
-	th "github.com/gophercloud/gophercloud/testhelper"
+	"github.com/gophercloud/gophercloud/openstack/identity/v2/users"
 )
 
-func TestRoles(t *testing.T) {
-	client := authenticatedClient(t)
+func TestRolesAddToUser(t *testing.T) {
+	client, err := clients.NewIdentityV2AdminClient()
+	if err != nil {
+		t.Fatalf("Unable to obtain an identity client: %v", err)
+	}
 
-	tenantID := findTenant(t, client)
-	userID := createUser(t, client, tenantID)
-	roleID := listRoles(t, client)
+	tenant, err := FindTenant(t, client)
+	if err != nil {
+		t.Fatalf("Unable to get a tenant: %v", err)
+	}
 
-	addUserRole(t, client, tenantID, userID, roleID)
+	role, err := FindRole(t, client)
+	if err != nil {
+		t.Fatalf("Unable to get a role: %v", err)
+	}
 
-	deleteUserRole(t, client, tenantID, userID, roleID)
+	user, err := CreateUser(t, client, tenant)
+	if err != nil {
+		t.Fatalf("Unable to create a user: %v", err)
+	}
+	defer DeleteUser(t, client, user)
 
-	deleteUser(t, client, userID)
+	err = AddUserRole(t, client, tenant, user, role)
+	if err != nil {
+		t.Fatalf("Unable to add role to user: %v", err)
+	}
+	defer DeleteUserRole(t, client, tenant, user, role)
+
+	allPages, err := users.ListRoles(client, tenant.ID, user.ID).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to obtain roles for user: %v", err)
+	}
+
+	allRoles, err := users.ExtractRoles(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract roles: %v", err)
+	}
+
+	t.Logf("Roles of user %s:", user.Name)
+	for _, role := range allRoles {
+		PrintUserRole(t, &role)
+	}
 }
 
-func listRoles(t *testing.T, client *gophercloud.ServiceClient) string {
-	var roleID string
+func TestRolesList(t *testing.T) {
+	client, err := clients.NewIdentityV2AdminClient()
+	if err != nil {
+		t.Fatalf("Unable to create an identity client: %v", err)
+	}
 
-	err := roles.List(client).EachPage(func(page pagination.Page) (bool, error) {
-		roleList, err := roles.ExtractRoles(page)
-		th.AssertNoErr(t, err)
+	allPages, err := roles.List(client).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to list all roles: %v", err)
+	}
 
-		for _, role := range roleList {
-			t.Logf("Listing role: ID [%s] Name [%s]", role.ID, role.Name)
-			roleID = role.ID
-		}
+	allRoles, err := roles.ExtractRoles(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract roles: %v", err)
+	}
 
-		return true, nil
-	})
-
-	th.AssertNoErr(t, err)
-
-	return roleID
-}
-
-func addUserRole(t *testing.T, client *gophercloud.ServiceClient, tenantID, userID, roleID string) {
-	err := roles.AddUserRole(client, tenantID, userID, roleID).ExtractErr()
-	th.AssertNoErr(t, err)
-	t.Logf("Added role %s to user %s", roleID, userID)
-}
-
-func deleteUserRole(t *testing.T, client *gophercloud.ServiceClient, tenantID, userID, roleID string) {
-	err := roles.DeleteUserRole(client, tenantID, userID, roleID).ExtractErr()
-	th.AssertNoErr(t, err)
-	t.Logf("Removed role %s from user %s", roleID, userID)
+	for _, r := range allRoles {
+		PrintRole(t, &r)
+	}
 }
diff --git a/acceptance/openstack/identity/v2/tenant_test.go b/acceptance/openstack/identity/v2/tenant_test.go
index 5f7440d..ed457d4 100644
--- a/acceptance/openstack/identity/v2/tenant_test.go
+++ b/acceptance/openstack/identity/v2/tenant_test.go
@@ -5,28 +5,27 @@
 import (
 	"testing"
 
-	tenants2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
-	"github.com/gophercloud/gophercloud/pagination"
-	th "github.com/gophercloud/gophercloud/testhelper"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
+	"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
 )
 
-func TestEnumerateTenants(t *testing.T) {
-	service := authenticatedClient(t)
+func TestTenantsList(t *testing.T) {
+	client, err := clients.NewIdentityV2Client()
+	if err != nil {
+		t.Fatalf("Unable to obtain an identity client: %v")
+	}
 
-	t.Logf("Tenants to which your current token grants access:")
-	count := 0
-	err := tenants2.List(service, nil).EachPage(func(page pagination.Page) (bool, error) {
-		t.Logf("--- Page %02d ---", count)
+	allPages, err := tenants.List(client, nil).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to list tenants: %v", err)
+	}
 
-		tenants, err := tenants2.ExtractTenants(page)
-		th.AssertNoErr(t, err)
-		for i, tenant := range tenants {
-			t.Logf("[%02d] name=[%s] id=[%s] description=[%s] enabled=[%v]",
-				i, tenant.Name, tenant.ID, tenant.Description, tenant.Enabled)
-		}
+	allTenants, err := tenants.ExtractTenants(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract tenants: %v", err)
+	}
 
-		count++
-		return true, nil
-	})
-	th.AssertNoErr(t, err)
+	for _, tenant := range allTenants {
+		PrintTenant(t, &tenant)
+	}
 }
diff --git a/acceptance/openstack/identity/v2/token_test.go b/acceptance/openstack/identity/v2/token_test.go
index c2f7e51..4cd8301 100644
--- a/acceptance/openstack/identity/v2/token_test.go
+++ b/acceptance/openstack/identity/v2/token_test.go
@@ -5,50 +5,64 @@
 import (
 	"testing"
 
-	tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
-	th "github.com/gophercloud/gophercloud/testhelper"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
+	"github.com/gophercloud/gophercloud/openstack"
+	"github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
 )
 
-func TestAuthenticateAndValidate(t *testing.T) {
-	// 1. TestAuthenticate
-	ao := v2AuthOptions(t)
-	service := unauthenticatedClient(t)
-
-	// Authenticated!
-	result := tokens2.Create(service, tokens2.WrapOptions(ao))
-
-	// Extract and print the token.
-	token, err := result.ExtractToken()
-	th.AssertNoErr(t, err)
-
-	t.Logf("Acquired token: [%s]", token.ID)
-	t.Logf("The token will expire at: [%s]", token.ExpiresAt.String())
-	t.Logf("The token is valid for tenant: [%#v]", token.Tenant)
-
-	// Extract and print the service catalog.
-	catalog, err := result.ExtractServiceCatalog()
-	th.AssertNoErr(t, err)
-
-	t.Logf("Acquired service catalog listing [%d] services", len(catalog.Entries))
-	for i, entry := range catalog.Entries {
-		t.Logf("[%02d]: name=[%s], type=[%s]", i, entry.Name, entry.Type)
-		for _, endpoint := range entry.Endpoints {
-			t.Logf("      - region=[%s] publicURL=[%s]", endpoint.Region, endpoint.PublicURL)
-		}
+func TestTokenAuthenticate(t *testing.T) {
+	client, err := clients.NewIdentityV2UnauthenticatedClient()
+	if err != nil {
+		t.Fatalf("Unable to obtain an identity client: %v", err)
 	}
 
-	// 2. TestValidate
-	client := authenticatedClient(t)
+	authOptions, err := openstack.AuthOptionsFromEnv()
+	if err != nil {
+		t.Fatalf("Unable to obtain authentication options: %v", err)
+	}
 
-	// Validate Token!
-	getResult := tokens2.Get(client, token.ID)
+	result := tokens.Create(client, authOptions)
+	token, err := result.ExtractToken()
+	if err != nil {
+		t.Fatalf("Unable to extract token: %v", err)
+	}
 
-	// Extract and print the user.
+	PrintToken(t, token)
+
+	catalog, err := result.ExtractServiceCatalog()
+	if err != nil {
+		t.Fatalf("Unable to extract service catalog: %v", err)
+	}
+
+	for _, entry := range catalog.Entries {
+		PrintCatalogEntry(t, &entry)
+	}
+}
+
+func TestTokenValidate(t *testing.T) {
+	client, err := clients.NewIdentityV2Client()
+	if err != nil {
+		t.Fatalf("Unable to obtain an identity client: %v", err)
+	}
+
+	authOptions, err := openstack.AuthOptionsFromEnv()
+	if err != nil {
+		t.Fatalf("Unable to obtain authentication options: %v", err)
+	}
+
+	result := tokens.Create(client, authOptions)
+	token, err := result.ExtractToken()
+	if err != nil {
+		t.Fatalf("Unable to extract token: %v", err)
+	}
+
+	PrintToken(t, token)
+
+	getResult := tokens.Get(client, token.ID)
 	user, err := getResult.ExtractUser()
-	th.AssertNoErr(t, err)
+	if err != nil {
+		t.Fatalf("Unable to extract user: %v", err)
+	}
 
-	t.Logf("Acquired User: [%s]", user.Name)
-	t.Logf("The User id: [%s]", user.ID)
-	t.Logf("The User username: [%s]", user.UserName)
-	t.Logf("The User roles: [%#v]", user.Roles)
+	PrintTokenUser(t, user)
 }
diff --git a/acceptance/openstack/identity/v2/user_test.go b/acceptance/openstack/identity/v2/user_test.go
index 7938b37..5c6bfd9 100644
--- a/acceptance/openstack/identity/v2/user_test.go
+++ b/acceptance/openstack/identity/v2/user_test.go
@@ -3,125 +3,56 @@
 package v2
 
 import (
-	"strconv"
 	"testing"
 
-	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/acceptance/tools"
-	"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/openstack/identity/v2/users"
-	"github.com/gophercloud/gophercloud/pagination"
-	th "github.com/gophercloud/gophercloud/testhelper"
 )
 
-func TestUsers(t *testing.T) {
-	client := authenticatedClient(t)
-
-	tenantID := findTenant(t, client)
-
-	userID := createUser(t, client, tenantID)
-
-	listUsers(t, client)
-
-	getUser(t, client, userID)
-
-	updateUser(t, client, userID)
-
-	listUserRoles(t, client, tenantID, userID)
-
-	deleteUser(t, client, userID)
-}
-
-func findTenant(t *testing.T, client *gophercloud.ServiceClient) string {
-	var tenantID string
-	err := tenants.List(client, nil).EachPage(func(page pagination.Page) (bool, error) {
-		tenantList, err := tenants.ExtractTenants(page)
-		th.AssertNoErr(t, err)
-
-		for _, t := range tenantList {
-			tenantID = t.ID
-			break
-		}
-
-		return true, nil
-	})
-	th.AssertNoErr(t, err)
-
-	return tenantID
-}
-
-func createUser(t *testing.T, client *gophercloud.ServiceClient, tenantID string) string {
-	t.Log("Creating user")
-
-	opts := users.CreateOpts{
-		Name:     tools.RandomString("user_", 5),
-		Enabled:  users.Disabled,
-		TenantID: tenantID,
-		Email:    "new_user@foo.com",
+func TestUsersList(t *testing.T) {
+	client, err := clients.NewIdentityV2AdminClient()
+	if err != nil {
+		t.Fatalf("Unable to obtain an identity client: %v", err)
 	}
 
-	user, err := users.Create(client, opts).Extract()
-	th.AssertNoErr(t, err)
-	t.Logf("Created user %s on tenant %s", user.ID, tenantID)
-
-	return user.ID
-}
-
-func listUsers(t *testing.T, client *gophercloud.ServiceClient) {
-	err := users.List(client).EachPage(func(page pagination.Page) (bool, error) {
-		userList, err := users.ExtractUsers(page)
-		th.AssertNoErr(t, err)
-
-		for _, user := range userList {
-			t.Logf("Listing user: ID [%s] Name [%s] Email [%s] Enabled? [%s]",
-				user.ID, user.Name, user.Email, strconv.FormatBool(user.Enabled))
-		}
-
-		return true, nil
-	})
-
-	th.AssertNoErr(t, err)
-}
-
-func getUser(t *testing.T, client *gophercloud.ServiceClient, userID string) {
-	_, err := users.Get(client, userID).Extract()
-	th.AssertNoErr(t, err)
-	t.Logf("Getting user %s", userID)
-}
-
-func updateUser(t *testing.T, client *gophercloud.ServiceClient, userID string) {
-	opts := users.UpdateOpts{Name: tools.RandomString("new_name", 5), Email: "new@foo.com"}
-	user, err := users.Update(client, userID, opts).Extract()
-	th.AssertNoErr(t, err)
-	t.Logf("Updated user %s: Name [%s] Email [%s]", userID, user.Name, user.Email)
-}
-
-func listUserRoles(t *testing.T, client *gophercloud.ServiceClient, tenantID, userID string) {
-	count := 0
-	err := users.ListRoles(client, tenantID, userID).EachPage(func(page pagination.Page) (bool, error) {
-		count++
-
-		roleList, err := users.ExtractRoles(page)
-		th.AssertNoErr(t, err)
-
-		t.Logf("Listing roles for user %s", userID)
-
-		for _, r := range roleList {
-			t.Logf("- %s (%s)", r.Name, r.ID)
-		}
-
-		return true, nil
-	})
-
-	if count == 0 {
-		t.Logf("No roles for user %s", userID)
+	allPages, err := users.List(client).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to list users: %v", err)
 	}
 
-	th.AssertNoErr(t, err)
+	allUsers, err := users.ExtractUsers(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract users: %v", err)
+	}
+
+	for _, user := range allUsers {
+		PrintUser(t, &user)
+	}
 }
 
-func deleteUser(t *testing.T, client *gophercloud.ServiceClient, userID string) {
-	res := users.Delete(client, userID)
-	th.AssertNoErr(t, res.Err)
-	t.Logf("Deleted user %s", userID)
+func TestUsersCreateUpdateDelete(t *testing.T) {
+	client, err := clients.NewIdentityV2AdminClient()
+	if err != nil {
+		t.Fatalf("Unable to obtain an identity client: %v", err)
+	}
+
+	tenant, err := FindTenant(t, client)
+	if err != nil {
+		t.Fatalf("Unable to get a tenant: %v", err)
+	}
+
+	user, err := CreateUser(t, client, tenant)
+	if err != nil {
+		t.Fatalf("Unable to create a user: %v", err)
+	}
+	defer DeleteUser(t, client, user)
+
+	PrintUser(t, user)
+
+	newUser, err := UpdateUser(t, client, user)
+	if err != nil {
+		t.Fatalf("Unable to update user: %v", err)
+	}
+
+	PrintUser(t, newUser)
 }
diff --git a/acceptance/openstack/identity/v3/endpoint_test.go b/acceptance/openstack/identity/v3/endpoint_test.go
index f575649..6a52f26 100644
--- a/acceptance/openstack/identity/v3/endpoint_test.go
+++ b/acceptance/openstack/identity/v3/endpoint_test.go
@@ -6,106 +6,80 @@
 	"testing"
 
 	"github.com/gophercloud/gophercloud"
-	endpoints3 "github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints"
-	services3 "github.com/gophercloud/gophercloud/openstack/identity/v3/services"
-	"github.com/gophercloud/gophercloud/pagination"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
+	"github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints"
+	"github.com/gophercloud/gophercloud/openstack/identity/v3/services"
 )
 
-func TestListEndpoints(t *testing.T) {
-	// Create a service client.
-	serviceClient := createAuthenticatedClient(t)
-	if serviceClient == nil {
-		return
+func TestEndpointsList(t *testing.T) {
+	client, err := clients.NewIdentityV3Client()
+	if err != nil {
+		t.Fatalf("Unable to obtain an identity client: %v")
 	}
 
-	// Use the service to list all available endpoints.
-	pager := endpoints3.List(serviceClient, endpoints3.ListOpts{})
-	err := pager.EachPage(func(page pagination.Page) (bool, error) {
-		t.Logf("--- Page ---")
-
-		endpoints, err := endpoints3.ExtractEndpoints(page)
-		if err != nil {
-			t.Fatalf("Error extracting endpoings: %v", err)
-		}
-
-		for _, endpoint := range endpoints {
-			t.Logf("Endpoint: %8s %10s %9s %s",
-				endpoint.ID,
-				endpoint.Availability,
-				endpoint.Name,
-				endpoint.URL)
-		}
-
-		return true, nil
-	})
+	allPages, err := endpoints.List(client, nil).AllPages()
 	if err != nil {
-		t.Errorf("Unexpected error while iterating endpoint pages: %v", err)
+		t.Fatalf("Unable to list endpoints: %v", err)
+	}
+
+	allEndpoints, err := endpoints.ExtractEndpoints(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract endpoints: %v", err)
+	}
+
+	for _, endpoint := range allEndpoints {
+		PrintEndpoint(t, &endpoint)
 	}
 }
 
-func TestNavigateCatalog(t *testing.T) {
-	// Create a service client.
-	client := createAuthenticatedClient(t)
-	if client == nil {
-		return
+func TestEndpointsNavigateCatalog(t *testing.T) {
+	client, err := clients.NewIdentityV3Client()
+	if err != nil {
+		t.Fatalf("Unable to obtain an identity client: %v")
 	}
 
-	var compute *services3.Service
-	var endpoint *endpoints3.Endpoint
-
 	// Discover the service we're interested in.
-	servicePager := services3.List(client, services3.ListOpts{ServiceType: "compute"})
-	err := servicePager.EachPage(func(page pagination.Page) (bool, error) {
-		part, err := services3.ExtractServices(page)
-		if err != nil {
-			return false, err
-		}
-		if compute != nil {
-			t.Fatalf("Expected one service, got more than one page")
-			return false, nil
-		}
-		if len(part) != 1 {
-			t.Fatalf("Expected one service, got %d", len(part))
-			return false, nil
-		}
+	serviceListOpts := services.ListOpts{
+		ServiceType: "compute",
+	}
 
-		compute = &part[0]
-		return true, nil
-	})
+	allPages, err := services.List(client, serviceListOpts).AllPages()
 	if err != nil {
-		t.Fatalf("Unexpected error iterating pages: %v", err)
+		t.Fatalf("Unable to lookup compute service: %v", err)
 	}
 
-	if compute == nil {
-		t.Fatalf("No compute service found.")
+	allServices, err := services.ExtractServices(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract service: %v")
 	}
 
+	if len(allServices) != 1 {
+		t.Fatalf("Expected one service, got %d", len(allServices))
+	}
+
+	computeService := allServices[0]
+	PrintService(t, &computeService)
+
 	// Enumerate the endpoints available for this service.
-	computePager := endpoints3.List(client, endpoints3.ListOpts{
+	endpointListOpts := endpoints.ListOpts{
 		Availability: gophercloud.AvailabilityPublic,
-		ServiceID:    compute.ID,
-	})
-	err = computePager.EachPage(func(page pagination.Page) (bool, error) {
-		part, err := endpoints3.ExtractEndpoints(page)
-		if err != nil {
-			return false, err
-		}
-		if endpoint != nil {
-			t.Fatalf("Expected one endpoint, got more than one page")
-			return false, nil
-		}
-		if len(part) != 1 {
-			t.Fatalf("Expected one endpoint, got %d", len(part))
-			return false, nil
-		}
-
-		endpoint = &part[0]
-		return true, nil
-	})
-
-	if endpoint == nil {
-		t.Fatalf("No endpoint found.")
+		ServiceID:    computeService.ID,
 	}
 
-	t.Logf("Success. The compute endpoint is at %s.", endpoint.URL)
+	allPages, err = endpoints.List(client, endpointListOpts).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to lookup compute endpoint: %v", err)
+	}
+
+	allEndpoints, err := endpoints.ExtractEndpoints(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract endpoint: %v")
+	}
+
+	if len(allEndpoints) != 1 {
+		t.Fatalf("Expected one endpoint, got %d", len(allEndpoints))
+	}
+
+	PrintEndpoint(t, &allEndpoints[0])
+
 }
diff --git a/acceptance/openstack/identity/v3/identity.go b/acceptance/openstack/identity/v3/identity.go
new file mode 100644
index 0000000..268fd79
--- /dev/null
+++ b/acceptance/openstack/identity/v3/identity.go
@@ -0,0 +1,33 @@
+package v3
+
+import (
+	"testing"
+
+	"github.com/gophercloud/gophercloud/openstack/identity/v3/endpoints"
+	"github.com/gophercloud/gophercloud/openstack/identity/v3/services"
+	"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
+)
+
+// PrintEndpoint will print an endpoint and all of its attributes.
+func PrintEndpoint(t *testing.T, endpoint *endpoints.Endpoint) {
+	t.Logf("ID: %s", endpoint.ID)
+	t.Logf("Availability: %s", endpoint.Availability)
+	t.Logf("Name: %s", endpoint.Name)
+	t.Logf("Region: %s", endpoint.Region)
+	t.Logf("ServiceID: %s", endpoint.ServiceID)
+	t.Logf("URL: %s", endpoint.URL)
+}
+
+// PrintService will print a service and all of its attributes.
+func PrintService(t *testing.T, service *services.Service) {
+	t.Logf("ID: %s", service.ID)
+	t.Logf("Description: %s", service.Description)
+	t.Logf("Name: %s", service.Name)
+	t.Logf("Type: %s", service.Type)
+}
+
+// PrintToken will print a token and all of its attributes.
+func PrintToken(t *testing.T, token *tokens.Token) {
+	t.Logf("ID: %s", token.ID)
+	t.Logf("ExpiresAt: %v", token.ExpiresAt)
+}
diff --git a/acceptance/openstack/identity/v3/identity_test.go b/acceptance/openstack/identity/v3/identity_test.go
deleted file mode 100644
index 6974ad0..0000000
--- a/acceptance/openstack/identity/v3/identity_test.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// +build acceptance
-
-package v3
-
-import (
-	"testing"
-
-	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/openstack"
-	th "github.com/gophercloud/gophercloud/testhelper"
-)
-
-func createAuthenticatedClient(t *testing.T) *gophercloud.ServiceClient {
-	// Obtain credentials from the environment.
-	ao, err := openstack.AuthOptionsFromEnv()
-	th.AssertNoErr(t, err)
-
-	// Trim out unused fields.
-	ao.Username, ao.TenantID, ao.TenantName = "", "", ""
-
-	if ao.UserID == "" {
-		t.Logf("Skipping identity v3 tests because no OS_USERID is present.")
-		return nil
-	}
-
-	// Create a client and manually authenticate against v3.
-	providerClient, err := openstack.NewClient(ao.IdentityEndpoint)
-	if err != nil {
-		t.Fatalf("Unable to instantiate client: %v", err)
-	}
-
-	err = openstack.AuthenticateV3(providerClient, ao)
-	if err != nil {
-		t.Fatalf("Unable to authenticate against identity v3: %v", err)
-	}
-
-	// Create a service client.
-	return openstack.NewIdentityV3(providerClient)
-}
diff --git a/acceptance/openstack/identity/v3/service_test.go b/acceptance/openstack/identity/v3/service_test.go
index b39ba7f..364f69b 100644
--- a/acceptance/openstack/identity/v3/service_test.go
+++ b/acceptance/openstack/identity/v3/service_test.go
@@ -5,32 +5,28 @@
 import (
 	"testing"
 
-	services3 "github.com/gophercloud/gophercloud/openstack/identity/v3/services"
-	"github.com/gophercloud/gophercloud/pagination"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
+	"github.com/gophercloud/gophercloud/openstack/identity/v3/services"
 )
 
-func TestListServices(t *testing.T) {
-	// Create a service client.
-	serviceClient := createAuthenticatedClient(t)
-	if serviceClient == nil {
-		return
-	}
-
-	// Use the client to list all available services.
-	pager := services3.List(serviceClient, services3.ListOpts{})
-	err := pager.EachPage(func(page pagination.Page) (bool, error) {
-		parts, err := services3.ExtractServices(page)
-		if err != nil {
-			return false, err
-		}
-
-		t.Logf("--- Page ---")
-		for _, service := range parts {
-			t.Logf("Service: %32s %15s %10s %s", service.ID, service.Type, service.Name, *service.Description)
-		}
-		return true, nil
-	})
+func TestServicesList(t *testing.T) {
+	client, err := clients.NewIdentityV3Client()
 	if err != nil {
-		t.Errorf("Unexpected error traversing pages: %v", err)
+		t.Fatalf("Unable to obtain an identity client: %v")
 	}
+
+	allPages, err := services.List(client, nil).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to list services: %v", err)
+	}
+
+	allServices, err := services.ExtractServices(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract services: %v", err)
+	}
+
+	for _, service := range allServices {
+		PrintService(t, &service)
+	}
+
 }
diff --git a/acceptance/openstack/identity/v3/token_test.go b/acceptance/openstack/identity/v3/token_test.go
index 5340ead..13b38f8 100644
--- a/acceptance/openstack/identity/v3/token_test.go
+++ b/acceptance/openstack/identity/v3/token_test.go
@@ -5,38 +5,32 @@
 import (
 	"testing"
 
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/openstack"
-	tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
+	"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
 )
 
 func TestGetToken(t *testing.T) {
-	// Obtain credentials from the environment.
+	client, err := clients.NewIdentityV3UnauthenticatedClient()
+	if err != nil {
+		t.Fatalf("Unable to obtain an identity client: %v")
+	}
+
 	ao, err := openstack.AuthOptionsFromEnv()
 	if err != nil {
-		t.Fatalf("Unable to acquire credentials: %v", err)
+		t.Fatalf("Unable to obtain environment auth options: %v", err)
 	}
 
-	// Trim out unused fields. Skip if we don't have a UserID.
-	ao.Username, ao.TenantID, ao.TenantName = "", "", ""
-	if ao.UserID == "" {
-		t.Logf("Skipping identity v3 tests because no OS_USERID is present.")
-		return
+	authOptions := tokens.AuthOptions{
+		Username:   ao.Username,
+		Password:   ao.Password,
+		DomainName: "default",
 	}
 
-	// Create an unauthenticated client.
-	provider, err := openstack.NewClient(ao.IdentityEndpoint)
-	if err != nil {
-		t.Fatalf("Unable to instantiate client: %v", err)
-	}
-
-	// Create a service client.
-	service := openstack.NewIdentityV3(provider)
-
-	// Use the service to create a token.
-	token, err := tokens3.Create(service, ao, nil).Extract()
+	token, err := tokens.Create(client, &authOptions).Extract()
 	if err != nil {
 		t.Fatalf("Unable to get token: %v", err)
 	}
 
-	t.Logf("Acquired token: %s", token.ID)
+	PrintToken(t, token)
 }