Skip to content
This repository has been archived by the owner on Aug 1, 2023. It is now read-only.

Commit

Permalink
[WIP] OpenStack Glance support
Browse files Browse the repository at this point in the history
    * added image member create API support
    * added image memeber list API support
  • Loading branch information
zaletniy authored and Samuel Ortiz committed Jun 21, 2016
1 parent 36482be commit 0fdf619
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 0 deletions.
90 changes: 90 additions & 0 deletions openstack/imageservice/v2/fixtures.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}`)
})
}
47 changes: 47 additions & 0 deletions openstack/imageservice/v2/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
82 changes: 82 additions & 0 deletions openstack/imageservice/v2/requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io"
"io/ioutil"
"strings"
"testing"

"github.com/rackspace/gophercloud/pagination"
Expand Down Expand Up @@ -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))
}
48 changes: 48 additions & 0 deletions openstack/imageservice/v2/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
9 changes: 9 additions & 0 deletions testhelper/convenience.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 0fdf619

Please sign in to comment.