Compute Acceptance Test Changes (#33)

This commit makes more changes to the compute acceptance tests:

* Makes all reusable functions exportable so other APIs can use them.
* Centralizes client initialization and environment variable checks.
diff --git a/acceptance/openstack/compute/v2/bootfromvolume_test.go b/acceptance/openstack/compute/v2/bootfromvolume_test.go
index 6b73347..05c0a99 100644
--- a/acceptance/openstack/compute/v2/bootfromvolume_test.go
+++ b/acceptance/openstack/compute/v2/bootfromvolume_test.go
@@ -5,10 +5,8 @@
 import (
 	"testing"
 
-	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/acceptance/tools"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
-	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
 )
 
 func TestBootFromVolumeSingleVolume(t *testing.T) {
@@ -16,12 +14,12 @@
 		t.Skip("Skipping test that requires server creation in short mode.")
 	}
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -34,16 +32,13 @@
 		},
 	}
 
-	server, err := createBootableVolumeServer(t, client, blockDevices, choices)
+	server, err := CreateBootableVolumeServer(t, client, blockDevices, choices)
 	if err != nil {
 		t.Fatal("Unable to create server: %v", err)
 	}
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatal("Unable to wait for server: %v", err)
-	}
-	defer deleteServer(t, client, server)
+	defer DeleteServer(t, client, server)
 
-	printServer(t, server)
+	PrintServer(t, server)
 }
 
 func TestBootFromVolumeMultiEphemeral(t *testing.T) {
@@ -51,12 +46,12 @@
 		t.Skip("Skipping test that requires server creation in short mode.")
 	}
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -88,48 +83,11 @@
 		},
 	}
 
-	server, err := createBootableVolumeServer(t, client, blockDevices, choices)
+	server, err := CreateBootableVolumeServer(t, client, blockDevices, choices)
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
 	}
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatalf("Unable to wait for server: %v", err)
-	}
-	defer deleteServer(t, client, server)
+	defer DeleteServer(t, client, server)
 
-	printServer(t, server)
-}
-
-func createBootableVolumeServer(t *testing.T, client *gophercloud.ServiceClient, blockDevices []bootfromvolume.BlockDevice, choices *ComputeChoices) (*servers.Server, error) {
-	if testing.Short() {
-		t.Skip("Skipping test that requires server creation in short mode.")
-	}
-
-	networkID, err := getNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
-	if err != nil {
-		t.Fatalf("Failed to obtain network ID: %v", err)
-	}
-
-	name := tools.RandomString("ACPTTEST", 16)
-	t.Logf("Attempting to create bootable volume server: %s", name)
-
-	serverCreateOpts := servers.CreateOpts{
-		Name:      name,
-		FlavorRef: choices.FlavorID,
-		ImageRef:  choices.ImageID,
-		Networks: []servers.Network{
-			servers.Network{UUID: networkID},
-		},
-	}
-
-	server, err := bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{
-		serverCreateOpts,
-		blockDevices,
-	}).Extract()
-
-	if err != nil {
-		return server, err
-	}
-
-	return server, nil
+	PrintServer(t, server)
 }
