Merge pull request #246 from jamiehannaford/os-neutron-fix

Fixes to Neutron LBaaS
diff --git a/acceptance/README.md b/acceptance/README.md
index 95c174d..df84a46 100644
--- a/acceptance/README.md
+++ b/acceptance/README.md
@@ -12,11 +12,11 @@
 
 ### Step 1. Set environment variables
 
-A lot of tests rely on environment variables for configuration - so you will need 
+A lot of tests rely on environment variables for configuration - so you will need
 to set them before running the suite. If you're testing against pure OpenStack APIs,
-you can download a file that contains all of these variables for you: just visit 
-the `project/access_and_security` page in your control panel and click the "Download 
-OpenStack RC File" button at the top right. For all other providers, you will need 
+you can download a file that contains all of these variables for you: just visit
+the `project/access_and_security` page in your control panel and click the "Download
+OpenStack RC File" button at the top right. For all other providers, you will need
 to set them manually.
 
 #### Authentication
@@ -45,12 +45,8 @@
 
 ### 2. Run the test suite
 
-From any directory, run:
+From the root directory, run:
 
 ```
-go test -v -tags acceptance github.com/rackspace/gophercloud/...
+./script/acceptancetest
 ```
-
-Alternatively, you can execute the above from your nested git folder (i.e. the
-  workspace visible when browsing the Github repository) by replacing
-  `github.com/rackspace/gophercloud/...` with `./...`
diff --git a/acceptance/openstack/client_test.go b/acceptance/openstack/client_test.go
index 52c0cf5..6c0f9ee 100644
--- a/acceptance/openstack/client_test.go
+++ b/acceptance/openstack/client_test.go
@@ -30,7 +30,7 @@
 	t.Logf("Client successfully acquired a token: %v", client.TokenID)
 
 	// Find the storage service in the service catalog.
-	storage, err := openstack.NewStorageV1(client, gophercloud.EndpointOpts{
+	storage, err := openstack.NewObjectStorageV1(client, gophercloud.EndpointOpts{
 		Region: os.Getenv("OS_REGION_NAME"),
 	})
 	if err != nil {
diff --git a/acceptance/openstack/compute/v2/servers_test.go b/acceptance/openstack/compute/v2/servers_test.go
index cf1f427..2c47a86 100644
--- a/acceptance/openstack/compute/v2/servers_test.go
+++ b/acceptance/openstack/compute/v2/servers_test.go
@@ -4,11 +4,15 @@
 
 import (
 	"fmt"
+	"os"
 	"testing"
 
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/acceptance/tools"
+	"github.com/rackspace/gophercloud/openstack"
 	"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
+	"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
+	"github.com/rackspace/gophercloud/openstack/utils"
 	"github.com/rackspace/gophercloud/pagination"
 )
 
@@ -42,7 +46,49 @@
 	fmt.Printf("--------\n%d servers listed on %d pages.\n", count, pages)
 }
 
+func networkingClient() (*gophercloud.ServiceClient, error) {
+	opts, err := utils.AuthOptions()
+	if err != nil {
+		return nil, err
+	}
+
+	provider, err := openstack.AuthenticatedClient(opts)
+	if err != nil {
+		return nil, err
+	}
+
+	return openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
+		Name:   "neutron",
+		Region: os.Getenv("OS_REGION_NAME"),
+	})
+}
+
 func createServer(t *testing.T, client *gophercloud.ServiceClient, choices *ComputeChoices) (*servers.Server, error) {
+	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: "public", 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)
 
@@ -50,6 +96,9 @@
 		Name:      name,
 		FlavorRef: choices.FlavorID,
 		ImageRef:  choices.ImageID,
+		Networks: []servers.Network{
+			servers.Network{UUID: network.ID},
+		},
 	}).Extract()
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
@@ -69,9 +118,6 @@
 		t.Fatalf("Unable to create a compute client: %v", err)
 	}
 
