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/... $@