diff --git a/acceptance/openstack/compute/v2/compute.go b/acceptance/openstack/compute/v2/compute.go
new file mode 100644
index 0000000..80bdf86
--- /dev/null
+++ b/acceptance/openstack/compute/v2/compute.go
@@ -0,0 +1,773 @@
+// Package v2 contains common functions for creating compute-based resources
+// for use in acceptance tests. See the `*_test.go` files for example usages.
+package v2
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"fmt"
+	"testing"
+
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
+	"github.com/gophercloud/gophercloud/acceptance/tools"
+	"github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
+	dsr "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/networks"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
+
+	"golang.org/x/crypto/ssh"
+)
+
+// AssociateFloatingIP will associate a floating IP with an instance. An error
+// will be returned if the floating IP was unable to be associated.
+func AssociateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server) error {
+	associateOpts := floatingips.AssociateOpts{
+		FloatingIP: floatingIP.IP,
+	}
+
+	t.Logf("Attempting to associate floating IP %s to instance %s", floatingIP.IP, server.ID)
+	err := floatingips.AssociateInstance(client, server.ID, associateOpts).ExtractErr()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// AssociateFloatingIPWithFixedIP will associate a floating IP with an
+// instance's specific fixed IP. An error will be returend if the floating IP
+// was unable to be associated.
+func AssociateFloatingIPWithFixedIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server, fixedIP string) error {
+	associateOpts := floatingips.AssociateOpts{
+		FloatingIP: floatingIP.IP,
+		FixedIP:    fixedIP,
+	}
+
+	t.Logf("Attempting to associate floating IP %s to fixed IP %s on instance %s", floatingIP.IP, fixedIP, server.ID)
+	err := floatingips.AssociateInstance(client, server.ID, associateOpts).ExtractErr()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// CreateBootableVolumeServer works like CreateServer but is configured with
+// one or more block devices defined by passing in []bootfromvolume.BlockDevice.
+// An error will be returned if a server was unable to be created.
+func CreateBootableVolumeServer(t *testing.T, client *gophercloud.ServiceClient, blockDevices []bootfromvolume.BlockDevice, choices *clients.AcceptanceTestChoices) (*servers.Server, error) {
+	if testing.Short() {
+		t.Skip("Skipping test that requires server creation in short mode.")
+	}
+
+	var server *servers.Server
+
+	networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
+	if err != nil {
+		return server, err
+	}
+
+	name := tools.RandomString("ACPTTEST", 16)
+	t.Logf("Attempting to create bootable volume server: %s", name)
+
+	serverCreateOpts := servers.CreateOpts{
+		Name:      name,
+		FlavorRef: choices.FlavorID,
+		ImageRef:  choices.ImageID,
+		Networks: []servers.Network{
+			servers.Network{UUID: networkID},
+		},
+	}
+
+	server, err = bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{
+		serverCreateOpts,
+		blockDevices,
+	}).Extract()
+
+	if err != nil {
+		return server, err
+	}
+
+	return server, nil
+}
+
+// CreateDefaultRule will create a default security group rule with a
+// random port range between 80 and 90. An error will be returned if
+// a default rule was unable to be created.
+func CreateDefaultRule(t *testing.T, client *gophercloud.ServiceClient) (dsr.DefaultRule, error) {
+	createOpts := dsr.CreateOpts{
+		FromPort:   tools.RandomInt(80, 89),
+		ToPort:     tools.RandomInt(90, 99),
+		IPProtocol: "TCP",
+		CIDR:       "0.0.0.0/0",
+	}
+
+	defaultRule, err := dsr.Create(client, createOpts).Extract()
+	if err != nil {
+		return *defaultRule, err
+	}
+
+	t.Logf("Created default rule: %s", defaultRule.ID)
+
+	return *defaultRule, nil
+}
+
+// CreateFloatingIP will allocate a floating IP.
+// An error will be returend if one was unable to be allocated.
+func CreateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, choices *clients.AcceptanceTestChoices) (*floatingips.FloatingIP, error) {
+	createOpts := floatingips.CreateOpts{
+		Pool: choices.FloatingIPPoolName,
+	}
+	floatingIP, err := floatingips.Create(client, createOpts).Extract()
+	if err != nil {
+		return floatingIP, err
+	}
+
+	t.Logf("Created floating IP: %s", floatingIP.ID)
+	return floatingIP, nil
+}
+
+func createKey() (string, error) {
+	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		return "", err
+	}
+
+	publicKey := privateKey.PublicKey
+	pub, err := ssh.NewPublicKey(&publicKey)
+	if err != nil {
+		return "", err
+	}
+
+	pubBytes := ssh.MarshalAuthorizedKey(pub)
+	pk := string(pubBytes)
+	return pk, nil
+}
+
+// CreateKeyPair will create a KeyPair with a random name. An error will occur
+// if the keypair failed to be created. An error will be returned if the
+// keypair was unable to be created.
+func CreateKeyPair(t *testing.T, client *gophercloud.ServiceClient) (*keypairs.KeyPair, error) {
+	keyPairName := tools.RandomString("keypair_", 5)
+
+	t.Logf("Attempting to create keypair: %s", keyPairName)
+	createOpts := keypairs.CreateOpts{
+		Name: keyPairName,
+	}
+	keyPair, err := keypairs.Create(client, createOpts).Extract()
+	if err != nil {
+		return keyPair, err
+	}
+
+	t.Logf("Created keypair: %s", keyPairName)
+	return keyPair, nil
+}
+
+// CreateSecurityGroup will create a security group with a random name.
+// An error will be returned if one was failed to be created.
+func CreateSecurityGroup(t *testing.T, client *gophercloud.ServiceClient) (secgroups.SecurityGroup, error) {
+	createOpts := secgroups.CreateOpts{
+		Name:        tools.RandomString("secgroup_", 5),
+		Description: "something",
+	}
+
+	securityGroup, err := secgroups.Create(client, createOpts).Extract()
+	if err != nil {
+		return *securityGroup, err
+	}
+
+	t.Logf("Created security group: %s", securityGroup.ID)
+	return *securityGroup, nil
+}
+
+// CreateSecurityGroupRule will create a security group rule with a random name
+// and a random TCP port range between port 80 and 99. An error will be
+// returned if the rule failed to be created.
+func CreateSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, securityGroupID string) (secgroups.Rule, error) {
+	createOpts := secgroups.CreateRuleOpts{
+		ParentGroupID: securityGroupID,
+		FromPort:      tools.RandomInt(80, 89),
+		ToPort:        tools.RandomInt(90, 99),
+		IPProtocol:    "TCP",
+		CIDR:          "0.0.0.0/0",
+	}
+
+	rule, err := secgroups.CreateRule(client, createOpts).Extract()
+	if err != nil {
+		return *rule, err
+	}
+
+	t.Logf("Created security group rule: %s", rule.ID)
+	return *rule, nil
+}
+
+// CreateServer creates a basic instance with a randomly generated name.
+// The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable.
+// The image will be the value of the OS_IMAGE_ID environment variable.
+// The instance will be launched on the network specified in OS_NETWORK_NAME.
+// An error will be returned if the instance was unable to be created.
+func CreateServer(t *testing.T, client *gophercloud.ServiceClient, choices *clients.AcceptanceTestChoices) (*servers.Server, error) {
+	if testing.Short() {
+		t.Skip("Skipping test that requires server creation in short mode.")
+	}
+
+	var server *servers.Server
+
+	networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
+	if err != nil {
+		return server, err
+	}
+
+	name := tools.RandomString("ACPTTEST", 16)
+	t.Logf("Attempting to create server: %s", name)
+
+	pwd := tools.MakeNewPassword("")
+
+	server, err = servers.Create(client, servers.CreateOpts{
+		Name:      name,
+		FlavorRef: choices.FlavorID,
+		ImageRef:  choices.ImageID,
+		AdminPass: pwd,
+		Networks: []servers.Network{
+			servers.Network{UUID: networkID},
+		},
+		Personality: servers.Personality{
+			&servers.File{
+				Path:     "/etc/test",
+				Contents: []byte("hello world"),
+			},
+		},
+	}).Extract()
+	if err != nil {
+		return server, err
+	}
+
+	if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
+		return server, err
+	}
+
+	return server, nil
+}
+
+// CreateServerGroup will create a server with a random name. An error will be
+// returned if the server group failed to be created.
+func CreateServerGroup(t *testing.T, client *gophercloud.ServiceClient, policy string) (*servergroups.ServerGroup, error) {
+	sg, err := servergroups.Create(client, &servergroups.CreateOpts{
+		Name:     "test",
+		Policies: []string{policy},
+	}).Extract()
+
+	if err != nil {
+		return sg, err
+	}
+
+	return sg, nil
+}
+
+// CreateServerInServerGroup works like CreateServer but places the instance in
+// a specified Server Group.
+func CreateServerInServerGroup(t *testing.T, client *gophercloud.ServiceClient, choices *clients.AcceptanceTestChoices, serverGroup *servergroups.ServerGroup) (*servers.Server, error) {
+	if testing.Short() {
+		t.Skip("Skipping test that requires server creation in short mode.")
+	}
+
+	var server *servers.Server
+
+	networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
+	if err != nil {
+		return server, err
+	}
+
+	name := tools.RandomString("ACPTTEST", 16)
+	t.Logf("Attempting to create server: %s", name)
+
+	pwd := tools.MakeNewPassword("")
+
+	serverCreateOpts := servers.CreateOpts{
+		Name:      name,
+		FlavorRef: choices.FlavorID,
+		ImageRef:  choices.ImageID,
+		AdminPass: pwd,
+		Networks: []servers.Network{
+			servers.Network{UUID: networkID},
+		},
+	}
+
+	schedulerHintsOpts := schedulerhints.CreateOptsExt{
+		serverCreateOpts,
+		schedulerhints.SchedulerHints{
+			Group: serverGroup.ID,
+		},
+	}
+	server, err = servers.Create(client, schedulerHintsOpts).Extract()
+	if err != nil {
+		return server, err
+	}
+
+	return server, nil
+}
+
+// CreateServerWithPublicKey works the same as CreateServer, but additionally
+// configures the server with a specified Key Pair name.
+func CreateServerWithPublicKey(t *testing.T, client *gophercloud.ServiceClient, choices *clients.AcceptanceTestChoices, keyPairName string) (*servers.Server, error) {
+	if testing.Short() {
+		t.Skip("Skipping test that requires server creation in short mode.")
+	}
+
+	var server *servers.Server
+
+	networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
+	if err != nil {
+		return server, err
+	}
+
+	name := tools.RandomString("ACPTTEST", 16)
+	t.Logf("Attempting to create server: %s", name)
+
+	serverCreateOpts := servers.CreateOpts{
+		Name:      name,
+		FlavorRef: choices.FlavorID,
+		ImageRef:  choices.ImageID,
+		Networks: []servers.Network{
+			servers.Network{UUID: networkID},
+		},
+	}
+
+	server, err = servers.Create(client, keypairs.CreateOptsExt{
+		serverCreateOpts,
+		keyPairName,
+	}).Extract()
+	if err != nil {
+		return server, err
+	}
+
+	if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
+		return server, err
+	}
+
+	return server, nil
+}
+
+// CreateVolumeAttachment will attach a volume to a servert. An error will be
+// returned if the volume failed to attach.
+func CreateVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, server *servers.Server, volume *volumes.Volume) (*volumeattach.VolumeAttachment, error) {
+	volumeAttachOptions := volumeattach.CreateOpts{
+		VolumeID: volume.ID,
+	}
+
+	t.Logf("Attempting to attach volume %s to server %s", volume.ID, server.ID)
+	volumeAttachment, err := volumeattach.Create(client, server.ID, volumeAttachOptions).Extract()
+	if err != nil {
+		return volumeAttachment, err
+	}
+
+	if err = volumes.WaitForStatus(blockClient, volume.ID, "in-use", 60); err != nil {
+		return volumeAttachment, err
+	}
+
+	return volumeAttachment, nil
+}
+
+// DeleteDefaultRule deletes a default security group rule.
+// A fatal error will occur if the rule failed to delete. This works best when
+// using it as a deferred function.
+func DeleteDefaultRule(t *testing.T, client *gophercloud.ServiceClient, defaultRule dsr.DefaultRule) {
+	err := dsr.Delete(client, defaultRule.ID).ExtractErr()
+	if err != nil {
+		t.Fatalf("Unable to delete default rule %s: %v", defaultRule.ID, err)
+	}
+
+	t.Logf("Deleted default rule: %s", defaultRule.ID)
+}
+
+// DeleteFloatingIP will de-allocate a floating IP. A fatal error will occur if
+// the floating IP failed to de-allocate. This works best when using it as a
+// deferred function.
+func DeleteFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP) {
+	err := floatingips.Delete(client, floatingIP.ID).ExtractErr()
+	if err != nil {
+		t.Fatalf("Unable to delete floating IP %s: %v", floatingIP.ID, err)
+	}
+
+	t.Logf("Deleted floating IP: %s", floatingIP.ID)
+}
+
+// DeleteKeyPair will delete a specified keypair. A fatal error will occur if
+// the keypair failed to be deleted. This works best when used as a deferred
+// function.
+func DeleteKeyPair(t *testing.T, client *gophercloud.ServiceClient, keyPair *keypairs.KeyPair) {
+	err := keypairs.Delete(client, keyPair.Name).ExtractErr()
+	if err != nil {
+		t.Fatalf("Unable to delete keypair %s: %v", keyPair.Name, err)
+	}
+
+	t.Logf("Deleted keypair: %s", keyPair.Name)
+}
+
+// DeleteSecurityGroup will delete a security group. A fatal error will occur
+// if the group failed to be deleted. This works best as a deferred function.
+func DeleteSecurityGroup(t *testing.T, client *gophercloud.ServiceClient, securityGroup secgroups.SecurityGroup) {
+	err := secgroups.Delete(client, securityGroup.ID).ExtractErr()
+	if err != nil {
+		t.Fatalf("Unable to delete security group %s: %s", securityGroup.ID, err)
+	}
+
+	t.Logf("Deleted security group: %s", securityGroup.ID)
+}
+
+// DeleteSecurityGroupRule will delete a security group rule. A fatal error
+// will occur if the rule failed to be deleted. This works best when used
+// as a deferred function.
+func DeleteSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, rule secgroups.Rule) {
+	err := secgroups.DeleteRule(client, rule.ID).ExtractErr()
+	if err != nil {
+		t.Fatalf("Unable to delete rule: %v", err)
+	}
+
+	t.Logf("Deleted security group rule: %s", rule.ID)
+}
+
+// DeleteServer deletes an instance via its UUID.
+// A fatal error will occur if the instance failed to be destroyed. This works
+// best when using it as a deferred function.
+func DeleteServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) {
+	err := servers.Delete(client, server.ID).ExtractErr()
+	if err != nil {
+		t.Fatalf("Unable to delete server %s: %s", server.ID, err)
+	}
+
+	t.Logf("Deleted server: %s", server.ID)
+}
+
+// DeleteServerGroup will delete a server group. A fatal error will occur if
+// the server group failed to be deleted. This works best when used as a
+// deferred function.
+func DeleteServerGroup(t *testing.T, client *gophercloud.ServiceClient, serverGroup *servergroups.ServerGroup) {
+	err := servergroups.Delete(client, serverGroup.ID).ExtractErr()
+	if err != nil {
+		t.Fatalf("Unable to delete server group %s: %v", serverGroup.ID, err)
+	}
+
+	t.Logf("Deleted server group %s", serverGroup.ID)
+}
+
+// DeleteVolumeAttachment will disconnect a volume from an instance. A fatal
+// error will occur if the volume failed to detach. This works best when used
+// as a deferred functino.
+func DeleteVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, server *servers.Server, volumeAttachment *volumeattach.VolumeAttachment) {
+
+	err := volumeattach.Delete(client, server.ID, volumeAttachment.VolumeID).ExtractErr()
+	if err != nil {
+		t.Fatalf("Unable to detach volume: %v", err)
+	}
+
+	if err = volumes.WaitForStatus(blockClient, volumeAttachment.ID, "available", 60); err != nil {
+		t.Fatalf("Unable to wait for volume: %v", err)
+	}
+	t.Logf("Deleted volume: %s", volumeAttachment.VolumeID)
+}
+
+// DisassociateFloatingIP will disassociate a floating IP from an instance. A
+// fatal error will occur if the floating IP failed to disassociate. This works
+// best when using it as a deferred function.
+func DisassociateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server) {
+	disassociateOpts := floatingips.DisassociateOpts{
+		FloatingIP: floatingIP.IP,
+	}
+
+	err := floatingips.DisassociateInstance(client, server.ID, disassociateOpts).ExtractErr()
+	if err != nil {
+		t.Fatalf("Unable to disassociate floating IP %s from server %s: %v", floatingIP.IP, server.ID, err)
+	}
+
+	t.Logf("Disassociated floating IP %s from server %s", floatingIP.IP, server.ID)
+}
+
+// GetNetworkIDFromNetworks will return the network ID from a specified network
+// UUID using the os-networks API extension. An error will be returned if the
+// network could not be retrieved.
+func GetNetworkIDFromNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
+	allPages, err := networks.List(client).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to list networks: %v", err)
+	}
+
+	networkList, err := networks.ExtractNetworks(allPages)
+	if err != nil {
+		t.Fatalf("Unable to list networks: %v", err)
+	}
+
+	networkID := ""
+	for _, network := range networkList {
+		t.Logf("Network: %v", network)
+		if network.Label == networkName {
+			networkID = network.ID
+		}
+	}
+
+	t.Logf("Found network ID for %s: %s", networkName, networkID)
+
+	return networkID, nil
+}
+
+// GetNetworkIDFromTenantNetworks will return the network UUID for a given
+// network name using the os-tenant-networks API extension. An error will be
+// returned if the network could not be retrieved.
+func GetNetworkIDFromTenantNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
+	allPages, err := tenantnetworks.List(client).AllPages()
+	if err != nil {
+		return "", err
+	}
+
+	allTenantNetworks, err := tenantnetworks.ExtractNetworks(allPages)
+	if err != nil {
+		return "", err
+	}
+
+	for _, network := range allTenantNetworks {
+		if network.Name == networkName {
+			return network.ID, nil
+		}
+	}
+
+	return "", fmt.Errorf("Failed to obtain network ID for network %s", networkName)
+}
+
+// ImportPublicKey will create a KeyPair with a random name and a specified
+// public key. An error will be returned if the keypair failed to be created.
+func ImportPublicKey(t *testing.T, client *gophercloud.ServiceClient, publicKey string) (*keypairs.KeyPair, error) {
+	keyPairName := tools.RandomString("keypair_", 5)
+
+	t.Logf("Attempting to create keypair: %s", keyPairName)
+	createOpts := keypairs.CreateOpts{
+		Name:      keyPairName,
+		PublicKey: publicKey,
+	}
+	keyPair, err := keypairs.Create(client, createOpts).Extract()
+	if err != nil {
+		return keyPair, err
+	}
+
+	t.Logf("Created keypair: %s", keyPairName)
+	return keyPair, nil
+}
+
+// ResizeServer performs a resize action on an instance. An error will be
+// returned if the instance failed to resize.
+// The new flavor that the instance will be resized to is specified in OS_FLAVOR_ID_RESIZE.
+func ResizeServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server, choices *clients.AcceptanceTestChoices) error {
+	opts := &servers.ResizeOpts{
+		FlavorRef: choices.FlavorIDResize,
+	}
+	if res := servers.Resize(client, server.ID, opts); res.Err != nil {
+		return res.Err
+	}
+
+	if err := WaitForComputeStatus(client, server, "VERIFY_RESIZE"); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// WaitForComputeStatus will poll an instance's status until it either matches
+// the specified status or the status becomes ERROR.
+func WaitForComputeStatus(client *gophercloud.ServiceClient, server *servers.Server, status string) error {
+	return tools.WaitFor(func() (bool, error) {
+		latest, err := servers.Get(client, server.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+
+		if latest.Status == status {
+			// Success!
+			return true, nil
+		}
+
+		if latest.Status == "ERROR" {
+			return false, fmt.Errorf("Instance in ERROR state")
+		}
+
+		return false, nil
+	})
+}
+
+// PrintServer will print an instance and all of its attributes.
+func PrintServer(t *testing.T, server *servers.Server) {
+	t.Logf("ID: %s", server.ID)
+	t.Logf("TenantID: %s", server.TenantID)
+	t.Logf("UserID: %s", server.UserID)
+	t.Logf("Name: %s", server.Name)
+	t.Logf("Updated: %s", server.Updated)
+	t.Logf("Created: %s", server.Created)
+	t.Logf("HostID: %s", server.HostID)
+	t.Logf("Status: %s", server.Status)
+	t.Logf("Progress: %d", server.Progress)
+	t.Logf("AccessIPv4: %s", server.AccessIPv4)
+	t.Logf("AccessIPv6: %s", server.AccessIPv6)
+	t.Logf("Image: %s", server.Image)
+	t.Logf("Flavor: %s", server.Flavor)
+	t.Logf("Addresses: %#v", server.Addresses)
+	t.Logf("Metadata: %#v", server.Metadata)
+	t.Logf("Links: %#v", server.Links)
+	t.Logf("KeyName: %s", server.KeyName)
+	t.Logf("AdminPass: %s", server.AdminPass)
+	t.Logf("SecurityGroups: %#v", server.SecurityGroups)
+}
+
+// PrintDefaultRule will print a default security group rule and all of its attributes.
+func PrintDefaultRule(t *testing.T, defaultRule *dsr.DefaultRule) {
+	t.Logf("\tID: %s", defaultRule.ID)
+	t.Logf("\tFrom Port: %d", defaultRule.FromPort)
+	t.Logf("\tTo Port: %d", defaultRule.ToPort)
+	t.Logf("\tIP Protocol: %s", defaultRule.IPProtocol)
+	t.Logf("\tIP Range: %s", defaultRule.IPRange.CIDR)
+	t.Logf("\tParent Group ID: %s", defaultRule.ParentGroupID)
+	t.Logf("\tGroup Tenant ID: %s", defaultRule.Group.TenantID)
+	t.Logf("\tGroup Name: %s", defaultRule.Group.Name)
+}
+
+// PrintFlavor will print a flavor and all of its attributes.
+func PrintFlavor(t *testing.T, flavor *flavors.Flavor) {
+	t.Logf("ID: %s", flavor.ID)
+	t.Logf("Name: %s", flavor.Name)
+	t.Logf("RAM: %d", flavor.RAM)
+	t.Logf("Disk: %d", flavor.Disk)
+	t.Logf("Swap: %d", flavor.Swap)
+	t.Logf("RxTxFactor: %f", flavor.RxTxFactor)
+}
+
+// PrintFloatingIP will print a floating IP and all of its attributes.
+func PrintFloatingIP(t *testing.T, floatingIP *floatingips.FloatingIP) {
+	t.Logf("ID: %s", floatingIP.ID)
+	t.Logf("Fixed IP: %s", floatingIP.FixedIP)
+	t.Logf("Instance ID: %s", floatingIP.InstanceID)
+	t.Logf("IP: %s", floatingIP.IP)
+	t.Logf("Pool: %s", floatingIP.Pool)
+}
+
+// PrintImage will print an image and all of its attributes.
+func PrintImage(t *testing.T, image images.Image) {
+	t.Logf("ID: %s", image.ID)
+	t.Logf("Name: %s", image.Name)
+	t.Logf("MinDisk: %d", image.MinDisk)
+	t.Logf("MinRAM: %d", image.MinRAM)
+	t.Logf("Status: %s", image.Status)
+	t.Logf("Progress: %d", image.Progress)
+	t.Logf("Metadata: %#v", image.Metadata)
+	t.Logf("Created: %s", image.Created)
+	t.Logf("Updated: %s", image.Updated)
+}
+
+// PrintKeyPair will print keypair and all of its attributes.
+func PrintKeyPair(t *testing.T, keypair *keypairs.KeyPair) {
+	t.Logf("Name: %s", keypair.Name)
+	t.Logf("Fingerprint: %s", keypair.Fingerprint)
+	t.Logf("Public Key: %s", keypair.PublicKey)
+	t.Logf("Private Key: %s", keypair.PrivateKey)
+	t.Logf("UserID: %s", keypair.UserID)
+}
+
+//  PrintNetwork will print an os-networks based network and all of its attributes.
+func PrintNetwork(t *testing.T, network *networks.Network) {
+	t.Logf("Bridge: %s", network.Bridge)
+	t.Logf("BridgeInterface: %s", network.BridgeInterface)
+	t.Logf("Broadcast: %s", network.Broadcast)
+	t.Logf("CIDR: %s", network.CIDR)
+	t.Logf("CIDRv6: %s", network.CIDRv6)
+	t.Logf("CreatedAt: %v", network.CreatedAt)
+	t.Logf("Deleted: %t", network.Deleted)
+	t.Logf("DeletedAt: %v", network.DeletedAt)
+	t.Logf("DHCPStart: %s", network.DHCPStart)
+	t.Logf("DNS1: %s", network.DNS1)
+	t.Logf("DNS2: %s", network.DNS2)
+	t.Logf("Gateway: %s", network.Gateway)
+	t.Logf("Gatewayv6: %s", network.Gatewayv6)
+	t.Logf("Host: %s", network.Host)
+	t.Logf("ID: %s", network.ID)
+	t.Logf("Injected: %t", network.Injected)
+	t.Logf("Label: %s", network.Label)
+	t.Logf("MultiHost: %t", network.MultiHost)
+	t.Logf("Netmask: %s", network.Netmask)
+	t.Logf("Netmaskv6: %s", network.Netmaskv6)
+	t.Logf("Priority: %d", network.Priority)
+	t.Logf("ProjectID: %s", network.ProjectID)
+	t.Logf("RXTXBase: %d", network.RXTXBase)
+	t.Logf("UpdatedAt: %v", network.UpdatedAt)
+	t.Logf("VLAN: %d", network.VLAN)
+	t.Logf("VPNPrivateAddress: %s", network.VPNPrivateAddress)
+	t.Logf("VPNPublicAddress: %s", network.VPNPublicAddress)
+	t.Logf("VPNPublicPort: %d", network.VPNPublicPort)
+}
+
+//  PrintQuotaSet will print a quota set and all of its attributes.
+func PrintQuotaSet(t *testing.T, quotaSet *quotasets.QuotaSet) {
+	t.Logf("instances: %d\n", quotaSet.Instances)
+	t.Logf("cores: %d\n", quotaSet.Cores)
+	t.Logf("ram: %d\n", quotaSet.Ram)
+	t.Logf("key_pairs: %d\n", quotaSet.KeyPairs)
+	t.Logf("metadata_items: %d\n", quotaSet.MetadataItems)
+	t.Logf("security_groups: %d\n", quotaSet.SecurityGroups)
+	t.Logf("security_group_rules: %d\n", quotaSet.SecurityGroupRules)
+	t.Logf("fixed_ips: %d\n", quotaSet.FixedIps)
+	t.Logf("floating_ips: %d\n", quotaSet.FloatingIps)
+	t.Logf("injected_file_content_bytes: %d\n", quotaSet.InjectedFileContentBytes)
+	t.Logf("injected_file_path_bytes: %d\n", quotaSet.InjectedFilePathBytes)
+	t.Logf("injected_files: %d\n", quotaSet.InjectedFiles)
+}
+
+//  PrintSecurityGroup will print a security group and all of its attributes and rules.
+func PrintSecurityGroup(t *testing.T, securityGroup *secgroups.SecurityGroup) {
+	t.Logf("ID: %s", securityGroup.ID)
+	t.Logf("Name: %s", securityGroup.Name)
+	t.Logf("Description: %s", securityGroup.Description)
+	t.Logf("Tenant ID: %s", securityGroup.TenantID)
+	t.Logf("Rules:")
+
+	for _, rule := range securityGroup.Rules {
+		t.Logf("\tID: %s", rule.ID)
+		t.Logf("\tFrom Port: %d", rule.FromPort)
+		t.Logf("\tTo Port: %d", rule.ToPort)
+		t.Logf("\tIP Protocol: %s", rule.IPProtocol)
+		t.Logf("\tIP Range: %s", rule.IPRange.CIDR)
+		t.Logf("\tParent Group ID: %s", rule.ParentGroupID)
+		t.Logf("\tGroup Tenant ID: %s", rule.Group.TenantID)
+		t.Logf("\tGroup Name: %s", rule.Group.Name)
+	}
+}
+
+// PrintServerGroup will print a server group and all of its attributes.
+func PrintServerGroup(t *testing.T, serverGroup *servergroups.ServerGroup) {
+	t.Logf("ID: %s", serverGroup.ID)
+	t.Logf("Name: %s", serverGroup.Name)
+	t.Logf("Policies: %#v", serverGroup.Policies)
+	t.Logf("Members: %#v", serverGroup.Members)
+	t.Logf("Metadata: %#v", serverGroup.Metadata)
+}
+
+// PrintTenantNetwork will print an os-tenant-networks based network and all of its attributes.
+func PrintTenantNetwork(t *testing.T, network *tenantnetworks.Network) {
+	t.Logf("ID: %s", network.ID)
+	t.Logf("Name: %s", network.Name)
+	t.Logf("CIDR: %s", network.CIDR)
+}
+
+// PrintVolumeAttachment will print a volume attachment and all of its attributes.
+func PrintVolumeAttachment(t *testing.T, volumeAttachment *volumeattach.VolumeAttachment) {
+	t.Logf("ID: %s", volumeAttachment.ID)
+	t.Logf("Device: %s", volumeAttachment.Device)
+	t.Logf("VolumeID: %s", volumeAttachment.VolumeID)
+	t.Logf("ServerID: %s", volumeAttachment.ServerID)
+}
diff --git a/acceptance/openstack/compute/v2/compute_test.go b/acceptance/openstack/compute/v2/compute_test.go
deleted file mode 100644
index b76cfd1..0000000
--- a/acceptance/openstack/compute/v2/compute_test.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// +build acceptance common
-
-package v2
-
-import (
-	"fmt"
-	"os"
-	"strings"
-
-	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/acceptance/tools"
-	"github.com/gophercloud/gophercloud/openstack"
-	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
-)
-
-func newClient() (*gophercloud.ServiceClient, error) {
-	ao, err := openstack.AuthOptionsFromEnv()
-	if err != nil {
-		return nil, err
-	}
-
-	client, err := openstack.AuthenticatedClient(ao)
-	if err != nil {
-		return nil, err
-	}
-
-	return openstack.NewComputeV2(client, gophercloud.EndpointOpts{
-		Region: os.Getenv("OS_REGION_NAME"),
-	})
-}
-
-func newIdentityClient() (*gophercloud.ServiceClient, error) {
-	ao, err := openstack.AuthOptionsFromEnv()
-	if err != nil {
-		return nil, err
-	}
-
-	client, err := openstack.AuthenticatedClient(ao)
-	if err != nil {
-		return nil, err
-	}
-
-	return openstack.NewIdentityV2(client, gophercloud.EndpointOpts{
-		Region: os.Getenv("OS_REGION_NAME"),
-	})
-}
-
-func newBlockClient() (*gophercloud.ServiceClient, error) {
-	ao, err := openstack.AuthOptionsFromEnv()
-	if err != nil {
-		return nil, err
-	}
-
-	client, err := openstack.AuthenticatedClient(ao)
-	if err != nil {
-		return nil, err
-	}
-
-	return openstack.NewBlockStorageV1(client, gophercloud.EndpointOpts{
-		Region: os.Getenv("OS_REGION_NAME"),
-	})
-}
-
-func waitForStatus(client *gophercloud.ServiceClient, server *servers.Server, status string) error {
-	return tools.WaitFor(func() (bool, error) {
-		latest, err := servers.Get(client, server.ID).Extract()
-		if err != nil {
-			return false, err
-		}
-
-		if latest.Status == status {
-			// Success!
-			return true, nil
-		}
-
-		if latest.Status == "ERROR" {
-			return false, fmt.Errorf("Instance in ERROR state")
-		}
-
-		return false, nil
-	})
-}
-
-// ComputeChoices contains image and flavor selections for use by the acceptance tests.
-type ComputeChoices struct {
-	// ImageID contains the ID of a valid image.
-	ImageID string
-
-	// FlavorID contains the ID of a valid flavor.
-	FlavorID string
-
-	// FlavorIDResize contains the ID of a different flavor available on the same OpenStack installation, that is distinct
-	// from FlavorID.
-	FlavorIDResize string
-
-	// FloatingIPPool contains the name of the pool from where to obtain floating IPs.
-	FloatingIPPoolName string
-
-	// NetworkName is the name of a network to launch the instance on.
-	NetworkName string
-}
-
-// ComputeChoicesFromEnv populates a ComputeChoices struct from environment variables.
-// If any required state is missing, an `error` will be returned that enumerates the missing properties.
-func ComputeChoicesFromEnv() (*ComputeChoices, error) {
-	imageID := os.Getenv("OS_IMAGE_ID")
-	flavorID := os.Getenv("OS_FLAVOR_ID")
-	flavorIDResize := os.Getenv("OS_FLAVOR_ID_RESIZE")
-	networkName := os.Getenv("OS_NETWORK_NAME")
-	floatingIPPoolName := os.Getenv("OS_POOL_NAME")
-
-	missing := make([]string, 0, 3)
-	if imageID == "" {
-		missing = append(missing, "OS_IMAGE_ID")
-	}
-	if flavorID == "" {
-		missing = append(missing, "OS_FLAVOR_ID")
-	}
-	if flavorIDResize == "" {
-		missing = append(missing, "OS_FLAVOR_ID_RESIZE")
-	}
-	if floatingIPPoolName == "" {
-		missing = append(missing, "OS_POOL_NAME")
-	}
-	if networkName == "" {
-		networkName = "private"
-	}
-
-	notDistinct := ""
-	if flavorID == flavorIDResize {
-		notDistinct = "OS_FLAVOR_ID and OS_FLAVOR_ID_RESIZE must be distinct."
-	}
-
-	if len(missing) > 0 || notDistinct != "" {
-		text := "You're missing some important setup:\n"
-		if len(missing) > 0 {
-			text += " * These environment variables must be provided: " + strings.Join(missing, ", ") + "\n"
-		}
-		if notDistinct != "" {
-			text += " * " + notDistinct + "\n"
-		}
-
-		return nil, fmt.Errorf(text)
-	}
-
-	return &ComputeChoices{ImageID: imageID, FlavorID: flavorID, FlavorIDResize: flavorIDResize, FloatingIPPoolName: floatingIPPoolName, NetworkName: networkName}, nil
-}
diff --git a/acceptance/openstack/compute/v2/defsecrules_test.go b/acceptance/openstack/compute/v2/defsecrules_test.go
index dbee8e3..ca221be 100644
--- a/acceptance/openstack/compute/v2/defsecrules_test.go
+++ b/acceptance/openstack/compute/v2/defsecrules_test.go
@@ -5,13 +5,12 @@
 import (
 	"testing"
 
-	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/acceptance/tools"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	dsr "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules"
 )
 
 func TestDefSecRulesList(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
@@ -27,79 +26,41 @@
 	}
 
 	for _, defaultRule := range allDefaultRules {
-		printDefaultRule(t, &defaultRule)
+		PrintDefaultRule(t, &defaultRule)
 	}
 }
 
 func TestDefSecRulesCreate(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	defaultRule, err := createDefaultRule(t, client)
+	defaultRule, err := CreateDefaultRule(t, client)
 	if err != nil {
 		t.Fatalf("Unable to create default rule: %v", err)
 	}
-	defer deleteDefaultRule(t, client, defaultRule)
+	defer DeleteDefaultRule(t, client, defaultRule)
 
-	printDefaultRule(t, &defaultRule)
+	PrintDefaultRule(t, &defaultRule)
 }
 
 func TestDefSecRulesGet(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	defaultRule, err := createDefaultRule(t, client)
+	defaultRule, err := CreateDefaultRule(t, client)
 	if err != nil {
 		t.Fatalf("Unable to create default rule: %v", err)
 	}
-	defer deleteDefaultRule(t, client, defaultRule)
+	defer DeleteDefaultRule(t, client, defaultRule)
 
 	newDefaultRule, err := dsr.Get(client, defaultRule.ID).Extract()
 	if err != nil {
 		t.Fatalf("Unable to get default rule %s: %v", defaultRule.ID, err)
 	}
 
-	printDefaultRule(t, newDefaultRule)
-}
-
-func createDefaultRule(t *testing.T, client *gophercloud.ServiceClient) (dsr.DefaultRule, error) {
-	createOpts := dsr.CreateOpts{
-		FromPort:   tools.RandomInt(80, 89),
-		ToPort:     tools.RandomInt(90, 99),
-		IPProtocol: "TCP",
-		CIDR:       "0.0.0.0/0",
-	}
-
-	defaultRule, err := dsr.Create(client, createOpts).Extract()
-	if err != nil {
-		return *defaultRule, err
-	}
-
-	t.Logf("Created default rule: %s", defaultRule.ID)
-
-	return *defaultRule, nil
-}
-
-func deleteDefaultRule(t *testing.T, client *gophercloud.ServiceClient, defaultRule dsr.DefaultRule) {
-	err := dsr.Delete(client, defaultRule.ID).ExtractErr()
-	if err != nil {
-		t.Fatalf("Unable to delete default rule %s: %v", defaultRule.ID, err)
-	}
-
-	t.Logf("Deleted default rule: %s", defaultRule.ID)
-}
-
-func printDefaultRule(t *testing.T, defaultRule *dsr.DefaultRule) {
-	t.Logf("\tID: %s", defaultRule.ID)
-	t.Logf("\tFrom Port: %d", defaultRule.FromPort)
-	t.Logf("\tTo Port: %d", defaultRule.ToPort)
-	t.Logf("\tIP Protocol: %s", defaultRule.IPProtocol)
-	t.Logf("\tIP Range: %s", defaultRule.IPRange.CIDR)
-	t.Logf("\tParent Group ID: %s", defaultRule.ParentGroupID)
-	t.Logf("\tGroup Tenant ID: %s", defaultRule.Group.TenantID)
-	t.Logf("\tGroup Name: %s", defaultRule.Group.Name)
+	PrintDefaultRule(t, newDefaultRule)
 }
diff --git a/acceptance/openstack/compute/v2/extension_test.go b/acceptance/openstack/compute/v2/extension_test.go
index 626797a..5905cfc 100644
--- a/acceptance/openstack/compute/v2/extension_test.go
+++ b/acceptance/openstack/compute/v2/extension_test.go
@@ -5,11 +5,12 @@
 import (
 	"testing"
 
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/openstack/common/extensions"
 )
 
 func TestExtensionsList(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
@@ -30,7 +31,7 @@
 }
 
 func TestExtensionGet(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
diff --git a/acceptance/openstack/compute/v2/flavors_test.go b/acceptance/openstack/compute/v2/flavors_test.go
index bc6a2c7..6f6490e 100644
--- a/acceptance/openstack/compute/v2/flavors_test.go
+++ b/acceptance/openstack/compute/v2/flavors_test.go
@@ -5,11 +5,12 @@
 import (
 	"testing"
 
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
 )
 
 func TestFlavorsList(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
@@ -25,17 +26,17 @@
 	}
 
 	for _, flavor := range allFlavors {
-		printFlavor(t, &flavor)
+		PrintFlavor(t, &flavor)
 	}
 }
 
 func TestFlavorsGet(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err :=clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -45,14 +46,5 @@
 		t.Fatalf("Unable to get flavor information: %v", err)
 	}
 
-	printFlavor(t, flavor)
-}
-
-func printFlavor(t *testing.T, flavor *flavors.Flavor) {
-	t.Logf("ID: %s", flavor.ID)
-	t.Logf("Name: %s", flavor.Name)
-	t.Logf("RAM: %d", flavor.RAM)
-	t.Logf("Disk: %d", flavor.Disk)
-	t.Logf("Swap: %d", flavor.Swap)
-	t.Logf("RxTxFactor: %f", flavor.RxTxFactor)
+	PrintFlavor(t, flavor)
 }