-	name := tools.RandomString("ACPTTEST", 16)
-	t.Logf("Attempting to create server: %s\n", name)
-
 	server, err := createServer(t, client, choices)
 	if err != nil {
 		t.Fatalf("Unable to create server: %v", err)
diff --git a/acceptance/openstack/networking/v2/extensions/security_test.go b/acceptance/openstack/networking/v2/extensions/security_test.go
index 526e217..33c9c4b 100644
--- a/acceptance/openstack/networking/v2/extensions/security_test.go
+++ b/acceptance/openstack/networking/v2/extensions/security_test.go
@@ -21,6 +21,9 @@
 	// create security group
 	groupID := createSecGroup(t)
 
+	// delete security group
+	defer deleteSecGroup(t, groupID)
+
 	// list security group
 	listSecGroups(t)
 
@@ -30,14 +33,11 @@
 	// create port with security group
 	networkID, portID := createPort(t, groupID)
 
-	// delete port
-	deletePort(t, portID)
-
-	// delete security group
-	deleteSecGroup(t, groupID)
-
 	// teardown
-	deleteNetwork(t, networkID)
+	defer deleteNetwork(t, networkID)
+
+	// delete port
+	defer deletePort(t, portID)
 }
 
 func TestSecurityGroupRules(t *testing.T) {
@@ -47,6 +47,8 @@
 	// create security group
 	groupID := createSecGroup(t)
 
+	defer deleteSecGroup(t, groupID)
+
 	// create security group rule
 	ruleID := createSecRule(t, groupID)
 
diff --git a/acceptance/openstack/objectstorage/v1/accounts_test.go b/acceptance/openstack/objectstorage/v1/accounts_test.go
index 6768927..b55f2a6 100644
--- a/acceptance/openstack/objectstorage/v1/accounts_test.go
+++ b/acceptance/openstack/objectstorage/v1/accounts_test.go
@@ -7,46 +7,32 @@
 	"testing"
 
 	"github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts"
+	th "github.com/rackspace/gophercloud/testhelper"
 )
 
 func TestAccounts(t *testing.T) {
 	// Create a provider client for making the HTTP requests.
 	// See common.go in this directory for more information.
 	client, err := newClient()
-	if err != nil {
-		t.Error(err)
-		return
-	}
+	th.AssertNoErr(t, err)
 
 	// Update an account's metadata.
-	err = accounts.Update(client, accounts.UpdateOpts{
-		Metadata: metadata,
-	})
-	if err != nil {
-		t.Error(err)
-		return
-	}
+	res = accounts.Update(client, accounts.UpdateOpts{Metadata: metadata})
+	th.AssertNoErr(t, res.Err)
+
 	// Defer the deletion of the metadata set above.
 	defer func() {
 		tempMap := make(map[string]string)
 		for k := range metadata {
 			tempMap[k] = ""
 		}
-		err = accounts.Update(client, accounts.UpdateOpts{
-			Metadata: tempMap,
-		})
-		if err != nil {
-			t.Error(err)
-			return
-		}
+		res = accounts.Update(client, accounts.UpdateOpts{Metadata: tempMap})
+		th.AssertNoErr(t, res.Err)
 	}()
 
 	// Retrieve account metadata.
-	gr, err := accounts.Get(client, accounts.GetOpts{})
-	if err != nil {
-		t.Error(err)
-		return
-	}
+	res := accounts.Get(client, accounts.GetOpts{})
+	th.AssertNoErr(res.Err)
 	// Extract the custom metadata from the 'Get' response.
 	am := accounts.ExtractMetadata(gr)
 	for k := range metadata {
diff --git a/acceptance/openstack/objectstorage/v1/common.go b/acceptance/openstack/objectstorage/v1/common.go
index 08065a4..4e2f9b5 100644
--- a/acceptance/openstack/objectstorage/v1/common.go
+++ b/acceptance/openstack/objectstorage/v1/common.go
@@ -3,26 +3,24 @@
 package v1
 
 import (
+	"os"
+
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/openstack"
 	"github.com/rackspace/gophercloud/openstack/utils"
-	"os"
+	th "github.com/rackspace/gophercloud/testhelper"
 )
 
 var metadata = map[string]string{"gopher": "cloud"}
 
 func newClient() (*gophercloud.ServiceClient, error) {
 	ao, err := utils.AuthOptions()
-	if err != nil {
-		return nil, err
-	}
+	th.AssertNoErr(t, err)
 
 	client, err := openstack.AuthenticatedClient(ao)
-	if err != nil {
-		return nil, err
-	}
+	th.AssertNoErr(t, err)
 
-	return openstack.NewStorageV1(client, gophercloud.EndpointOpts{
+	return openstack.NewObjectStorageV1(client, gophercloud.EndpointOpts{
 		Region: os.Getenv("OS_REGION_NAME"),
-	})
+	}), nil
 }
diff --git a/acceptance/openstack/objectstorage/v1/containers_test.go b/acceptance/openstack/objectstorage/v1/containers_test.go
index b541307..b2c91c5 100644
--- a/acceptance/openstack/objectstorage/v1/containers_test.go
+++ b/acceptance/openstack/objectstorage/v1/containers_test.go
@@ -9,6 +9,7 @@
 	"github.com/rackspace/gophercloud/acceptance/tools"
 	"github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
 	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
 )
 
 // numContainers is the number of containers to create for testing.
@@ -17,9 +18,7 @@
 func TestContainers(t *testing.T) {
 	// Create a new client to execute the HTTP requests. See common.go for newClient body.
 	client, err := newClient()
-	if err != nil {
-		t.Error(err)
-	}
+	th.AssertNoErr(err)
 
 	// Create a slice of random container names.
 	cNames := make([]string, numContainers)
@@ -29,18 +28,14 @@
 
 	// Create numContainers containers.
 	for i := 0; i < len(cNames); i++ {
-		_, err := containers.Create(client, cNames[i], nil).ExtractHeaders()
-		if err != nil {
-			t.Error(err)
-		}
+		res := containers.Create(client, cNames[i], nil)
+		th.AssertNoErr(res.Err)
 	}
 	// Delete the numContainers containers after function completion.
 	defer func() {
 		for i := 0; i < len(cNames); i++ {
-			_, err = containers.Delete(client, cNames[i]).ExtractHeaders()
-			if err != nil {
-				t.Error(err)
-			}
+			res = containers.Delete(client, cNames[i])
+			th.AssertNoErr(res.Err)
 		}
 	}()
 
@@ -48,9 +43,8 @@
 	// the 'prefix' parameter is used.
 	err = containers.List(client, &containers.ListOpts{Full: true, Prefix: "gophercloud-test-container-"}).EachPage(func(page pagination.Page) (bool, error) {
 		containerList, err := containers.ExtractInfo(page)
-		if err != nil {
-			t.Error(err)
-		}
+		th.AssertNoErr(err)
+
 		for _, n := range containerList {
 			t.Logf("Container: Name [%s] Count [%d] Bytes [%d]",
 				n.Name, n.Count, n.Bytes)
@@ -58,48 +52,36 @@
 
 		return true, nil
 	})
-	if err != nil {
-		t.Error(err)
-	}
+	th.AssertNoErr(err)
 
 	// List the info for the numContainer containers that were created.
 	err = containers.List(client, &containers.ListOpts{Full: false, Prefix: "gophercloud-test-container-"}).EachPage(func(page pagination.Page) (bool, error) {
 		containerList, err := containers.ExtractNames(page)
-		if err != nil {
-			return false, err
-		}
+		th.AssertNoErr(err)
 		for _, n := range containerList {
 			t.Logf("Container: Name [%s]", n)
 		}
 
 		return true, nil
 	})
-	if err != nil {
-		t.Error(err)
-	}
+	th.AssertNoErr(err)
 
 	// Update one of the numContainer container metadata.
-	_, err = containers.Update(client, cNames[0], &containers.UpdateOpts{Metadata: metadata}).ExtractHeaders()
-	if err != nil {
-		t.Error(err)
-	}
+	res = containers.Update(client, cNames[0], &containers.UpdateOpts{Metadata: metadata})
+	th.AssertNoErr(res.Err)
 	// After the tests are done, delete the metadata that was set.
 	defer func() {
 		tempMap := make(map[string]string)
 		for k := range metadata {
 			tempMap[k] = ""
 		}
-		_, err = containers.Update(client, cNames[0], &containers.UpdateOpts{Metadata: tempMap}).ExtractHeaders()
-		if err != nil {
-			t.Error(err)
-		}
+		res = containers.Update(client, cNames[0], &containers.UpdateOpts{Metadata: tempMap})
+		th.AssertNoErr(res.Err)
 	}()
 
 	// Retrieve a container's metadata.
 	cm, err := containers.Get(client, cNames[0]).ExtractMetadata()
-	if err != nil {
-		t.Error(err)
-	}
+	th.AssertNoErr(err)
 	for k := range metadata {
 		if cm[k] != metadata[strings.Title(k)] {
 			t.Errorf("Expected custom metadata with key: %s", k)
diff --git a/acceptance/openstack/objectstorage/v1/objects_test.go b/acceptance/openstack/objectstorage/v1/objects_test.go
index 5a63a4c..1454063 100644
--- a/acceptance/openstack/objectstorage/v1/objects_test.go
+++ b/acceptance/openstack/objectstorage/v1/objects_test.go
@@ -11,6 +11,7 @@
 	"github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
 	"github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects"
 	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
 )
 
 // numObjects is the number of objects to create for testing.
@@ -20,10 +21,7 @@
 	// Create a provider client for executing the HTTP request.
 	// See common.go for more information.
 	client, err := newClient()
-	if err != nil {
-		t.Error(err)
-		return
-	}
+	th.AssertNoErr(err)
 
 	// Make a slice of length numObjects to hold the random object names.
 	oNames := make([]string, numObjects)
@@ -33,51 +31,38 @@
 
 	// Create a container to hold the test objects.
 	cName := tools.RandomString("test-container-", 8)
-	_, err = containers.Create(client, cName, nil).ExtractHeaders()
-	if err != nil {
-		t.Error(err)
-		return
-	}
+	res = containers.Create(client, cName, nil)
+	th.AssertNoErr(res.Err)
+
 	// Defer deletion of the container until after testing.
 	defer func() {
-		_, err = containers.Delete(client, cName).ExtractHeaders()
-		if err != nil {
-			t.Error(err)
-			return
-		}
+		res = containers.Delete(client, cName)
+		th.AssertNoErr(res.Err)
 	}()
 
 	// Create a slice of buffers to hold the test object content.
 	oContents := make([]*bytes.Buffer, numObjects)
 	for i := 0; i < numObjects; i++ {
 		oContents[i] = bytes.NewBuffer([]byte(tools.RandomString("", 10)))
-		_, err = objects.Create(client, cName, oNames[i], oContents[i], nil).ExtractHeaders()
-		if err != nil {
-			t.Error(err)
-			return
-		}
+		res = objects.Create(client, cName, oNames[i], oContents[i], nil)
+		th.AssertNoErr(res.Err)
 	}
 	// Delete the objects after testing.
 	defer func() {
 		for i := 0; i < numObjects; i++ {
-			_, err = objects.Delete(client, cName, oNames[i], nil).ExtractHeaders()
+			res = objects.Delete(client, cName, oNames[i], nil)
 		}
 	}()
 
 	ons := make([]string, 0, len(oNames))
 	err = objects.List(client, cName, &objects.ListOpts{Full: false, Prefix: "test-object-"}).EachPage(func(page pagination.Page) (bool, error) {
 		names, err := objects.ExtractNames(page)
-		if err != nil {
-			return false, err
-		}
+		th.AssertNoErr(err)
 		ons = append(ons, names...)
 
 		return true, nil
 	})
-	if err != nil {
-		t.Error(err)
-		return
-	}
+	th.AssertNoErr(err)
 	if len(ons) != len(oNames) {
 		t.Errorf("Expected %d names and got %d", len(oNames), len(ons))
 		return
@@ -86,42 +71,30 @@
 	ois := make([]objects.Object, 0, len(oNames))
 	err = objects.List(client, cName, &objects.ListOpts{Full: true, Prefix: "test-object-"}).EachPage(func(page pagination.Page) (bool, error) {
 		info, err := objects.ExtractInfo(page)
-		if err != nil {
-			return false, nil
-		}
+		th.AssertNoErr(err)
 
 		ois = append(ois, info...)
 
 		return true, nil
 	})
-	if err != nil {
-		t.Error(err)
-		return
-	}
+	th.AssertNoErr(err)
 	if len(ois) != len(oNames) {
 		t.Errorf("Expected %d containers and got %d", len(oNames), len(ois))
 		return
 	}
 
 	// Copy the contents of one object to another.
-	_, err = objects.Copy(client, cName, oNames[0], &objects.CopyOpts{Destination: cName + "/" + oNames[1]}).ExtractHeaders()
-	if err != nil {
-		t.Error(err)
-		return
-	}
+	res = objects.Copy(client, cName, oNames[0], &objects.CopyOpts{Destination: cName + "/" + oNames[1]})
+	th.AssertNoErr(res.Err)
 
 	// Download one of the objects that was created above.
 	o1Content, err := objects.Download(client, cName, oNames[0], nil).ExtractContent()
-	if err != nil {
-		t.Error(err)
-		return
-	}
+	th.AssertNoErr(err)
+
 	// Download the another object that was create above.
 	o2Content, err := objects.Download(client, cName, oNames[1], nil).ExtractContent()
-	if err != nil {
-		t.Error(err)
-		return
-	}
+	th.AssertNoErr(err)
+
 	// Compare the two object's contents to test that the copy worked.
 	if string(o2Content) != string(o1Content) {
 		t.Errorf("Copy failed. Expected\n%s\nand got\n%s", string(o1Content), string(o2Content))
@@ -129,30 +102,22 @@
 	}
 
 	// Update an object's metadata.
-	_, err = objects.Update(client, cName, oNames[0], &objects.UpdateOpts{Metadata: metadata}).ExtractHeaders()
-	if err != nil {
-		t.Error(err)
-		return
-	}
+	res = objects.Update(client, cName, oNames[0], &objects.UpdateOpts{Metadata: metadata})
+	th.AssertNoErr(res.Err)
+
 	// Delete the object's metadata after testing.
 	defer func() {
 		tempMap := make(map[string]string)
 		for k := range metadata {
 			tempMap[k] = ""
 		}
-		_, err = objects.Update(client, cName, oNames[0], &objects.UpdateOpts{Metadata: tempMap}).ExtractHeaders()
-		if err != nil {
-			t.Error(err)
-			return
-		}
+		res = objects.Update(client, cName, oNames[0], &objects.UpdateOpts{Metadata: tempMap})
+		th.AssertNoErr(res.Err)
 	}()
 
 	// Retrieve an object's metadata.
 	om, err := objects.Get(client, cName, oNames[0], nil).ExtractMetadata()
-	if err != nil {
-		t.Error(err)
-		return
-	}
+	th.AssertNoErr(err)
 	for k := range metadata {
 		if om[k] != metadata[strings.Title(k)] {
 			t.Errorf("Expected custom metadata with key: %s", k)
diff --git a/acceptance/rackspace/objectstorage/v1/accounts_test.go b/acceptance/rackspace/objectstorage/v1/accounts_test.go
new file mode 100644
index 0000000..3a05646
--- /dev/null
+++ b/acceptance/rackspace/objectstorage/v1/accounts_test.go
@@ -0,0 +1,37 @@
+// +build acceptance rackspace
+
+package v1
+
+import (
+	"testing"
+
+	raxAccounts "github.com/rackspace/gophercloud/rackspace/objectstorage/v1/accounts"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestAccounts(t *testing.T) {
+	c, err := createClient(t, false)
+	th.AssertNoErr(t, err)
+
+	headers, err := raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": "mountains"}}).ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Update Account request: %+v\n", headers)
+	defer func() {
+		_, err := raxAccounts.Update(c, raxAccounts.UpdateOpts{Metadata: map[string]string{"white": ""}}).ExtractHeaders()
+		th.AssertNoErr(t, err)
+		metadata, err := raxAccounts.Get(c).ExtractMetadata()
+		th.AssertNoErr(t, err)
+		t.Logf("Metadata from Get Account request (after update reverted): %+v\n", metadata)
+		th.CheckEquals(t, metadata["White"], "")
+	}()
+
+	getResult := raxAccounts.Get(c)
+	headers, err = getResult.ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Get Account request (after update): %+v\n", headers)
+	metadata, err := getResult.ExtractMetadata()
+	th.AssertNoErr(t, err)
+	t.Logf("Metadata from Get Account request (after update): %+v\n", metadata)
+
+	th.CheckEquals(t, metadata["White"], "mountains")
+}
diff --git a/acceptance/rackspace/objectstorage/v1/bulk_test.go b/acceptance/rackspace/objectstorage/v1/bulk_test.go
new file mode 100644
index 0000000..79013a5
--- /dev/null
+++ b/acceptance/rackspace/objectstorage/v1/bulk_test.go
@@ -0,0 +1,23 @@
+// +build acceptance rackspace objectstorage v1
+
+package v1
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud/rackspace/objectstorage/v1/bulk"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestBulk(t *testing.T) {
+	c, err := createClient(t, false)
+	th.AssertNoErr(t, err)
+
+	var options bulk.DeleteOpts
+	options = append(options, "container/object1")
+	res := bulk.Delete(c, options)
+	th.AssertNoErr(t, res.Err)
+	body, err := res.ExtractBody()
+	th.AssertNoErr(t, err)
+	t.Logf("Response body from Bulk Delete Request: %+v\n", body)
+}
diff --git a/acceptance/rackspace/objectstorage/v1/cdncontainers_test.go b/acceptance/rackspace/objectstorage/v1/cdncontainers_test.go
new file mode 100644
index 0000000..2765f00
--- /dev/null
+++ b/acceptance/rackspace/objectstorage/v1/cdncontainers_test.go
@@ -0,0 +1,61 @@
+// +build acceptance rackspace objectstorage v1
+
+package v1
+
+import (
+	"testing"
+
+	osContainers "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
+	"github.com/rackspace/gophercloud/pagination"
+	raxCDNContainers "github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers"
+	raxContainers "github.com/rackspace/gophercloud/rackspace/objectstorage/v1/containers"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestCDNContainers(t *testing.T) {
+	raxClient, err := createClient(t, false)
+	th.AssertNoErr(t, err)
+
+	headers, err := raxContainers.Create(raxClient, "gophercloud-test", nil).ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Create Container request: %+v\n", headers)
+	defer func() {
+		_, err := raxContainers.Delete(raxClient, "gophercloud-test").ExtractHeaders()
+		th.AssertNoErr(t, err)
+	}()
+
+	raxCDNClient, err := createClient(t, true)
+	th.AssertNoErr(t, err)
+
+	headers, err = raxCDNContainers.Enable(raxCDNClient, "gophercloud-test", raxCDNContainers.EnableOpts{CDNEnabled: true, TTL: 900}).ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Enable CDN Container request: %+v\n", headers)
+
+	t.Logf("Container Names available to the currently issued token:")
+	count := 0
+	err = raxCDNContainers.List(raxCDNClient, &osContainers.ListOpts{Full: false}).EachPage(func(page pagination.Page) (bool, error) {
+		t.Logf("--- Page %02d ---", count)
+
+		names, err := raxCDNContainers.ExtractNames(page)
+		th.AssertNoErr(t, err)
+
+		for i, name := range names {
+			t.Logf("[%02d] %s", i, name)
+		}
+
+		count++
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	if count == 0 {
+		t.Errorf("No containers listed for your current token.")
+	}
+
+	headers, err = raxCDNContainers.Update(raxCDNClient, "gophercloud-test", raxCDNContainers.UpdateOpts{CDNEnabled: false}).ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Update CDN Container request: %+v\n", headers)
+
+	headers, err = raxCDNContainers.Get(raxCDNClient, "gophercloud-test").ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Get CDN Container request (after update): %+v\n", headers)
+}
diff --git a/acceptance/rackspace/objectstorage/v1/cdnobjects_test.go b/acceptance/rackspace/objectstorage/v1/cdnobjects_test.go
new file mode 100644
index 0000000..2d816f0
--- /dev/null
+++ b/acceptance/rackspace/objectstorage/v1/cdnobjects_test.go
@@ -0,0 +1,46 @@
+// +build acceptance rackspace objectstorage v1
+
+package v1
+
+import (
+	"bytes"
+	"testing"
+
+	raxCDNContainers "github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdncontainers"
+	raxCDNObjects "github.com/rackspace/gophercloud/rackspace/objectstorage/v1/cdnobjects"
+	raxContainers "github.com/rackspace/gophercloud/rackspace/objectstorage/v1/containers"
+	raxObjects "github.com/rackspace/gophercloud/rackspace/objectstorage/v1/objects"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestCDNObjects(t *testing.T) {
+	raxClient, err := createClient(t, false)
+	th.AssertNoErr(t, err)
+
+	headers, err := raxContainers.Create(raxClient, "gophercloud-test", nil).ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Create Container request: %+v\n", headers)
+	defer func() {
+		_, err := raxContainers.Delete(raxClient, "gophercloud-test").ExtractHeaders()
+		th.AssertNoErr(t, err)
+	}()
+
+	headers, err = raxObjects.Create(raxClient, "gophercloud-test", "test-object", bytes.NewBufferString("gophercloud cdn test"), nil).ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Create Object request: %+v\n", headers)
+	defer func() {
+		_, err := raxObjects.Delete(raxClient, "gophercloud-test", "test-object", nil).ExtractHeaders()
+		th.AssertNoErr(t, err)
+	}()
+
+	raxCDNClient, err := createClient(t, true)
+	th.AssertNoErr(t, err)
+
+	headers, err = raxCDNContainers.Enable(raxCDNClient, "gophercloud-test", raxCDNContainers.EnableOpts{CDNEnabled: true, TTL: 900}).ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Enable CDN Container request: %+v\n", headers)
+
+	headers, err = raxCDNObjects.Delete(raxCDNClient, "gophercloud-test", "test-object", nil).ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Delete CDN Object request: %+v\n", headers)
+}
diff --git a/acceptance/rackspace/objectstorage/v1/common.go b/acceptance/rackspace/objectstorage/v1/common.go
new file mode 100644
index 0000000..6422203
--- /dev/null
+++ b/acceptance/rackspace/objectstorage/v1/common.go
@@ -0,0 +1,54 @@
+// +build acceptance rackspace objectstorage v1
+
+package v1
+
+import (
+	"os"
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/rackspace"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func rackspaceAuthOptions(t *testing.T) gophercloud.AuthOptions {
+	// Obtain credentials from the environment.
+	options := gophercloud.AuthOptions{
+		Username: os.Getenv("RS_USERNAME"),
+		APIKey:   os.Getenv("RS_APIKEY"),
+	}
+
+	if options.Username == "" {
+		t.Fatal("Please provide a Rackspace username as RS_USERNAME.")
+	}
+	if options.APIKey == "" {
+		t.Fatal("Please provide a Rackspace API key as RS_APIKEY.")
+	}
+
+	return options
+}
+
+func createClient(t *testing.T, cdn bool) (*gophercloud.ServiceClient, error) {
+	region := os.Getenv("RS_REGION")
+	if region == "" {
+		t.Fatal("Please provide a Rackspace region as RS_REGION")
+	}
+
+	ao := rackspaceAuthOptions(t)
+
+	provider, err := rackspace.NewClient(ao.IdentityEndpoint)
+	th.AssertNoErr(t, err)
+
+	err = rackspace.Authenticate(provider, ao)
+	th.AssertNoErr(t, err)
+
+	if cdn {
+		return rackspace.NewObjectCDNV1(provider, gophercloud.EndpointOpts{
+			Region: region,
+		})
+	}
+
+	return rackspace.NewObjectStorageV1(provider, gophercloud.EndpointOpts{
+		Region: region,
+	})
+}
diff --git a/acceptance/rackspace/objectstorage/v1/containers_test.go b/acceptance/rackspace/objectstorage/v1/containers_test.go
new file mode 100644
index 0000000..d22057b
--- /dev/null
+++ b/acceptance/rackspace/objectstorage/v1/containers_test.go
@@ -0,0 +1,87 @@
+// +build acceptance rackspace objectstorage v1
+
+package v1
+
+import (
+	"testing"
+
+	osContainers "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
+	"github.com/rackspace/gophercloud/pagination"
+	raxContainers "github.com/rackspace/gophercloud/rackspace/objectstorage/v1/containers"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestContainers(t *testing.T) {
+	c, err := createClient(t, false)
+	th.AssertNoErr(t, err)
+
+	t.Logf("Containers Info available to the currently issued token:")
+	count := 0
+	err = raxContainers.List(c, &osContainers.ListOpts{Full: true}).EachPage(func(page pagination.Page) (bool, error) {
+		t.Logf("--- Page %02d ---", count)
+
+		containers, err := raxContainers.ExtractInfo(page)
+		th.AssertNoErr(t, err)
+
+		for i, container := range containers {
+			t.Logf("[%02d]      name=[%s]", i, container.Name)
+			t.Logf("            count=[%d]", container.Count)
+			t.Logf("            bytes=[%d]", container.Bytes)
+		}
+
+		count++
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	if count == 0 {
+		t.Errorf("No containers listed for your current token.")
+	}
+
+	t.Logf("Container Names available to the currently issued token:")
+	count = 0
+	err = raxContainers.List(c, &osContainers.ListOpts{Full: false}).EachPage(func(page pagination.Page) (bool, error) {
+		t.Logf("--- Page %02d ---", count)
+
+		names, err := raxContainers.ExtractNames(page)
+		th.AssertNoErr(t, err)
+
+		for i, name := range names {
+			t.Logf("[%02d] %s", i, name)
+		}
+
+		count++
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	if count == 0 {
+		t.Errorf("No containers listed for your current token.")
+	}
+
+	headers, err := raxContainers.Create(c, "gophercloud-test", nil).ExtractHeaders()
+	th.AssertNoErr(t, err)
+	defer func() {
+		_, err := raxContainers.Delete(c, "gophercloud-test").ExtractHeaders()
+		th.AssertNoErr(t, err)
+	}()
+
+	headers, err = raxContainers.Update(c, "gophercloud-test", raxContainers.UpdateOpts{Metadata: map[string]string{"white": "mountains"}}).ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Update Account request: %+v\n", headers)
+	defer func() {
+		_, err := raxContainers.Update(c, "gophercloud-test", raxContainers.UpdateOpts{Metadata: map[string]string{"white": ""}}).ExtractHeaders()
+		th.AssertNoErr(t, err)
+		metadata, err := raxContainers.Get(c, "gophercloud-test").ExtractMetadata()
+		th.AssertNoErr(t, err)
+		t.Logf("Metadata from Get Account request (after update reverted): %+v\n", metadata)
+		th.CheckEquals(t, metadata["White"], "")
+	}()
+
+	getResult := raxContainers.Get(c, "gophercloud-test")
+	headers, err = getResult.ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Get Account request (after update): %+v\n", headers)
+	metadata, err := getResult.ExtractMetadata()
+	th.AssertNoErr(t, err)
+	t.Logf("Metadata from Get Account request (after update): %+v\n", metadata)
+	th.CheckEquals(t, metadata["White"], "mountains")
+}
diff --git a/acceptance/rackspace/objectstorage/v1/objects_test.go b/acceptance/rackspace/objectstorage/v1/objects_test.go
new file mode 100644
index 0000000..749f6d3
--- /dev/null
+++ b/acceptance/rackspace/objectstorage/v1/objects_test.go
@@ -0,0 +1,113 @@
+// +build acceptance rackspace objectstorage v1
+
+package v1
+
+import (
+	"bytes"
+	"testing"
+
+	osObjects "github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects"
+	"github.com/rackspace/gophercloud/pagination"
+	raxContainers "github.com/rackspace/gophercloud/rackspace/objectstorage/v1/containers"
+	raxObjects "github.com/rackspace/gophercloud/rackspace/objectstorage/v1/objects"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestObjects(t *testing.T) {
+	c, err := createClient(t, false)
+	th.AssertNoErr(t, err)
+
+	_, err = raxContainers.Create(c, "gophercloud-test", nil).ExtractHeaders()
+	th.AssertNoErr(t, err)
+
+	defer func() {
+		_, err := raxContainers.Delete(c, "gophercloud-test").ExtractHeaders()
+		th.AssertNoErr(t, err)
+	}()
+
+	content := bytes.NewBufferString("Lewis Carroll")
+	options := &osObjects.CreateOpts{ContentType: "text/plain"}
+	_, err = raxObjects.Create(c, "gophercloud-test", "o1", content, options).ExtractHeaders()
+	th.AssertNoErr(t, err)
+	defer func() {
+		_, err := raxObjects.Delete(c, "gophercloud-test", "o1", nil).ExtractHeaders()
+		th.AssertNoErr(t, err)
+	}()
+
+	t.Logf("Objects Info available to the currently issued token:")
+	count := 0
+	err = raxObjects.List(c, "gophercloud-test", &osObjects.ListOpts{Full: true}).EachPage(func(page pagination.Page) (bool, error) {
+		t.Logf("--- Page %02d ---", count)
+
+		objects, err := raxObjects.ExtractInfo(page)
+		th.AssertNoErr(t, err)
+
+		for i, object := range objects {
+			t.Logf("[%02d]      name=[%s]", i, object.Name)
+			t.Logf("            content-type=[%s]", object.ContentType)
+			t.Logf("            bytes=[%d]", object.Bytes)
+			t.Logf("            last-modified=[%s]", object.LastModified)
+			t.Logf("            hash=[%s]", object.Hash)
+		}
+
+		count++
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	if count == 0 {
+		t.Errorf("No objects listed for your current token.")
+	}
+	t.Logf("Container Names available to the currently issued token:")
+	count = 0
+	err = raxObjects.List(c, "gophercloud-test", &osObjects.ListOpts{Full: false}).EachPage(func(page pagination.Page) (bool, error) {
+		t.Logf("--- Page %02d ---", count)
+
+		names, err := raxObjects.ExtractNames(page)
+		th.AssertNoErr(t, err)
+
+		for i, name := range names {
+			t.Logf("[%02d] %s", i, name)
+		}
+
+		count++
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	if count == 0 {
+		t.Errorf("No objects listed for your current token.")
+	}
+
+	_, err = raxObjects.Copy(c, "gophercloud-test", "o1", &raxObjects.CopyOpts{Destination: "gophercloud-test/o2"}).ExtractHeaders()
+	th.AssertNoErr(t, err)
+	defer func() {
+		_, err := raxObjects.Delete(c, "gophercloud-test", "o2", nil).ExtractHeaders()
+		th.AssertNoErr(t, err)
+	}()
+
+	o1Content, err := raxObjects.Download(c, "gophercloud-test", "o1", nil).ExtractContent()
+	th.AssertNoErr(t, err)
+	o2Content, err := raxObjects.Download(c, "gophercloud-test", "o2", nil).ExtractContent()
+	th.AssertNoErr(t, err)
+	th.AssertEquals(t, string(o2Content), string(o1Content))
+
+	headers, err := raxObjects.Update(c, "gophercloud-test", "o2", osObjects.UpdateOpts{Metadata: map[string]string{"white": "mountains"}}).ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Update Account request: %+v\n", headers)
+	defer func() {
+		_, err := raxObjects.Update(c, "gophercloud-test", "o2", osObjects.UpdateOpts{Metadata: map[string]string{"white": ""}}).ExtractHeaders()
+		th.AssertNoErr(t, err)
+		metadata, err := raxObjects.Get(c, "gophercloud-test", "o2", nil).ExtractMetadata()
+		th.AssertNoErr(t, err)
+		t.Logf("Metadata from Get Account request (after update reverted): %+v\n", metadata)
+		th.CheckEquals(t, metadata["White"], "")
+	}()
+
+	getResult := raxObjects.Get(c, "gophercloud-test", "o2", nil)
+	headers, err = getResult.ExtractHeaders()
+	th.AssertNoErr(t, err)
+	t.Logf("Headers from Get Account request (after update): %+v\n", headers)
+	metadata, err := getResult.ExtractMetadata()
+	th.AssertNoErr(t, err)
+	t.Logf("Metadata from Get Account request (after update): %+v\n", metadata)
+	th.CheckEquals(t, metadata["White"], "mountains")
+}
diff --git a/openstack/client.go b/openstack/client.go
index 97556d6..a00ed72 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -160,8 +160,8 @@
 	}
 }
 
-// NewStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
-func NewStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+// NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
+func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
 	eo.ApplyDefaults("object-store")
 	url, err := client.EndpointLocator(eo)
 	if err != nil {
diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go
index 9207474..a997484 100644
--- a/openstack/compute/v2/servers/requests.go
+++ b/openstack/compute/v2/servers/requests.go
@@ -165,6 +165,7 @@
 			securityGroups[i] = map[string]interface{}{"name": groupName}
 		}
 	}
+
 	if len(opts.Networks) > 0 {
 		networks := make([]map[string]interface{}, len(opts.Networks))
 		for i, net := range opts.Networks {
@@ -179,6 +180,7 @@
 				networks[i]["fixed_ip"] = net.FixedIP
 			}
 		}
+		server["networks"] = networks
 	}
 
 	return map[string]interface{}{"server": server}
diff --git a/openstack/objectstorage/v1/accounts/fixtures.go b/openstack/objectstorage/v1/accounts/fixtures.go
new file mode 100644
index 0000000..3dad0c5
--- /dev/null
+++ b/openstack/objectstorage/v1/accounts/fixtures.go
@@ -0,0 +1,38 @@
+// +build fixtures
+
+package accounts
+
+import (
+	"net/http"
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+// HandleGetAccountSuccessfully creates an HTTP handler at `/` on the test handler mux that
+// responds with a `Get` response.
+func HandleGetAccountSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "X-Account-Meta-Gophercloud-Test", "accounts")
+
+		w.Header().Set("X-Account-Container-Count", "2")
+		w.Header().Set("X-Account-Bytes-Used", "14")
+		w.Header().Set("X-Account-Meta-Subject", "books")
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+}
+
+// HandleUpdateAccountSuccessfully creates an HTTP handler at `/` on the test handler mux that
+// responds with a `Update` response.
+func HandleUpdateAccountSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "HEAD")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		w.Header().Set("X-Account-Meta-Foo", "bar")
+		w.WriteHeader(http.StatusNoContent)
+	})
+}
diff --git a/openstack/objectstorage/v1/accounts/requests_test.go b/openstack/objectstorage/v1/accounts/requests_test.go
index d109214..0ad8d33 100644
--- a/openstack/objectstorage/v1/accounts/requests_test.go
+++ b/openstack/objectstorage/v1/accounts/requests_test.go
@@ -1,7 +1,6 @@
 package accounts
 
 import (
-	"net/http"
 	"testing"
 
 	th "github.com/rackspace/gophercloud/testhelper"
@@ -13,18 +12,7 @@
 func TestUpdateAccount(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-
-	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "POST")
-		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		th.TestHeader(t, r, "X-Account-Meta-Gophercloud-Test", "accounts")
-
-		w.Header().Set("X-Account-Container-Count", "2")
-		w.Header().Set("X-Account-Bytes-Used", "14")
-		w.Header().Set("X-Account-Meta-Subject", "books")
-
-		w.WriteHeader(http.StatusNoContent)
-	})
+	HandleGetAccountSuccessfully(t)
 
 	options := &UpdateOpts{Metadata: map[string]string{"gophercloud-test": "accounts"}}
 	_, err := Update(fake.ServiceClient(), options).Extract()
@@ -34,13 +22,7 @@
 func TestGetAccount(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-
-	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "HEAD")
-		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		w.Header().Set("X-Account-Meta-Foo", "bar")
-		w.WriteHeader(http.StatusNoContent)
-	})
+	HandleUpdateAccountSuccessfully(t)
 
 	expected := map[string]string{"Foo": "bar"}
 	actual, err := Get(fake.ServiceClient(), &GetOpts{}).ExtractMetadata()
diff --git a/openstack/objectstorage/v1/containers/fixtures.go b/openstack/objectstorage/v1/containers/fixtures.go
new file mode 100644
index 0000000..1c0a915
--- /dev/null
+++ b/openstack/objectstorage/v1/containers/fixtures.go
@@ -0,0 +1,132 @@
+// +build fixtures
+
+package containers
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+// ExpectedListInfo is the result expected from a call to `List` when full
+// info is requested.
+var ExpectedListInfo = []Container{
+	Container{
+		Count: 0,
+		Bytes: 0,
+		Name:  "janeausten",
+	},
+	Container{
+		Count: 1,
+		Bytes: 14,
+		Name:  "marktwain",
+	},
+}
+
+// ExpectedListNames is the result expected from a call to `List` when just
+// container names are requested.
+var ExpectedListNames = []string{"janeausten", "marktwain"}
+
+// HandleListContainerInfoSuccessfully creates an HTTP handler at `/` on the test handler mux that
+// responds with a `List` response when full info is requested.
+func HandleListContainerInfoSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		r.ParseForm()
+		marker := r.Form.Get("marker")
+		switch marker {
+		case "":
+			fmt.Fprintf(w, `[
+        {
+          "count": 0,
+          "bytes": 0,
+          "name": "janeausten"
+        },
+        {
+          "count": 1,
+          "bytes": 14,
+          "name": "marktwain"
+        }
+      ]`)
+		case "marktwain":
+			fmt.Fprintf(w, `[]`)
+		default:
+			t.Fatalf("Unexpected marker: [%s]", marker)
+		}
+	})
+}
+
+// HandleListContainerNamesSuccessfully creates an HTTP handler at `/` on the test handler mux that
+// responds with a `ListNames` response when only container names are requested.
+func HandleListContainerNamesSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "text/plain")
+
+		w.Header().Set("Content-Type", "text/plain")
+		r.ParseForm()
+		marker := r.Form.Get("marker")
+		switch marker {
+		case "":
+			fmt.Fprintf(w, "janeausten\nmarktwain\n")
+		case "marktwain":
+			fmt.Fprintf(w, ``)
+		default:
+			t.Fatalf("Unexpected marker: [%s]", marker)
+		}
+	})
+}
+
+// HandleCreateContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that
+// responds with a `Create` response.
+func HandleCreateContainerSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "PUT")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Add("X-Container-Meta-Foo", "bar")
+		w.WriteHeader(http.StatusNoContent)
+	})
+}
+
+// HandleDeleteContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that
+// responds with a `Delete` response.
+func HandleDeleteContainerSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+		w.WriteHeader(http.StatusNoContent)
+	})
+}
+
+// HandleUpdateContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that
+// responds with a `Update` response.
+func HandleUpdateContainerSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+		w.WriteHeader(http.StatusNoContent)
+	})
+}
+
+// HandleGetContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that
+// responds with a `Get` response.
+func HandleGetContainerSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "HEAD")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+		w.WriteHeader(http.StatusNoContent)
+	})
+}
diff --git a/openstack/objectstorage/v1/containers/requests.go b/openstack/objectstorage/v1/containers/requests.go
index 25b68fd..89fa014 100644
--- a/openstack/objectstorage/v1/containers/requests.go
+++ b/openstack/objectstorage/v1/containers/requests.go
@@ -113,7 +113,7 @@
 
 	resp, err := perigee.Request("PUT", createURL(c, containerName), perigee.Options{
 		MoreHeaders: h,
-		OkCodes:     []int{201, 204},
+		OkCodes:     []int{201, 202, 204},
 	})
 	res.Header = resp.HttpResponse.Header
 	res.Err = err
