Merge pull request #17 from jrperritt/5

Use EndpointOpts to query ServiceCatalog for endpoint for Identity service clients
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0d85ca8..e9d5560 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -14,6 +14,10 @@
 [README](/README.md#how-to-install) but add `-tags "fixtures acceptance"` to
 get dependencies for unit and acceptance tests.
 
+   ```bash
+   go get -tags "fixtures acceptance" github.com/gophercloud/gophercloud
+   ```
+
 2. Move into the directory that houses your local repository:
 
    ```bash
@@ -26,7 +30,7 @@
 
    ```bash
    git remote rename origin upstream
-   git remote add origin git@github.com/<my_username>/gophercloud
+   git remote add origin git@github.com:<my_username>/gophercloud.git
    ```
 
 4. Checkout the latest development branch:
diff --git a/MIGRATING.md b/MIGRATING.md
index 8b13789..62bdf8c 100644
--- a/MIGRATING.md
+++ b/MIGRATING.md
@@ -1 +1,23 @@
+# Compute
 
+## Floating IPs
+
+* `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingip` is now `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips`
+* `floatingips.Associate` and `floatingips.Disassociate` have been removed.
+* `floatingips.DisassociateOpts` is now required to disassociate a Floating IP.
+
+## Security Groups
+
+* `secgroups.AddServerToGroup` is now `secgroups.AddServer`.
+* `secgroups.RemoveServerFromGroup` is now `secgroups.RemoveServer`.
+
+## Servers
+
+* `servers.Reboot` now requires a `servers.RebootOpts` struct:
+
+  ```golang
+  rebootOpts := &servers.RebootOpts{
+          Type: servers.SoftReboot,
+  }
+  res := servers.Reboot(client, server.ID, rebootOpts)
+  ```
diff --git a/acceptance/README.md b/acceptance/README.md
index 3199837..d969e03 100644
--- a/acceptance/README.md
+++ b/acceptance/README.md
@@ -28,15 +28,12 @@
 |`OS_AUTH_URL`|The identity URL you need to authenticate|
 |`OS_TENANT_NAME`|Your API tenant name|
 |`OS_TENANT_ID`|Your API tenant ID|
-|`RS_USERNAME`|Your Rackspace username|
-|`RS_API_KEY`|Your Rackspace API key|
 
 #### General
 
 |Name|Description|
 |---|---|
 |`OS_REGION_NAME`|The region you want your resources to reside in|
-|`RS_REGION`|Rackspace region you want your resource to reside in|
 
 #### Compute
 
@@ -45,8 +42,8 @@
 |`OS_IMAGE_ID`|The ID of the image your want your server to be based on|
 |`OS_FLAVOR_ID`|The ID of the flavor you want your server to be based on|
 |`OS_FLAVOR_ID_RESIZE`|The ID of the flavor you want your server to be resized to|
-|`RS_IMAGE_ID`|The ID of the image you want servers to be created with|
-|`RS_FLAVOR_ID`|The ID of the flavor you want your server to be created with|
+|`OS_POOL_NAME`|The Pool from where to obtain Floating IPs|
+|`OS_NETWORK_NAME`|The network to launch instances on|
 
 ### 2. Run the test suite
 
@@ -55,3 +52,30 @@
 ```
 ./script/acceptancetest
 ```
+
+Alternatively, add the following to your `.bashrc`:
+
+```bash
+gophercloudtest() {
+  if [[ -n $1 ]] && [[ -n $2 ]]; then
+    pushd  ~/go/src/github.com/gophercloud/gophercloud
+    go test -v -tags "fixtures acceptance" -run "$1" github.com/gophercloud/gophercloud/acceptance/openstack/$2 | tee ~/gophercloud.log
+    popd
+fi
+}
+```
+
+Then run either groups or individual tests by doing:
+
+```shell
+$ gophercloudtest TestFlavorsList compute/v2
+$ gophercloudtest TestFlavors compute/v2
+$ gophercloudtest Test compute/v2
+```
+
+### 3. Notes
+
+#### Compute Tests
+
+* In order to run the `TestBootFromVolumeMultiEphemeral` test, a flavor with ephemeral disk space must be used.
+* The `TestDefSecRules` tests require a compatible network driver and admin privileges.
diff --git a/acceptance/openstack/compute/v2/bootfromvolume_test.go b/acceptance/openstack/compute/v2/bootfromvolume_test.go
index d1607be..6b73347 100644
--- a/acceptance/openstack/compute/v2/bootfromvolume_test.go
+++ b/acceptance/openstack/compute/v2/bootfromvolume_test.go
@@ -1,33 +1,32 @@
-// +build acceptance
+// +build acceptance compute bootfromvolume
 
 package v2
 
 import (
 	"testing"
 
+	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/acceptance/tools"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
-	th "github.com/gophercloud/gophercloud/testhelper"
 )
 
-func TestBootFromVolume(t *testing.T) {
-	client, err := newClient()
-	th.AssertNoErr(t, err)
-
+func TestBootFromVolumeSingleVolume(t *testing.T) {
 	if testing.Short() {
 		t.Skip("Skipping test that requires server creation in short mode.")
 	}
 
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
 	choices, err := ComputeChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	name := tools.RandomString("Gophercloud-", 8)
-	t.Logf("Creating server [%s].", name)
-
-	bd := []bootfromvolume.BlockDevice{
+	blockDevices := []bootfromvolume.BlockDevice{
 		bootfromvolume.BlockDevice{
 			UUID:       choices.ImageID,
 			SourceType: bootfromvolume.Image,
@@ -35,48 +34,41 @@
 		},
 	}
 
-	serverCreateOpts := servers.CreateOpts{
-		Name:      name,
-		FlavorRef: choices.FlavorID,
-		ImageRef:  choices.ImageID,
+	server, err := createBootableVolumeServer(t, client, blockDevices, choices)
+	if err != nil {
+		t.Fatal("Unable to create server: %v", err)
 	}
-	server, err := bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{
-		serverCreateOpts,
-		bd,
-	}).Extract()
-	th.AssertNoErr(t, err)
 	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatal(err)
+		t.Fatal("Unable to wait for server: %v", err)
 	}
+	defer deleteServer(t, client, server)
 
-	t.Logf("Created server: %+v\n", server)
-	defer servers.Delete(client, server.ID)
-	t.Logf("Deleting server [%s]...", name)
+	printServer(t, server)
 }
 
-func TestMultiEphemeral(t *testing.T) {
-	client, err := newClient()
-	th.AssertNoErr(t, err)
-
+func TestBootFromVolumeMultiEphemeral(t *testing.T) {
 	if testing.Short() {
 		t.Skip("Skipping test that requires server creation in short mode.")
 	}
 
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
 	choices, err := ComputeChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	name := tools.RandomString("Gophercloud-", 8)
-	t.Logf("Creating server [%s].", name)
-
-	bd := []bootfromvolume.BlockDevice{
+	blockDevices := []bootfromvolume.BlockDevice{
 		bootfromvolume.BlockDevice{
 			BootIndex:           0,
 			UUID:                choices.ImageID,
 			SourceType:          bootfromvolume.Image,
 			DestinationType:     "local",
 			DeleteOnTermination: true,
+			VolumeSize:          5,
 		},
 		bootfromvolume.BlockDevice{
 			BootIndex:           -1,
@@ -96,21 +88,48 @@
 		},
 	}
 
+	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)
+
+	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,
-	}
-	server, err := bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{
-		serverCreateOpts,
-		bd,
-	}).Extract()
-	th.AssertNoErr(t, err)
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatal(err)
+		Networks: []servers.Network{
+			servers.Network{UUID: networkID},
+		},
 	}
 
-	t.Logf("Created server: %+v\n", server)
-	defer servers.Delete(client, server.ID)
-	t.Logf("Deleting server [%s]...", name)
+	server, err := bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{
+		serverCreateOpts,
+		blockDevices,
+	}).Extract()
+
+	if err != nil {
+		return server, err
+	}
+
+	return server, nil
 }
diff --git a/acceptance/openstack/compute/v2/compute_test.go b/acceptance/openstack/compute/v2/compute_test.go
index 83b0e35..b76cfd1 100644
--- a/acceptance/openstack/compute/v2/compute_test.go
+++ b/acceptance/openstack/compute/v2/compute_test.go
@@ -29,6 +29,38 @@
 	})
 }
 
+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()
@@ -41,6 +73,10 @@
 			return true, nil
 		}
 
+		if latest.Status == "ERROR" {
+			return false, fmt.Errorf("Instance in ERROR state")
+		}
+
 		return false, nil
 	})
 }
@@ -57,6 +93,9 @@
 	// 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
 }
@@ -68,6 +107,7 @@
 	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 == "" {
@@ -79,8 +119,11 @@
 	if flavorIDResize == "" {
 		missing = append(missing, "OS_FLAVOR_ID_RESIZE")
 	}
+	if floatingIPPoolName == "" {
+		missing = append(missing, "OS_POOL_NAME")
+	}
 	if networkName == "" {
-		networkName = "public"
+		networkName = "private"
 	}
 
 	notDistinct := ""
@@ -100,5 +143,5 @@
 		return nil, fmt.Errorf(text)
 	}
 
-	return &ComputeChoices{ImageID: imageID, FlavorID: flavorID, FlavorIDResize: flavorIDResize, NetworkName: networkName}, nil
+	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
new file mode 100644
index 0000000..dbee8e3
--- /dev/null
+++ b/acceptance/openstack/compute/v2/defsecrules_test.go
@@ -0,0 +1,105 @@
+// +build acceptance compute defsecrules
+
+package v2
+
+import (
+	"testing"
+
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/acceptance/tools"
+	dsr "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules"
+)
+
+func TestDefSecRulesList(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	allPages, err := dsr.List(client).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to list default rules: %v", err)
+	}
+
+	allDefaultRules, err := dsr.ExtractDefaultRules(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract default rules: %v", err)
+	}
+
+	for _, defaultRule := range allDefaultRules {
+		printDefaultRule(t, &defaultRule)
+	}
+}
+
+func TestDefSecRulesCreate(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	defaultRule, err := createDefaultRule(t, client)
+	if err != nil {
+		t.Fatalf("Unable to create default rule: %v", err)
+	}
+	defer deleteDefaultRule(t, client, defaultRule)
+
+	printDefaultRule(t, &defaultRule)
+}
+
+func TestDefSecRulesGet(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	defaultRule, err := createDefaultRule(t, client)
+	if err != nil {
+		t.Fatalf("Unable to create default rule: %v", err)
+	}
+	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)
+}
diff --git a/acceptance/openstack/compute/v2/extension_test.go b/acceptance/openstack/compute/v2/extension_test.go
index 0fd6fec..626797a 100644
--- a/acceptance/openstack/compute/v2/extension_test.go
+++ b/acceptance/openstack/compute/v2/extension_test.go
@@ -1,47 +1,53 @@
-// +build acceptance compute extensionss
+// +build acceptance compute extensions
 
 package v2
 
 import (
 	"testing"
 
-	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions"
-	"github.com/gophercloud/gophercloud/pagination"
-	th "github.com/gophercloud/gophercloud/testhelper"
+	"github.com/gophercloud/gophercloud/openstack/common/extensions"
 )
 
-func TestListExtensions(t *testing.T) {
+func TestExtensionsList(t *testing.T) {
 	client, err := newClient()
-	th.AssertNoErr(t, err)
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
 
-	err = extensions.List(client).EachPage(func(page pagination.Page) (bool, error) {
-		t.Logf("--- Page ---")
+	allPages, err := extensions.List(client).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to list extensions: %v", err)
+	}
 
-		exts, err := extensions.ExtractExtensions(page)
-		th.AssertNoErr(t, err)
+	allExtensions, err := extensions.ExtractExtensions(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract extensions: %v", err)
+	}
 
-		for i, ext := range exts {
-			t.Logf("[%02d]    name=[%s]\n", i, ext.Name)
-			t.Logf("       alias=[%s]\n", ext.Alias)
-			t.Logf(" description=[%s]\n", ext.Description)
-		}
-
-		return true, nil
-	})
-	th.AssertNoErr(t, err)
+	for _, extension := range allExtensions {
+		printExtension(t, &extension)
+	}
 }
 
-func TestGetExtension(t *testing.T) {
+func TestExtensionGet(t *testing.T) {
 	client, err := newClient()
-	th.AssertNoErr(t, err)
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
 
-	ext, err := extensions.Get(client, "os-admin-actions").Extract()
-	th.AssertNoErr(t, err)
+	extension, err := extensions.Get(client, "os-admin-actions").Extract()
+	if err != nil {
+		t.Fatalf("Unable to get extension os-admin-actions: %v", err)
+	}
 
-	t.Logf("Extension details:")
-	t.Logf("        name=[%s]\n", ext.Name)
-	t.Logf("   namespace=[%s]\n", ext.Namespace)
-	t.Logf("       alias=[%s]\n", ext.Alias)
-	t.Logf(" description=[%s]\n", ext.Description)
-	t.Logf("     updated=[%s]\n", ext.Updated)
+	printExtension(t, extension)
+}
+
+func printExtension(t *testing.T, extension *extensions.Extension) {
+	t.Logf("Name: %s", extension.Name)
+	t.Logf("Namespace: %s", extension.Namespace)
+	t.Logf("Alias: %s", extension.Alias)
+	t.Logf("Description: %s", extension.Description)
+	t.Logf("Updated: %s", extension.Updated)
+	t.Logf("Links: %v", extension.Links)
 }
diff --git a/acceptance/openstack/compute/v2/flavors_test.go b/acceptance/openstack/compute/v2/flavors_test.go
index 365281d..bc6a2c7 100644
--- a/acceptance/openstack/compute/v2/flavors_test.go
+++ b/acceptance/openstack/compute/v2/flavors_test.go
@@ -6,38 +6,30 @@
 	"testing"
 
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
-	"github.com/gophercloud/gophercloud/pagination"
 )
 
-func TestListFlavors(t *testing.T) {
+func TestFlavorsList(t *testing.T) {
 	client, err := newClient()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	t.Logf("ID\tRegion\tName\tStatus\tCreated")
+	allPages, err := flavors.ListDetail(client, nil).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to retrieve flavors: %v", err)
+	}
 
-	pager := flavors.ListDetail(client, nil)
-	count, pages := 0, 0
-	pager.EachPage(func(page pagination.Page) (bool, error) {
-		t.Logf("---")
-		pages++
-		flavors, err := flavors.ExtractFlavors(page)
-		if err != nil {
-			return false, err
-		}
+	allFlavors, err := flavors.ExtractFlavors(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract flavor results: %v", err)
+	}
 
-		for _, f := range flavors {
-			t.Logf("%s\t%s\t%d\t%d\t%d", f.ID, f.Name, f.RAM, f.Disk, f.VCPUs)
-		}
-
-		return true, nil
-	})
-
-	t.Logf("--------\n%d flavors listed on %d pages.", count, pages)
+	for _, flavor := range allFlavors {
+		printFlavor(t, &flavor)
+	}
 }
 
-func TestGetFlavor(t *testing.T) {
+func TestFlavorsGet(t *testing.T) {
 	client, err := newClient()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
@@ -53,5 +45,14 @@
 		t.Fatalf("Unable to get flavor information: %v", err)
 	}
 
-	t.Logf("Flavor: %#v", flavor)
+	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)
 }
diff --git a/acceptance/openstack/compute/v2/floatingip_test.go b/acceptance/openstack/compute/v2/floatingip_test.go
index 8231bd6..92249c7 100644
--- a/acceptance/openstack/compute/v2/floatingip_test.go
+++ b/acceptance/openstack/compute/v2/floatingip_test.go
@@ -3,97 +3,147 @@
 package v2
 
 import (
-	"os"
 	"testing"
 
 	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/acceptance/tools"
-	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingip"
+	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
-	th "github.com/gophercloud/gophercloud/testhelper"
 )
 
-func createFIPServer(t *testing.T, client *gophercloud.ServiceClient, choices *ComputeChoices) (*servers.Server, error) {
+func TestFloatingIPsList(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	allPages, err := floatingips.List(client).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to retrieve floating IPs: %v", err)
+	}
+
+	allFloatingIPs, err := floatingips.ExtractFloatingIPs(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract floating IPs: %v", err)
+	}
+
+	for _, floatingIP := range allFloatingIPs {
+		printFloatingIP(t, &floatingIP)
+	}
+}
+
+func TestFloatingIPsCreate(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	choices, err := ComputeChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	floatingIP, err := createFloatingIP(t, client, choices)
+	if err != nil {
+		t.Fatalf("Unable to create floating IP: %v", err)
+	}
+	defer deleteFloatingIP(t, client, floatingIP)
+
+	printFloatingIP(t, floatingIP)
+}
+
+func TestFloatingIPsAssociate(t *testing.T) {
 	if testing.Short() {
 		t.Skip("Skipping test that requires server creation in short mode.")
 	}
 
-	name := tools.RandomString("ACPTTEST", 16)
-	t.Logf("Attempting to create server: %s\n", name)
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
 
-	pwd := tools.MakeNewPassword("")
+	choices, err := ComputeChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
 
-	server, err := servers.Create(client, servers.CreateOpts{
-		Name:      name,
-		FlavorRef: choices.FlavorID,
-		ImageRef:  choices.ImageID,
-		AdminPass: pwd,
-	}).Extract()
+	server, err := createServer(t, client, choices)
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
 	}
 
-	th.AssertEquals(t, pwd, server.AdminPass)
-
-	return server, err
-}
-
-func createFloatingIP(t *testing.T, client *gophercloud.ServiceClient) (*floatingip.FloatingIP, error) {
-	pool := os.Getenv("OS_POOL_NAME")
-	fip, err := floatingip.Create(client, &floatingip.CreateOpts{
-		Pool: pool,
-	}).Extract()
-	th.AssertNoErr(t, err)
-	t.Logf("Obtained Floating IP: %v", fip.IP)
-
-	return fip, err
-}
-
-func associateFloatingIPDeprecated(t *testing.T, client *gophercloud.ServiceClient, serverId string, fip *floatingip.FloatingIP) {
-	// This form works, but is considered deprecated.
-	// See associateFloatingIP or associateFloatingIPFixed
-	err := floatingip.Associate(client, serverId, fip.IP).ExtractErr()
-	th.AssertNoErr(t, err)
-	t.Logf("Associated floating IP %v from instance %v", fip.IP, serverId)
-	defer func() {
-		err = floatingip.Disassociate(client, serverId, fip.IP).ExtractErr()
-		th.AssertNoErr(t, err)
-		t.Logf("Disassociated floating IP %v from instance %v", fip.IP, serverId)
-	}()
-	floatingIp, err := floatingip.Get(client, fip.ID).Extract()
-	th.AssertNoErr(t, err)
-	t.Logf("Floating IP %v is associated with Fixed IP %v", fip.IP, floatingIp.FixedIP)
-}
-
-func associateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, serverId string, fip *floatingip.FloatingIP) {
-	associateOpts := floatingip.AssociateOpts{
-		ServerID:   serverId,
-		FloatingIP: fip.IP,
+	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
+		t.Fatalf("Unable to wait for server: %v", err)
 	}
+	defer deleteServer(t, client, server)
 
-	err := floatingip.AssociateInstance(client, associateOpts).ExtractErr()
-	th.AssertNoErr(t, err)
-	t.Logf("Associated floating IP %v from instance %v", fip.IP, serverId)
-	defer func() {
-		err = floatingip.DisassociateInstance(client, associateOpts).ExtractErr()
-		th.AssertNoErr(t, err)
-		t.Logf("Disassociated floating IP %v from instance %v", fip.IP, serverId)
-	}()
-	floatingIp, err := floatingip.Get(client, fip.ID).Extract()
-	th.AssertNoErr(t, err)
-	t.Logf("Floating IP %v is associated with Fixed IP %v", fip.IP, floatingIp.FixedIP)
-}
-
-func associateFloatingIPFixed(t *testing.T, client *gophercloud.ServiceClient, serverId string, fip *floatingip.FloatingIP) {
-
-	network := os.Getenv("OS_NETWORK_NAME")
-	server, err := servers.Get(client, serverId).Extract()
+	floatingIP, err := createFloatingIP(t, client, choices)
 	if err != nil {
-		t.Fatalf("%s", err)
+		t.Fatalf("Unable to create floating IP: %v", err)
 	}
+	defer deleteFloatingIP(t, client, 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()
+	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)
+
+	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)
+}
+
+func TestFloatingIPsFixedIPAssociate(t *testing.T) {
+	if testing.Short() {
+		t.Skip("Skipping test that requires server creation in short mode.")
+	}
+
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	choices, err := ComputeChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	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)
+
+	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)
+	if err != nil {
+		t.Fatalf("Unable to create floating IP: %v", err)
+	}
+	defer deleteFloatingIP(t, client, floatingIP)
+
+	printFloatingIP(t, floatingIP)
 
 	var fixedIP string
-	for _, networkAddresses := range server.Addresses[network].([]interface{}) {
+	for _, networkAddresses := range newServer.Addresses[choices.NetworkName].([]interface{}) {
 		address := networkAddresses.(map[string]interface{})
 		if address["OS-EXT-IPS:type"] == "fixed" {
 			if address["version"].(float64) == 4 {
@@ -102,67 +152,66 @@
 		}
 	}
 
-	associateOpts := floatingip.AssociateOpts{
-		ServerID:   serverId,
-		FloatingIP: fip.IP,
+	associateOpts := floatingips.AssociateOpts{
+		FloatingIP: floatingIP.IP,
 		FixedIP:    fixedIP,
 	}
 
-	err = floatingip.AssociateInstance(client, associateOpts).ExtractErr()
-	th.AssertNoErr(t, err)
-	t.Logf("Associated floating IP %v from instance %v with Fixed IP %v", fip.IP, serverId, fixedIP)
-	defer func() {
-		err = floatingip.DisassociateInstance(client, associateOpts).ExtractErr()
-		th.AssertNoErr(t, err)
-		t.Logf("Disassociated floating IP %v from instance %v with Fixed IP %v", fip.IP, serverId, fixedIP)
-	}()
-	floatingIp, err := floatingip.Get(client, fip.ID).Extract()
-	th.AssertNoErr(t, err)
-	th.AssertEquals(t, floatingIp.FixedIP, fixedIP)
-	t.Logf("Floating IP %v is associated with Fixed IP %v", fip.IP, floatingIp.FixedIP)
+	t.Logf("Attempting to associate floating IP %s to instance %s", floatingIP.IP, newServer.ID)
+	err = floatingips.AssociateInstance(client, newServer.ID, associateOpts).ExtractErr()
+	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)
+
+	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)
 }
 
-func TestFloatingIP(t *testing.T) {
-	pool := os.Getenv("OS_POOL_NAME")
-	if pool == "" {
-		t.Fatalf("OS_POOL_NAME must be set")
+func createFloatingIP(t *testing.T, client *gophercloud.ServiceClient, choices *ComputeChoices) (*floatingips.FloatingIP, error) {
+	createOpts := floatingips.CreateOpts{
+		Pool: choices.FloatingIPPoolName,
 	}
-
-	choices, err := ComputeChoicesFromEnv()
+	floatingIP, err := floatingips.Create(client, createOpts).Extract()
 	if err != nil {
-		t.Fatal(err)
+		return floatingIP, err
 	}
 
-	client, err := newClient()
+	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 create a compute client: %v", err)
+		t.Fatalf("Unable to delete floating IP %s: %v", floatingIP.ID, err)
 	}
 
-	server, err := createFIPServer(t, client, choices)
+	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 create server: %v", err)
-	}
-	defer func() {
-		servers.Delete(client, server.ID)
-		t.Logf("Server deleted.")
-	}()
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatalf("Unable to wait for server: %v", err)
+		t.Fatalf("Unable to disassociate floating IP %s from server %s: %v", floatingIP.IP, server.ID, err)
 	}
 
-	fip, err := createFloatingIP(t, client)
-	if err != nil {
-		t.Fatalf("Unable to create floating IP: %v", err)
-	}
-	defer func() {
-		err = floatingip.Delete(client, fip.ID).ExtractErr()
-		th.AssertNoErr(t, err)
-		t.Logf("Floating IP deleted.")
-	}()
+	t.Logf("Disassociated floating IP %s from server %s", floatingIP.IP, server.ID)
+}
 
-	associateFloatingIPDeprecated(t, client, server.ID, fip)
-	associateFloatingIP(t, client, server.ID, fip)
-	associateFloatingIPFixed(t, client, server.ID, fip)
-
+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)
 }
diff --git a/acceptance/openstack/compute/v2/images_test.go b/acceptance/openstack/compute/v2/images_test.go
index 9b740ad..15af9af 100644
--- a/acceptance/openstack/compute/v2/images_test.go
+++ b/acceptance/openstack/compute/v2/images_test.go
@@ -6,32 +6,56 @@
 	"testing"
 
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
-	"github.com/gophercloud/gophercloud/pagination"
 )
 
-func TestListImages(t *testing.T) {
+func TestImagesList(t *testing.T) {
 	client, err := newClient()
 	if err != nil {
 		t.Fatalf("Unable to create a compute: client: %v", err)
 	}
 
-	t.Logf("ID\tRegion\tName\tStatus\tCreated")
+	allPages, err := images.ListDetail(client, nil).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to retrieve images: %v", err)
+	}
 
-	pager := images.ListDetail(client, nil)
-	count, pages := 0, 0
-	pager.EachPage(func(page pagination.Page) (bool, error) {
-		pages++
-		images, err := images.ExtractImages(page)
-		if err != nil {
-			return false, err
-		}
+	allImages, err := images.ExtractImages(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract image results: %v", err)
+	}
 
-		for _, i := range images {
-			t.Logf("%s\t%s\t%s\t%s", i.ID, i.Name, i.Status, i.Created)
-		}
+	for _, image := range allImages {
+		printImage(t, image)
+	}
+}
 
-		return true, nil
-	})
+func TestImagesGet(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute: client: %v", err)
+	}
 
-	t.Logf("--------\n%d images listed on %d pages.", count, pages)
+	choices, err := ComputeChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	image, err := images.Get(client, choices.ImageID).Extract()
+	if err != nil {
+		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)
 }
diff --git a/acceptance/openstack/compute/v2/keypairs_test.go b/acceptance/openstack/compute/v2/keypairs_test.go
index 326c6f9..b9fb57a 100644
--- a/acceptance/openstack/compute/v2/keypairs_test.go
+++ b/acceptance/openstack/compute/v2/keypairs_test.go
@@ -1,4 +1,4 @@
-// +build acceptance
+// +build acceptance compute keypairs
 
 package v2
 
@@ -7,68 +7,180 @@
 	"crypto/rsa"
 	"testing"
 
+	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/acceptance/tools"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
-	th "github.com/gophercloud/gophercloud/testhelper"
 
 	"golang.org/x/crypto/ssh"
 )
 
 const keyName = "gophercloud_test_key_pair"
 
-func TestCreateServerWithKeyPair(t *testing.T) {
+func TestKeypairsList(t *testing.T) {
 	client, err := newClient()
-	th.AssertNoErr(t, err)
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
 
+	allPages, err := keypairs.List(client).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to retrieve keypairs: %s", err)
+	}
+
+	allKeys, err := keypairs.ExtractKeyPairs(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract keypairs results: %s", err)
+	}
+
+	for _, keypair := range allKeys {
+		printKeyPair(t, &keypair)
+	}
+}
+
+func TestKeypairsCreate(t *testing.T) {
+	client, err := newClient()
+	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()
+	if err != nil {
+		t.Fatalf("Unable to create keypair: %s", err)
+	}
+	defer deleteKeyPair(t, client, keyPair)
+
+	printKeyPair(t, keyPair)
+}
+
+func TestKeypairsImportPublicKey(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	publicKey, err := createKey()
+	if err != nil {
+		t.Fatalf("Unable to create public key: %s", err)
+	}
+
+	createOpts := keypairs.CreateOpts{
+		Name:      keyName,
+		PublicKey: publicKey,
+	}
+	keyPair, err := keypairs.Create(client, createOpts).Extract()
+	if err != nil {
+		t.Fatalf("Unable to create keypair: %s", err)
+	}
+	defer deleteKeyPair(t, client, keyPair)
+
+	printKeyPair(t, keyPair)
+}
+
+func TestKeypairsServerCreateWithKey(t *testing.T) {
 	if testing.Short() {
 		t.Skip("Skipping test that requires server creation in short mode.")
 	}
 
-	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
-	publicKey := privateKey.PublicKey
-	pub, err := ssh.NewPublicKey(&publicKey)
-	th.AssertNoErr(t, err)
-	pubBytes := ssh.MarshalAuthorizedKey(pub)
-	pk := string(pubBytes)
-
-	kp, err := keypairs.Create(client, keypairs.CreateOpts{
-		Name:      keyName,
-		PublicKey: pk,
-	}).Extract()
-	th.AssertNoErr(t, err)
-	t.Logf("Created key pair: %s\n", kp)
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
 
 	choices, err := ComputeChoicesFromEnv()
-	th.AssertNoErr(t, err)
+	if err != nil {
+		t.Fatal(err)
+	}
 
-	name := tools.RandomString("Gophercloud-", 8)
-	t.Logf("Creating server [%s] with key pair.", name)
+	publicKey, err := createKey()
+	if err != nil {
+		t.Fatalf("Unable to create public key: %s", err)
+	}
+
+	createOpts := keypairs.CreateOpts{
+		Name:      keyName,
+		PublicKey: publicKey,
+	}
+
+	keyPair, err := keypairs.Create(client, createOpts).Extract()
+	if err != nil {
+		t.Fatalf("Unable to create keypair: %s", err)
+	}
+	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()
-	th.AssertNoErr(t, err)
-	defer servers.Delete(client, server.ID)
+	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)
 
 	server, err = servers.Get(client, server.ID).Extract()
-	t.Logf("Created server: %+v\n", server)
-	th.AssertNoErr(t, err)
-	th.AssertEquals(t, server.KeyName, keyName)
+	if err != nil {
+		t.Fatalf("Unable to retrieve server: %s", err)
+	}
 
-	t.Logf("Deleting key pair [%s]...", kp.Name)
-	err = keypairs.Delete(client, keyName).ExtractErr()
-	th.AssertNoErr(t, err)
+	if server.KeyName != keyName {
+		t.Fatalf("key name of server %s is %s, not %s", server.ID, server.KeyName, keyName)
+	}
+}
 
-	t.Logf("Deleting server [%s]...", 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 615e391..c7ff1a3 100644
--- a/acceptance/openstack/compute/v2/network_test.go
+++ b/acceptance/openstack/compute/v2/network_test.go
@@ -3,15 +3,58 @@
 package v2
 
 import (
-	"os"
+	"fmt"
 	"testing"
 
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/networks"
-	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
-	th "github.com/gophercloud/gophercloud/testhelper"
 )
 
+func TestNetworksList(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	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 {
+		printNetwork(t, &network)
+	}
+}
+
+func TestNetworksGet(t *testing.T) {
+	choices, err := ComputeChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	networkID, err := getNetworkIDFromNetworks(t, client, choices.NetworkName)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	network, err := networks.Get(client, networkID).Extract()
+	if err != nil {
+		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 {
@@ -36,43 +79,53 @@
 	return networkID, nil
 }
 
-func TestNetworks(t *testing.T) {
-	networkName := os.Getenv("OS_NETWORK_NAME")
-	if networkName == "" {
-		t.Fatalf("OS_NETWORK_NAME must be set")
-	}
-
-	choices, err := ComputeChoicesFromEnv()
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	client, err := newClient()
-	if err != nil {
-		t.Fatalf("Unable to create a compute client: %v", err)
-	}
-
-	networkID, err := getNetworkIDFromNetworkExtension(t, client, networkName)
-	if err != nil {
-		t.Fatalf("Unable to get network ID: %v", err)
-	}
-
-	// createNetworkServer is defined in tenantnetworks_test.go
-	server, err := createNetworkServer(t, client, choices, networkID)
-	if err != nil {
-		t.Fatalf("Unable to create server: %v", err)
-	}
-	defer func() {
-		servers.Delete(client, server.ID)
-		t.Logf("Server deleted.")
-	}()
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatalf("Unable to wait for server: %v", err)
-	}
-
+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)
-	th.AssertNoErr(t, err)
-	t.Logf("Retrieved all %d networks: %+v", len(allNetworks), allNetworks)
+	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)
 }
diff --git a/acceptance/openstack/compute/v2/quotaset_test.go b/acceptance/openstack/compute/v2/quotaset_test.go
index d7883df..60b8563 100644
--- a/acceptance/openstack/compute/v2/quotaset_test.go
+++ b/acceptance/openstack/compute/v2/quotaset_test.go
@@ -1,60 +1,69 @@
-// +build acceptance compute
+// +build acceptance compute quotasets
 
 package v2
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/openstack"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets"
 	"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
-	"github.com/gophercloud/gophercloud/pagination"
-	th "github.com/gophercloud/gophercloud/testhelper"
 )
 
-func TestGetQuotaset(t *testing.T) {
+func TestQuotasetGet(t *testing.T) {
 	client, err := newClient()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	idclient := openstack.NewIdentityV2(client.ProviderClient)
-	quotaset, err := quotasets.Get(client, findTenant(t, idclient)).Extract()
+	identityClient, err := newIdentityClient()
+	if err != nil {
+		t.Fatalf("Unable to get a new identity client: %v", err)
+	}
+
+	tenantID, err := getTenantID(t, identityClient)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	t.Logf("QuotaSet details:\n")
-	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)
+	quotaSet, err := quotasets.Get(client, tenantID).Extract()
+	if err != nil {
+		t.Fatal(err)
+	}
 
+	printQuotaSet(t, quotaSet)
 }
 
-func findTenant(t *testing.T, client *gophercloud.ServiceClient) string {
-	var tenantID string
-	err := tenants.List(client, nil).EachPage(func(page pagination.Page) (bool, error) {
-		tenantList, err := tenants.ExtractTenants(page)
-		th.AssertNoErr(t, err)
+func getTenantID(t *testing.T, client *gophercloud.ServiceClient) (string, error) {
+	allPages, err := tenants.List(client, nil).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to get list of tenants: %v", err)
+	}
 
-		for _, t := range tenantList {
-			tenantID = t.ID
-			break
-		}
+	allTenants, err := tenants.ExtractTenants(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract tenants: %v", err)
+	}
 
-		return true, nil
-	})
-	th.AssertNoErr(t, err)
+	for _, tenant := range allTenants {
+		return tenant.ID, nil
+	}
 
-	return tenantID
+	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/secdefrules_test.go b/acceptance/openstack/compute/v2/secdefrules_test.go
deleted file mode 100644
index 15809e2..0000000
--- a/acceptance/openstack/compute/v2/secdefrules_test.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// +build acceptance compute defsecrules
-
-package v2
-
-import (
-	"testing"
-
-	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/acceptance/tools"
-	dsr "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules"
-	"github.com/gophercloud/gophercloud/pagination"
-	th "github.com/gophercloud/gophercloud/testhelper"
-)
-
-func TestSecDefRules(t *testing.T) {
-	client, err := newClient()
-	th.AssertNoErr(t, err)
-
-	id := createDefRule(t, client)
-
-	listDefRules(t, client)
-
-	getDefRule(t, client, id)
-
-	deleteDefRule(t, client, id)
-}
-
-func createDefRule(t *testing.T, client *gophercloud.ServiceClient) string {
-	opts := dsr.CreateOpts{
-		FromPort:   tools.RandomInt(80, 89),
-		ToPort:     tools.RandomInt(90, 99),
-		IPProtocol: "TCP",
-		CIDR:       "0.0.0.0/0",
-	}
-
-	rule, err := dsr.Create(client, opts).Extract()
-	th.AssertNoErr(t, err)
-
-	t.Logf("Created default rule %s", rule.ID)
-
-	return rule.ID
-}
-
-func listDefRules(t *testing.T, client *gophercloud.ServiceClient) {
-	err := dsr.List(client).EachPage(func(page pagination.Page) (bool, error) {
-		drList, err := dsr.ExtractDefaultRules(page)
-		th.AssertNoErr(t, err)
-
-		for _, dr := range drList {
-			t.Logf("Listing default rule %s: Name [%s] From Port [%s] To Port [%s] Protocol [%s]",
-				dr.ID, dr.FromPort, dr.ToPort, dr.IPProtocol)
-		}
-
-		return true, nil
-	})
-
-	th.AssertNoErr(t, err)
-}
-
-func getDefRule(t *testing.T, client *gophercloud.ServiceClient, id string) {
-	rule, err := dsr.Get(client, id).Extract()
-	th.AssertNoErr(t, err)
-
-	t.Logf("Getting rule %s: %#v", id, rule)
-}
-
-func deleteDefRule(t *testing.T, client *gophercloud.ServiceClient, id string) {
-	err := dsr.Delete(client, id).ExtractErr()
-	th.AssertNoErr(t, err)
-
-	t.Logf("Deleted rule %s", id)
-}
diff --git a/acceptance/openstack/compute/v2/secgroup_test.go b/acceptance/openstack/compute/v2/secgroup_test.go
index da06c35..8a2e7f7 100644
--- a/acceptance/openstack/compute/v2/secgroup_test.go
+++ b/acceptance/openstack/compute/v2/secgroup_test.go
@@ -8,188 +8,201 @@
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/acceptance/tools"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
-	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
-	"github.com/gophercloud/gophercloud/pagination"
-	th "github.com/gophercloud/gophercloud/testhelper"
 )
 
-func TestSecGroups(t *testing.T) {
+func TestSecGroupsList(t *testing.T) {
 	client, err := newClient()
-	th.AssertNoErr(t, err)
-
-	serverID, needsDeletion := findServer(t, client)
-
-	groupID := createSecGroup(t, client)
-
-	listSecGroups(t, client)
-
-	newName := tools.RandomString("secgroup_", 5)
-	updateSecGroup(t, client, groupID, newName)
-
-	getSecGroup(t, client, groupID)
-
-	addRemoveRules(t, client, groupID)
-
-	addServerToSecGroup(t, client, serverID, newName)
-
-	removeServerFromSecGroup(t, client, serverID, newName)
-
-	if needsDeletion {
-		servers.Delete(client, serverID)
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	deleteSecGroup(t, client, groupID)
+	allPages, err := secgroups.List(client).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to retrieve security groups: %v", err)
+	}
+
+	allSecGroups, err := secgroups.ExtractSecurityGroups(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract security groups: %v", err)
+	}
+
+	for _, secgroup := range allSecGroups {
+		printSecurityGroup(t, &secgroup)
+	}
 }
 
-func createSecGroup(t *testing.T, client *gophercloud.ServiceClient) string {
-	opts := secgroups.CreateOpts{
+func TestSecGroupsCreate(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	securityGroup, err := createSecurityGroup(t, client)
+	if err != nil {
+		t.Fatalf("Unable to create security group: %v", err)
+	}
+	defer deleteSecurityGroup(t, client, securityGroup)
+}
+
+func TestSecGroupsUpdate(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	securityGroup, err := createSecurityGroup(t, client)
+	if err != nil {
+		t.Fatalf("Unable to create security group: %v", err)
+	}
+	defer deleteSecurityGroup(t, client, securityGroup)
+
+	updateOpts := secgroups.UpdateOpts{
+		Name:        tools.RandomString("secgroup_", 4),
+		Description: tools.RandomString("dec_", 10),
+	}
+	updatedSecurityGroup, err := secgroups.Update(client, securityGroup.ID, updateOpts).Extract()
+	if err != nil {
+		t.Fatalf("Unable to update security group: %v", err)
+	}
+
+	t.Logf("Updated %s's name to %s", updatedSecurityGroup.ID, updatedSecurityGroup.Name)
+}
+
+func TestSecGroupsRuleCreate(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	securityGroup, err := createSecurityGroup(t, client)
+	if err != nil {
+		t.Fatalf("Unable to create security group: %v", err)
+	}
+	defer deleteSecurityGroup(t, client, securityGroup)
+
+	rule, err := createSecurityGroupRule(t, client, securityGroup.ID)
+	if err != nil {
+		t.Fatalf("Unable to create rule: %v", err)
+	}
+	defer deleteSecurityGroupRule(t, client, rule)
+}
+
+func TestSecGroupsAddGroupToServer(t *testing.T) {
+	if testing.Short() {
+		t.Skip("Skipping test that requires server creation in short mode.")
+	}
+
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	choices, err := ComputeChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	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)
+
+	securityGroup, err := createSecurityGroup(t, client)
+	if err != nil {
+		t.Fatalf("Unable to create security group: %v", err)
+	}
+	defer deleteSecurityGroup(t, client, securityGroup)
+
+	rule, err := createSecurityGroupRule(t, client, securityGroup.ID)
+	if err != nil {
+		t.Fatalf("Unable to create rule: %v", err)
+	}
+	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()
+	if err != nil && err.Error() != "EOF" {
+		t.Fatalf("Unable to add group %s to server %s: %s", securityGroup.ID, server.ID, err)
+	}
+
+	t.Logf("Removing group %s from server %s", securityGroup.ID, server.ID)
+	err = secgroups.RemoveServer(client, server.ID, securityGroup.Name).ExtractErr()
+	if err != nil && err.Error() != "EOF" {
+		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",
 	}
 
-	group, err := secgroups.Create(client, opts).Extract()
-	th.AssertNoErr(t, err)
-
-	t.Logf("Created secgroup %s %s", group.ID, group.Name)
-
-	return group.ID
-}
-
-func listSecGroups(t *testing.T, client *gophercloud.ServiceClient) {
-	err := secgroups.List(client).EachPage(func(page pagination.Page) (bool, error) {
-		secGrpList, err := secgroups.ExtractSecurityGroups(page)
-		th.AssertNoErr(t, err)
-
-		for _, sg := range secGrpList {
-			t.Logf("Listing secgroup %s: Name [%s] Desc [%s] TenantID [%s]", sg.ID,
-				sg.Name, sg.Description, sg.TenantID)
-		}
-
-		return true, nil
-	})
-
-	th.AssertNoErr(t, err)
-}
-
-func updateSecGroup(t *testing.T, client *gophercloud.ServiceClient, id, newName string) {
-	opts := secgroups.UpdateOpts{
-		Name:        newName,
-		Description: tools.RandomString("dec_", 10),
+	securityGroup, err := secgroups.Create(client, createOpts).Extract()
+	if err != nil {
+		return *securityGroup, err
 	}
-	group, err := secgroups.Update(client, id, opts).Extract()
-	th.AssertNoErr(t, err)
 
-	t.Logf("Updated %s's name to %s", group.ID, group.Name)
+	t.Logf("Created security group: %s", securityGroup.ID)
+	return *securityGroup, nil
 }
 
-func getSecGroup(t *testing.T, client *gophercloud.ServiceClient, id string) {
-	group, err := secgroups.Get(client, id).Extract()
-	th.AssertNoErr(t, err)
+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("Getting %s: %#v", id, group)
+	t.Logf("Deleted security group: %s", securityGroup.ID)
 }
 
-func addRemoveRules(t *testing.T, client *gophercloud.ServiceClient, id string) {
-	opts := secgroups.CreateRuleOpts{
-		ParentGroupID: id,
-		FromPort:      22,
-		ToPort:        22,
+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, opts).Extract()
-	th.AssertNoErr(t, err)
-
-	t.Logf("Adding rule %s to group %s", rule.ID, id)
-
-	err = secgroups.DeleteRule(client, rule.ID).ExtractErr()
-	th.AssertNoErr(t, err)
-
-	t.Logf("Deleted rule %s from group %s", rule.ID, id)
-
-	icmpOpts := secgroups.CreateRuleOpts{
-		ParentGroupID: id,
-		FromPort:      0,
-		ToPort:        0,
-		IPProtocol:    "ICMP",
-		CIDR:          "0.0.0.0/0",
+	rule, err := secgroups.CreateRule(client, createOpts).Extract()
+	if err != nil {
+		return *rule, err
 	}
 
-	icmpRule, err := secgroups.CreateRule(client, icmpOpts).Extract()
-	th.AssertNoErr(t, err)
-
-	t.Logf("Adding ICMP rule %s to group %s", icmpRule.ID, id)
-
-	err = secgroups.DeleteRule(client, icmpRule.ID).ExtractErr()
-	th.AssertNoErr(t, err)
-
-	t.Logf("Deleted ICMP rule %s from group %s", icmpRule.ID, id)
+	t.Logf("Created security group rule: %s", rule.ID)
+	return *rule, nil
 }
 
-func findServer(t *testing.T, client *gophercloud.ServiceClient) (string, bool) {
-	var serverID string
-	var needsDeletion bool
-
-	err := servers.List(client, nil).EachPage(func(page pagination.Page) (bool, error) {
-		sList, err := servers.ExtractServers(page)
-		th.AssertNoErr(t, err)
-
-		for _, s := range sList {
-			serverID = s.ID
-			needsDeletion = false
-
-			t.Logf("Found an existing server: ID [%s]", serverID)
-			break
-		}
-
-		return true, nil
-	})
-	th.AssertNoErr(t, err)
-
-	if serverID == "" {
-		t.Log("No server found, creating one")
-
-		choices, err := ComputeChoicesFromEnv()
-		th.AssertNoErr(t, err)
-
-		opts := &servers.CreateOpts{
-			Name:      tools.RandomString("secgroup_test_", 5),
-			ImageRef:  choices.ImageID,
-			FlavorRef: choices.FlavorID,
-		}
-
-		s, err := servers.Create(client, opts).Extract()
-		th.AssertNoErr(t, err)
-		serverID = s.ID
-
-		t.Logf("Created server %s, waiting for it to build", s.ID)
-		err = servers.WaitForStatus(client, serverID, "ACTIVE", 300)
-		th.AssertNoErr(t, err)
-
-		needsDeletion = true
+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)
 	}
 
-	return serverID, needsDeletion
+	t.Logf("Deleted security group rule: %s", rule.ID)
 }
 
-func addServerToSecGroup(t *testing.T, client *gophercloud.ServiceClient, serverID, groupName string) {
-	err := secgroups.AddServerToGroup(client, serverID, groupName).ExtractErr()
-	th.AssertNoErr(t, err)
+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:")
 
-	t.Logf("Adding group %s to server %s", groupName, serverID)
-}
-
-func removeServerFromSecGroup(t *testing.T, client *gophercloud.ServiceClient, serverID, groupName string) {
-	err := secgroups.RemoveServerFromGroup(client, serverID, groupName).ExtractErr()
-	th.AssertNoErr(t, err)
-
-	t.Logf("Removing group %s from server %s", groupName, serverID)
-}
-
-func deleteSecGroup(t *testing.T, client *gophercloud.ServiceClient, id string) {
-	err := secgroups.Delete(client, id).ExtractErr()
-	th.AssertNoErr(t, err)
-
-	t.Logf("Deleted group %s", id)
+	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 79f7c92..0ee47ea 100644
--- a/acceptance/openstack/compute/v2/servergroup_test.go
+++ b/acceptance/openstack/compute/v2/servergroup_test.go
@@ -1,9 +1,8 @@
-// +build acceptance compute servers
+// +build acceptance compute servergroups
 
 package v2
 
 import (
-	"fmt"
 	"testing"
 
 	"github.com/gophercloud/gophercloud"
@@ -11,43 +10,120 @@
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
-	th "github.com/gophercloud/gophercloud/testhelper"
 )
 
-func createServerGroup(t *testing.T, computeClient *gophercloud.ServiceClient) (*servergroups.ServerGroup, error) {
-	sg, err := servergroups.Create(computeClient, &servergroups.CreateOpts{
+func TestServergroupsList(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	allPages, err := servergroups.List(client).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to list server groups: %v", err)
+	}
+
+	allServerGroups, err := servergroups.ExtractServerGroups(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract server groups: %v", err)
+	}
+
+	for _, serverGroup := range allServerGroups {
+		printServerGroup(t, &serverGroup)
+	}
+}
+
+func TestServergroupsCreate(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	serverGroup, err := createServerGroup(t, client, "anti-affinity")
+	if err != nil {
+		t.Fatalf("Unable to create server group: %v", err)
+	}
+	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)
+}
+
+func TestServergroupsAffinityPolicy(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	choices, err := ComputeChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	serverGroup, err := createServerGroup(t, client, "affinity")
+	if err != nil {
+		t.Fatalf("Unable to create server group: %v", err)
+	}
+	defer deleteServerGroup(t, client, 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)
+
+	firstServer, err = servers.Get(client, firstServer.ID).Extract()
+
+	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 {
+		t.Fatalf("Unable to wait for server: %v", err)
+	}
+	defer deleteServer(t, client, secondServer)
+
+	secondServer, err = servers.Get(client, secondServer.ID).Extract()
+
+	if firstServer.HostID != secondServer.HostID {
+		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{"affinity"},
+		Policies: []string{policy},
 	}).Extract()
 
 	if err != nil {
 		t.Fatalf("Unable to create server group: %v", err)
 	}
 
-	t.Logf("Created server group: %v", sg.ID)
-	t.Logf("It has policies: %v", sg.Policies)
-
 	return sg, nil
 }
 
-func getServerGroup(t *testing.T, computeClient *gophercloud.ServiceClient, sgID string) error {
-	sg, err := servergroups.Get(computeClient, sgID).Extract()
-	if err != nil {
-		t.Fatalf("Unable to get server group: %v", err)
-	}
-
-	t.Logf("Got server group: %v", sg.Name)
-
-	return nil
-}
-
-func createServerInGroup(t *testing.T, computeClient *gophercloud.ServiceClient, choices *ComputeChoices, serverGroup *servergroups.ServerGroup) (*servers.Server, error) {
+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\n", name)
+	t.Logf("Attempting to create server: %s", name)
 
 	pwd := tools.MakeNewPassword("")
 
@@ -56,88 +132,38 @@
 		FlavorRef: choices.FlavorID,
 		ImageRef:  choices.ImageID,
 		AdminPass: pwd,
+		Networks: []servers.Network{
+			servers.Network{UUID: networkID},
+		},
 	}
-	server, err := servers.Create(computeClient, schedulerhints.CreateOptsExt{
+
+	schedulerHintsOpts := schedulerhints.CreateOptsExt{
 		serverCreateOpts,
 		schedulerhints.SchedulerHints{
 			Group: serverGroup.ID,
 		},
-	}).Extract()
+	}
+	server, err := servers.Create(client, schedulerHintsOpts).Extract()
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
 	}
 
-	th.AssertEquals(t, pwd, server.AdminPass)
-
 	return server, err
 }
 
-func verifySchedulerWorked(t *testing.T, firstServer, secondServer *servers.Server) error {
-	t.Logf("First server hostID: %v", firstServer.HostID)
-	t.Logf("Second server hostID: %v", secondServer.HostID)
-	if firstServer.HostID == secondServer.HostID {
-		return nil
+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)
 	}
 
-	return fmt.Errorf("%s and %s were not scheduled on the same host.", firstServer.ID, secondServer.ID)
+	t.Logf("Deleted server group %s", serverGroup.ID)
 }
 
-func TestServerGroups(t *testing.T) {
-	choices, err := ComputeChoicesFromEnv()
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	computeClient, err := newClient()
-	if err != nil {
-		t.Fatalf("Unable to create a compute client: %v", err)
-	}
-
-	sg, err := createServerGroup(t, computeClient)
-	if err != nil {
-		t.Fatalf("Unable to create server group: %v", err)
-	}
-	defer func() {
-		servergroups.Delete(computeClient, sg.ID)
-		t.Logf("Server Group deleted.")
-	}()
-
-	err = getServerGroup(t, computeClient, sg.ID)
-	if err != nil {
-		t.Fatalf("Unable to get server group: %v", err)
-	}
-
-	firstServer, err := createServerInGroup(t, computeClient, choices, sg)
-	if err != nil {
-		t.Fatalf("Unable to create server: %v", err)
-	}
-	defer func() {
-		servers.Delete(computeClient, firstServer.ID)
-		t.Logf("Server deleted.")
-	}()
-
-	if err = waitForStatus(computeClient, firstServer, "ACTIVE"); err != nil {
-		t.Fatalf("Unable to wait for server: %v", err)
-	}
-
-	firstServer, err = servers.Get(computeClient, firstServer.ID).Extract()
-
-	secondServer, err := createServerInGroup(t, computeClient, choices, sg)
-	if err != nil {
-		t.Fatalf("Unable to create server: %v", err)
-	}
-	defer func() {
-		servers.Delete(computeClient, secondServer.ID)
-		t.Logf("Server deleted.")
-	}()
-
-	if err = waitForStatus(computeClient, secondServer, "ACTIVE"); err != nil {
-		t.Fatalf("Unable to wait for server: %v", err)
-	}
-
-	secondServer, err = servers.Get(computeClient, secondServer.ID).Extract()
-
-	if err = verifySchedulerWorked(t, firstServer, secondServer); err != nil {
-		t.Fatalf("Scheduling did not work: %v", err)
-	}
+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 4c4fb42..34a3fd2 100644
--- a/acceptance/openstack/compute/v2/servers_test.go
+++ b/acceptance/openstack/compute/v2/servers_test.go
@@ -3,178 +3,93 @@
 package v2
 
 import (
-	"os"
 	"testing"
 
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/acceptance/tools"
-	"github.com/gophercloud/gophercloud/openstack"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
-	"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
-	"github.com/gophercloud/gophercloud/pagination"
 	th "github.com/gophercloud/gophercloud/testhelper"
 )
 
-func TestListServers(t *testing.T) {
+func TestServersList(t *testing.T) {
 	client, err := newClient()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	t.Logf("ID\tRegion\tName\tStatus\tIPv4\tIPv6")
+	allPages, err := servers.List(client, servers.ListOpts{}).AllPages()
+	if err != nil {
+		t.Fatalf("Unable to retrieve servers: %v", err)
+	}
 
-	pager := servers.List(client, servers.ListOpts{})
-	count, pages := 0, 0
-	pager.EachPage(func(page pagination.Page) (bool, error) {
-		pages++
-		t.Logf("---")
+	allServers, err := servers.ExtractServers(allPages)
+	if err != nil {
+		t.Fatalf("Unable to extract servers: %v", err)
+	}
 
-		servers, err := servers.ExtractServers(page)
-		if err != nil {
-			return false, err
-		}
-
-		for _, s := range servers {
-			t.Logf("%s\t%s\t%s\t%s\t%s\t\n", s.ID, s.Name, s.Status, s.AccessIPv4, s.AccessIPv6)
-			count++
-		}
-
-		return true, nil
-	})
-
-	t.Logf("--------\n%d servers listed on %d pages.\n", count, pages)
+	for _, server := range allServers {
+		printServer(t, &server)
+	}
 }
 
-func networkingClient() (*gophercloud.ServiceClient, error) {
-	opts, err := openstack.AuthOptionsFromEnv()
+func TestServersCreateDestroy(t *testing.T) {
+	client, err := newClient()
 	if err != nil {
-		return nil, err
+		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	provider, err := openstack.AuthenticatedClient(opts)
-	if err != nil {
-		return nil, err
-	}
-
-	return openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
-		Region: os.Getenv("OS_REGION_NAME"),
-	})
-}
-
-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.")
-	}
-
-	var network networks.Network
-
-	networkingClient, err := networkingClient()
-	if err != nil {
-		t.Fatalf("Unable to create a networking client: %v", err)
-	}
-
-	pager := networks.List(networkingClient, networks.ListOpts{
-		Name:  choices.NetworkName,
-		Limit: 1,
-	})
-	pager.EachPage(func(page pagination.Page) (bool, error) {
-		networks, err := networks.ExtractNetworks(page)
-		if err != nil {
-			t.Errorf("Failed to extract networks: %v", err)
-			return false, err
-		}
-
-		if len(networks) == 0 {
-			t.Fatalf("No networks to attach to server")
-			return false, err
-		}
-
-		network = networks[0]
-
-		return false, nil
-	})
-
-	name := tools.RandomString("ACPTTEST", 16)
-	t.Logf("Attempting to create server: %s\n", name)
-
-	pwd := tools.MakeNewPassword("")
-
-	server, err := servers.Create(client, servers.CreateOpts{
-		Name:      name,
-		FlavorRef: choices.FlavorID,
-		ImageRef:  choices.ImageID,
-		Networks: []servers.Network{
-			servers.Network{UUID: network.ID},
-		},
-		AdminPass: pwd,
-		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 TestCreateDestroyServer(t *testing.T) {
 	choices, err := ComputeChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	client, err := newClient()
-	if err != nil {
-		t.Fatalf("Unable to create a compute client: %v", err)
-	}
-
 	server, err := createServer(t, client, choices)
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
 	}
-	defer func() {
-		servers.Delete(client, server.ID)
-		t.Logf("Server deleted.")
-	}()
 
 	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
 		t.Fatalf("Unable to wait for server: %v", err)
 	}
+	defer deleteServer(t, client, server)
 
-	pager := servers.ListAddresses(client, server.ID)
-	pager.EachPage(func(page pagination.Page) (bool, error) {
-		networks, err := servers.ExtractAddresses(page)
-		if err != nil {
-			return false, err
-		}
+	newServer, err := servers.Get(client, server.ID).Extract()
+	if err != nil {
+		t.Errorf("Unable to retrieve server: %v", err)
+	}
+	printServer(t, newServer)
 
-		for n, a := range networks {
-			t.Logf("%s: %+v\n", n, a)
-		}
-		return true, nil
-	})
+	allAddressPages, err := servers.ListAddresses(client, server.ID).AllPages()
+	if err != nil {
+		t.Errorf("Unable to list server addresses: %v", err)
+	}
 
-	pager = servers.ListAddressesByNetwork(client, server.ID, choices.NetworkName)
-	pager.EachPage(func(page pagination.Page) (bool, error) {
-		addresses, err := servers.ExtractNetworkAddresses(page)
-		if err != nil {
-			return false, err
-		}
+	allAddresses, err := servers.ExtractAddresses(allAddressPages)
+	if err != nil {
+		t.Errorf("Unable to extract server addresses: %v", err)
+	}
 
-		for _, a := range addresses {
-			t.Logf("%+v\n", a)
-		}
-		return true, nil
-	})
+	for network, address := range allAddresses {
+		t.Logf("Addresses on %s: %+v", network, address)
+	}
+
+	allNetworkAddressPages, err := servers.ListAddressesByNetwork(client, server.ID, choices.NetworkName).AllPages()
+	if err != nil {
+		t.Errorf("Unable to list server addresses: %v", err)
+	}
+
+	allNetworkAddresses, err := servers.ExtractNetworkAddresses(allNetworkAddressPages)
+	if err != nil {
+		t.Errorf("Unable to extract server addresses: %v", err)
+	}
+
+	t.Logf("Addresses on %s:", choices.NetworkName)
+	for _, address := range allNetworkAddresses {
+		t.Logf("%+v", address)
+	}
 }
 
-func TestUpdateServer(t *testing.T) {
+func TestServersUpdate(t *testing.T) {
 	client, err := newClient()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
@@ -189,11 +104,11 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	defer servers.Delete(client, server.ID)
 
 	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
 		t.Fatal(err)
 	}
+	defer deleteServer(t, client, server)
 
 	alternateName := tools.RandomString("ACPTTEST", 16)
 	for alternateName == server.Name {
@@ -202,7 +117,11 @@
 
 	t.Logf("Attempting to rename the server to %s.", alternateName)
 
-	updated, err := servers.Update(client, server.ID, servers.UpdateOpts{Name: alternateName}).Extract()
+	updateOpts := servers.UpdateOpts{
+		Name: alternateName,
+	}
+
+	updated, err := servers.Update(client, server.ID, updateOpts).Extract()
 	if err != nil {
 		t.Fatalf("Unable to rename server: %v", err)
 	}
@@ -221,7 +140,73 @@
 	})
 }
 
-func TestActionChangeAdminPassword(t *testing.T) {
+func TestServersMetadata(t *testing.T) {
+	t.Parallel()
+
+	choices, err := ComputeChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	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)
+
+	metadata, err := servers.UpdateMetadata(client, server.ID, servers.MetadataOpts{
+		"foo":  "bar",
+		"this": "that",
+	}).Extract()
+	if err != nil {
+		t.Fatalf("Unable to update metadata: %v", err)
+	}
+	t.Logf("UpdateMetadata result: %+v\n", metadata)
+
+	err = servers.DeleteMetadatum(client, server.ID, "foo").ExtractErr()
+	if err != nil {
+		t.Fatalf("Unable to delete metadatum: %v", err)
+	}
+
+	metadata, err = servers.CreateMetadatum(client, server.ID, servers.MetadatumOpts{
+		"foo": "baz",
+	}).Extract()
+	if err != nil {
+		t.Fatalf("Unable to create metadatum: %v", err)
+	}
+	t.Logf("CreateMetadatum result: %+v\n", metadata)
+
+	metadata, err = servers.Metadatum(client, server.ID, "foo").Extract()
+	if err != nil {
+		t.Fatalf("Unable to get metadatum: %v", err)
+	}
+	t.Logf("Metadatum result: %+v\n", metadata)
+	th.AssertEquals(t, "baz", metadata["foo"])
+
+	metadata, err = servers.Metadata(client, server.ID).Extract()
+	if err != nil {
+		t.Fatalf("Unable to get metadata: %v", err)
+	}
+	t.Logf("Metadata result: %+v\n", metadata)
+
+	metadata, err = servers.ResetMetadata(client, server.ID, servers.MetadataOpts{}).Extract()
+	if err != nil {
+		t.Fatalf("Unable to reset metadata: %v", err)
+	}
+	t.Logf("ResetMetadata result: %+v\n", metadata)
+	th.AssertDeepEquals(t, map[string]string{}, metadata)
+}
+
+func TestServersActionChangeAdminPassword(t *testing.T) {
 	t.Parallel()
 
 	client, err := newClient()
@@ -238,16 +223,16 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	defer servers.Delete(client, server.ID)
 
 	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
 		t.Fatal(err)
 	}
+	defer deleteServer(t, client, server)
 
 	randomPassword := tools.MakeNewPassword(server.AdminPass)
 	res := servers.ChangeAdminPassword(client, server.ID, randomPassword)
 	if res.Err != nil {
-		t.Fatal(err)
+		t.Fatal(res.Err)
 	}
 
 	if err = waitForStatus(client, server, "PASSWORD"); err != nil {
@@ -259,7 +244,7 @@
 	}
 }
 
-func TestActionReboot(t *testing.T) {
+func TestServersActionReboot(t *testing.T) {
 	t.Parallel()
 
 	client, err := newClient()
@@ -276,21 +261,20 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	defer servers.Delete(client, server.ID)
 
 	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
 		t.Fatal(err)
 	}
+	defer deleteServer(t, client, server)
 
-	res := servers.Reboot(client, server.ID, "aldhjflaskhjf")
-	if res.Err == nil {
-		t.Fatal("Expected the SDK to provide an ArgumentError here")
+	rebootOpts := &servers.RebootOpts{
+		Type: servers.SoftReboot,
 	}
 
 	t.Logf("Attempting reboot of server %s", server.ID)
-	res = servers.Reboot(client, server.ID, servers.OSReboot)
+	res := servers.Reboot(client, server.ID, rebootOpts)
 	if res.Err != nil {
-		t.Fatalf("Unable to reboot server: %v", err)
+		t.Fatalf("Unable to reboot server: %v", res.Err)
 	}
 
 	if err = waitForStatus(client, server, "REBOOT"); err != nil {
@@ -302,7 +286,7 @@
 	}
 }
 
-func TestActionRebuild(t *testing.T) {
+func TestServersActionRebuild(t *testing.T) {
 	t.Parallel()
 
 	client, err := newClient()
@@ -319,11 +303,11 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	defer servers.Delete(client, server.ID)
 
 	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
 		t.Fatal(err)
 	}
+	defer deleteServer(t, client, server)
 
 	t.Logf("Attempting to rebuild server %s", server.ID)
 
@@ -351,13 +335,118 @@
 	}
 }
 
-func resizeServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server, choices *ComputeChoices) {
-	if err := waitForStatus(client, server, "ACTIVE"); err != nil {
+func TestServersActionResizeConfirm(t *testing.T) {
+	t.Parallel()
+
+	choices, err := ComputeChoicesFromEnv()
+	if err != nil {
 		t.Fatal(err)
 	}
 
-	t.Logf("Attempting to resize server [%s]", server.ID)
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
 
+	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)
+
+	t.Logf("Attempting to resize server %s", server.ID)
+	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 {
+		t.Fatal(err)
+	}
+}
+
+func TestServersActionResizeRevert(t *testing.T) {
+	t.Parallel()
+
+	choices, err := ComputeChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
+	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)
+
+	t.Logf("Attempting to resize server %s", server.ID)
+	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 {
+		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,
 	}
@@ -370,115 +459,33 @@
 	}
 }
 
-func TestActionResizeConfirm(t *testing.T) {
-	t.Parallel()
-
-	choices, err := ComputeChoicesFromEnv()
+func deleteServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) {
+	err := servers.Delete(client, server.ID).ExtractErr()
 	if err != nil {
-		t.Fatal(err)
+		t.Fatalf("Unable to delete server %s: %s", server.ID, err)
 	}
 
-	client, err := newClient()
-	if err != nil {
-		t.Fatalf("Unable to create a compute client: %v", err)
-	}
-
-	server, err := createServer(t, client, choices)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer servers.Delete(client, server.ID)
-	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(err)
-	}
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatal(err)
-	}
+	t.Logf("Deleted server: %s", server.ID)
 }
 
-func TestActionResizeRevert(t *testing.T) {
-	t.Parallel()
-
-	choices, err := ComputeChoicesFromEnv()
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	client, err := newClient()
-	if err != nil {
-		t.Fatalf("Unable to create a compute client: %v", err)
-	}
-
-	server, err := createServer(t, client, choices)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer servers.Delete(client, server.ID)
-	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(err)
-	}
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatal(err)
-	}
-}
-
-func TestServerMetadata(t *testing.T) {
-	t.Parallel()
-
-	choices, err := ComputeChoicesFromEnv()
-	th.AssertNoErr(t, err)
-
-	client, err := newClient()
-	if err != nil {
-		t.Fatalf("Unable to create a compute client: %v", err)
-	}
-
-	server, err := createServer(t, client, choices)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer servers.Delete(client, server.ID)
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatal(err)
-	}
-
-	metadata, err := servers.UpdateMetadata(client, server.ID, servers.MetadataOpts{
-		"foo":  "bar",
-		"this": "that",
-	}).Extract()
-	th.AssertNoErr(t, err)
-	t.Logf("UpdateMetadata result: %+v\n", metadata)
-
-	err = servers.DeleteMetadatum(client, server.ID, "foo").ExtractErr()
-	th.AssertNoErr(t, err)
-
-	metadata, err = servers.CreateMetadatum(client, server.ID, servers.MetadatumOpts{
-		"foo": "baz",
-	}).Extract()
-	th.AssertNoErr(t, err)
-	t.Logf("CreateMetadatum result: %+v\n", metadata)
-
-	metadata, err = servers.Metadatum(client, server.ID, "foo").Extract()
-	th.AssertNoErr(t, err)
-	t.Logf("Metadatum result: %+v\n", metadata)
-	th.AssertEquals(t, "baz", metadata["foo"])
-
-	metadata, err = servers.Metadata(client, server.ID).Extract()
-	th.AssertNoErr(t, err)
-	t.Logf("Metadata result: %+v\n", metadata)
-
-	metadata, err = servers.ResetMetadata(client, server.ID, servers.MetadataOpts{}).Extract()
-	th.AssertNoErr(t, err)
-	t.Logf("ResetMetadata result: %+v\n", metadata)
-	th.AssertDeepEquals(t, map[string]string{}, metadata)
+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 58208c0..3092077 100644
--- a/acceptance/openstack/compute/v2/tenantnetworks_test.go
+++ b/acceptance/openstack/compute/v2/tenantnetworks_test.go
@@ -3,77 +3,35 @@
 package v2
 
 import (
-	"os"
+	"fmt"
 	"testing"
 
 	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/acceptance/tools"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks"
-	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
-	th "github.com/gophercloud/gophercloud/testhelper"
 )
 
-func getNetworkID(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
+func TestTenantNetworksList(t *testing.T) {
+	client, err := newClient()
+	if err != nil {
+		t.Fatalf("Unable to create a compute client: %v", err)
+	}
+
 	allPages, err := tenantnetworks.List(client).AllPages()
 	if err != nil {
 		t.Fatalf("Unable to list networks: %v", err)
 	}
 
-	networkList, err := tenantnetworks.ExtractNetworks(allPages)
+	allTenantNetworks, err := tenantnetworks.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.Name == networkName {
-			networkID = network.ID
-		}
+	for _, network := range allTenantNetworks {
+		printTenantNetwork(t, &network)
 	}
-
-	t.Logf("Found network ID for %s: %s\n", networkName, networkID)
-
-	return networkID, nil
 }
 
-func createNetworkServer(t *testing.T, client *gophercloud.ServiceClient, choices *ComputeChoices, networkID string) (*servers.Server, error) {
-	if testing.Short() {
-		t.Skip("Skipping test that requires server creation in short mode.")
-	}
-
-	name := tools.RandomString("ACPTTEST", 16)
-	t.Logf("Attempting to create server: %s\n", name)
-
-	pwd := tools.MakeNewPassword("")
-
-	networks := make([]servers.Network, 1)
-	networks[0] = servers.Network{
-		UUID: networkID,
-	}
-
-	server, err := servers.Create(client, servers.CreateOpts{
-		Name:      name,
-		FlavorRef: choices.FlavorID,
-		ImageRef:  choices.ImageID,
-		AdminPass: pwd,
-		Networks:  networks,
-	}).Extract()
-	if err != nil {
-		t.Fatalf("Unable to create server: %v", err)
-	}
-
-	th.AssertEquals(t, pwd, server.AdminPass)
-
-	return server, err
-}
-
-func TestTenantNetworks(t *testing.T) {
-	networkName := os.Getenv("OS_NETWORK_NAME")
-	if networkName == "" {
-		t.Fatalf("OS_NETWORK_NAME must be set")
-	}
-
+func TestTenantNetworksGet(t *testing.T) {
 	choices, err := ComputeChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
@@ -84,26 +42,41 @@
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	networkID, err := getNetworkID(t, client, networkName)
+	networkID, err := getNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
 	if err != nil {
-		t.Fatalf("Unable to get network ID: %v", err)
+		t.Fatal(err)
 	}
 
-	server, err := createNetworkServer(t, client, choices, networkID)
+	network, err := tenantnetworks.Get(client, networkID).Extract()
 	if err != nil {
-		t.Fatalf("Unable to create server: %v", err)
-	}
-	defer func() {
-		servers.Delete(client, server.ID)
-		t.Logf("Server deleted.")
-	}()
-
-	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
-		t.Fatalf("Unable to wait for server: %v", err)
+		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()
-	allNetworks, err := tenantnetworks.ExtractNetworks(allPages)
-	th.AssertNoErr(t, err)
-	t.Logf("Retrieved all %d networks: %+v", len(allNetworks), allNetworks)
+	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)
 }
diff --git a/acceptance/openstack/compute/v2/volumeattach_test.go b/acceptance/openstack/compute/v2/volumeattach_test.go
index 459d283..a1b10a9 100644
--- a/acceptance/openstack/compute/v2/volumeattach_test.go
+++ b/acceptance/openstack/compute/v2/volumeattach_test.go
@@ -1,125 +1,126 @@
-// +build acceptance compute servers
+// +build acceptance compute volumeattach
 
 package v2
 
 import (
-	"os"
 	"testing"
 
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/acceptance/tools"
-	"github.com/gophercloud/gophercloud/openstack"
 	"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"
-	th "github.com/gophercloud/gophercloud/testhelper"
 )
 
-func newBlockClient(t *testing.T) (*gophercloud.ServiceClient, error) {
-	ao, err := openstack.AuthOptionsFromEnv()
-	th.AssertNoErr(t, err)
-
-	client, err := openstack.AuthenticatedClient(ao)
-	th.AssertNoErr(t, err)
-
-	return openstack.NewBlockStorageV1(client, gophercloud.EndpointOpts{
-		Region: os.Getenv("OS_REGION_NAME"),
-	})
-}
-
-func createVAServer(t *testing.T, computeClient *gophercloud.ServiceClient, choices *ComputeChoices) (*servers.Server, error) {
+func TestVolumeAttachAttachment(t *testing.T) {
 	if testing.Short() {
 		t.Skip("Skipping test that requires server creation in short mode.")
 	}
 
-	name := tools.RandomString("ACPTTEST", 16)
-	t.Logf("Attempting to create server: %s\n", name)
-
-	pwd := tools.MakeNewPassword("")
-
-	server, err := servers.Create(computeClient, servers.CreateOpts{
-		Name:      name,
-		FlavorRef: choices.FlavorID,
-		ImageRef:  choices.ImageID,
-		AdminPass: pwd,
-	}).Extract()
+	client, err := newClient()
 	if err != nil {
-		t.Fatalf("Unable to create server: %v", err)
+		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	th.AssertEquals(t, pwd, server.AdminPass)
-
-	return server, err
-}
-
-func createVAVolume(t *testing.T, blockClient *gophercloud.ServiceClient) (*volumes.Volume, error) {
-	volume, err := volumes.Create(blockClient, &volumes.CreateOpts{
-		Size: 1,
-		Name: "gophercloud-test-volume",
-	}).Extract()
-	th.AssertNoErr(t, err)
-	defer func() {
-		err = volumes.WaitForStatus(blockClient, volume.ID, "available", 60)
-		th.AssertNoErr(t, err)
-	}()
-
-	return volume, err
-}
-
-func createVolumeAttachment(t *testing.T, computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, volumeId string) {
-	va, err := volumeattach.Create(computeClient, serverId, &volumeattach.CreateOpts{
-		VolumeID: volumeId,
-	}).Extract()
-	th.AssertNoErr(t, err)
-	defer func() {
-		err = volumes.WaitForStatus(blockClient, volumeId, "in-use", 60)
-		th.AssertNoErr(t, err)
-		err = volumeattach.Delete(computeClient, serverId, va.ID).ExtractErr()
-		th.AssertNoErr(t, err)
-		err = volumes.WaitForStatus(blockClient, volumeId, "available", 60)
-		th.AssertNoErr(t, err)
-	}()
-}
-
-func TestAttachVolume(t *testing.T) {
 	choices, err := ComputeChoicesFromEnv()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	computeClient, err := newClient()
-	if err != nil {
-		t.Fatalf("Unable to create a compute client: %v", err)
-	}
-
-	blockClient, err := newBlockClient(t)
+	blockClient, err := newBlockClient()
 	if err != nil {
 		t.Fatalf("Unable to create a blockstorage client: %v", err)
 	}
 
-	server, err := createVAServer(t, computeClient, choices)
+	server, err := createServer(t, client, choices)
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
 	}
-	defer func() {
-		servers.Delete(computeClient, server.ID)
-		t.Logf("Server deleted.")
-	}()
 
-	if err = waitForStatus(computeClient, server, "ACTIVE"); err != nil {
+	if err = waitForStatus(client, server, "ACTIVE"); err != nil {
 		t.Fatalf("Unable to wait for server: %v", err)
 	}
+	defer deleteServer(t, client, server)
 
-	volume, err := createVAVolume(t, blockClient)
+	volume, err := createVolume(t, blockClient)
 	if err != nil {
 		t.Fatalf("Unable to create volume: %v", err)
 	}
-	defer func() {
-		err = volumes.Delete(blockClient, volume.ID).ExtractErr()
-		th.AssertNoErr(t, err)
-		t.Logf("Volume deleted.")
-	}()
 
-	createVolumeAttachment(t, computeClient, blockClient, server.ID, volume.ID)
+	if err = volumes.WaitForStatus(blockClient, volume.ID, "available", 60); err != nil {
+		t.Fatalf("Unable to wait for volume: %v", err)
+	}
+	defer deleteVolume(t, blockClient, 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)
+
+	printVolumeAttachment(t, volumeAttachment)
+
+}
+
+func createVolume(t *testing.T, blockClient *gophercloud.ServiceClient) (*volumes.Volume, error) {
+	volumeName := tools.RandomString("ACPTTEST", 16)
+	createOpts := volumes.CreateOpts{
+		Size: 1,
+		Name: volumeName,
+	}
+
+	volume, err := volumes.Create(blockClient, createOpts).Extract()
+	if err != nil {
+		return volume, err
+	}
+
+	t.Logf("Created volume: %s", volume.ID)
+	return volume, nil
+}
+
+func deleteVolume(t *testing.T, blockClient *gophercloud.ServiceClient, volume *volumes.Volume) {
+	err := volumes.Delete(blockClient, volume.ID).ExtractErr()
+	if err != nil {
+		t.Fatalf("Unable to delete volume: %v", err)
+	}
+
+	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)
 }
diff --git a/openstack/blockstorage/v1/volumes/results.go b/openstack/blockstorage/v1/volumes/results.go
index b056b8c..5c954bf 100644
--- a/openstack/blockstorage/v1/volumes/results.go
+++ b/openstack/blockstorage/v1/volumes/results.go
@@ -18,7 +18,7 @@
 	// Indicates whether this is a bootable volume.
 	Bootable string `json:"bootable"`
 	// The date when this volume was created.
-	CreatedAt gophercloud.JSONRFC3339Milli `json:"created_at"`
+	CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
 	// Human-readable description for the volume.
 	Description string `json:"display_description"`
 	// The type of volume to create, either SATA or SSD.
diff --git a/openstack/blockstorage/v1/volumes/testing/fixtures.go b/openstack/blockstorage/v1/volumes/testing/fixtures.go
index 421cbf4..c841676 100644
--- a/openstack/blockstorage/v1/volumes/testing/fixtures.go
+++ b/openstack/blockstorage/v1/volumes/testing/fixtures.go
@@ -66,7 +66,7 @@
 			                "device": "/"
 			            }
 			        ],
-			        "created_at": "2012-02-14T20:53:07Z"
+			        "created_at": "2012-02-14T20:53:07"
 			    }
 			}
       `)