diff --git a/acceptance/openstack/compute/v2/floatingip_test.go b/acceptance/openstack/compute/v2/floatingip_test.go
index 92249c7..001dc9f 100644
--- a/acceptance/openstack/compute/v2/floatingip_test.go
+++ b/acceptance/openstack/compute/v2/floatingip_test.go
@@ -5,13 +5,13 @@
 import (
 	"testing"
 
-	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
 )
 
 func TestFloatingIPsList(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
@@ -27,28 +27,28 @@
 	}
 
 	for _, floatingIP := range allFloatingIPs {
-		printFloatingIP(t, &floatingIP)
+		PrintFloatingIP(t, &floatingIP)
 	}
 }
 
 func TestFloatingIPsCreate(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	floatingIP, err := createFloatingIP(t, client, choices)
+	floatingIP, err := CreateFloatingIP(t, client, choices)
 	if err != nil {
 		t.Fatalf("Unable to create floating IP: %v", err)
 	}
-	defer deleteFloatingIP(t, client, floatingIP)
+	defer DeleteFloatingIP(t, client, floatingIP)
 
-	printFloatingIP(t, floatingIP)
+	PrintFloatingIP(t, floatingIP)
 }
 
 func TestFloatingIPsAssociate(t *testing.T) {
@@ -56,52 +56,44 @@
 		t.Skip("Skipping test that requires server creation in short mode.")
 	}
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	server, err := createServer(t, client, choices)
+	server, err := CreateServer(t, client, choices)
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
 	}