@@ -125,7 +125,7 @@
 	var res DeleteResult
 	resp, err := perigee.Request("DELETE", deleteURL(c, containerName), perigee.Options{
 		MoreHeaders: c.Provider.AuthenticatedHeaders(),
-		OkCodes:     []int{204},
+		OkCodes:     []int{202, 204},
 	})
 	res.Header = resp.HttpResponse.Header
 	res.Err = err
@@ -184,7 +184,7 @@
 
 	resp, err := perigee.Request("POST", updateURL(c, containerName), perigee.Options{
 		MoreHeaders: h,
-		OkCodes:     []int{204},
+		OkCodes:     []int{202, 204},
 	})
 	res.Header = resp.HttpResponse.Header
 	res.Err = err
@@ -198,7 +198,7 @@
 	var res GetResult
 	resp, err := perigee.Request("HEAD", getURL(c, containerName), perigee.Options{
 		MoreHeaders: c.Provider.AuthenticatedHeaders(),
-		OkCodes:     []int{204},
+		OkCodes:     []int{200, 204},
 	})
 	res.Header = resp.HttpResponse.Header
 	res.Err = err
diff --git a/openstack/objectstorage/v1/containers/requests_test.go b/openstack/objectstorage/v1/containers/requests_test.go
index a134272..d0ce7f1 100644
--- a/openstack/objectstorage/v1/containers/requests_test.go
+++ b/openstack/objectstorage/v1/containers/requests_test.go
@@ -1,8 +1,6 @@
 package containers
 
 import (
-	"fmt"
-	"net/http"
 	"testing"
 
 	"github.com/rackspace/gophercloud/pagination"
@@ -15,94 +13,29 @@
 func TestListContainerInfo(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-
-	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "GET")
-		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		th.TestHeader(t, r, "Accept", "application/json")
-
-		w.Header().Set("Content-Type", "application/json")
-		r.ParseForm()
-		marker := r.Form.Get("marker")
-		switch marker {
-		case "":
-			fmt.Fprintf(w, `[
-				{
-					"count": 0,
-					"bytes": 0,
-					"name": "janeausten"
-				},
-				{
-					"count": 1,
-					"bytes": 14,
-					"name": "marktwain"
-				}
-			]`)
-		case "marktwain":
-			fmt.Fprintf(w, `[]`)
-		default:
-			t.Fatalf("Unexpected marker: [%s]", marker)
-		}
-	})
+	HandleListContainerInfoSuccessfully(t)
 
 	count := 0