diff --git a/openstack/blockstorage/v1/volumes/testing/requests_test.go b/openstack/blockstorage/v1/volumes/testing/requests_test.go
index 85a6cd8..898aca5 100644
--- a/openstack/blockstorage/v1/volumes/testing/requests_test.go
+++ b/openstack/blockstorage/v1/volumes/testing/requests_test.go
@@ -98,7 +98,7 @@
 		},
 		AvailabilityZone: "us-east1",
 		Bootable:         "false",
-		CreatedAt:        gophercloud.JSONRFC3339Milli(time.Date(2012, 2, 14, 20, 53, 07, 0, time.UTC)),
+		CreatedAt:        gophercloud.JSONRFC3339MilliNoZ(time.Date(2012, 2, 14, 20, 53, 07, 0, time.UTC)),
 		Description:      "Another volume.",
 		VolumeType:       "289da7f8-6440-407c-9fb4-7db01ec49164",
 		SnapshotID:       "",
diff --git a/openstack/client.go b/openstack/client.go
index fdaee49..ebad6dc 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -175,16 +175,16 @@
 	}
 
 	v3Opts := tokens3.AuthOptions{
-		IdentityEndpoint: options.IdentityEndpoint,
-		Username:         options.Username,
-		UserID:           options.UserID,
-		Password:         options.Password,
-		DomainID:         options.DomainID,
-		DomainName:       options.DomainName,
-		TenantID:         options.TenantID,
-		TenantName:       options.TenantName,
-		AllowReauth:      options.AllowReauth,
-		TokenID:          options.TokenID,
+		IdentityEndpoint: v3Options.IdentityEndpoint,
+		Username:         v3Options.Username,
+		UserID:           v3Options.UserID,
+		Password:         v3Options.Password,
+		DomainID:         v3Options.DomainID,
+		DomainName:       v3Options.DomainName,
+		TenantID:         v3Options.TenantID,
+		TenantName:       v3Options.TenantName,
+		AllowReauth:      v3Options.AllowReauth,
+		TokenID:          v3Options.TokenID,
 	}
 
 	result := tokens3.Create(v3Client, v3Opts, scope)