+	defer DeleteServer(t, client, server)
 
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatalf("Unable to wait for server: %v", err)
-	}
-	defer deleteServer(t, client, server)
-
-	floatingIP, err := createFloatingIP(t, client, choices)
+	floatingIP, err := CreateFloatingIP(t, client, choices)
 	if err != nil {
 		t.Fatalf("Unable to create floating IP: %v", err)
 	}
-	defer deleteFloatingIP(t, client, floatingIP)
+	defer DeleteFloatingIP(t, client, floatingIP)
 
-	printFloatingIP(t, floatingIP)
+	PrintFloatingIP(t, floatingIP)
 
-	associateOpts := floatingips.AssociateOpts{
-		FloatingIP: floatingIP.IP,
-	}
-
-	t.Logf("Attempting to associate floating IP %s to instance %s", floatingIP.IP, server.ID)
-	err = floatingips.AssociateInstance(client, server.ID, associateOpts).ExtractErr()
+	err = AssociateFloatingIP(t, client, floatingIP, server)
 	if err != nil {
 		t.Fatalf("Unable to associate floating IP %s with server %s: %v", floatingIP.IP, server.ID, err)
 	}
-	defer disassociateFloatingIP(t, client, floatingIP, server)
-	t.Logf("Floating IP %s is associated with Fixed IP %s", floatingIP.IP, floatingIP.FixedIP)
+	defer DisassociateFloatingIP(t, client, floatingIP, server)
 
 	newFloatingIP, err := floatingips.Get(client, floatingIP.ID).Extract()
 	if err != nil {
 		t.Fatalf("Unable to get floating IP %s: %v", floatingIP.ID, err)
 	}
 