-
-	List(fake.ServiceClient(), &ListOpts{Full: true}).EachPage(func(page pagination.Page) (bool, error) {
+	err := List(fake.ServiceClient(), &ListOpts{Full: true}).EachPage(func(page pagination.Page) (bool, error) {
 		count++
 		actual, err := ExtractInfo(page)
-		if err != nil {
-			t.Errorf("Failed to extract container info: %v", err)
-			return false, err
-		}
+		th.AssertNoErr(t, err)
 
-		expected := []Container{
-			Container{
-				Count: 0,
-				Bytes: 0,
-				Name:  "janeausten",
-			},
-			Container{
-				Count: 1,
-				Bytes: 14,
-				Name:  "marktwain",
-			},
-		}
-
-		th.CheckDeepEquals(t, expected, actual)
+		th.CheckDeepEquals(t, ExpectedListInfo, actual)
 
 		return true, nil
 	})
-
-	if count != 1 {
-		t.Errorf("Expected 1 page, got %d", count)
-	}
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
 }
 
 func TestListContainerNames(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-
-	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "GET")
-		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		th.TestHeader(t, r, "Accept", "text/plain")
-
-		w.Header().Set("Content-Type", "text/plain")
-		r.ParseForm()
-		marker := r.Form.Get("marker")
-		switch marker {
-		case "":
-			fmt.Fprintf(w, "janeausten\nmarktwain\n")
-		case "marktwain":
-			fmt.Fprintf(w, ``)
-		default:
-			t.Fatalf("Unexpected marker: [%s]", marker)
-		}
-	})
+	HandleListContainerNamesSuccessfully(t)
 
 	count := 0