diff --git a/openstack/compute/v2/servers/results.go b/openstack/compute/v2/servers/results.go
index 023d0dd..7659c75 100644
--- a/openstack/compute/v2/servers/results.go
+++ b/openstack/compute/v2/servers/results.go
@@ -3,13 +3,13 @@
 import (
 	"crypto/rsa"
 	"encoding/base64"
+	"encoding/json"
 	"fmt"
 	"net/url"
 	"path"
 
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/pagination"
-	"github.com/mitchellh/mapstructure"
 )
 
 type serverResult struct {
@@ -75,20 +75,14 @@
 // If privateKey == nil the encrypted password is returned and can be decrypted with:
 //   echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key>
 func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) {
-
-	if r.Err != nil {
-		return "", r.Err
+	var s struct {
+		Password string `json:"password"`
 	}
-
-	var response struct {
-		Password string `mapstructure:"password"`
+	err := r.ExtractInto(&s)
+	if err == nil && privateKey != nil && s.Password != "" {
+		return decryptPassword(s.Password, privateKey)
 	}
-
-	err := mapstructure.Decode(r.Body, &response)
-	if err == nil && privateKey != nil && response.Password != "" {
-		return decryptPassword(response.Password, privateKey)
-	}
-	return response.Password, err
+	return s.Password, err
 }
 
 func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) {
@@ -136,58 +130,68 @@
 type Server struct {
 	// ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
 	ID string `json:"id"`
-
 	// TenantID identifies the tenant owning this server resource.
 	TenantID string `json:"tenant_id"`
-
 	// UserID uniquely identifies the user account owning the tenant.
 	UserID string `json:"user_id"`
-
 	// Name contains the human-readable name for the server.
 	Name string `json:"name"`
-
 	// Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
 	Updated string
 	Created string
-
-	HostID string
-
+	HostID  string
 	// Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE.
 	Status string
-
 	// Progress ranges from 0..100.
 	// A request made against the server completes only once Progress reaches 100.
 	Progress int
-
 	// AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
 	AccessIPv4, AccessIPv6 string
-
 	// Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
 	Image map[string]interface{}
-
 	// Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
 	Flavor map[string]interface{}
-
 	// Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
 	Addresses map[string]interface{}
-
 	// Metadata includes a list of all user-specified key-value pairs attached to the server.
 	Metadata map[string]interface{}
-
 	// Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
 	Links []interface{}
-
 	// KeyName indicates which public key was injected into the server on launch.
 	KeyName string `json:"key_name"`
-
 	// AdminPass will generally be empty ("").  However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place.
 	// Note that this is the ONLY time this field will be valid.
 	AdminPass string `json:"adminPass"`
-
 	// SecurityGroups includes the security groups that this instance has applied to it
 	SecurityGroups []map[string]interface{} `json:"security_groups"`
 }
 
+func (s *Server) UnmarshalJSON(b []byte) error {
+	type tmp Server
+	var server *struct {
+		tmp
+		Image interface{}
+	}
+	err := json.Unmarshal(b, &server)
+	if err != nil {
+		return err
+	}
+
+	*s = Server(server.tmp)
+
+	switch t := server.Image.(type) {
+	case map[string]interface{}:
+		s.Image = t
+	case string:
+		switch t {
+		case "":
+			s.Image = nil
+		}
+	}
+
+	return nil
+}
+
 // ServerPage abstracts the raw results of making a List() request against the API.
 // As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
 // data provided through the ExtractServers call.
diff --git a/openstack/compute/v2/servers/testing/fixtures.go b/openstack/compute/v2/servers/testing/fixtures.go
index b4fb7ff..613dc1e 100644
--- a/openstack/compute/v2/servers/testing/fixtures.go
+++ b/openstack/compute/v2/servers/testing/fixtures.go
@@ -154,7 +154,69 @@
 			"OS-EXT-STS:power_state": 1,
 			"config_drive": "",
 			"metadata": {}
-		}
+		},
+		{
+		"status": "ACTIVE",
+		"updated": "2014-09-25T13:04:49Z",
+		"hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
+		"OS-EXT-SRV-ATTR:host": "devstack",
+		"addresses": {
+			"private": [
+				{
+					"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
+					"version": 4,
+					"addr": "10.0.0.31",
+					"OS-EXT-IPS:type": "fixed"
+				}
+			]
+		},
+		"links": [
+			{
+				"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+				"rel": "self"
+			},
+			{
+				"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+				"rel": "bookmark"
+			}
+		],
+		"key_name": null,
+		"image": "",
+		"OS-EXT-STS:task_state": null,
+		"OS-EXT-STS:vm_state": "active",
+		"OS-EXT-SRV-ATTR:instance_name": "instance-0000001d",
+		"OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000",
+		"OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
+		"flavor": {
+			"id": "1",
+			"links": [
+				{
+					"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
+					"rel": "bookmark"
+				}
+			]
+		},
+		"id": "9e5476bd-a4ec-4653-93d6-72c93aa682bb",
+		"security_groups": [
+			{
+				"name": "default"
+			}
+		],
+		"OS-SRV-USG:terminated_at": null,
+		"OS-EXT-AZ:availability_zone": "nova",
+		"user_id": "9349aff8be7545ac9d2f1d00999a23cd",
+		"name": "merp",
+		"created": "2014-09-25T13:04:41Z",
+		"tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
+		"OS-DCF:diskConfig": "MANUAL",
+		"os-extended-volumes:volumes_attached": [],
+		"accessIPv4": "",
+		"accessIPv6": "",
+		"progress": 0,
+		"OS-EXT-STS:power_state": 1,
+		"config_drive": "",
+		"metadata": {}
+	}
 	]
 }
 `
@@ -353,6 +415,54 @@
 			},
 		},
 	}
+
+	// ServerMerp is a Server struct that should correspond to the second server in ServerListBody.
+	ServerMerp = servers.Server{
+		Status:  "ACTIVE",
+		Updated: "2014-09-25T13:04:49Z",
+		HostID:  "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
+		Addresses: map[string]interface{}{
+			"private": []interface{}{
+				map[string]interface{}{
+					"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
+					"version":                 float64(4),
+					"addr":                    "10.0.0.31",
+					"OS-EXT-IPS:type":         "fixed",
+				},
+			},
+		},
+		Links: []interface{}{
+			map[string]interface{}{
+				"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+				"rel":  "self",
+			},
+			map[string]interface{}{
+				"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+				"rel":  "bookmark",
+			},
+		},
+		Image: nil,
+		Flavor: map[string]interface{}{
+			"id": "1",
+			"links": []interface{}{
+				map[string]interface{}{
+					"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
+					"rel":  "bookmark",
+				},
+			},
+		},
+		ID:       "9e5476bd-a4ec-4653-93d6-72c93aa682bb",
+		UserID:   "9349aff8be7545ac9d2f1d00999a23cd",
+		Name:     "merp",
+		Created:  "2014-09-25T13:04:41Z",
+		TenantID: "fcad67a6189847c4aecfa3c81a05783b",
+		Metadata: map[string]interface{}{},
+		SecurityGroups: []map[string]interface{}{
+			map[string]interface{}{
+				"name": "default",
+			},
+		},
+	}
 )
 
 type CreateOptsWithCustomField struct {
diff --git a/openstack/compute/v2/servers/testing/requests_test.go b/openstack/compute/v2/servers/testing/requests_test.go
index 7db6b93..6d792d5 100644
--- a/openstack/compute/v2/servers/testing/requests_test.go
+++ b/openstack/compute/v2/servers/testing/requests_test.go
@@ -26,11 +26,12 @@
 			return false, err
 		}
 
-		if len(actual) != 2 {
-			t.Fatalf("Expected 2 servers, got %d", len(actual))
+		if len(actual) != 3 {
+			t.Fatalf("Expected 3 servers, got %d", len(actual))
 		}
 		th.CheckDeepEquals(t, ServerHerp, actual[0])
 		th.CheckDeepEquals(t, ServerDerp, actual[1])
+		th.CheckDeepEquals(t, ServerMerp, actual[2])
 
 		return true, nil
 	})
diff --git a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go
index ce493c9..bc4a3c6 100644
--- a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go
+++ b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go
@@ -22,6 +22,7 @@
 	TenantID           string `q:"tenant_id"`
 	ProvisioningStatus string `q:"provisioning_status"`
 	VipAddress         string `q:"vip_address"`
+	VipPortID          string `q:"vip_port_id"`
 	VipSubnetID        string `q:"vip_subnet_id"`
 	ID                 string `q:"id"`
 	OperatingStatus    string `q:"operating_status"`
diff --git a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go
index 168e531..4423c24 100644
--- a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go
+++ b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go
@@ -20,6 +20,8 @@
 	ProvisioningStatus string `json:"provisioning_status"`
 	// The IP address of the Loadbalancer.
 	VipAddress string `json:"vip_address"`
+	// The UUID of the port associated with the IP address.
+	VipPortID string `json:"vip_port_id"`
 	// The UUID of the subnet on which to allocate the virtual IP for the Loadbalancer address.
 	VipSubnetID string `json:"vip_subnet_id"`
 	// The unique ID for the LoadBalancer.
diff --git a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/fixtures.go b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/fixtures.go
index f882949..a452236 100644
--- a/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/fixtures.go
+++ b/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/testing/fixtures.go
@@ -25,6 +25,7 @@
 			"description": "lb config for the web tier",
 			"vip_subnet_id": "8a49c438-848f-467b-9655-ea1548708154",
 			"vip_address": "10.30.176.47",
+			"vip_port_id": "2a22e552-a347-44fd-b530-1f2b1b2a6735",
 			"flavor": "small",
 			"provider": "haproxy",
 			"admin_state_up": true,
@@ -38,6 +39,7 @@
 			"description": "lb config for the db tier",
 			"vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086",
 			"vip_address": "10.30.176.48",
+			"vip_port_id": "2bf413c8-41a9-4477-b505-333d5cbe8b55",
 			"flavor": "medium",
 			"provider": "haproxy",
 			"admin_state_up": true,
@@ -58,6 +60,7 @@
 		"description": "lb config for the db tier",
 		"vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086",
 		"vip_address": "10.30.176.48",
+		"vip_port_id": "2bf413c8-41a9-4477-b505-333d5cbe8b55",
 		"flavor": "medium",
 		"provider": "haproxy",
 		"admin_state_up": true,
@@ -77,6 +80,7 @@
 		"description": "lb config for the db tier",
 		"vip_subnet_id": "9cedb85d-0759-4898-8a4b-fa5a5ea10086",
 		"vip_address": "10.30.176.48",
+		"vip_port_id": "2bf413c8-41a9-4477-b505-333d5cbe8b55",
 		"flavor": "medium",
 		"provider": "haproxy",
 		"admin_state_up": true,
@@ -126,6 +130,7 @@
 		Description:        "lb config for the web tier",
 		VipSubnetID:        "8a49c438-848f-467b-9655-ea1548708154",
 		VipAddress:         "10.30.176.47",
+		VipPortID:          "2a22e552-a347-44fd-b530-1f2b1b2a6735",
 		Flavor:             "small",
 		Provider:           "haproxy",
 		AdminStateUp:       true,
@@ -139,6 +144,7 @@
 		Description:        "lb config for the db tier",
 		VipSubnetID:        "9cedb85d-0759-4898-8a4b-fa5a5ea10086",
 		VipAddress:         "10.30.176.48",
+		VipPortID:          "2bf413c8-41a9-4477-b505-333d5cbe8b55",
 		Flavor:             "medium",
 		Provider:           "haproxy",
 		AdminStateUp:       true,
@@ -152,6 +158,7 @@
 		Description:        "lb config for the db tier",
 		VipSubnetID:        "9cedb85d-0759-4898-8a4b-fa5a5ea10086",
 		VipAddress:         "10.30.176.48",
+		VipPortID:          "2bf413c8-41a9-4477-b505-333d5cbe8b55",
 		Flavor:             "medium",
 		Provider:           "haproxy",
 		AdminStateUp:       true,
diff --git a/openstack/testing/client_test.go b/openstack/testing/client_test.go
index 93f4d7c..570b3a0 100644
--- a/openstack/testing/client_test.go
+++ b/openstack/testing/client_test.go
@@ -49,8 +49,10 @@
 	})
 
 	options := gophercloud.AuthOptions{
-		UserID:           "me",
+		Username:         "me",
 		Password:         "secret",
+		DomainName:       "default",
+		TenantName: 	  "project",
 		IdentityEndpoint: th.Endpoint(),
 	}
 	client, err := openstack.AuthenticatedClient(options)
diff --git a/script/acceptancetest_environments/keystonev2-lbaasv1.sh b/script/acceptancetest_environments/keystonev2-lbaasv1.sh
new file mode 100644
index 0000000..a68a346
--- /dev/null
+++ b/script/acceptancetest_environments/keystonev2-lbaasv1.sh
@@ -0,0 +1,175 @@
+#!/bin/bash
+#
+# This script is useful for creating a devstack environment to run gophercloud
+# acceptance tests on.
+#
+# This can be considered a "legacy" devstack environment since it uses
+# Keystone v2 and LBaaS v1.
+#
+# To run, simply execute this script within a virtual machine.
+#
+# The following OpenStack versions are installed:
+# * OpenStack Mitaka
+# * Keystone v2
+# * Glance v1 and v2
+# * Nova v2 and v2.1
+# * Cinder v1 and v2
+# * Trove v1
+# * Swift v1
+# * Neutron v2
+# * Neutron LBaaS v1.0
+# * Neutron FWaaS v2.0
+#
+# Go 1.6 is also installed.
+
+set -e
+
+cd
+sudo apt-get update
+sudo apt-get install -y git make mercurial
+
+sudo wget -O /usr/local/bin/gimme https://raw.githubusercontent.com/travis-ci/gimme/master/gimme
+sudo chmod +x /usr/local/bin/gimme
+gimme 1.6 >> .bashrc
+
+mkdir ~/go
+eval "$(/usr/local/bin/gimme 1.6)"
+echo 'export GOPATH=$HOME/go' >> .bashrc
+export GOPATH=$HOME/go
+source .bashrc
+
+git clone https://git.openstack.org/openstack-dev/devstack -b stable/mitaka
+cd devstack
+cat >local.conf <<EOF
+[[local|localrc]]
+# OpenStack version
+OPENSTACK_VERSION="mitaka"
+
+# devstack password
+DEVSTACK_PASSWORD="password"
+
+# Configure passwords and the Swift Hash
+MYSQL_PASSWORD=\$DEVSTACK_PASSWORD
+RABBIT_PASSWORD=\$DEVSTACK_PASSWORD
+SERVICE_TOKEN=\$DEVSTACK_PASSWORD
+ADMIN_PASSWORD=\$DEVSTACK_PASSWORD
+SERVICE_PASSWORD=\$DEVSTACK_PASSWORD
+SWIFT_HASH=\$DEVSTACK_PASSWORD
+
+# Configure the stable OpenStack branches used by DevStack
+# For stable branches see
+# http://git.openstack.org/cgit/openstack-dev/devstack/refs/
+CINDER_BRANCH=stable/\$OPENSTACK_VERSION
+CEILOMETER_BRANCH=stable/\$OPENSTACK_VERSION
+GLANCE_BRANCH=stable/\$OPENSTACK_VERSION
+HEAT_BRANCH=stable/\$OPENSTACK_VERSION
+HORIZON_BRANCH=stable/\$OPENSTACK_VERSION
+KEYSTONE_BRANCH=stable/\$OPENSTACK_VERSION
+NEUTRON_BRANCH=stable/\$OPENSTACK_VERSION
+NOVA_BRANCH=stable/\$OPENSTACK_VERSION
+SWIFT_BRANCH=stable/\$OPENSTACK_VERSION
+ZAQAR_BRANCH=stable/\$OPENSTACK_VERSION
+
+# Enable Swift
+enable_service s-proxy
+enable_service s-object
+enable_service s-container
+enable_service s-account
+
+# Disable Nova Network and enable Neutron
+disable_service n-net
+enable_service q-svc
+enable_service q-agt
+enable_service q-dhcp
+enable_service q-l3
+enable_service q-meta
+#enable_service q-flavors
+
+# Disable Neutron metering
+disable_service q-metering
+
+# Enable LBaaS V1
+enable_service q-lbaas
+
+# Enable FWaaS
+enable_service q-fwaas
+
+# Enable LBaaS v2
+#enable_plugin neutron-lbaas https://git.openstack.org/openstack/neutron-lbaas stable/\$OPENSTACK_VERSION
+#enable_plugin octavia https://git.openstack.org/openstack/octavia stable/\$OPENSTACK_VERSION
+#enable_service q-lbaasv2
+#enable_service octavia
+#enable_service o-cw
+#enable_service o-hk
+#enable_service o-hm
+#enable_service o-api
+
+# Enable Trove
+enable_plugin trove git://git.openstack.org/openstack/trove.git stable/\$OPENSTACK_VERSION
+enable_service trove,tr-api,tr-tmgr,tr-cond
+
+# Disable Temptest
+disable_service tempest
+
+# Disable Horizon
+disable_service horizon
+
+# Disable Keystone v2
+#ENABLE_IDENTITY_V2=False
+
+# Enable SSL/tls
+#enable_service tls-proxy
+#USE_SSL=True
+
+# Enable Ceilometer
+#enable_service ceilometer-acompute
+#enable_service ceilometer-acentral
+#enable_service ceilometer-anotification
+#enable_service ceilometer-collector
+#enable_service ceilometer-alarm-evaluator
+#enable_service ceilometer-alarm-notifier
+#enable_service ceilometer-api
+
+# Enable Zaqar
+#enable_plugin zaqar https://github.com/openstack/zaqar
+#enable_service zaqar-server
+
+# Automatically download and register a VM image that Heat can launch
+# For more information on Heat and DevStack see
+# http://docs.openstack.org/developer/heat/getting_started/on_devstack.html
+#IMAGE_URLS+=",http://cloud.fedoraproject.org/fedora-20.x86_64.qcow2"
+#IMAGE_URLS+=",https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img"
+
+# Logging
+LOGDAYS=1
+LOGFILE=/opt/stack/logs/stack.sh.log
+LOGDIR=/opt/stack/logs
+EOF
+./stack.sh
+
+# Patch openrc
+#cat >> openrc <<EOF
+#
+# Currently, in order to use openstackclient with Identity API v3,
+# we need to set the domain which the user and project belong to.
+#if [ "$OS_IDENTITY_API_VERSION" = "3" ]; then
+#  export OS_USER_DOMAIN_ID=${OS_USER_DOMAIN_ID:-"default"}
+#  export OS_PROJECT_DOMAIN_ID=${OS_PROJECT_DOMAIN_ID:-"default"}
+#fi
+#EOF
+
+# Prep the testing environment by creating the required testing resources and environment variables
+source openrc admin
+wget http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-disk.img
+glance image-create --name CirrOS --disk-format qcow2 --container-format bare < cirros-0.3.4-x86_64-disk.img
+nova flavor-create m1.tform 99 512 5 1 --ephemeral 10
+_NETWORK_ID=$(nova net-list | grep private | awk -F\| '{print $2}' | tr -d ' ')
+_EXTGW_ID=$(nova net-list | grep public | awk -F\| '{print $2}' | tr -d ' ')
+_IMAGE_ID=$(nova image-list | grep CirrOS | awk -F\| '{print $2}' | tr -d ' ' | head -1)
+echo export OS_IMAGE_NAME="cirros-0.3.4-x86_64-uec" >> openrc
+echo export OS_IMAGE_ID="$_IMAGE_ID" >> openrc
+echo export OS_NETWORK_ID=$_NETWORK_ID >> openrc
+echo export OS_EXTGW_ID=$_EXTGW_ID >> openrc
+echo export OS_POOL_NAME="public" >> openrc
+echo export OS_FLAVOR_ID=99 >> openrc
+source openrc demo