-	printFloatingIP(t, newFloatingIP)
+	t.Logf("Floating IP %s is associated with Fixed IP %s", floatingIP.IP, newFloatingIP.FixedIP)
+
+	PrintFloatingIP(t, newFloatingIP)
 }
 
 func TestFloatingIPsFixedIPAssociate(t *testing.T) {
@@ -109,38 +101,34 @@
 		t.Skip("Skipping test that requires server creation in short mode.")
 	}
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	server, err := createServer(t, client, choices)
+	server, err := CreateServer(t, client, choices)
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
 	}
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatalf("Unable to wait for server: %v", err)
-	}
-	defer deleteServer(t, client, server)
+	defer DeleteServer(t, client, server)
 
 	newServer, err := servers.Get(client, server.ID).Extract()
 	if err != nil {
 		t.Fatalf("Unable to get server %s: %v", server.ID, err)
 	}
 
-	floatingIP, err := createFloatingIP(t, client, choices)
+	floatingIP, err := CreateFloatingIP(t, client, choices)
 	if err != nil {
 		t.Fatalf("Unable to create floating IP: %v", err)
 	}
-	defer deleteFloatingIP(t, client, floatingIP)
+	defer DeleteFloatingIP(t, client, floatingIP)
 
-	printFloatingIP(t, floatingIP)
+	PrintFloatingIP(t, floatingIP)
 
 	var fixedIP string
 	for _, networkAddresses := range newServer.Addresses[choices.NetworkName].([]interface{}) {
@@ -152,66 +140,18 @@
 		}
 	}
 
-	associateOpts := floatingips.AssociateOpts{
-		FloatingIP: floatingIP.IP,
-		FixedIP:    fixedIP,
-	}
-
-	t.Logf("Attempting to associate floating IP %s to instance %s", floatingIP.IP, newServer.ID)
-	err = floatingips.AssociateInstance(client, newServer.ID, associateOpts).ExtractErr()
+	err = AssociateFloatingIPWithFixedIP(t, client, floatingIP, newServer, fixedIP)
 	if err != nil {
 		t.Fatalf("Unable to associate floating IP %s with server %s: %v", floatingIP.IP, newServer.ID, err)
 	}
-	defer disassociateFloatingIP(t, client, floatingIP, newServer)
-	t.Logf("Floating IP %s is associated with Fixed IP %s", floatingIP.IP, floatingIP.FixedIP)
+	defer DisassociateFloatingIP(t, client, floatingIP, newServer)
 
 	newFloatingIP, err := floatingips.Get(client, floatingIP.ID).Extract()
 	if err != nil {
 		t.Fatalf("Unable to get floating IP %s: %v", floatingIP.ID, err)
 	}
 
-	printFloatingIP(t, newFloatingIP)
-}
+	t.Logf("Floating IP %s is associated with Fixed IP %s", floatingIP.IP, newFloatingIP.FixedIP)
 
-func createFloatingIP(t *testing.T, client *gophercloud.ServiceClient, choices *ComputeChoices) (*floatingips.FloatingIP, error) {
-	createOpts := floatingips.CreateOpts{
-		Pool: choices.FloatingIPPoolName,
-	}
-	floatingIP, err := floatingips.Create(client, createOpts).Extract()
-	if err != nil {
-		return floatingIP, err
-	}
-
-	t.Logf("Created floating IP: %s", floatingIP.ID)
-	return floatingIP, nil
-}
-
-func deleteFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP) {
-	err := floatingips.Delete(client, floatingIP.ID).ExtractErr()
-	if err != nil {
-		t.Fatalf("Unable to delete floating IP %s: %v", floatingIP.ID, err)
-	}
-
-	t.Logf("Deleted floating IP: %s", floatingIP.ID)
-}
-
-func disassociateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server) {
-	disassociateOpts := floatingips.DisassociateOpts{
-		FloatingIP: floatingIP.IP,
-	}
-
-	err := floatingips.DisassociateInstance(client, server.ID, disassociateOpts).ExtractErr()
-	if err != nil {
-		t.Fatalf("Unable to disassociate floating IP %s from server %s: %v", floatingIP.IP, server.ID, err)
-	}
-
-	t.Logf("Disassociated floating IP %s from server %s", floatingIP.IP, server.ID)
-}
-
-func printFloatingIP(t *testing.T, floatingIP *floatingips.FloatingIP) {
-	t.Logf("ID: %s", floatingIP.ID)
-	t.Logf("Fixed IP: %s", floatingIP.FixedIP)
-	t.Logf("Instance ID: %s", floatingIP.InstanceID)
-	t.Logf("IP: %s", floatingIP.IP)
-	t.Logf("Pool: %s", floatingIP.Pool)
+	PrintFloatingIP(t, newFloatingIP)
 }
diff --git a/acceptance/openstack/compute/v2/images_test.go b/acceptance/openstack/compute/v2/images_test.go
index 15af9af..53291dd 100644
--- a/acceptance/openstack/compute/v2/images_test.go
+++ b/acceptance/openstack/compute/v2/images_test.go
@@ -5,11 +5,12 @@
 import (
 	"testing"
 
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
 )
 
 func TestImagesList(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute: client: %v", err)
 	}
@@ -25,17 +26,17 @@
 	}
 
 	for _, image := range allImages {
-		printImage(t, image)
+		PrintImage(t, image)
 	}
 }
 
 func TestImagesGet(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute: client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -45,17 +46,5 @@
 		t.Fatalf("Unable to get image information: %v", err)
 	}
 
-	printImage(t, *image)
-}
-
-func printImage(t *testing.T, image images.Image) {
-	t.Logf("ID: %s", image.ID)
-	t.Logf("Name: %s", image.Name)
-	t.Logf("MinDisk: %d", image.MinDisk)
-	t.Logf("MinRAM: %d", image.MinRAM)
-	t.Logf("Status: %s", image.Status)
-	t.Logf("Progress: %d", image.Progress)
-	t.Logf("Metadata: %#v", image.Metadata)
-	t.Logf("Created: %s", image.Created)
-	t.Logf("Updated: %s", image.Updated)
+	PrintImage(t, *image)
 }
diff --git a/acceptance/openstack/compute/v2/keypairs_test.go b/acceptance/openstack/compute/v2/keypairs_test.go
index b9fb57a..3c6abb4 100644
--- a/acceptance/openstack/compute/v2/keypairs_test.go
+++ b/acceptance/openstack/compute/v2/keypairs_test.go
@@ -3,22 +3,17 @@
 package v2
 
 import (
-	"crypto/rand"
-	"crypto/rsa"
 	"testing"
 
-	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/acceptance/tools"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
-
-	"golang.org/x/crypto/ssh"
 )
 
 const keyName = "gophercloud_test_key_pair"
 
 func TestKeypairsList(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
@@ -34,30 +29,27 @@
 	}
 
 	for _, keypair := range allKeys {
-		printKeyPair(t, &keypair)
+		PrintKeyPair(t, &keypair)
 	}
 }
 
 func TestKeypairsCreate(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	createOpts := keypairs.CreateOpts{
-		Name: keyName,
-	}
-	keyPair, err := keypairs.Create(client, createOpts).Extract()
+	keyPair, err := CreateKeyPair(t, client)
 	if err != nil {
-		t.Fatalf("Unable to create keypair: %s", err)
+		t.Fatalf("Unable to create key pair: %v", err)
 	}
-	defer deleteKeyPair(t, client, keyPair)
+	defer DeleteKeyPair(t, client, keyPair)
 
-	printKeyPair(t, keyPair)
+	PrintKeyPair(t, keyPair)
 }
 
 func TestKeypairsImportPublicKey(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
@@ -67,17 +59,13 @@
 		t.Fatalf("Unable to create public key: %s", err)
 	}
 
-	createOpts := keypairs.CreateOpts{
-		Name:      keyName,
-		PublicKey: publicKey,
-	}
-	keyPair, err := keypairs.Create(client, createOpts).Extract()
+	keyPair, err := ImportPublicKey(t, client, publicKey)
 	if err != nil {
 		t.Fatalf("Unable to create keypair: %s", err)
 	}
-	defer deleteKeyPair(t, client, keyPair)
+	defer DeleteKeyPair(t, client, keyPair)
 
-	printKeyPair(t, keyPair)
+	PrintKeyPair(t, keyPair)
 }
 
 func TestKeypairsServerCreateWithKey(t *testing.T) {
@@ -85,12 +73,12 @@
 		t.Skip("Skipping test that requires server creation in short mode.")
 	}
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -100,87 +88,24 @@
 		t.Fatalf("Unable to create public key: %s", err)
 	}
 
-	createOpts := keypairs.CreateOpts{
-		Name:      keyName,
-		PublicKey: publicKey,
-	}
-
-	keyPair, err := keypairs.Create(client, createOpts).Extract()
+	keyPair, err := ImportPublicKey(t, client, publicKey)
 	if err != nil {
 		t.Fatalf("Unable to create keypair: %s", err)
 	}
-	defer deleteKeyPair(t, client, keyPair)
+	defer DeleteKeyPair(t, client, keyPair)
 
-	networkID, err := getNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
-	if err != nil {
-		t.Fatalf("Failed to obtain network ID: %v", err)
-	}
-
-	name := tools.RandomString("ACPTTEST", 16)
-	t.Logf("Attempting to create server: %s", name)
-
-	serverCreateOpts := servers.CreateOpts{
-		Name:      name,
-		FlavorRef: choices.FlavorID,
-		ImageRef:  choices.ImageID,
-		Networks: []servers.Network{
-			servers.Network{UUID: networkID},
-		},
-	}
-
-	server, err := servers.Create(client, keypairs.CreateOptsExt{
-		serverCreateOpts,
-		keyName,
-	}).Extract()
+	server, err := CreateServerWithPublicKey(t, client, choices, keyPair.Name)
 	if err != nil {
 		t.Fatalf("Unable to create server: %s", err)
 	}
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatalf("Unable to wait for server: %v", err)
-	}
-	defer deleteServer(t, client, server)
+	defer DeleteServer(t, client, server)
 
 	server, err = servers.Get(client, server.ID).Extract()
 	if err != nil {
 		t.Fatalf("Unable to retrieve server: %s", err)
 	}
 
-	if server.KeyName != keyName {
-		t.Fatalf("key name of server %s is %s, not %s", server.ID, server.KeyName, keyName)
+	if server.KeyName != keyPair.Name {
+		t.Fatalf("key name of server %s is %s, not %s", server.ID, server.KeyName, keyPair.Name)
 	}
 }