-
-	List(fake.ServiceClient(), &ListOpts{Full: false}).EachPage(func(page pagination.Page) (bool, error) {
+	err := List(fake.ServiceClient(), &ListOpts{Full: false}).EachPage(func(page pagination.Page) (bool, error) {
 		count++
 		actual, err := ExtractNames(page)
 		if err != nil {
@@ -110,87 +43,49 @@
 			return false, err
 		}
 
-		expected := []string{"janeausten", "marktwain"}
-
-		th.CheckDeepEquals(t, expected, actual)
+		th.CheckDeepEquals(t, ExpectedListNames, actual)
 
 		return true, nil
 	})
-
-	if count != 1 {
-		t.Errorf("Expected 1 page, got %d", count)
-	}
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
 }
 
 func TestCreateContainer(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-
-	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "PUT")
-		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		th.TestHeader(t, r, "Accept", "application/json")
-
-		w.Header().Add("X-Container-Meta-Foo", "bar")
-		w.WriteHeader(http.StatusNoContent)
-	})
+	HandleCreateContainerSuccessfully(t)
 
 	options := CreateOpts{ContentType: "application/json", Metadata: map[string]string{"foo": "bar"}}
-	headers, err := Create(fake.ServiceClient(), "testContainer", options).Extract()
-	if err != nil {
-		t.Fatalf("Unexpected error creating container: %v", err)
-	}
-	th.CheckEquals(t, "bar", headers["X-Container-Meta-Foo"][0])
+	res := Create(fake.ServiceClient(), "testContainer", options)
+	th.CheckNoErr(t, res.Err)
+	th.CheckEquals(t, "bar", res.Header["X-Container-Meta-Foo"][0])
 }
 
 func TestDeleteContainer(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
+	HandleDeleteContainerSuccessfully(t)
 
-	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "DELETE")
-		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		th.TestHeader(t, r, "Accept", "application/json")
-		w.WriteHeader(http.StatusNoContent)
-	})
-
-	_, err := Delete(fake.ServiceClient(), "testContainer").Extract()
-	if err != nil {
-		t.Fatalf("Unexpected error deleting container: %v", err)
-	}
+	res := Delete(fake.ServiceClient(), "testContainer")
+	th.CheckNoErr(t, res.Err)
 }
 
 func TestUpateContainer(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-
-	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "POST")
-		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		th.TestHeader(t, r, "Accept", "application/json")
-		w.WriteHeader(http.StatusNoContent)
-	})
+	HandleUpdateContainerSuccessfully(t)
 
 	options := &UpdateOpts{Metadata: map[string]string{"foo": "bar"}}
-	_, err := Update(fake.ServiceClient(), "testContainer", options).Extract()
-	if err != nil {
-		t.Fatalf("Unexpected error updating container metadata: %v", err)
-	}
+	res := Update(fake.ServiceClient(), "testContainer", options)
+	th.CheckNoErr(t, res.Err)
 }
 
 func TestGetContainer(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-
-	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "HEAD")
-		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		th.TestHeader(t, r, "Accept", "application/json")
-		w.WriteHeader(http.StatusNoContent)
-	})
+	HandleGetContainerSuccessfully(t)
 
 	_, err := Get(fake.ServiceClient(), "testContainer").ExtractMetadata()
-	if err != nil {
-		t.Fatalf("Unexpected error getting container metadata: %v", err)
-	}
+	th.CheckNoErr(t, err)
 }
diff --git a/openstack/objectstorage/v1/objects/fixtures.go b/openstack/objectstorage/v1/objects/fixtures.go
new file mode 100644
index 0000000..d951160
--- /dev/null
+++ b/openstack/objectstorage/v1/objects/fixtures.go
@@ -0,0 +1,164 @@
+// +build fixtures
+
+package objects
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+// HandleDownloadObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that
+// responds with a `Download` response.
+func HandleDownloadObjectSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, "Successful download with Gophercloud")
+	})
+}
+
+// ExpectedListInfo is the result expected from a call to `List` when full
+// info is requested.
+var ExpectedListInfo = []Object{
+	Object{
+		Hash:         "451e372e48e0f6b1114fa0724aa79fa1",
+		LastModified: "2009-11-10 23:00:00 +0000 UTC",
+		Bytes:        14,
+		Name:         "goodbye",
+		ContentType:  "application/octet-stream",
+	},
+	Object{
+		Hash:         "451e372e48e0f6b1114fa0724aa79fa1",
+		LastModified: "2009-11-10 23:00:00 +0000 UTC",
+		Bytes:        14,
+		Name:         "hello",
+		ContentType:  "application/octet-stream",
+	},
+}
+
+// ExpectedListNames is the result expected from a call to `List` when just
+// object names are requested.
+var ExpectedListNames = []string{"hello", "goodbye"}
+
+// HandleListObjectsInfoSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that
+// responds with a `List` response when full info is requested.
+func HandleListObjectsInfoSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		r.ParseForm()
+		marker := r.Form.Get("marker")
+		switch marker {
+		case "":
+			fmt.Fprintf(w, `[
+      {
+        "hash": "451e372e48e0f6b1114fa0724aa79fa1",
+        "last_modified": "2009-11-10 23:00:00 +0000 UTC",
+        "bytes": 14,
+        "name": "goodbye",
+        "content_type": "application/octet-stream"
+      },
+      {
+        "hash": "451e372e48e0f6b1114fa0724aa79fa1",
+        "last_modified": "2009-11-10 23:00:00 +0000 UTC",
+        "bytes": 14,
+        "name": "hello",
+        "content_type": "application/octet-stream"
+      }
+    ]`)
+		case "hello":
+			fmt.Fprintf(w, `[]`)
+		default:
+			t.Fatalf("Unexpected marker: [%s]", marker)
+		}
+	})
+}
+
+// HandleListObjectNamesSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that
+// responds with a `List` response when only object names are requested.
+func HandleListObjectNamesSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "text/plain")
+
+		w.Header().Set("Content-Type", "text/plain")
+		r.ParseForm()
+		marker := r.Form.Get("marker")
+		switch marker {
+		case "":
+			fmt.Fprintf(w, "hello\ngoodbye\n")
+		case "goodbye":
+			fmt.Fprintf(w, "")
+		default:
+			t.Fatalf("Unexpected marker: [%s]", marker)
+		}
+	})
+}
+
+// HandleCreateObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that
+// responds with a `Create` response.
+func HandleCreateObjectSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "PUT")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+		w.WriteHeader(http.StatusCreated)
+	})
+}
+
+// HandleCopyObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that
+// responds with a `Copy` response.
+func HandleCopyObjectSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "COPY")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+		th.TestHeader(t, r, "Destination", "/newTestContainer/newTestObject")
+		w.WriteHeader(http.StatusCreated)
+	})
+}
+
+// HandleDeleteObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that
+// responds with a `Delete` response.
+func HandleDeleteObjectSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+		w.WriteHeader(http.StatusNoContent)
+	})
+}
+
+// HandleUpdateObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that
+// responds with a `Update` response.
+func HandleUpdateObjectSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+		th.TestHeader(t, r, "X-Object-Meta-Gophercloud-Test", "objects")
+		w.WriteHeader(http.StatusAccepted)
+	})
+}
+
+// HandleGetObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that
+// responds with a `Get` response.
+func HandleGetObjectSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "HEAD")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+		w.Header().Add("X-Object-Meta-Gophercloud-Test", "objects")
+		w.WriteHeader(http.StatusNoContent)
+	})
+}
diff --git a/openstack/objectstorage/v1/objects/requests.go b/openstack/objectstorage/v1/objects/requests.go
index 13d94f8..7d598b8 100644
--- a/openstack/objectstorage/v1/objects/requests.go
+++ b/openstack/objectstorage/v1/objects/requests.go
@@ -185,7 +185,6 @@
 // Create is a function that creates a new object or replaces an existing object.
 func Create(c *gophercloud.ServiceClient, containerName, objectName string, content io.Reader, opts CreateOptsBuilder) CreateResult {
 	var res CreateResult
-	var reqBody []byte
 
 	url := createURL(c, containerName, objectName)
 	h := c.Provider.AuthenticatedHeaders()
@@ -204,19 +203,13 @@
 		url += query
 	}
 
-	if content != nil {
-		reqBody = make([]byte, 0)
-		_, err := content.Read(reqBody)
-		if err != nil {
-			res.Err = err
-			return res
-		}
-	}
+	contentType := h["Content-Type"]
 
 	resp, err := perigee.Request("PUT", url, perigee.Options{
-		ReqBody:     reqBody,
+		ContentType: contentType,
+		ReqBody:     content,
 		MoreHeaders: h,
-		OkCodes:     []int{201},
+		OkCodes:     []int{201, 202},
 	})
 	res.Header = resp.HttpResponse.Header
 	res.Err = err
diff --git a/openstack/objectstorage/v1/objects/requests_test.go b/openstack/objectstorage/v1/objects/requests_test.go
index 7147782..7ab40f2 100644
--- a/openstack/objectstorage/v1/objects/requests_test.go
+++ b/openstack/objectstorage/v1/objects/requests_test.go
@@ -2,247 +2,113 @@
 
 import (
 	"bytes"
-	"fmt"
-	"net/http"
 	"testing"
 
 	"github.com/rackspace/gophercloud/pagination"
-	"github.com/rackspace/gophercloud/testhelper"
+	th "github.com/rackspace/gophercloud/testhelper"
 	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
-var metadata = map[string]string{"Gophercloud-Test": "objects"}
-
 func TestDownloadObject(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
-
-	testhelper.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "GET")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "application/json")
-		w.WriteHeader(http.StatusOK)
-		fmt.Fprintf(w, "Successful download with Gophercloud")
-	})
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleDownloadObjectSuccessfully(t)
 
 	content, err := Download(fake.ServiceClient(), "testContainer", "testObject", nil).ExtractContent()
-	if err != nil {
-		t.Fatalf("Unexpected error downloading object: %v", err)
-	}
-	if string(content) != "Successful download with Gophercloud" {
-		t.Errorf("Expected %s, got %s", "Successful download with Gophercloud", content)
-	}
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, string(content), "Successful download with Gophercloud")
 }
 
 func TestListObjectInfo(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
-
-	testhelper.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "GET")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "application/json")
-
-		w.Header().Set("Content-Type", "application/json")
-		r.ParseForm()
-		marker := r.Form.Get("marker")
-		switch marker {
-		case "":
-			fmt.Fprintf(w, `[
-				{
-					"hash": "451e372e48e0f6b1114fa0724aa79fa1",
-					"last_modified": "2009-11-10 23:00:00 +0000 UTC",
-					"bytes": 14,
-					"name": "goodbye",
-					"content_type": "application/octet-stream"
-				},
-				{
-					"hash": "451e372e48e0f6b1114fa0724aa79fa1",
-					"last_modified": "2009-11-10 23:00:00 +0000 UTC",
-					"bytes": 14,
-					"name": "hello",
-					"content_type": "application/octet-stream"
-				}
-			]`)
-		case "hello":
-			fmt.Fprintf(w, `[]`)
-		default:
-			t.Fatalf("Unexpected marker: [%s]", marker)
-		}
-	})
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleListObjectsInfoSuccessfully(t)
 
 	count := 0
-
-	err := List(fake.ServiceClient(), "testContainer", &ListOpts{Full: true}).EachPage(func(page pagination.Page) (bool, error) {
+	options := &ListOpts{Full: true}
+	err := List(fake.ServiceClient(), "testContainer", options).EachPage(func(page pagination.Page) (bool, error) {
 		count++
 		actual, err := ExtractInfo(page)
-		if err != nil {
-			t.Errorf("Failed to extract object info: %v", err)
-			return false, err
-		}
+		th.AssertNoErr(t, err)
 
-		expected := []Object{
-			Object{
-				Hash:         "451e372e48e0f6b1114fa0724aa79fa1",
-				LastModified: "2009-11-10 23:00:00 +0000 UTC",
-				Bytes:        14,
-				Name:         "goodbye",
-				ContentType:  "application/octet-stream",
-			},
-			Object{
-				Hash:         "451e372e48e0f6b1114fa0724aa79fa1",
-				LastModified: "2009-11-10 23:00:00 +0000 UTC",
-				Bytes:        14,
-				Name:         "hello",
-				ContentType:  "application/octet-stream",
-			},
-		}
-
-		testhelper.CheckDeepEquals(t, actual, expected)
+		th.CheckDeepEquals(t, ExpectedListInfo, actual)
 
 		return true, nil
 	})
-	if err != nil {
-		t.Error(err)
-	}
-
-	if count != 1 {
-		t.Errorf("Expected 1 page, got %d", count)
-	}
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
 }
 
 func TestListObjectNames(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
-
-	testhelper.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "GET")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "text/plain")
-
-		w.Header().Set("Content-Type", "text/plain")
-		r.ParseForm()
-		marker := r.Form.Get("marker")
-		switch marker {
-		case "":
-			fmt.Fprintf(w, "hello\ngoodbye\n")
-		case "goodbye":
-			fmt.Fprintf(w, "")
-		default:
-			t.Fatalf("Unexpected marker: [%s]", marker)
-		}
-	})
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleListObjectNamesSuccessfully(t)
 
 	count := 0
