Permalink
Browse files

[WIP] OpenStack Glance support

    * added image member create API support
    * added image memeber list API support
  • Loading branch information...
1 parent 36482be commit 0fdf6199938f31e3550cac96e4c5131bfb072dbb @zaletniy zaletniy committed with sameo Aug 10, 2015
@@ -357,3 +357,93 @@ func HandleGetImageDataSuccessfully(t *testing.T) {
th.AssertNoErr(t, err)
})
}
+
+// HandleCreateImageMemberSuccessfully setup
+func HandleCreateImageMemberSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID)
+
+ th.TestJSONRequest(t, r, `{"member": "8989447062e04a818baf9e073fd04fa7"}`)
+
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, `{
+ "created_at": "2013-09-20T19:22:19Z",
+ "image_id": "da3b75d9-3f4a-40e7-8a2c-bfab23927dea",
+ "member_id": "8989447062e04a818baf9e073fd04fa7",
+ "schema": "/v2/schemas/member",
+ "status": "pending",
+ "updated_at": "2013-09-20T19:25:31Z"
+ }`)
+
+ })
+}
+
+// HandleCreateImageMemberInvalidVisibility setup for case when visibility=public
+func HandleCreateImageMemberInvalidVisibility(t *testing.T) {
+ th.Mux.HandleFunc("/images/da3b75d9-invalid-visibility/members", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID)
+
+ th.TestJSONRequest(t, r, `{"member": "8989447062e04a818baf9e073fd04fa7"}`)
+ w.WriteHeader(http.StatusForbidden)
+ })
+
+}
+
+// HandleCreateImageMemberConflict setup for case when member is already image member
+func HandleCreateImageMemberConflict(t *testing.T) {
+ th.Mux.HandleFunc("/images/da3b75d9-memberConflict/members", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID)
+
+ th.TestJSONRequest(t, r, `{"member": "8989447062e04a818baf9e073fd04fa7"}`)
+
+ w.WriteHeader(http.StatusConflict)
+ })
+}
+
+// HandleImageMemberList happy path setup
+func HandleImageMemberList(t *testing.T) {
+ th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID)
+
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, `{
+ "members": [
+ {
+ "created_at": "2013-10-07T17:58:03Z",
+ "image_id": "da3b75d9-3f4a-40e7-8a2c-bfab23927dea",
+ "member_id": "123456789",
+ "schema": "/v2/schemas/member",
+ "status": "pending",
+ "updated_at": "2013-10-07T17:58:03Z"
+ },
+ {
+ "created_at": "2013-10-07T17:58:55Z",
+ "image_id": "da3b75d9-3f4a-40e7-8a2c-bfab23927dea",
+ "member_id": "987654321",
+ "schema": "/v2/schemas/member",
+ "status": "accepted",
+ "updated_at": "2013-10-08T12:08:55Z"
+ }
+ ],
+ "schema": "/v2/schemas/members"
+ }`)
+ })
+}
+
+// HandleImageMemberEmptyList happy path setup
+func HandleImageMemberEmptyList(t *testing.T) {
+ th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID)
+
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, `{
+ "members": [],
+ "schema": "/v2/schemas/members"
+ }`)
+ })
+}
@@ -180,6 +180,53 @@ func Get(client *gophercloud.ServiceClient, id string) GetResult {
return res
}
+// CreateMember for specific image
+//
+// Preconditions
+// The specified images must exist.
+// You can only add a new member to an image which 'visibility' attribute is private.
+// You must be the owner of the specified image.
+// Synchronous Postconditions
+// With correct permissions, you can see the member status of the image as pending through API calls.
+//
+// More details here: http://developer.openstack.org/api-ref-image-v2.html#createImageMember-v2
+func CreateMember(client *gophercloud.ServiceClient, id string, member string) CreateMemberResult {
+ var res CreateMemberResult
+ body := map[string]interface{}{}
+ body["member"] = member
+
+ response, err := client.Post(imageMembersURL(client, id), body, &res.Body,
+ &gophercloud.RequestOpts{OkCodes: []int{200, 409, 403}})
+
+ //some problems in http stack or lower
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
+ // membership conflict
+ if response.StatusCode == 409 {
+ res.Err = fmt.Errorf("Given tenant '%s' is already member for image '%s'.", member, id)
+ return res
+ }
+
+ // visibility conflict
+ if response.StatusCode == 403 {
+ res.Err = fmt.Errorf("You can only add a new member to an image "+
+ "which 'visibility' attribute is private (image '%s')", id)
+ return res
+ }
+
+ return res
+}
+
+// ListMembers returns list of members for specifed image id
+func ListMembers(client *gophercloud.ServiceClient, id string) ListMembersResult {
+ var res ListMembersResult
+ _, res.Err = client.Get(listMembersURL(client, id), &res.Body, &gophercloud.RequestOpts{OkCodes: []int{200}})
+ return res
+}
+
// Update implements image updated request
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
@@ -4,6 +4,7 @@ import (
"fmt"
"io"
"io/ioutil"
+ "strings"
"testing"
"github.com/rackspace/gophercloud/pagination"
@@ -316,3 +317,84 @@ func TestGetImageData(t *testing.T) {
th.AssertByteArrayEquals(t, []byte{34, 87, 0, 23, 23, 23, 56, 255, 254, 0}, bs)
}
+
+func TestCreateMemberSuccessfully(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleCreateImageMemberSuccessfully(t)
+ im, err := CreateMember(fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea",
+ "8989447062e04a818baf9e073fd04fa7").Extract()
+ th.AssertNoErr(t, err)
+
+ th.AssertDeepEquals(t, ImageMember{
+ CreatedAt: "2013-09-20T19:22:19Z",
+ ImageID: "da3b75d9-3f4a-40e7-8a2c-bfab23927dea",
+ MemberID: "8989447062e04a818baf9e073fd04fa7",
+ Schema: "/v2/schemas/member",
+ Status: "pending",
+ UpdatedAt: "2013-09-20T19:25:31Z",
+ }, *im)
+
+}
+func TestCreateMemberMemberConflict(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleCreateImageMemberConflict(t)
+
+ result := CreateMember(fakeclient.ServiceClient(), "da3b75d9-memberConflict",
+ "8989447062e04a818baf9e073fd04fa7")
+
+ if result.Err == nil {
+ t.Fatalf("Expected error in result defined (Err: %v)", result.Err)
+ }
+
+ message := result.Err.Error()
+ if !strings.Contains(message, "is already member for image") {
+ t.Fatalf("Wrong error message: %s", message)
+ }
+
+}
+func TestCreateMemberInvalidVisibility(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleCreateImageMemberInvalidVisibility(t)
+
+ result := CreateMember(fakeclient.ServiceClient(), "da3b75d9-invalid-visibility",
+ "8989447062e04a818baf9e073fd04fa7")
+
+ if result.Err == nil {
+ t.Fatalf("Expected error in result defined (Err: %v)", result.Err)
+ }
+
+ message := result.Err.Error()
+ if !strings.Contains(message, "which 'visibility' attribute is private") {
+ t.Fatalf("Wrong error message: %s", message)
+ }
+}
+
+func TestMemberListSuccessfully(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleImageMemberList(t)
+
+ images, err := ListMembers(fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea").Extract()
+ th.AssertNoErr(t, err)
+ th.AssertNotNil(t, images)
+ th.AssertEquals(t, 2, len(*images))
+}
+
+func TestMemberListEmpty(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleImageMemberEmptyList(t)
+
+ images, err := ListMembers(fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea").Extract()
+ th.AssertNoErr(t, err)
+ th.AssertNotNil(t, images)
+ th.AssertEquals(t, 0, len(*images))
+}
@@ -402,3 +402,51 @@ func ExtractImages(page pagination.Page) ([]Image, error) {
return response.Images, err
}
+
+// ImageMember model
+type ImageMember struct {
+ CreatedAt string `mapstructure:"created_at"`
+ ImageID string `mapstructure:"image_id"`
+ MemberID string `mapstructure:"member_id"`
+ Schema string
+ // Status could be one of pending, accepted, reject
+ Status string
+ UpdatedAt string `mapstructure:"updated_at"`
+}
+
+// CreateMemberResult result model
+type CreateMemberResult struct {
+ gophercloud.ErrResult
+}
+
+// Extract ImageMember model from request if possible
+func (cm CreateMemberResult) Extract() (*ImageMember, error) {
+ if cm.Err != nil {
+ return nil, cm.Err
+ }
+ casted := cm.Body.(map[string]interface{})
+ var results ImageMember
+
+ err := mapstructure.Decode(casted, &results)
+ return &results, err
+}
+
+// ListMembersResult model
+type ListMembersResult struct {
+ gophercloud.ErrResult
+}
+
+// Extract returns list of image members
+func (lm ListMembersResult) Extract() (*[]ImageMember, error) {
+ if lm.Err != nil {
+ return nil, lm.Err
+ }
+ casted := lm.Body.(map[string]interface{})
+
+ var results struct {
+ ImageMembers []ImageMember `mapstructure:"members"`
+ }
+
+ err := mapstructure.Decode(casted, &results)
+ return &results.ImageMembers, err
+}
@@ -276,12 +276,14 @@ func isByteArrayEquals(t *testing.T, expectedBytes []byte, actualBytes []byte) b
return bytes.Equal(expectedBytes, actualBytes)
}
+// AssertByteArrayEquals a convenience function for checking whether two byte arrays are equal
func AssertByteArrayEquals(t *testing.T, expectedBytes []byte, actualBytes []byte) {
if !isByteArrayEquals(t, expectedBytes, actualBytes) {
logFatal(t, "The bytes differed.")
}
}
+// CheckByteArrayEquals a convenience function for silent checking whether two byte arrays are equal
func CheckByteArrayEquals(t *testing.T, expectedBytes []byte, actualBytes []byte) {
if !isByteArrayEquals(t, expectedBytes, actualBytes) {
logError(t, "The bytes differed.")
@@ -353,6 +355,13 @@ func AssertNoErr(t *testing.T, e error) {
}
}
+// AssertNotNil is a convenience function for checking whether given value is not nil
+func AssertNotNil(t *testing.T, actual interface{}) {
+ if actual == nil || !reflect.ValueOf(actual).Elem().IsValid() {
+ logFatal(t, fmt.Sprintf("Not nil expexted, but was %v", actual))
+ }
+}
+
// CheckNoErr is similar to AssertNoErr, except with a non-fatal error
func CheckNoErr(t *testing.T, e error) {
if e != nil {

0 comments on commit 0fdf619

Please sign in to comment.