-
-func createKey() (string, error) {
-	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
-	if err != nil {
-		return "", err
-	}
-
-	publicKey := privateKey.PublicKey
-	pub, err := ssh.NewPublicKey(&publicKey)
-	if err != nil {
-		return "", err
-	}
-
-	pubBytes := ssh.MarshalAuthorizedKey(pub)
-	pk := string(pubBytes)
-	return pk, nil
-}
-
-func deleteKeyPair(t *testing.T, client *gophercloud.ServiceClient, keyPair *keypairs.KeyPair) {
-	err := keypairs.Delete(client, keyPair.Name).ExtractErr()
-	if err != nil {
-		t.Fatalf("Unable to delete keypair %s: %v", keyPair.Name, err)
-	}
-
-	t.Logf("Deleted keypair: %s", keyPair.Name)
-}
-
-func printKeyPair(t *testing.T, keypair *keypairs.KeyPair) {
-	t.Logf("Name: %s", keypair.Name)
-	t.Logf("Fingerprint: %s", keypair.Fingerprint)
-	t.Logf("Public Key: %s", keypair.PublicKey)
-	t.Logf("Private Key: %s", keypair.PrivateKey)
-	t.Logf("UserID: %s", keypair.UserID)
-}
diff --git a/acceptance/openstack/compute/v2/network_test.go b/acceptance/openstack/compute/v2/network_test.go
index c7ff1a3..38a6d54 100644
--- a/acceptance/openstack/compute/v2/network_test.go
+++ b/acceptance/openstack/compute/v2/network_test.go
@@ -3,15 +3,14 @@
 package v2
 
 import (
-	"fmt"
 	"testing"
 
-	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/networks"
 )
 
 func TestNetworksList(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
@@ -27,22 +26,22 @@
 	}
 
 	for _, network := range allNetworks {
-		printNetwork(t, &network)
+		PrintNetwork(t, &network)
 	}
 }
 
 func TestNetworksGet(t *testing.T) {
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	networkID, err := getNetworkIDFromNetworks(t, client, choices.NetworkName)
+	networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -52,80 +51,5 @@
 		t.Fatalf("Unable to get network %s: %v", networkID, err)
 	}
 
-	printNetwork(t, network)
-}
-
-func getNetworkIDFromNetworkExtension(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
-	allPages, err := networks.List(client).AllPages()
-	if err != nil {
-		t.Fatalf("Unable to list networks: %v", err)
-	}
-
-	networkList, err := networks.ExtractNetworks(allPages)
-	if err != nil {
-		t.Fatalf("Unable to list networks: %v", err)
-	}
-
-	networkID := ""
-	for _, network := range networkList {
-		t.Logf("Network: %v", network)
-		if network.Label == networkName {
-			networkID = network.ID
-		}
-	}
-
-	t.Logf("Found network ID for %s: %s\n", networkName, networkID)
-
-	return networkID, nil
-}
-
-func getNetworkIDFromNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
-	allPages, err := networks.List(client).AllPages()
-	if err != nil {
-		t.Fatalf("Unable to list networks: %v", err)
-	}
-
-	allNetworks, err := networks.ExtractNetworks(allPages)
-	if err != nil {
-		t.Fatalf("Unable to list networks: %v", err)
-	}
-
-	for _, network := range allNetworks {
-		if network.Label == networkName {
-			return network.ID, nil
-		}
-	}
-
-	return "", fmt.Errorf("Failed to obtain network ID for network %s", networkName)
-}
-
-func printNetwork(t *testing.T, network *networks.Network) {
-	t.Logf("Bridge: %s", network.Bridge)
-	t.Logf("BridgeInterface: %s", network.BridgeInterface)
-	t.Logf("Broadcast: %s", network.Broadcast)
-	t.Logf("CIDR: %s", network.CIDR)
-	t.Logf("CIDRv6: %s", network.CIDRv6)
-	t.Logf("CreatedAt: %v", network.CreatedAt)
-	t.Logf("Deleted: %t", network.Deleted)
-	t.Logf("DeletedAt: %v", network.DeletedAt)
-	t.Logf("DHCPStart: %s", network.DHCPStart)
-	t.Logf("DNS1: %s", network.DNS1)
-	t.Logf("DNS2: %s", network.DNS2)
-	t.Logf("Gateway: %s", network.Gateway)
-	t.Logf("Gatewayv6: %s", network.Gatewayv6)
-	t.Logf("Host: %s", network.Host)
-	t.Logf("ID: %s", network.ID)
-	t.Logf("Injected: %t", network.Injected)
-	t.Logf("Label: %s", network.Label)
-	t.Logf("MultiHost: %t", network.MultiHost)
-	t.Logf("Netmask: %s", network.Netmask)
-	t.Logf("Netmaskv6: %s", network.Netmaskv6)
-	t.Logf("Priority: %d", network.Priority)
-	t.Logf("ProjectID: %s", network.ProjectID)
-	t.Logf("RXTXBase: %d", network.RXTXBase)
-	t.Logf("UpdatedAt: %v", network.UpdatedAt)
-	t.Logf("VLAN: %d", network.VLAN)
-	t.Logf("VPNPrivateAddress: %s", network.VPNPrivateAddress)
-	t.Logf("VPNPublicAddress: %s", network.VPNPublicAddress)
-	t.Logf("VPNPublicPort: %d", network.VPNPublicPort)
+	PrintNetwork(t, network)
 }
diff --git a/acceptance/openstack/compute/v2/quotaset_test.go b/acceptance/openstack/compute/v2/quotaset_test.go
index 60b8563..d9e73e9 100644
--- a/acceptance/openstack/compute/v2/quotaset_test.go
+++ b/acceptance/openstack/compute/v2/quotaset_test.go
@@ -7,17 +7,18 @@
 	"testing"
 
 	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets"
 	"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
 )
 
 func TestQuotasetGet(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	identityClient, err := newIdentityClient()
+	identityClient, err := clients.NewIdentityV2Client()
 	if err != nil {
 		t.Fatalf("Unable to get a new identity client: %v", err)
 	}
@@ -32,7 +33,7 @@
 		t.Fatal(err)
 	}
 
-	printQuotaSet(t, quotaSet)
+	PrintQuotaSet(t, quotaSet)
 }
 
 func getTenantID(t *testing.T, client *gophercloud.ServiceClient) (string, error) {
@@ -52,18 +53,3 @@
 
 	return "", fmt.Errorf("Unable to get tenant ID")
 }
-
-func printQuotaSet(t *testing.T, quotaSet *quotasets.QuotaSet) {
-	t.Logf("instances: %d\n", quotaSet.Instances)
-	t.Logf("cores: %d\n", quotaSet.Cores)
-	t.Logf("ram: %d\n", quotaSet.Ram)
-	t.Logf("key_pairs: %d\n", quotaSet.KeyPairs)
-	t.Logf("metadata_items: %d\n", quotaSet.MetadataItems)
-	t.Logf("security_groups: %d\n", quotaSet.SecurityGroups)
-	t.Logf("security_group_rules: %d\n", quotaSet.SecurityGroupRules)
-	t.Logf("fixed_ips: %d\n", quotaSet.FixedIps)
-	t.Logf("floating_ips: %d\n", quotaSet.FloatingIps)
-	t.Logf("injected_file_content_bytes: %d\n", quotaSet.InjectedFileContentBytes)
-	t.Logf("injected_file_path_bytes: %d\n", quotaSet.InjectedFilePathBytes)
-	t.Logf("injected_files: %d\n", quotaSet.InjectedFiles)
-}
diff --git a/acceptance/openstack/compute/v2/secgroup_test.go b/acceptance/openstack/compute/v2/secgroup_test.go
index 8a2e7f7..9ec7ba4 100644
--- a/acceptance/openstack/compute/v2/secgroup_test.go
+++ b/acceptance/openstack/compute/v2/secgroup_test.go
@@ -5,13 +5,13 @@
 import (
 	"testing"
 
-	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/acceptance/tools"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
 )
 
 func TestSecGroupsList(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
@@ -27,34 +27,34 @@
 	}
 
 	for _, secgroup := range allSecGroups {
-		printSecurityGroup(t, &secgroup)
+		PrintSecurityGroup(t, &secgroup)
 	}
 }
 
 func TestSecGroupsCreate(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	securityGroup, err := createSecurityGroup(t, client)
+	securityGroup, err := CreateSecurityGroup(t, client)
 	if err != nil {
 		t.Fatalf("Unable to create security group: %v", err)
 	}
-	defer deleteSecurityGroup(t, client, securityGroup)
+	defer DeleteSecurityGroup(t, client, securityGroup)
 }
 
 func TestSecGroupsUpdate(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	securityGroup, err := createSecurityGroup(t, client)
+	securityGroup, err := CreateSecurityGroup(t, client)
 	if err != nil {
 		t.Fatalf("Unable to create security group: %v", err)
 	}
-	defer deleteSecurityGroup(t, client, securityGroup)
+	defer DeleteSecurityGroup(t, client, securityGroup)
 
 	updateOpts := secgroups.UpdateOpts{
 		Name:        tools.RandomString("secgroup_", 4),
@@ -69,22 +69,22 @@
 }
 
 func TestSecGroupsRuleCreate(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	securityGroup, err := createSecurityGroup(t, client)
+	securityGroup, err := CreateSecurityGroup(t, client)
 	if err != nil {
 		t.Fatalf("Unable to create security group: %v", err)
 	}
-	defer deleteSecurityGroup(t, client, securityGroup)
+	defer DeleteSecurityGroup(t, client, securityGroup)
 
-	rule, err := createSecurityGroupRule(t, client, securityGroup.ID)
+	rule, err := CreateSecurityGroupRule(t, client, securityGroup.ID)
 	if err != nil {
 		t.Fatalf("Unable to create rule: %v", err)
 	}
-	defer deleteSecurityGroupRule(t, client, rule)
+	defer DeleteSecurityGroupRule(t, client, rule)
 }
 
 func TestSecGroupsAddGroupToServer(t *testing.T) {
@@ -92,37 +92,33 @@
 		t.Skip("Skipping test that requires server creation in short mode.")
 	}
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	server, err := createServer(t, client, choices)
+	server, err := CreateServer(t, client, choices)
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
 	}
+	defer DeleteServer(t, client, server)
 
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatalf("Unable to wait for server: %v", err)
-	}
-	defer deleteServer(t, client, server)
-
-	securityGroup, err := createSecurityGroup(t, client)
+	securityGroup, err := CreateSecurityGroup(t, client)
 	if err != nil {
 		t.Fatalf("Unable to create security group: %v", err)
 	}
-	defer deleteSecurityGroup(t, client, securityGroup)
+	defer DeleteSecurityGroup(t, client, securityGroup)
 
-	rule, err := createSecurityGroupRule(t, client, securityGroup.ID)
+	rule, err := CreateSecurityGroupRule(t, client, securityGroup.ID)
 	if err != nil {
 		t.Fatalf("Unable to create rule: %v", err)
 	}
-	defer deleteSecurityGroupRule(t, client, rule)
+	defer DeleteSecurityGroupRule(t, client, rule)
 
 	t.Logf("Adding group %s to server %s", securityGroup.ID, server.ID)
 	err = secgroups.AddServer(client, server.ID, securityGroup.Name).ExtractErr()
@@ -136,73 +132,3 @@
 		t.Fatalf("Unable to remove group %s from server %s: %s", securityGroup.ID, server.ID, err)
 	}
 }
