Merge pull request #198 from smashwilson/storage-consistency
Storage consistency
diff --git a/acceptance/openstack/identity/v3/pkg.go b/acceptance/openstack/identity/v3/pkg.go
new file mode 100644
index 0000000..d3b5573
--- /dev/null
+++ b/acceptance/openstack/identity/v3/pkg.go
@@ -0,0 +1,2 @@
+// Package v3 contains acceptance tests for identity v3 resources.
+package v3
diff --git a/acceptance/openstack/storage_test.go b/acceptance/openstack/storage_test.go
index 1cb3bad..833e5a3 100644
--- a/acceptance/openstack/storage_test.go
+++ b/acceptance/openstack/storage_test.go
@@ -4,36 +4,37 @@
import (
"bytes"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/acceptance/tools"
- storage "github.com/rackspace/gophercloud/openstack/storage/v1"
+ "github.com/rackspace/gophercloud/openstack"
"github.com/rackspace/gophercloud/openstack/storage/v1/accounts"
"github.com/rackspace/gophercloud/openstack/storage/v1/containers"
"github.com/rackspace/gophercloud/openstack/storage/v1/objects"
"github.com/rackspace/gophercloud/openstack/utils"
- "os"
- "strings"
- "testing"
)
var metadata = map[string]string{"gopher": "cloud"}
var numContainers = 2
var numObjects = 2
-func newClient() (*storage.Client, error) {
+func newClient() (*gophercloud.ServiceClient, error) {
ao, err := utils.AuthOptions()
if err != nil {
return nil, err
}
- client, err := utils.NewClient(ao, utils.EndpointOpts{
- Region: os.Getenv("OS_REGION_NAME"),
- Type: "object-store",
- })
+ client, err := openstack.AuthenticatedClient(ao)
if err != nil {
return nil, err
}
- return storage.NewClient(client.Endpoint, client.Authority, client.Options), nil
+ return openstack.NewStorageV1(client, gophercloud.EndpointOpts{
+ Region: os.Getenv("OS_REGION_NAME"),
+ })
}
func TestAccount(t *testing.T) {
diff --git a/acceptance/tools/tools.go b/acceptance/tools/tools.go
index 5852650..396241c 100644
--- a/acceptance/tools/tools.go
+++ b/acceptance/tools/tools.go
@@ -10,7 +10,7 @@
"time"
"github.com/rackspace/gophercloud"
- servers "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
+ "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
identity "github.com/rackspace/gophercloud/openstack/identity/v2"
"github.com/rackspace/gophercloud/openstack/utils"
)
diff --git a/openstack/client.go b/openstack/client.go
index 7279bca..39d39a8 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -31,12 +31,8 @@
u.Path, u.RawQuery, u.Fragment = "", "", ""
base := u.String()
- if !strings.HasSuffix(endpoint, "/") {
- endpoint = endpoint + "/"
- }
- if !strings.HasSuffix(base, "/") {
- base = base + "/"
- }
+ endpoint = normalizeURL(endpoint)
+ base = normalizeURL(base)
if hadPath {
return &gophercloud.ProviderClient{
@@ -155,9 +151,9 @@
for _, endpoint := range endpoints {
switch opts.Availability {
case gophercloud.AvailabilityPublic:
- return endpoint.PublicURL, nil
+ return normalizeURL(endpoint.PublicURL), nil
case gophercloud.AvailabilityInternal:
- return endpoint.InternalURL, nil
+ return normalizeURL(endpoint.InternalURL), nil
default:
return "", fmt.Errorf("Unexpected availability in endpoint query: %s", opts.Availability)
}
@@ -195,11 +191,6 @@
}
func v3endpointLocator(v3Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) {
- // Default Availability to InterfacePublic, if it isn't provided.
- if opts.Availability == "" {
- opts.Availability = gophercloud.AvailabilityPublic
- }
-
// Discover the service we're interested in.
serviceResults, err := services3.List(v3Client, services3.ListOpts{ServiceType: opts.Type})
if err != nil {
@@ -264,7 +255,15 @@
endpoint := endpoints[0]
- return endpoint.URL, nil
+ return normalizeURL(endpoint.URL), nil
+}
+
+// normalizeURL ensures that each endpoint URL has a closing `/`, as expected by ServiceClient.
+func normalizeURL(url string) string {
+ if !strings.HasSuffix(url, "/") {
+ return url + "/"
+ }
+ return url
}
// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
diff --git a/openstack/storage/v1/accounts/requests.go b/openstack/storage/v1/accounts/requests.go
index 7b84497..d5b623a 100644
--- a/openstack/storage/v1/accounts/requests.go
+++ b/openstack/storage/v1/accounts/requests.go
@@ -1,20 +1,19 @@
package accounts
import (
- "github.com/racker/perigee"
- storage "github.com/rackspace/gophercloud/openstack/storage/v1"
"net/http"
+
+ "github.com/racker/perigee"
+ "github.com/rackspace/gophercloud"
)
// GetResult is a *http.Response that is returned from a call to the Get function.
type GetResult *http.Response
// Update is a function that creates, updates, or deletes an account's metadata.
-func Update(c *storage.Client, opts UpdateOpts) error {
- h, err := c.GetHeaders()
- if err != nil {
- return err
- }
+func Update(c *gophercloud.ServiceClient, opts UpdateOpts) error {
+ h := c.Provider.AuthenticatedHeaders()
+
for k, v := range opts.Headers {
h[k] = v
}
@@ -23,28 +22,25 @@
h["X-Account-Meta-"+k] = v
}
- url := c.GetAccountURL()
- _, err = perigee.Request("POST", url, perigee.Options{
+ _, err := perigee.Request("POST", getAccountURL(c), perigee.Options{
MoreHeaders: h,
+ OkCodes: []int{204},
})
return err
}
// Get is a function that retrieves an account's metadata. To extract just the custom
// metadata, pass the GetResult response to the ExtractMetadata function.
-func Get(c *storage.Client, opts GetOpts) (GetResult, error) {
- h, err := c.GetHeaders()
- if err != nil {
- return nil, err
- }
+func Get(c *gophercloud.ServiceClient, opts GetOpts) (GetResult, error) {
+ h := c.Provider.AuthenticatedHeaders()
for k, v := range opts.Headers {
h[k] = v
}
- url := c.GetAccountURL()
- resp, err := perigee.Request("HEAD", url, perigee.Options{
+ resp, err := perigee.Request("HEAD", getAccountURL(c), perigee.Options{
MoreHeaders: h,
+ OkCodes: []int{204},
})
return &resp.HttpResponse, err
}
diff --git a/openstack/storage/v1/accounts/urls.go b/openstack/storage/v1/accounts/urls.go
new file mode 100644
index 0000000..ae78ff2
--- /dev/null
+++ b/openstack/storage/v1/accounts/urls.go
@@ -0,0 +1,8 @@
+package accounts
+
+import "github.com/rackspace/gophercloud"
+
+// getAccountURL returns the URI for making Account requests.
+func getAccountURL(c *gophercloud.ServiceClient) string {
+ return c.Endpoint
+}
diff --git a/openstack/storage/v1/client.go b/openstack/storage/v1/client.go
deleted file mode 100644
index 51312eb..0000000
--- a/openstack/storage/v1/client.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package v1
-
-import (
- "fmt"
-
- "github.com/rackspace/gophercloud"
- identity "github.com/rackspace/gophercloud/openstack/identity/v2"
-)
-
-// Client is a structure that contains information for communicating with a provider.
-type Client struct {
- endpoint string
- authority identity.AuthResults
- options gophercloud.AuthOptions
- token *identity.Token
-}
-
-// NewClient creates and returns a *Client.
-func NewClient(e string, a identity.AuthResults, o gophercloud.AuthOptions) *Client {
- return &Client{
- endpoint: e,
- authority: a,
- options: o,
- }
-}
-
-// GetAccountURL returns the URI for making Account requests. This function is exported to allow
-// the 'Accounts' subpackage to use it. It is not meant for public consumption.
-func (c *Client) GetAccountURL() string {
- return fmt.Sprintf("%s", c.endpoint)
-}
-
-// GetContainerURL returns the URI for making Container requests. This function is exported to allow
-// the 'Containers' subpackage to use it. It is not meant for public consumption.
-func (c *Client) GetContainerURL(container string) string {
- return fmt.Sprintf("%s/%s", c.endpoint, container)
-}
-
-// GetObjectURL returns the URI for making Object requests. This function is exported to allow
-// the 'Objects' subpackage to use it. It is not meant for public consumption.
-func (c *Client) GetObjectURL(container, object string) string {
- return fmt.Sprintf("%s/%s/%s", c.endpoint, container, object)
-}
-
-// GetHeaders is a function that gets the header for token authentication against a client's endpoint.
-// This function is exported to allow the subpackages to use it. It is not meant for public consumption.
-func (c *Client) GetHeaders() (map[string]string, error) {
- t, err := c.getAuthToken()
- if err != nil {
- return map[string]string{}, err
- }
-
- return map[string]string{
- "X-Auth-Token": t,
- }, nil
-}
-
-// getAuthToken is a function that tries to retrieve an authentication token from a client's endpoint.
-func (c *Client) getAuthToken() (string, error) {
- var err error
-
- if c.token == nil {
- c.token, err = identity.GetToken(c.authority)
- if err != nil {
- return "", err
- }
- }
-
- return c.token.ID, err
-}
diff --git a/openstack/storage/v1/containers/requests.go b/openstack/storage/v1/containers/requests.go
index b6d3a89..0db691c 100644
--- a/openstack/storage/v1/containers/requests.go
+++ b/openstack/storage/v1/containers/requests.go
@@ -1,10 +1,11 @@
package containers
import (
- "github.com/racker/perigee"
- storage "github.com/rackspace/gophercloud/openstack/storage/v1"
- "github.com/rackspace/gophercloud/openstack/utils"
"net/http"
+
+ "github.com/racker/perigee"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/openstack/utils"
)
// ListResult is a *http.Response that is returned from a call to the List function.
@@ -16,13 +17,10 @@
// List is a function that retrieves all objects in a container. It also returns the details
// for the account. To extract just the container information or names, pass the ListResult
// response to the ExtractInfo or ExtractNames function, respectively.
-func List(c *storage.Client, opts ListOpts) (ListResult, error) {
+func List(c *gophercloud.ServiceClient, opts ListOpts) (ListResult, error) {
contentType := ""
- h, err := c.GetHeaders()
- if err != nil {
- return nil, err
- }
+ h := c.Provider.AuthenticatedHeaders()
query := utils.BuildQuery(opts.Params)
@@ -30,22 +28,20 @@
contentType = "text/plain"
}
- url := c.GetAccountURL() + query
+ url := getAccountURL(c) + query
resp, err := perigee.Request("GET", url, perigee.Options{
MoreHeaders: h,
Accept: contentType,
+ OkCodes: []int{200, 204},
})
return &resp.HttpResponse, err
}
// Create is a function that creates a new container.
-func Create(c *storage.Client, opts CreateOpts) (Container, error) {
+func Create(c *gophercloud.ServiceClient, opts CreateOpts) (Container, error) {
var ci Container
- h, err := c.GetHeaders()
- if err != nil {
- return Container{}, err
- }
+ h := c.Provider.AuthenticatedHeaders()
for k, v := range opts.Headers {
h[k] = v
@@ -55,9 +51,10 @@
h["X-Container-Meta-"+k] = v
}
- url := c.GetContainerURL(opts.Name)
- _, err = perigee.Request("PUT", url, perigee.Options{
+ url := getContainerURL(c, opts.Name)
+ _, err := perigee.Request("PUT", url, perigee.Options{
MoreHeaders: h,
+ OkCodes: []int{201, 204},
})
if err == nil {
ci = Container{
@@ -68,27 +65,22 @@
}
// Delete is a function that deletes a container.
-func Delete(c *storage.Client, opts DeleteOpts) error {
- h, err := c.GetHeaders()
- if err != nil {
- return err
- }
+func Delete(c *gophercloud.ServiceClient, opts DeleteOpts) error {
+ h := c.Provider.AuthenticatedHeaders()
query := utils.BuildQuery(opts.Params)
- url := c.GetContainerURL(opts.Name) + query
- _, err = perigee.Request("DELETE", url, perigee.Options{
+ url := getContainerURL(c, opts.Name) + query
+ _, err := perigee.Request("DELETE", url, perigee.Options{
MoreHeaders: h,
+ OkCodes: []int{204},
})
return err
}
// Update is a function that creates, updates, or deletes a container's metadata.
-func Update(c *storage.Client, opts UpdateOpts) error {
- h, err := c.GetHeaders()
- if err != nil {
- return err
- }
+func Update(c *gophercloud.ServiceClient, opts UpdateOpts) error {
+ h := c.Provider.AuthenticatedHeaders()
for k, v := range opts.Headers {
h[k] = v
@@ -98,28 +90,27 @@
h["X-Container-Meta-"+k] = v
}
- url := c.GetContainerURL(opts.Name)
- _, err = perigee.Request("POST", url, perigee.Options{
+ url := getContainerURL(c, opts.Name)
+ _, err := perigee.Request("POST", url, perigee.Options{
MoreHeaders: h,
+ OkCodes: []int{204},
})
return err
}
// 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 *storage.Client, opts GetOpts) (GetResult, error) {
- h, err := c.GetHeaders()
- if err != nil {
- return nil, err
- }
+func Get(c *gophercloud.ServiceClient, opts GetOpts) (GetResult, error) {
+ h := c.Provider.AuthenticatedHeaders()
for k, v := range opts.Metadata {
h["X-Container-Meta-"+k] = v
}
- url := c.GetContainerURL(opts.Name)
+ url := getContainerURL(c, opts.Name)
resp, err := perigee.Request("HEAD", url, perigee.Options{
MoreHeaders: h,
+ OkCodes: []int{204},
})
return &resp.HttpResponse, err
}
diff --git a/openstack/storage/v1/containers/urls.go b/openstack/storage/v1/containers/urls.go
new file mode 100644
index 0000000..4084bcc
--- /dev/null
+++ b/openstack/storage/v1/containers/urls.go
@@ -0,0 +1,13 @@
+package containers
+
+import "github.com/rackspace/gophercloud"
+
+// getAccountURL returns the URI used to list Containers.
+func getAccountURL(c *gophercloud.ServiceClient) string {
+ return c.Endpoint
+}
+
+// getContainerURL returns the URI for making Container requests.
+func getContainerURL(c *gophercloud.ServiceClient, container string) string {
+ return c.ServiceURL(container)
+}
diff --git a/openstack/storage/v1/objects/requests.go b/openstack/storage/v1/objects/requests.go
index 4e6f23a..931653e 100644
--- a/openstack/storage/v1/objects/requests.go
+++ b/openstack/storage/v1/objects/requests.go
@@ -2,10 +2,11 @@
import (
"fmt"
- "github.com/racker/perigee"
- storage "github.com/rackspace/gophercloud/openstack/storage/v1"
- "github.com/rackspace/gophercloud/openstack/utils"
"net/http"
+
+ "github.com/racker/perigee"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/openstack/utils"
)
// ListResult is a *http.Response that is returned from a call to the List function.
@@ -20,13 +21,10 @@
// List is a function that retrieves all objects in a container. It also returns the details
// for the container. To extract only the object information or names, pass the ListResult
// response to the ExtractInfo or ExtractNames function, respectively.
-func List(c *storage.Client, opts ListOpts) (ListResult, error) {
+func List(c *gophercloud.ServiceClient, opts ListOpts) (ListResult, error) {
contentType := ""
- h, err := c.GetHeaders()
- if err != nil {
- return nil, err
- }
+ h := c.Provider.AuthenticatedHeaders()
query := utils.BuildQuery(opts.Params)
@@ -34,10 +32,11 @@
contentType = "text/plain"
}
- url := c.GetContainerURL(opts.Container) + query
+ url := getContainerURL(c, opts.Container) + query
resp, err := perigee.Request("GET", url, perigee.Options{
MoreHeaders: h,
Accept: contentType,
+ OkCodes: []int{200, 204},
})
return &resp.HttpResponse, err
}
@@ -45,11 +44,8 @@
// 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 *storage.Client, opts DownloadOpts) (DownloadResult, error) {
- h, err := c.GetHeaders()
- if err != nil {
- return nil, err
- }
+func Download(c *gophercloud.ServiceClient, opts DownloadOpts) (DownloadResult, error) {
+ h := c.Provider.AuthenticatedHeaders()
for k, v := range opts.Headers {
h[k] = v
@@ -57,21 +53,19 @@
query := utils.BuildQuery(opts.Params)
- url := c.GetObjectURL(opts.Container, opts.Name) + query
+ url := getObjectURL(c, opts.Container, opts.Name) + query
resp, err := perigee.Request("GET", url, perigee.Options{
MoreHeaders: h,
+ OkCodes: []int{200},
})
return &resp.HttpResponse, err
}
// Create is a function that creates a new object or replaces an existing object.
-func Create(c *storage.Client, opts CreateOpts) error {
+func Create(c *gophercloud.ServiceClient, opts CreateOpts) error {
var reqBody []byte
- h, err := c.GetHeaders()
- if err != nil {
- return err
- }
+ h := c.Provider.AuthenticatedHeaders()
for k, v := range opts.Headers {
h[k] = v
@@ -86,26 +80,24 @@
content := opts.Content
if content != nil {
reqBody = make([]byte, 0)
- _, err = content.Read(reqBody)
+ _, err := content.Read(reqBody)
if err != nil {
return err
}
}
- url := c.GetObjectURL(opts.Container, opts.Name) + query
- _, err = perigee.Request("PUT", url, perigee.Options{
+ url := getObjectURL(c, opts.Container, opts.Name) + query
+ _, err := perigee.Request("PUT", url, perigee.Options{
ReqBody: reqBody,
MoreHeaders: h,
+ OkCodes: []int{201},
})
return err
}
// Copy is a function that copies one object to another.
-func Copy(c *storage.Client, opts CopyOpts) error {
- h, err := c.GetHeaders()
- if err != nil {
- return err
- }
+func Copy(c *gophercloud.ServiceClient, opts CopyOpts) error {
+ h := c.Provider.AuthenticatedHeaders()
for k, v := range opts.Metadata {
h["X-Object-Meta-"+k] = v
@@ -113,54 +105,48 @@
h["Destination"] = fmt.Sprintf("/%s/%s", opts.NewContainer, opts.NewName)
- url := c.GetObjectURL(opts.Container, opts.Name)
- _, err = perigee.Request("COPY", url, perigee.Options{
+ url := getObjectURL(c, opts.Container, opts.Name)
+ _, err := perigee.Request("COPY", url, perigee.Options{
MoreHeaders: h,
+ OkCodes: []int{201},
})
return err
}
// Delete is a function that deletes an object.
-func Delete(c *storage.Client, opts DeleteOpts) error {
- h, err := c.GetHeaders()
- if err != nil {
- return err
- }
+func Delete(c *gophercloud.ServiceClient, opts DeleteOpts) error {
+ h := c.Provider.AuthenticatedHeaders()
query := utils.BuildQuery(opts.Params)
- url := c.GetObjectURL(opts.Container, opts.Name) + query
- _, err = perigee.Request("DELETE", url, perigee.Options{
+ url := getObjectURL(c, opts.Container, opts.Name) + query
+ _, err := perigee.Request("DELETE", url, perigee.Options{
MoreHeaders: h,
+ OkCodes: []int{204},
})
return err
}
// 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 *storage.Client, opts GetOpts) (GetResult, error) {
- h, err := c.GetHeaders()
- if err != nil {
- return nil, err
- }
+func Get(c *gophercloud.ServiceClient, opts GetOpts) (GetResult, error) {
+ h := c.Provider.AuthenticatedHeaders()
for k, v := range opts.Headers {
h[k] = v
}
- url := c.GetObjectURL(opts.Container, opts.Name)
+ url := getObjectURL(c, opts.Container, opts.Name)
resp, err := perigee.Request("HEAD", url, perigee.Options{
MoreHeaders: h,
+ OkCodes: []int{204},
})
return &resp.HttpResponse, err
}
// Update is a function that creates, updates, or deletes an object's metadata.
-func Update(c *storage.Client, opts UpdateOpts) error {
- h, err := c.GetHeaders()
- if err != nil {
- return err
- }
+func Update(c *gophercloud.ServiceClient, opts UpdateOpts) error {
+ h := c.Provider.AuthenticatedHeaders()
for k, v := range opts.Headers {
h[k] = v
@@ -170,9 +156,10 @@
h["X-Object-Meta-"+k] = v
}
- url := c.GetObjectURL(opts.Container, opts.Name)
- _, err = perigee.Request("POST", url, perigee.Options{
+ url := getObjectURL(c, opts.Container, opts.Name)
+ _, err := perigee.Request("POST", url, perigee.Options{
MoreHeaders: h,
+ OkCodes: []int{202},
})
return err
}
diff --git a/openstack/storage/v1/objects/urls.go b/openstack/storage/v1/objects/urls.go
new file mode 100644
index 0000000..5a52aed
--- /dev/null
+++ b/openstack/storage/v1/objects/urls.go
@@ -0,0 +1,13 @@
+package objects
+
+import "github.com/rackspace/gophercloud"
+
+// getObjectURL returns the URI for making Object requests.
+func getObjectURL(c *gophercloud.ServiceClient, container, object string) string {
+ return c.ServiceURL(container, object)
+}
+
+// getContainerURL returns the URI for making Container requests.
+func getContainerURL(c *gophercloud.ServiceClient, container string) string {
+ return c.ServiceURL(container)
+}