-	List(fake.ServiceClient(), "testContainer", &ListOpts{Full: false}).EachPage(func(page pagination.Page) (bool, error) {
+	options := &ListOpts{Full: false}
+	err := List(fake.ServiceClient(), "testContainer", options).EachPage(func(page pagination.Page) (bool, error) {
 		count++
 		actual, err := ExtractNames(page)
 		if err != nil {
-			t.Errorf("Failed to extract object names: %v", err)
+			t.Errorf("Failed to extract container names: %v", err)
 			return false, err
 		}
 
-		expected := []string{"hello", "goodbye"}
-
-		testhelper.CheckDeepEquals(t, expected, actual)
+		th.CheckDeepEquals(t, ExpectedListNames, actual)
 
 		return true, nil
 	})
-
-	if count != 1 {
-		t.Errorf("Expected 1 page, got %d", count)
-	}
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
 }
 
 func TestCreateObject(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
-
-	testhelper.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "PUT")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "application/json")
-		w.WriteHeader(http.StatusCreated)
-	})
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleCreateObjectSuccessfully(t)
 
 	content := bytes.NewBufferString("Did gyre and gimble in the wabe")
 	options := &CreateOpts{ContentType: "application/json"}
-	_, err := Create(fake.ServiceClient(), "testContainer", "testObject", content, options).Extract()
-	if err != nil {
-		t.Fatalf("Unexpected error creating object: %v", err)
-	}
+	res := Create(fake.ServiceClient(), "testContainer", "testObject", content, options)
+	th.AssertNoErr(t, res.Err)
 }
 
 func TestCopyObject(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
-
-	testhelper.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "COPY")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "application/json")
-		testhelper.TestHeader(t, r, "Destination", "/newTestContainer/newTestObject")
-		w.WriteHeader(http.StatusCreated)
-	})
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleCopyObjectSuccessfully(t)
 
 	options := &CopyOpts{Destination: "/newTestContainer/newTestObject"}
-	_, err := Copy(fake.ServiceClient(), "testContainer", "testObject", options).Extract()
-	if err != nil {
-		t.Fatalf("Unexpected error copying object: %v", err)
-	}
+	res := Copy(fake.ServiceClient(), "testContainer", "testObject", options)
+	th.AssertNoErr(t, res.Err)
 }
 
 func TestDeleteObject(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleDeleteObjectSuccessfully(t)
 
-	testhelper.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "DELETE")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "application/json")
-		w.WriteHeader(http.StatusNoContent)
-	})
-
-	_, err := Delete(fake.ServiceClient(), "testContainer", "testObject", nil).Extract()
-	if err != nil {
-		t.Fatalf("Unexpected error deleting object: %v", err)
-	}
+	res := Delete(fake.ServiceClient(), "testContainer", "testObject", nil)
+	th.AssertNoErr(t, res.Err)
 }
 
 func TestUpateObjectMetadata(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleUpdateObjectSuccessfully(t)
 
-	testhelper.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "POST")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "application/json")
-		testhelper.TestHeader(t, r, "X-Object-Meta-Gophercloud-Test", "objects")
-		w.WriteHeader(http.StatusAccepted)
-	})
-
-	_, err := Update(fake.ServiceClient(), "testContainer", "testObject", &UpdateOpts{Metadata: metadata}).Extract()
-	if err != nil {
-		t.Fatalf("Unexpected error updating object metadata: %v", err)
-	}
+	options := &UpdateOpts{Metadata: map[string]string{"Gophercloud-Test": "objects"}}
+	res := Update(fake.ServiceClient(), "testContainer", "testObject", options)
+	th.AssertNoErr(t, res.Err)
 }
 
 func TestGetObject(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleGetObjectSuccessfully(t)
 
-	testhelper.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "HEAD")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "application/json")
-		w.Header().Add("X-Object-Meta-Gophercloud-Test", "objects")
-		w.WriteHeader(http.StatusNoContent)
-	})
-
-	expected := metadata
+	expected := map[string]string{"Gophercloud-Test": "objects"}
 	actual, err := Get(fake.ServiceClient(), "testContainer", "testObject", nil).ExtractMetadata()
-	if err != nil {
-		t.Fatalf("Unexpected error getting object metadata: %v", err)
-	}
-	testhelper.CheckDeepEquals(t, expected, actual)
+	th.AssertNoErr(t, err)
+	th.CheckDeepEquals(t, expected, actual)
 }
diff --git a/rackspace/client.go b/rackspace/client.go
index d8c4a19..9bfa4be 100644
--- a/rackspace/client.go
+++ b/rackspace/client.go
@@ -113,3 +113,18 @@
 		Endpoint: v2Endpoint,
 	}
 }