-
-func createSecurityGroup(t *testing.T, client *gophercloud.ServiceClient) (secgroups.SecurityGroup, error) {
-	createOpts := secgroups.CreateOpts{
-		Name:        tools.RandomString("secgroup_", 5),
-		Description: "something",
-	}
-
-	securityGroup, err := secgroups.Create(client, createOpts).Extract()
-	if err != nil {
-		return *securityGroup, err
-	}
-
-	t.Logf("Created security group: %s", securityGroup.ID)
-	return *securityGroup, nil
-}
-
-func deleteSecurityGroup(t *testing.T, client *gophercloud.ServiceClient, securityGroup secgroups.SecurityGroup) {
-	err := secgroups.Delete(client, securityGroup.ID).ExtractErr()
-	if err != nil {
-		t.Fatalf("Unable to delete security group %s: %s", securityGroup.ID, err)
-	}
-
-	t.Logf("Deleted security group: %s", securityGroup.ID)
-}
-
-func createSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, securityGroupID string) (secgroups.Rule, error) {
-	createOpts := secgroups.CreateRuleOpts{
-		ParentGroupID: securityGroupID,
-		FromPort:      tools.RandomInt(80, 89),
-		ToPort:        tools.RandomInt(90, 99),
-		IPProtocol:    "TCP",
-		CIDR:          "0.0.0.0/0",
-	}
-
-	rule, err := secgroups.CreateRule(client, createOpts).Extract()
-	if err != nil {
-		return *rule, err
-	}
-
-	t.Logf("Created security group rule: %s", rule.ID)
-	return *rule, nil
-}
-
-func deleteSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, rule secgroups.Rule) {
-	err := secgroups.DeleteRule(client, rule.ID).ExtractErr()
-	if err != nil {
-		t.Fatalf("Unable to delete rule: %v", err)
-	}
-
-	t.Logf("Deleted security group rule: %s", rule.ID)
-}
-
-func printSecurityGroup(t *testing.T, securityGroup *secgroups.SecurityGroup) {
-	t.Logf("ID: %s", securityGroup.ID)
-	t.Logf("Name: %s", securityGroup.Name)
-	t.Logf("Description: %s", securityGroup.Description)
-	t.Logf("Tenant ID: %s", securityGroup.TenantID)
-	t.Logf("Rules:")
-
-	for _, rule := range securityGroup.Rules {
-		t.Logf("\tID: %s", rule.ID)
-		t.Logf("\tFrom Port: %d", rule.FromPort)
-		t.Logf("\tTo Port: %d", rule.ToPort)
-		t.Logf("\tIP Protocol: %s", rule.IPProtocol)
-		t.Logf("\tIP Range: %s", rule.IPRange.CIDR)
-		t.Logf("\tParent Group ID: %s", rule.ParentGroupID)
-		t.Logf("\tGroup Tenant ID: %s", rule.Group.TenantID)
-		t.Logf("\tGroup Name: %s", rule.Group.Name)
-	}
-}
diff --git a/acceptance/openstack/compute/v2/servergroup_test.go b/acceptance/openstack/compute/v2/servergroup_test.go
index 0ee47ea..5921014 100644
--- a/acceptance/openstack/compute/v2/servergroup_test.go
+++ b/acceptance/openstack/compute/v2/servergroup_test.go
@@ -5,15 +5,13 @@
 import (
 	"testing"
 
-	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/acceptance/tools"
-	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
 )
 
 func TestServergroupsList(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
@@ -29,68 +27,64 @@
 	}
 
 	for _, serverGroup := range allServerGroups {
-		printServerGroup(t, &serverGroup)
+		PrintServerGroup(t, &serverGroup)
 	}
 }
 
 func TestServergroupsCreate(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	serverGroup, err := createServerGroup(t, client, "anti-affinity")
+	serverGroup, err := CreateServerGroup(t, client, "anti-affinity")
 	if err != nil {
 		t.Fatalf("Unable to create server group: %v", err)
 	}
-	defer deleteServerGroup(t, client, serverGroup)
+	defer DeleteServerGroup(t, client, serverGroup)
 
 	serverGroup, err = servergroups.Get(client, serverGroup.ID).Extract()
 	if err != nil {
 		t.Fatalf("Unable to get server group: %v", err)
 	}
 
-	printServerGroup(t, serverGroup)
+	PrintServerGroup(t, serverGroup)
 }
 
 func TestServergroupsAffinityPolicy(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	serverGroup, err := createServerGroup(t, client, "affinity")
+	serverGroup, err := CreateServerGroup(t, client, "affinity")
 	if err != nil {
 		t.Fatalf("Unable to create server group: %v", err)
 	}
-	defer deleteServerGroup(t, client, serverGroup)
+	defer DeleteServerGroup(t, client, serverGroup)
 
-	firstServer, err := createServerInServerGroup(t, client, choices, serverGroup)
+	firstServer, err := CreateServerInServerGroup(t, client, choices, serverGroup)
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
 	}
-
-	if err = waitForStatus(client, firstServer, "ACTIVE"); err != nil {
-		t.Fatalf("Unable to wait for server: %v", err)
-	}
-	defer deleteServer(t, client, firstServer)
+	defer DeleteServer(t, client, firstServer)
 
 	firstServer, err = servers.Get(client, firstServer.ID).Extract()
 
-	secondServer, err := createServerInServerGroup(t, client, choices, serverGroup)
+	secondServer, err := CreateServerInServerGroup(t, client, choices, serverGroup)
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
 	}
 
-	if err = waitForStatus(client, secondServer, "ACTIVE"); err != nil {
+	if err = WaitForComputeStatus(client, secondServer, "ACTIVE"); err != nil {
 		t.Fatalf("Unable to wait for server: %v", err)
 	}
-	defer deleteServer(t, client, secondServer)
+	defer DeleteServer(t, client, secondServer)
 
 	secondServer, err = servers.Get(client, secondServer.ID).Extract()
 
@@ -98,72 +92,3 @@
 		t.Fatalf("%s and %s were not scheduled on the same host.", firstServer.ID, secondServer.ID)
 	}
 }
-
-func createServerGroup(t *testing.T, client *gophercloud.ServiceClient, policy string) (*servergroups.ServerGroup, error) {
-	sg, err := servergroups.Create(client, &servergroups.CreateOpts{
-		Name:     "test",
-		Policies: []string{policy},
-	}).Extract()
-
-	if err != nil {
-		t.Fatalf("Unable to create server group: %v", err)
-	}
-
-	return sg, nil
-}
-
-func createServerInServerGroup(t *testing.T, client *gophercloud.ServiceClient, choices *ComputeChoices, serverGroup *servergroups.ServerGroup) (*servers.Server, error) {
-	if testing.Short() {
-		t.Skip("Skipping test that requires server creation in short mode.")
-	}
-
-	networkID, err := getNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
-	if err != nil {
-		t.Fatalf("Failed to obtain network ID: %v", err)
-	}
-
-	name := tools.RandomString("ACPTTEST", 16)
-	t.Logf("Attempting to create server: %s", name)
-
-	pwd := tools.MakeNewPassword("")
-
-	serverCreateOpts := servers.CreateOpts{
-		Name:      name,
-		FlavorRef: choices.FlavorID,
-		ImageRef:  choices.ImageID,
-		AdminPass: pwd,
-		Networks: []servers.Network{
-			servers.Network{UUID: networkID},
-		},
-	}
-
-	schedulerHintsOpts := schedulerhints.CreateOptsExt{
-		serverCreateOpts,
-		schedulerhints.SchedulerHints{
-			Group: serverGroup.ID,
-		},
-	}
-	server, err := servers.Create(client, schedulerHintsOpts).Extract()
-	if err != nil {
-		t.Fatalf("Unable to create server: %v", err)
-	}
-
-	return server, err
-}
-
-func deleteServerGroup(t *testing.T, client *gophercloud.ServiceClient, serverGroup *servergroups.ServerGroup) {
-	err := servergroups.Delete(client, serverGroup.ID).ExtractErr()
-	if err != nil {
-		t.Fatalf("Unable to delete server group %s: %v", serverGroup.ID, err)
-	}
-
-	t.Logf("Deleted server group %s", serverGroup.ID)
-}
-
-func printServerGroup(t *testing.T, serverGroup *servergroups.ServerGroup) {
-	t.Logf("ID: %s", serverGroup.ID)
-	t.Logf("Name: %s", serverGroup.Name)
-	t.Logf("Policies: %#v", serverGroup.Policies)
-	t.Logf("Members: %#v", serverGroup.Members)
-	t.Logf("Metadata: %#v", serverGroup.Metadata)
-}
diff --git a/acceptance/openstack/compute/v2/servers_test.go b/acceptance/openstack/compute/v2/servers_test.go
index 34a3fd2..f5b60c5 100644
--- a/acceptance/openstack/compute/v2/servers_test.go
+++ b/acceptance/openstack/compute/v2/servers_test.go
@@ -5,14 +5,14 @@
 import (
 	"testing"
 
-	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/acceptance/tools"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
 	th "github.com/gophercloud/gophercloud/testhelper"
 )
 
 func TestServersList(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
@@ -28,36 +28,33 @@
 	}
 
 	for _, server := range allServers {
-		printServer(t, &server)
+		PrintServer(t, &server)
 	}
 }
 
 func TestServersCreateDestroy(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	server, err := createServer(t, client, choices)
+	server, err := CreateServer(t, client, choices)
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
 	}
 
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatalf("Unable to wait for server: %v", err)
-	}
-	defer deleteServer(t, client, server)
+	defer DeleteServer(t, client, server)
 
 	newServer, err := servers.Get(client, server.ID).Extract()
 	if err != nil {
 		t.Errorf("Unable to retrieve server: %v", err)
 	}
-	printServer(t, newServer)
+	PrintServer(t, newServer)
 
 	allAddressPages, err := servers.ListAddresses(client, server.ID).AllPages()
 	if err != nil {
@@ -90,25 +87,21 @@
 }
 
 func TestServersUpdate(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	server, err := createServer(t, client, choices)
+	server, err := CreateServer(t, client, choices)
 	if err != nil {
 		t.Fatal(err)
 	}
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatal(err)
-	}
-	defer deleteServer(t, client, server)
+	defer DeleteServer(t, client, server)
 
 	alternateName := tools.RandomString("ACPTTEST", 16)
 	for alternateName == server.Name {
@@ -143,25 +136,21 @@
 func TestServersMetadata(t *testing.T) {
 	t.Parallel()
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	server, err := createServer(t, client, choices)
+	server, err := CreateServer(t, client, choices)
 	if err != nil {
 		t.Fatal(err)
 	}
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatal(err)
-	}
-	defer deleteServer(t, client, server)
+	defer DeleteServer(t, client, server)
 
 	metadata, err := servers.UpdateMetadata(client, server.ID, servers.MetadataOpts{
 		"foo":  "bar",
@@ -209,25 +198,21 @@
 func TestServersActionChangeAdminPassword(t *testing.T) {
 	t.Parallel()
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	server, err := createServer(t, client, choices)
+	server, err := CreateServer(t, client, choices)
 	if err != nil {
 		t.Fatal(err)
 	}
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatal(err)
-	}
-	defer deleteServer(t, client, server)
+	defer DeleteServer(t, client, server)
 
 	randomPassword := tools.MakeNewPassword(server.AdminPass)
 	res := servers.ChangeAdminPassword(client, server.ID, randomPassword)
@@ -235,11 +220,11 @@
 		t.Fatal(res.Err)
 	}
 
-	if err = waitForStatus(client, server, "PASSWORD"); err != nil {
+	if err = WaitForComputeStatus(client, server, "PASSWORD"); err != nil {
 		t.Fatal(err)
 	}
 
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
+	if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
 		t.Fatal(err)
 	}
 }
@@ -247,25 +232,21 @@
 func TestServersActionReboot(t *testing.T) {
 	t.Parallel()
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	server, err := createServer(t, client, choices)
+	server, err := CreateServer(t, client, choices)
 	if err != nil {
 		t.Fatal(err)
 	}
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatal(err)
-	}
-	defer deleteServer(t, client, server)
+	defer DeleteServer(t, client, server)
 
 	rebootOpts := &servers.RebootOpts{
 		Type: servers.SoftReboot,
@@ -277,11 +258,11 @@
 		t.Fatalf("Unable to reboot server: %v", res.Err)
 	}
 
-	if err = waitForStatus(client, server, "REBOOT"); err != nil {
+	if err = WaitForComputeStatus(client, server, "REBOOT"); err != nil {
 		t.Fatal(err)
 	}
 
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
+	if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
 		t.Fatal(err)
 	}
 }
