Image members bones
This commit is contained in:
@@ -33,13 +33,6 @@ def getid(obj):
|
||||
Abstracts the common pattern of allowing both an object or an object's ID
|
||||
(UUID) as a parameter when dealing with relationships.
|
||||
"""
|
||||
|
||||
# Try to return the object's UUID first, if we have a UUID.
|
||||
try:
|
||||
if obj.uuid:
|
||||
return obj.uuid
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
return obj.id
|
||||
except AttributeError:
|
||||
@@ -86,10 +79,12 @@ class Manager(object):
|
||||
methods = {"PUT": self.api.put,
|
||||
"POST": self.api.post}
|
||||
try:
|
||||
resp, body = methods[method](url, body=body)
|
||||
_method = methods[method]
|
||||
except KeyError:
|
||||
raise exceptions.ClientException("Invalid update method: %s"
|
||||
% method)
|
||||
msg = "Invalid update method: %s" % method
|
||||
raise exceptions.ClientException(msg)
|
||||
|
||||
resp, body = _method(url, body=body)
|
||||
# PUT requests may not return a body
|
||||
if body:
|
||||
return self.resource_class(self, body[response_key])
|
||||
|
||||
@@ -17,6 +17,7 @@ import logging
|
||||
|
||||
from glanceclient.common import http
|
||||
from glanceclient.v1 import images
|
||||
from glanceclient.v1 import image_members
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -36,3 +37,4 @@ class Client(http.HTTPClient):
|
||||
""" Initialize a new client for the Images v1 API. """
|
||||
super(Client, self).__init__(endpoint, token=token, timeout=timeout)
|
||||
self.images = images.ImageManager(self)
|
||||
self.image_members = image_members.ImageMemberManager(self)
|
||||
|
||||
64
glanceclient/v1/image_members.py
Normal file
64
glanceclient/v1/image_members.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from glanceclient.common import base
|
||||
|
||||
|
||||
class ImageMember(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<ImageMember %s>" % self._info
|
||||
|
||||
|
||||
class ImageMemberManager(base.Manager):
|
||||
resource_class = ImageMember
|
||||
|
||||
def get(self, image, member_id):
|
||||
url = '/v1/images/%s' % (base.getid(image), member_id)
|
||||
return self._get(url, 'member')
|
||||
|
||||
def list(self, image):
|
||||
url = '/v1/images/%s/members' % base.getid(image)
|
||||
return self._list(url, 'members')
|
||||
|
||||
def delete(self, image, member):
|
||||
image_id = base.getid(image)
|
||||
try:
|
||||
member_id = base.getid(member)
|
||||
except AttributeError:
|
||||
member_id = member
|
||||
|
||||
self._delete("/v1/images/%s/members/%s" % (image_id, member_id))
|
||||
|
||||
def create(self, image, member_id, can_share=False):
|
||||
"""Create an image"""
|
||||
url = '/v1/images/%s/members/%s' % (base.getid(image), member_id)
|
||||
body = {'member': {'can_share': can_share}}
|
||||
self._update(url, body=body)
|
||||
|
||||
def replace(self, image, members):
|
||||
memberships = []
|
||||
for member in members:
|
||||
try:
|
||||
obj = {
|
||||
'member_id': member.member_id,
|
||||
'can_share': member.can_share,
|
||||
}
|
||||
except AttributeError:
|
||||
obj = {'member_id': member['member_id']}
|
||||
if 'can_share' in member:
|
||||
obj['can_share'] = member['can_share']
|
||||
memberships.append(obj)
|
||||
url = '/v1/images/%s/members' % base.getid(image)
|
||||
self.api.put(url, {}, {'memberships': memberships})
|
||||
@@ -22,6 +22,9 @@ class Image(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Image %s>" % self._info
|
||||
|
||||
def update(self, **fields):
|
||||
self.manager.update(self, **fields)
|
||||
|
||||
def delete(self):
|
||||
return self.manager.delete(self)
|
||||
|
||||
@@ -49,13 +52,13 @@ class ImageManager(base.Manager):
|
||||
headers['x-image-meta-%s' % key] = value
|
||||
return headers
|
||||
|
||||
def get(self, image):
|
||||
def get(self, image_id):
|
||||
"""Get the metadata for a specific image.
|
||||
|
||||
:param image: image object or id to look up
|
||||
:rtype: :class:`Image`
|
||||
"""
|
||||
resp, body = self.api.head('/v1/images/%s' % base.getid(image))
|
||||
resp, body = self.api.head('/v1/images/%s' % image_id)
|
||||
meta = self._image_meta_from_headers(resp)
|
||||
return Image(self, meta)
|
||||
|
||||
@@ -97,3 +100,14 @@ class ImageManager(base.Manager):
|
||||
resp, body = self.api.put(url, headers=send_meta)
|
||||
recv_meta = self._image_meta_from_headers(resp)
|
||||
return Image(self, recv_meta)
|
||||
|
||||
def delete_member(self, image, image_member):
|
||||
"""Remove a member from an image"""
|
||||
image_id = base.getid(image)
|
||||
try:
|
||||
member_id = image_member.member_id
|
||||
except AttributeError:
|
||||
member_id = image_member
|
||||
|
||||
url = '/v1/images/%s/members/%s' % (image_id, member_id)
|
||||
resp, body = self.api.delete(url)
|
||||
|
||||
0
glanceclient/v1/shell.py
Executable file → Normal file
0
glanceclient/v1/shell.py
Executable file → Normal file
60
tests/v1/test_image_members.py
Normal file
60
tests/v1/test_image_members.py
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
import unittest
|
||||
|
||||
from tests.v1 import utils
|
||||
|
||||
import glanceclient.v1.images
|
||||
import glanceclient.v1.image_members
|
||||
|
||||
|
||||
class ImageMemberManagerTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.api = utils.FakeAPI()
|
||||
self.mgr = glanceclient.v1.image_members.ImageMemberManager(self.api)
|
||||
self.image = glanceclient.v1.images.Image(self.api, {'id': '1'}, True)
|
||||
|
||||
def test_list(self):
|
||||
members = self.mgr.list(self.image)
|
||||
expect = [('GET', '/v1/images/1/members', {}, None)]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
self.assertEqual(len(members), 1)
|
||||
self.assertEqual(members[0].member_id, '1')
|
||||
self.assertEqual(members[0].can_share, False)
|
||||
|
||||
def test_delete(self):
|
||||
self.mgr.delete(self.image, '1')
|
||||
expect = [('DELETE', '/v1/images/1/members/1', {}, None)]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
|
||||
def test_create(self):
|
||||
self.mgr.create(self.image, '1', can_share=True)
|
||||
expect_body = {'member': {'can_share': True}}
|
||||
expect = [('PUT', '/v1/images/1/members/1', {}, expect_body)]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
|
||||
def test_replace(self):
|
||||
body = [
|
||||
{'member_id': '2', 'can_share': False},
|
||||
{'member_id': '3'},
|
||||
]
|
||||
self.mgr.replace(self.image, body)
|
||||
expect = [('PUT', '/v1/images/1/members', {}, {'memberships': body})]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
|
||||
def test_replace_objects(self):
|
||||
body = [
|
||||
glanceclient.v1.image_members.ImageMember(
|
||||
self.mgr, {'member_id': '2', 'can_share': False}),
|
||||
glanceclient.v1.image_members.ImageMember(
|
||||
self.mgr, {'member_id': '3', 'can_share': True}),
|
||||
]
|
||||
self.mgr.replace(self.image, body)
|
||||
expect_body = {
|
||||
'memberships': [
|
||||
{'member_id': '2', 'can_share': False},
|
||||
{'member_id': '3', 'can_share': True},
|
||||
],
|
||||
}
|
||||
expect = [('PUT', '/v1/images/1/members', {}, expect_body)]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
@@ -57,3 +57,27 @@ class ImageManagerTest(unittest.TestCase):
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
self.assertEqual(image.id, '1')
|
||||
self.assertEqual(image.name, 'image-2')
|
||||
|
||||
|
||||
class ImageTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.api = utils.FakeAPI()
|
||||
self.mgr = glanceclient.v1.images.ImageManager(self.api)
|
||||
|
||||
def test_delete(self):
|
||||
image = self.mgr.get('1')
|
||||
image.delete()
|
||||
expect = [
|
||||
('HEAD', '/v1/images/1', {}, None),
|
||||
('DELETE', '/v1/images/1', {}, None),
|
||||
]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
|
||||
def test_update(self):
|
||||
image = self.mgr.get('1')
|
||||
image.update(name='image-5')
|
||||
expect = [
|
||||
('HEAD', '/v1/images/1', {}, None),
|
||||
('PUT', '/v1/images/1', {'x-image-meta-name': 'image-5'}, None),
|
||||
]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
fixtures = {
|
||||
'/v1/images': {
|
||||
@@ -39,9 +52,23 @@ fixtures = {
|
||||
},
|
||||
None),
|
||||
'DELETE': ({}, None),
|
||||
}
|
||||
},
|
||||
'/v1/images/1/members': {
|
||||
'GET': (
|
||||
{},
|
||||
{'members': [
|
||||
{'member_id': '1', 'can_share': False},
|
||||
]},
|
||||
),
|
||||
'PUT': ({}, None),
|
||||
},
|
||||
'/v1/images/1/members/1': {
|
||||
'PUT': ({}, None),
|
||||
'DELETE': ({}, None),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class FakeAPI(object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
Reference in New Issue
Block a user