+
+// NewObjectCDNV1 creates a ServiceClient that may be used with the Rackspace v1 CDN.
+func NewObjectCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+	eo.ApplyDefaults("rax:object-cdn")
+	url, err := client.EndpointLocator(eo)
+	if err != nil {
+		return nil, err
+	}
+	return &gophercloud.ServiceClient{Provider: client, Endpoint: url}, nil
+}
+
+// NewObjectStorageV1 creates a ServiceClient that may be used with the Rackspace v1 object storage package.
+func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+	return os.NewObjectStorageV1(client, eo)
+}
diff --git a/rackspace/objectstorage/v1/accounts/delegate.go b/rackspace/objectstorage/v1/accounts/delegate.go
new file mode 100644
index 0000000..ae3de26
--- /dev/null
+++ b/rackspace/objectstorage/v1/accounts/delegate.go
@@ -0,0 +1,38 @@
+package accounts
+
+import (
+	"github.com/rackspace/gophercloud"
+	os "github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts"
+)
+
+// Get is a function that retrieves an account's metadata. To extract just the
+// custom metadata, call the ExtractMetadata method on the GetResult. To extract
+// all the headers that are returned (including the metadata), call the
+// ExtractHeaders method on the GetResult.
+func Get(c *gophercloud.ServiceClient) os.GetResult {
+	return os.Get(c, nil)
+}
+
+// UpdateOpts is a structure that contains parameters for updating, creating, or
+// deleting an account's metadata.
+type UpdateOpts struct {
+	Metadata    map[string]string
+	TempURLKey  string `h:"X-Account-Meta-Temp-URL-Key"`
+	TempURLKey2 string `h:"X-Account-Meta-Temp-URL-Key-2"`
+}
+
+// ToAccountUpdateMap formats an UpdateOpts into a map[string]string of headers.
+func (opts UpdateOpts) ToAccountUpdateMap() (map[string]string, error) {
+	headers, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range opts.Metadata {
+		headers["X-Account-Meta-"+k] = v
+	}
+	return headers, err
+}
+
+func Update(c *gophercloud.ServiceClient, opts os.UpdateOptsBuilder) os.UpdateResult {
+	return os.Update(c, opts)
+}
diff --git a/rackspace/objectstorage/v1/accounts/delegate_test.go b/rackspace/objectstorage/v1/accounts/delegate_test.go
new file mode 100644
index 0000000..c568bd6
--- /dev/null
+++ b/rackspace/objectstorage/v1/accounts/delegate_test.go
@@ -0,0 +1,30 @@
+package accounts
+
+import (
+	"testing"
+
+	os "github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestGetAccounts(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleGetAccountSuccessfully(t)
+
+	options := &UpdateOpts{Metadata: map[string]string{"gophercloud-test": "accounts"}}
+	res := Update(fake.ServiceClient(), options)
+	th.CheckNoErr(t, res.Err)
+}
+
+func TestUpdateAccounts(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleUpdateAccountSuccessfully(t)
+
+	expected := map[string]string{"Foo": "bar"}
+	actual, err := Get(fake.ServiceClient()).ExtractMetadata()
+	th.CheckNoErr(t, err)
+	th.CheckDeepEquals(t, expected, actual)
+}
diff --git a/rackspace/objectstorage/v1/bulk/requests.go b/rackspace/objectstorage/v1/bulk/requests.go
new file mode 100644
index 0000000..7a08869
--- /dev/null
+++ b/rackspace/objectstorage/v1/bulk/requests.go
@@ -0,0 +1,51 @@
+package bulk
+
+import (
+	"net/url"
+	"strings"
+
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+)
+
+// DeleteOptsBuilder allows extensions to add additional parameters to the
+// Delete request.
+type DeleteOptsBuilder interface {
+	ToBulkDeleteBody() (string, error)
+}
+
+// DeleteOpts is a structure that holds parameters for deleting an object.
+type DeleteOpts []string
+
+// ToBulkDeleteBody formats a DeleteOpts into a request body.
+func (opts DeleteOpts) ToBulkDeleteBody() (string, error) {
+	return url.QueryEscape(strings.Join(opts, "\n")), nil
+}
+
+// Delete will delete objects or containers in bulk.
+func Delete(c *gophercloud.ServiceClient, opts DeleteOptsBuilder) DeleteResult {
+	var res DeleteResult
+
+	if opts == nil {
+		return res
+	}
+
+	reqString, err := opts.ToBulkDeleteBody()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
+	reqBody := strings.NewReader(reqString)
+
+	resp, err := perigee.Request("DELETE", deleteURL(c), perigee.Options{
+		ContentType: "text/plain",
+		MoreHeaders: c.Provider.AuthenticatedHeaders(),
+		OkCodes:     []int{200},
+		ReqBody:     reqBody,
+		Results:     &res.Body,
+	})
+	res.Header = resp.HttpResponse.Header
+	res.Err = err
+	return res
+}
diff --git a/rackspace/objectstorage/v1/bulk/requests_test.go b/rackspace/objectstorage/v1/bulk/requests_test.go
new file mode 100644
index 0000000..8b5578e
--- /dev/null
+++ b/rackspace/objectstorage/v1/bulk/requests_test.go
@@ -0,0 +1,36 @@
+package bulk
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestBulkDelete(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.AssertEquals(t, r.URL.RawQuery, "bulk-delete")
+
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, `
+      {
+        "Number Not Found": 1,
+        "Response Status": "200 OK",
+        "Errors": [],
+        "Number Deleted": 1,
+        "Response Body": ""
+      }
+    `)
+	})
+
+	options := DeleteOpts{"gophercloud-testcontainer1", "gophercloud-testcontainer2"}
+	actual, err := Delete(fake.ServiceClient(), options).ExtractBody()
+	th.AssertNoErr(t, err)
+	th.AssertEquals(t, actual.NumberDeleted, 1)
+}
diff --git a/rackspace/objectstorage/v1/bulk/results.go b/rackspace/objectstorage/v1/bulk/results.go
new file mode 100644
index 0000000..fddc125
--- /dev/null
+++ b/rackspace/objectstorage/v1/bulk/results.go
@@ -0,0 +1,28 @@
+package bulk
+
+import (
+	"github.com/rackspace/gophercloud"
+
+	"github.com/mitchellh/mapstructure"
+)
+
+// DeleteResult represents the result of a bulk delete operation.
+type DeleteResult struct {
+	gophercloud.Result
+}
+
+// DeleteRespBody is the form of the response body returned by a bulk delete request.
+type DeleteRespBody struct {
+	NumberNotFound int      `mapstructure:"Number Not Found"`
+	ResponseStatus string   `mapstructure:"Response Status"`
+	Errors         []string `mapstructure:"Errors"`
+	NumberDeleted  int      `mapstructure:"Number Deleted"`
+	ResponseBody   string   `mapstructure:"Response Body"`
+}
+
+// ExtractBody will extract the body returned by the bulk extract request.
+func (dr DeleteResult) ExtractBody() (DeleteRespBody, error) {
+	var resp DeleteRespBody
+	err := mapstructure.Decode(dr.Body, &resp)
+	return resp, err
+}
diff --git a/rackspace/objectstorage/v1/bulk/urls.go b/rackspace/objectstorage/v1/bulk/urls.go
new file mode 100644
index 0000000..2e11203
--- /dev/null
+++ b/rackspace/objectstorage/v1/bulk/urls.go
@@ -0,0 +1,11 @@
+package bulk
+
+import "github.com/rackspace/gophercloud"
+
+func deleteURL(c *gophercloud.ServiceClient) string {
+	return c.Endpoint + "?bulk-delete"
+}
+
+func extractURL(c *gophercloud.ServiceClient, ext string) string {
+	return c.Endpoint + "?extract-archive=" + ext
+}
diff --git a/rackspace/objectstorage/v1/bulk/urls_test.go b/rackspace/objectstorage/v1/bulk/urls_test.go
new file mode 100644
index 0000000..9169e52
--- /dev/null
+++ b/rackspace/objectstorage/v1/bulk/urls_test.go
@@ -0,0 +1,26 @@
+package bulk
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const endpoint = "http://localhost:57909/"
+
+func endpointClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestDeleteURL(t *testing.T) {
+	actual := deleteURL(endpointClient())
+	expected := endpoint + "?bulk-delete"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestExtractURL(t *testing.T) {
+	actual := extractURL(endpointClient(), "tar")
+	expected := endpoint + "?extract-archive=tar"
+	th.CheckEquals(t, expected, actual)
+}
diff --git a/rackspace/objectstorage/v1/cdncontainers/delegate.go b/rackspace/objectstorage/v1/cdncontainers/delegate.go
new file mode 100644
index 0000000..d7eef20
--- /dev/null
+++ b/rackspace/objectstorage/v1/cdncontainers/delegate.go
@@ -0,0 +1,71 @@
+package cdncontainers
+
+import (
+	"strconv"
+
+	"github.com/rackspace/gophercloud"
+	os "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// ExtractNames interprets a page of List results when just the container
+// names are requested.
+func ExtractNames(page pagination.Page) ([]string, error) {
+	return os.ExtractNames(page)
+}
+
+// ListOpts are options for listing Rackspace CDN containers.
+type ListOpts struct {
+	EndMarker string `q:"end_marker"`
+	Format    string `q:"format"`
+	Limit     int    `q:"limit"`
+	Marker    string `q:"marker"`
+}
+
+// ToContainerListParams formats a ListOpts into a query string and boolean
+// representing whether to list complete information for each container.
+func (opts ListOpts) ToContainerListParams() (bool, string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return false, "", err
+	}
+	return false, q.String(), nil
+}
+
+// List is a function that retrieves containers associated with the account as
+// well as account metadata. It returns a pager which can be iterated with the
+// EachPage function.
+func List(c *gophercloud.ServiceClient, opts os.ListOptsBuilder) pagination.Pager {
+	return os.List(c, opts)
+}
+
+// Get is a function that retrieves the metadata of a container. To extract just
+// the custom metadata, pass the GetResult response to the ExtractMetadata
+// function.
+func Get(c *gophercloud.ServiceClient, containerName string) os.GetResult {
+	return os.Get(c, containerName)
+}
+
+// UpdateOpts is a structure that holds parameters for updating, creating, or
+// deleting a container's metadata.
+type UpdateOpts struct {
+	CDNEnabled   bool `h:"X-Cdn-Enabled"`
+	LogRetention bool `h:"X-Log-Retention"`
+	TTL          int  `h:"X-Ttl"`
+}
+
+// ToContainerUpdateMap formats a CreateOpts into a map of headers.
+func (opts UpdateOpts) ToContainerUpdateMap() (map[string]string, error) {
+	h, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, err
+	}
+	h["X-Cdn-Enabled"] = strconv.FormatBool(opts.CDNEnabled)
+	return h, nil
+}
+
+// Update is a function that creates, updates, or deletes a container's
+// metadata.
+func Update(c *gophercloud.ServiceClient, containerName string, opts os.UpdateOptsBuilder) os.UpdateResult {
+	return os.Update(c, containerName, opts)
+}
diff --git a/rackspace/objectstorage/v1/cdncontainers/delegate_test.go b/rackspace/objectstorage/v1/cdncontainers/delegate_test.go
new file mode 100644
index 0000000..02c3c5e
--- /dev/null
+++ b/rackspace/objectstorage/v1/cdncontainers/delegate_test.go
@@ -0,0 +1,50 @@
+package cdncontainers
+
+import (
+	"testing"
+
+	os "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestListCDNContainers(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleListContainerNamesSuccessfully(t)
+
+	count := 0
+	err := List(fake.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractNames(page)
+		th.AssertNoErr(t, err)
+
+		th.CheckDeepEquals(t, os.ExpectedListNames, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
+}
+
+func TestGetCDNContainer(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleGetContainerSuccessfully(t)
+
+	_, err := Get(fake.ServiceClient(), "testContainer").ExtractMetadata()
+	th.CheckNoErr(t, err)
+
+}
+
+func TestUpdateCDNContainer(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleUpdateContainerSuccessfully(t)
+
+	options := &UpdateOpts{TTL: 3600}
+	res := Update(fake.ServiceClient(), "testContainer", options)
+	th.CheckNoErr(t, res.Err)
+
+}
diff --git a/rackspace/objectstorage/v1/cdncontainers/requests.go b/rackspace/objectstorage/v1/cdncontainers/requests.go
new file mode 100644
index 0000000..9cb6e9c
--- /dev/null
+++ b/rackspace/objectstorage/v1/cdncontainers/requests.go
@@ -0,0 +1,58 @@
+package cdncontainers
+
+import (
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+)
+
+// EnableOptsBuilder allows extensions to add additional parameters to the Enable
+// request.
+type EnableOptsBuilder interface {
+	ToCDNContainerEnableMap() (map[string]string, error)
+}
+
+// EnableOpts is a structure that holds options for enabling a CDN container.
+type EnableOpts struct {
+	// CDNEnabled indicates whether or not the container is CDN enabled. Set to
+	// `true` to enable the container. Note that changing this setting from true
+	// to false will disable the container in the CDN but only after the TTL has
+	// expired.
+	CDNEnabled bool `h:"X-Cdn-Enabled"`
+	// TTL is the time-to-live for the container (in seconds).
+	TTL int `h:"X-Ttl"`
+}
+
+// ToCDNContainerEnableMap formats an EnableOpts into a map of headers.
+func (opts EnableOpts) ToCDNContainerEnableMap() (map[string]string, error) {
+	h, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, err
+	}
+	return h, nil
+}
+
+// Enable is a function that enables/disables a CDN container.
+func Enable(c *gophercloud.ServiceClient, containerName string, opts EnableOptsBuilder) EnableResult {
+	var res EnableResult
+	h := c.Provider.AuthenticatedHeaders()
+
+	if opts != nil {
+		headers, err := opts.ToCDNContainerEnableMap()
+		if err != nil {
+			res.Err = err
+			return res
+		}
+
+		for k, v := range headers {
+			h[k] = v
+		}
+	}
+
+	resp, err := perigee.Request("PUT", enableURL(c, containerName), perigee.Options{
+		MoreHeaders: h,
+		OkCodes:     []int{201, 202, 204},
+	})
+	res.Header = resp.HttpResponse.Header
+	res.Err = err
+	return res
+}
diff --git a/rackspace/objectstorage/v1/cdncontainers/requests_test.go b/rackspace/objectstorage/v1/cdncontainers/requests_test.go
new file mode 100644
index 0000000..28b963d
--- /dev/null
+++ b/rackspace/objectstorage/v1/cdncontainers/requests_test.go
@@ -0,0 +1,29 @@
+package cdncontainers
+
+import (
+	"net/http"
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestEnableCDNContainer(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "PUT")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Add("X-Ttl", "259200")
+		w.Header().Add("X-Cdn-Enabled", "True")
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	options := &EnableOpts{CDNEnabled: true, TTL: 259200}
+	actual := Enable(fake.ServiceClient(), "testContainer", options)
+	th.AssertNoErr(t, actual.Err)
+	th.CheckEquals(t, actual.Header["X-Ttl"][0], "259200")
+	th.CheckEquals(t, actual.Header["X-Cdn-Enabled"][0], "True")
+}
diff --git a/rackspace/objectstorage/v1/cdncontainers/results.go b/rackspace/objectstorage/v1/cdncontainers/results.go
new file mode 100644
index 0000000..374d884
--- /dev/null
+++ b/rackspace/objectstorage/v1/cdncontainers/results.go
@@ -0,0 +1,8 @@
+package cdncontainers
+
+import "github.com/rackspace/gophercloud"
+
+// EnableResult represents the result of a get operation.
+type EnableResult struct {
+	gophercloud.Result
+}
diff --git a/rackspace/objectstorage/v1/cdncontainers/urls.go b/rackspace/objectstorage/v1/cdncontainers/urls.go
new file mode 100644
index 0000000..80653f2
--- /dev/null
+++ b/rackspace/objectstorage/v1/cdncontainers/urls.go
@@ -0,0 +1,7 @@
+package cdncontainers
+
+import "github.com/rackspace/gophercloud"
+
+func enableURL(c *gophercloud.ServiceClient, containerName string) string {
+	return c.ServiceURL(containerName)
+}
diff --git a/rackspace/objectstorage/v1/cdncontainers/urls_test.go b/rackspace/objectstorage/v1/cdncontainers/urls_test.go
new file mode 100644
index 0000000..aa5bfe6
--- /dev/null
+++ b/rackspace/objectstorage/v1/cdncontainers/urls_test.go
@@ -0,0 +1,20 @@
+package cdncontainers
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const endpoint = "http://localhost:57909/"
+
+func endpointClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestEnableURL(t *testing.T) {
+	actual := enableURL(endpointClient(), "foo")
+	expected := endpoint + "foo"
+	th.CheckEquals(t, expected, actual)
+}
diff --git a/rackspace/objectstorage/v1/cdnobjects/delegate.go b/rackspace/objectstorage/v1/cdnobjects/delegate.go
new file mode 100644
index 0000000..e9d2ff1
--- /dev/null
+++ b/rackspace/objectstorage/v1/cdnobjects/delegate.go
@@ -0,0 +1,11 @@
+package cdnobjects
+
+import (
+	"github.com/rackspace/gophercloud"
+	os "github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects"
+)
+
+// Delete is a function that deletes an object from the CDN.
+func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts os.DeleteOptsBuilder) os.DeleteResult {
+	return os.Delete(c, containerName, objectName, nil)
+}
diff --git a/rackspace/objectstorage/v1/cdnobjects/delegate_test.go b/rackspace/objectstorage/v1/cdnobjects/delegate_test.go
new file mode 100644
index 0000000..b5e04a9
--- /dev/null
+++ b/rackspace/objectstorage/v1/cdnobjects/delegate_test.go
@@ -0,0 +1,19 @@
+package cdnobjects
+
+import (
+	"testing"
+
+	os "github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestDeleteCDNObject(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleDeleteObjectSuccessfully(t)
+
+	res := Delete(fake.ServiceClient(), "testContainer", "testObject", nil)
+	th.AssertNoErr(t, res.Err)
+
+}
diff --git a/rackspace/objectstorage/v1/containers/delegate.go b/rackspace/objectstorage/v1/containers/delegate.go
new file mode 100644
index 0000000..77ed002
--- /dev/null
+++ b/rackspace/objectstorage/v1/containers/delegate.go
@@ -0,0 +1,93 @@
+package containers
+
+import (
+	"github.com/rackspace/gophercloud"
+	os "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// ExtractInfo interprets a page of List results when full container info
+// is requested.
+func ExtractInfo(page pagination.Page) ([]os.Container, error) {
+	return os.ExtractInfo(page)
+}
+
+// ExtractNames interprets a page of List results when just the container
+// names are requested.
+func ExtractNames(page pagination.Page) ([]string, error) {
+	return os.ExtractNames(page)
+}
+
+// List is a function that retrieves containers associated with the account as
+// well as account metadata. It returns a pager which can be iterated with the
+// EachPage function.
+func List(c *gophercloud.ServiceClient, opts os.ListOptsBuilder) pagination.Pager {
+	return os.List(c, opts)
+}
+
+// CreateOpts is a structure that holds parameters for creating a container.
+type CreateOpts struct {
+	Metadata         map[string]string
+	ContainerRead    string `h:"X-Container-Read"`
+	ContainerWrite   string `h:"X-Container-Write"`
+	VersionsLocation string `h:"X-Versions-Location"`
+}
+
+// ToContainerCreateMap formats a CreateOpts into a map of headers.
+func (opts CreateOpts) ToContainerCreateMap() (map[string]string, error) {
+	h, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range opts.Metadata {
+		h["X-Container-Meta-"+k] = v
+	}
+	return h, nil
+}
+
+// Create is a function that creates a new container.
+func Create(c *gophercloud.ServiceClient, containerName string, opts os.CreateOptsBuilder) os.CreateResult {
+	return os.Create(c, containerName, opts)
+}
+
+// Delete is a function that deletes a container.
+func Delete(c *gophercloud.ServiceClient, containerName string) os.DeleteResult {
+	return os.Delete(c, containerName)
+}
+
+// UpdateOpts is a structure that holds parameters for updating or creating a
+// container's metadata.
+type UpdateOpts struct {
+	Metadata               map[string]string
+	ContainerRead          string `h:"X-Container-Read"`
+	ContainerWrite         string `h:"X-Container-Write"`
+	ContentType            string `h:"Content-Type"`
+	DetectContentType      bool   `h:"X-Detect-Content-Type"`
+	RemoveVersionsLocation string `h:"X-Remove-Versions-Location"`
+	VersionsLocation       string `h:"X-Versions-Location"`
+}
+
+// ToContainerUpdateMap formats a CreateOpts into a map of headers.
+func (opts UpdateOpts) ToContainerUpdateMap() (map[string]string, error) {
+	h, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range opts.Metadata {
+		h["X-Container-Meta-"+k] = v
+	}
+	return h, nil
+}
+
+// Update is a function that creates, updates, or deletes a container's
+// metadata.
+func Update(c *gophercloud.ServiceClient, containerName string, opts os.UpdateOptsBuilder) os.UpdateResult {
+	return os.Update(c, containerName, opts)
+}
+
+// Get is a function that retrieves the metadata of a container. To extract just
+// the custom metadata, pass the GetResult response to the ExtractMetadata
+// function.
+func Get(c *gophercloud.ServiceClient, containerName string) os.GetResult {
+	return os.Get(c, containerName)
+}
diff --git a/rackspace/objectstorage/v1/containers/delegate_test.go b/rackspace/objectstorage/v1/containers/delegate_test.go
new file mode 100644
index 0000000..7ba4eb2
--- /dev/null
+++ b/rackspace/objectstorage/v1/containers/delegate_test.go
@@ -0,0 +1,91 @@
+package containers
+
+import (
+	"testing"
+
+	os "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestListContainerInfo(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleListContainerInfoSuccessfully(t)
+
+	count := 0
+	err := List(fake.ServiceClient(), &os.ListOpts{Full: true}).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractInfo(page)
+		th.AssertNoErr(t, err)
+
+		th.CheckDeepEquals(t, os.ExpectedListInfo, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
+}
+
+func TestListContainerNames(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleListContainerNamesSuccessfully(t)
+
+	count := 0
+	err := List(fake.ServiceClient(), &os.ListOpts{Full: false}).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractNames(page)
+		if err != nil {
+			t.Errorf("Failed to extract container names: %v", err)
+			return false, err
+		}
+
+		th.CheckDeepEquals(t, os.ExpectedListNames, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
+}
+
+func TestCreateContainers(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleCreateContainerSuccessfully(t)
+
+	options := os.CreateOpts{ContentType: "application/json", Metadata: map[string]string{"foo": "bar"}}
+	res := Create(fake.ServiceClient(), "testContainer", options)
+	th.CheckNoErr(t, res.Err)
+	th.CheckEquals(t, "bar", res.Header["X-Container-Meta-Foo"][0])
+
+}
+
+func TestDeleteContainers(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleDeleteContainerSuccessfully(t)
+
+	res := Delete(fake.ServiceClient(), "testContainer")
+	th.CheckNoErr(t, res.Err)
+}
+
+func TestUpdateContainers(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleUpdateContainerSuccessfully(t)
+
+	options := &os.UpdateOpts{Metadata: map[string]string{"foo": "bar"}}
+	res := Update(fake.ServiceClient(), "testContainer", options)
+	th.CheckNoErr(t, res.Err)
+}
+
+func TestGetContainers(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleGetContainerSuccessfully(t)
+
+	_, err := Get(fake.ServiceClient(), "testContainer").ExtractMetadata()
+	th.CheckNoErr(t, err)
+}
diff --git a/rackspace/objectstorage/v1/objects/delegate.go b/rackspace/objectstorage/v1/objects/delegate.go
new file mode 100644
index 0000000..bd4a4f0
--- /dev/null
+++ b/rackspace/objectstorage/v1/objects/delegate.go
@@ -0,0 +1,90 @@
+package objects
+
+import (
+	"io"
+
+	"github.com/rackspace/gophercloud"
+	os "github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// ExtractInfo is a function that takes a page of objects and returns their full information.
+func ExtractInfo(page pagination.Page) ([]os.Object, error) {
+	return os.ExtractInfo(page)
+}
+
+// ExtractNames is a function that takes a page of objects and returns only their names.
+func ExtractNames(page pagination.Page) ([]string, error) {
+	return os.ExtractNames(page)
+}
+
+// List is a function that retrieves objects in the container as
+// well as container metadata. It returns a pager which can be iterated with the
+// EachPage function.
+func List(c *gophercloud.ServiceClient, containerName string, opts os.ListOptsBuilder) pagination.Pager {
+	return os.List(c, containerName, opts)
+}
+
+// Download is a function that retrieves the content and metadata for an object.
+// To extract just the content, pass the DownloadResult response to the
+// ExtractContent function.
+func Download(c *gophercloud.ServiceClient, containerName, objectName string, opts os.DownloadOptsBuilder) os.DownloadResult {
+	return os.Download(c, containerName, objectName, opts)
+}
+
+// Create is a function that creates a new object or replaces an existing object.
+func Create(c *gophercloud.ServiceClient, containerName, objectName string, content io.Reader, opts os.CreateOptsBuilder) os.CreateResult {
+	return os.Create(c, containerName, objectName, content, opts)
+}
+
+// CopyOpts is a structure that holds parameters for copying one object to
+// another.
+type CopyOpts struct {
+	Metadata           map[string]string
+	ContentDisposition string `h:"Content-Disposition"`
+	ContentEncoding    string `h:"Content-Encoding"`
+	ContentLength      int    `h:"Content-Length"`
+	ContentType        string `h:"Content-Type"`
+	CopyFrom           string `h:"X-Copy_From"`
+	Destination        string `h:"Destination"`
+	DetectContentType  bool   `h:"X-Detect-Content-Type"`
+}
+
+// ToObjectCopyMap formats a CopyOpts into a map of headers.
+func (opts CopyOpts) ToObjectCopyMap() (map[string]string, error) {
+	h, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range opts.Metadata {
+		h["X-Object-Meta-"+k] = v
+	}
+	// `Content-Length` is required and a value of "0" is acceptable, but calling `gophercloud.BuildHeaders`
+	// will remove the `Content-Length` header if it's set to 0 (or equivalently not set). This will add
+	// the header if it's not already set.
+	if _, ok := h["Content-Length"]; !ok {
+		h["Content-Length"] = "0"
+	}
+	return h, nil
+}
+
+// Copy is a function that copies one object to another.
+func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts os.CopyOptsBuilder) os.CopyResult {
+	return os.Copy(c, containerName, objectName, opts)
+}
+
+// Delete is a function that deletes an object.
+func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts os.DeleteOptsBuilder) os.DeleteResult {
+	return os.Delete(c, containerName, objectName, opts)
+}
+
+// Get is a function that retrieves the metadata of an object. To extract just the custom
+// metadata, pass the GetResult response to the ExtractMetadata function.
+func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts os.GetOptsBuilder) os.GetResult {
+	return os.Get(c, containerName, objectName, opts)
+}
+
+// Update is a function that creates, updates, or deletes an object's metadata.
+func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts os.UpdateOptsBuilder) os.UpdateResult {
+	return os.Update(c, containerName, objectName, opts)
+}
diff --git a/rackspace/objectstorage/v1/objects/delegate_test.go b/rackspace/objectstorage/v1/objects/delegate_test.go
new file mode 100644
index 0000000..08831ec
--- /dev/null
+++ b/rackspace/objectstorage/v1/objects/delegate_test.go
@@ -0,0 +1,115 @@
+package objects
+
+import (
+	"bytes"
+	"testing"
+
+	os "github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects"
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestDownloadObject(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleDownloadObjectSuccessfully(t)
+
+	content, err := Download(fake.ServiceClient(), "testContainer", "testObject", nil).ExtractContent()
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, string(content), "Successful download with Gophercloud")
+}
+
+func TestListObjectsInfo(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleListObjectsInfoSuccessfully(t)
+
+	count := 0
+	options := &os.ListOpts{Full: true}
+	err := List(fake.ServiceClient(), "testContainer", options).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractInfo(page)
+		th.AssertNoErr(t, err)
+
+		th.CheckDeepEquals(t, os.ExpectedListInfo, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
+}
+
+func TestListObjectNames(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleListObjectNamesSuccessfully(t)
+
+	count := 0
+	options := &os.ListOpts{Full: false}
+	err := List(fake.ServiceClient(), "testContainer", options).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractNames(page)
+		if err != nil {
+			t.Errorf("Failed to extract container names: %v", err)
+			return false, err
+		}
+
+		th.CheckDeepEquals(t, os.ExpectedListNames, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
+}
+
+func TestCreateObject(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleCreateObjectSuccessfully(t)
+
+	content := bytes.NewBufferString("Did gyre and gimble in the wabe")
+	options := &os.CreateOpts{ContentType: "application/json"}
+	res := Create(fake.ServiceClient(), "testContainer", "testObject", content, options)
+	th.AssertNoErr(t, res.Err)
+}
+
+func TestCopyObject(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleCopyObjectSuccessfully(t)
+
+	options := &CopyOpts{Destination: "/newTestContainer/newTestObject"}
+	res := Copy(fake.ServiceClient(), "testContainer", "testObject", options)
+	th.AssertNoErr(t, res.Err)
+}
+
+func TestDeleteObject(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleDeleteObjectSuccessfully(t)
+
+	res := Delete(fake.ServiceClient(), "testContainer", "testObject", nil)
+	th.AssertNoErr(t, res.Err)
+}
+
+func TestUpdateObject(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleUpdateObjectSuccessfully(t)
+
+	options := &os.UpdateOpts{Metadata: map[string]string{"Gophercloud-Test": "objects"}}
+	res := Update(fake.ServiceClient(), "testContainer", "testObject", options)
+	th.AssertNoErr(t, res.Err)
+}
+
+func TestGetObject(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleGetObjectSuccessfully(t)
+
+	expected := map[string]string{"Gophercloud-Test": "objects"}
+	actual, err := Get(fake.ServiceClient(), "testContainer", "testObject", nil).ExtractMetadata()
+	th.AssertNoErr(t, err)
+	th.CheckDeepEquals(t, expected, actual)
+}
diff --git a/script/acceptancetest b/script/acceptancetest
index d8039ae..49039fd 100755
--- a/script/acceptancetest
+++ b/script/acceptancetest
@@ -2,4 +2,4 @@
 #
 # Run the acceptance tests.
 
-exec go test -tags 'acceptance fixtures' ./acceptance/... $@
+exec go test -v -p=1 -tags 'acceptance fixtures' ./acceptance/... $@