@@ -289,25 +270,21 @@
 func TestServersActionRebuild(t *testing.T) {
 	t.Parallel()
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	server, err := createServer(t, client, choices)
+	server, err := CreateServer(t, client, choices)
 	if err != nil {
 		t.Fatal(err)
 	}
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatal(err)
-	}
-	defer deleteServer(t, client, server)
+	defer DeleteServer(t, client, server)
 
 	t.Logf("Attempting to rebuild server %s", server.ID)
 
@@ -326,11 +303,11 @@
 		t.Errorf("Expected rebuilt server ID of [%s]; got [%s]", server.ID, rebuilt.ID)
 	}
 
-	if err = waitForStatus(client, rebuilt, "REBUILD"); err != nil {
+	if err = WaitForComputeStatus(client, rebuilt, "REBUILD"); err != nil {
 		t.Fatal(err)
 	}
 
-	if err = waitForStatus(client, rebuilt, "ACTIVE"); err != nil {
+	if err = WaitForComputeStatus(client, rebuilt, "ACTIVE"); err != nil {
 		t.Fatal(err)
 	}
 }
@@ -338,35 +315,31 @@
 func TestServersActionResizeConfirm(t *testing.T) {
 	t.Parallel()
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	server, err := createServer(t, client, choices)
+	server, err := CreateServer(t, client, choices)
 	if err != nil {
 		t.Fatal(err)
 	}
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatal(err)
-	}
-	defer deleteServer(t, client, server)
+	defer DeleteServer(t, client, server)
 
 	t.Logf("Attempting to resize server %s", server.ID)
-	resizeServer(t, client, server, choices)
+	ResizeServer(t, client, server, choices)
 
 	t.Logf("Attempting to confirm resize for server %s", server.ID)
 	if res := servers.ConfirmResize(client, server.ID); res.Err != nil {
 		t.Fatal(res.Err)
 	}
 
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
+	if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
 		t.Fatal(err)
 	}
 }
@@ -374,118 +347,31 @@
 func TestServersActionResizeRevert(t *testing.T) {
 	t.Parallel()
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	server, err := createServer(t, client, choices)
+	server, err := CreateServer(t, client, choices)
 	if err != nil {
 		t.Fatal(err)
 	}
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatal(err)
-	}
-	defer deleteServer(t, client, server)
+	defer DeleteServer(t, client, server)
 
 	t.Logf("Attempting to resize server %s", server.ID)
-	resizeServer(t, client, server, choices)
+	ResizeServer(t, client, server, choices)
 
 	t.Logf("Attempting to revert resize for server %s", server.ID)
 	if res := servers.RevertResize(client, server.ID); res.Err != nil {
 		t.Fatal(res.Err)
 	}
 
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
+	if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
 		t.Fatal(err)
 	}
 }
-
-func createServer(t *testing.T, client *gophercloud.ServiceClient, choices *ComputeChoices) (*servers.Server, error) {
-	if testing.Short() {
-		t.Skip("Skipping test that requires server creation in short mode.")
-	}
-
-	networkID, err := getNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
-	if err != nil {
-		t.Fatalf("Failed to obtain network ID: %v", err)
-	}
-
-	name := tools.RandomString("ACPTTEST", 16)
-	t.Logf("Attempting to create server: %s", name)
-
-	pwd := tools.MakeNewPassword("")
-
-	server, err := servers.Create(client, servers.CreateOpts{
-		Name:      name,
-		FlavorRef: choices.FlavorID,
-		ImageRef:  choices.ImageID,
-		AdminPass: pwd,
-		Networks: []servers.Network{
-			servers.Network{UUID: networkID},
-		},
-		Personality: servers.Personality{
-			&servers.File{
-				Path:     "/etc/test",
-				Contents: []byte("hello world"),
-			},
-		},
-	}).Extract()
-	if err != nil {
-		t.Fatalf("Unable to create server: %v", err)
-	}
-
-	th.AssertEquals(t, pwd, server.AdminPass)
-
-	return server, err
-}
-
-func resizeServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server, choices *ComputeChoices) {
-	opts := &servers.ResizeOpts{
-		FlavorRef: choices.FlavorIDResize,
-	}
-	if res := servers.Resize(client, server.ID, opts); res.Err != nil {
-		t.Fatal(res.Err)
-	}
-
-	if err := waitForStatus(client, server, "VERIFY_RESIZE"); err != nil {
-		t.Fatal(err)
-	}
-}
-
-func deleteServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) {
-	err := servers.Delete(client, server.ID).ExtractErr()
-	if err != nil {
-		t.Fatalf("Unable to delete server %s: %s", server.ID, err)
-	}
-
-	t.Logf("Deleted server: %s", server.ID)
-}
-
-func printServer(t *testing.T, server *servers.Server) {
-	t.Logf("ID: %s", server.ID)
-	t.Logf("TenantID: %s", server.TenantID)
-	t.Logf("UserID: %s", server.UserID)
-	t.Logf("Name: %s", server.Name)
-	t.Logf("Updated: %s", server.Updated)
-	t.Logf("Created: %s", server.Created)
-	t.Logf("HostID: %s", server.HostID)
-	t.Logf("Status: %s", server.Status)
-	t.Logf("Progress: %d", server.Progress)
-	t.Logf("AccessIPv4: %s", server.AccessIPv4)
-	t.Logf("AccessIPv6: %s", server.AccessIPv6)
-	t.Logf("Image: %s", server.Image)
-	t.Logf("Flavor: %s", server.Flavor)
-	t.Logf("Addresses: %#v", server.Addresses)
-	t.Logf("Metadata: %#v", server.Metadata)
-	t.Logf("Links: %#v", server.Links)
-	t.Logf("KeyName: %s", server.KeyName)
-	t.Logf("AdminPass: %s", server.AdminPass)
-	t.Logf("SecurityGroups: %#v", server.SecurityGroups)
-}
diff --git a/acceptance/openstack/compute/v2/tenantnetworks_test.go b/acceptance/openstack/compute/v2/tenantnetworks_test.go
index 3092077..7d9c167 100644
--- a/acceptance/openstack/compute/v2/tenantnetworks_test.go
+++ b/acceptance/openstack/compute/v2/tenantnetworks_test.go
@@ -3,15 +3,14 @@
 package v2
 
 import (
-	"fmt"
 	"testing"
 
-	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks"
 )
 
 func TestTenantNetworksList(t *testing.T) {
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
@@ -27,22 +26,22 @@
 	}
 
 	for _, network := range allTenantNetworks {
-		printTenantNetwork(t, &network)
+		PrintTenantNetwork(t, &network)
 	}
 }
 
 func TestTenantNetworksGet(t *testing.T) {
-	choices, err := ComputeChoicesFromEnv()
+	choices, err := clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	networkID, err := getNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
+	networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -52,31 +51,5 @@
 		t.Fatalf("Unable to get network %s: %v", networkID, err)
 	}
 
-	printTenantNetwork(t, network)
-}
-
-func getNetworkIDFromTenantNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
-	allPages, err := tenantnetworks.List(client).AllPages()
-	if err != nil {
-		t.Fatalf("Unable to list networks: %v", err)
-	}
-
-	allTenantNetworks, err := tenantnetworks.ExtractNetworks(allPages)
-	if err != nil {
-		t.Fatalf("Unable to list networks: %v", err)
-	}
-
-	for _, network := range allTenantNetworks {
-		if network.Name == networkName {
-			return network.ID, nil
-		}
-	}
-
-	return "", fmt.Errorf("Failed to obtain network ID for network %s", networkName)
-}
-
-func printTenantNetwork(t *testing.T, network *tenantnetworks.Network) {
-	t.Logf("ID: %s", network.ID)
-	t.Logf("Name: %s", network.Name)
-	t.Logf("CIDR: %s", network.CIDR)
+	PrintTenantNetwork(t, network)
 }
diff --git a/acceptance/openstack/compute/v2/volumeattach_test.go b/acceptance/openstack/compute/v2/volumeattach_test.go
index a1b10a9..d791992 100644
--- a/acceptance/openstack/compute/v2/volumeattach_test.go
+++ b/acceptance/openstack/compute/v2/volumeattach_test.go
@@ -6,10 +6,9 @@
 	"testing"
 
 	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/acceptance/clients"
 	"github.com/gophercloud/gophercloud/acceptance/tools"
 	"github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
-	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
-	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
 )
 
 func TestVolumeAttachAttachment(t *testing.T) {
@@ -17,30 +16,26 @@
 		t.Skip("Skipping test that requires server creation in short mode.")
 	}
 
-	client, err := newClient()
+	client, err := clients.NewComputeV2Client()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	choices, err := ComputeChoicesFromEnv()
+	choices, err :=clients.AcceptanceTestChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	blockClient, err := newBlockClient()
+	blockClient, err := clients.NewBlockStorageV1Client()
 	if err != nil {
 		t.Fatalf("Unable to create a blockstorage client: %v", err)
 	}
 
-	server, err := createServer(t, client, choices)
+	server, err := CreateServer(t, client, choices)
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
 	}
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatalf("Unable to wait for server: %v", err)
-	}
-	defer deleteServer(t, client, server)
+	defer DeleteServer(t, client, server)
 
 	volume, err := createVolume(t, blockClient)
 	if err != nil {
@@ -52,13 +47,13 @@
 	}
 	defer deleteVolume(t, blockClient, volume)
 
-	volumeAttachment, err := createVolumeAttachment(t, client, blockClient, server, volume)
+	volumeAttachment, err := CreateVolumeAttachment(t, client, blockClient, server, volume)
 	if err != nil {
 		t.Fatalf("Unable to attach volume: %v", err)
 	}
-	defer deleteVolumeAttachment(t, client, blockClient, server, volumeAttachment)
+	defer DeleteVolumeAttachment(t, client, blockClient, server, volumeAttachment)
 
-	printVolumeAttachment(t, volumeAttachment)
+	PrintVolumeAttachment(t, volumeAttachment)
 
 }
 
@@ -86,41 +81,3 @@
 
 	t.Logf("Deleted volume: %s", volume.ID)
 }
-
-func createVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, server *servers.Server, volume *volumes.Volume) (*volumeattach.VolumeAttachment, error) {
-	volumeAttachOptions := volumeattach.CreateOpts{
-		VolumeID: volume.ID,
-	}
-
-	t.Logf("Attempting to attach volume %s to server %s", volume.ID, server.ID)
-	volumeAttachment, err := volumeattach.Create(client, server.ID, volumeAttachOptions).Extract()
-	if err != nil {
-		return volumeAttachment, err
-	}
-
-	if err = volumes.WaitForStatus(blockClient, volume.ID, "in-use", 60); err != nil {
-		return volumeAttachment, err
-	}
-
-	return volumeAttachment, nil
-}
-
-func deleteVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, server *servers.Server, volumeAttachment *volumeattach.VolumeAttachment) {
-
-	err := volumeattach.Delete(client, server.ID, volumeAttachment.VolumeID).ExtractErr()
-	if err != nil {
-		t.Fatalf("Unable to detach volume: %v", err)
-	}
-
-	if err = volumes.WaitForStatus(blockClient, volumeAttachment.ID, "available", 60); err != nil {
-		t.Fatalf("Unable to wait for volume: %v", err)
-	}
-	t.Logf("Deleted volume: %s", volumeAttachment.VolumeID)
-}
-
-func printVolumeAttachment(t *testing.T, volumeAttachment *volumeattach.VolumeAttachment) {
-	t.Logf("ID: %s", volumeAttachment.ID)
-	t.Logf("Device: %s", volumeAttachment.Device)
-	t.Logf("VolumeID: %s", volumeAttachment.VolumeID)
-	t.Logf("ServerID: %s", volumeAttachment.ServerID)
-}