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
|
Abstracts the common pattern of allowing both an object or an object's ID
|
||||||
(UUID) as a parameter when dealing with relationships.
|
(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:
|
try:
|
||||||
return obj.id
|
return obj.id
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -86,10 +79,12 @@ class Manager(object):
|
|||||||
methods = {"PUT": self.api.put,
|
methods = {"PUT": self.api.put,
|
||||||
"POST": self.api.post}
|
"POST": self.api.post}
|
||||||
try:
|
try:
|
||||||
resp, body = methods[method](url, body=body)
|
_method = methods[method]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise exceptions.ClientException("Invalid update method: %s"
|
msg = "Invalid update method: %s" % method
|
||||||
% method)
|
raise exceptions.ClientException(msg)
|
||||||
|
|
||||||
|
resp, body = _method(url, body=body)
|
||||||
# PUT requests may not return a body
|
# PUT requests may not return a body
|
||||||
if body:
|
if body:
|
||||||
return self.resource_class(self, body[response_key])
|
return self.resource_class(self, body[response_key])
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import logging
|
|||||||
|
|
||||||
from glanceclient.common import http
|
from glanceclient.common import http
|
||||||
from glanceclient.v1 import images
|
from glanceclient.v1 import images
|
||||||
|
from glanceclient.v1 import image_members
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -36,3 +37,4 @@ class Client(http.HTTPClient):
|
|||||||
""" Initialize a new client for the Images v1 API. """
|
""" Initialize a new client for the Images v1 API. """
|
||||||
super(Client, self).__init__(endpoint, token=token, timeout=timeout)
|
super(Client, self).__init__(endpoint, token=token, timeout=timeout)
|
||||||
self.images = images.ImageManager(self)
|
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):
|
def __repr__(self):
|
||||||
return "<Image %s>" % self._info
|
return "<Image %s>" % self._info
|
||||||
|
|
||||||
|
def update(self, **fields):
|
||||||
|
self.manager.update(self, **fields)
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
return self.manager.delete(self)
|
return self.manager.delete(self)
|
||||||
|
|
||||||
@@ -49,13 +52,13 @@ class ImageManager(base.Manager):
|
|||||||
headers['x-image-meta-%s' % key] = value
|
headers['x-image-meta-%s' % key] = value
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
def get(self, image):
|
def get(self, image_id):
|
||||||
"""Get the metadata for a specific image.
|
"""Get the metadata for a specific image.
|
||||||
|
|
||||||
:param image: image object or id to look up
|
:param image: image object or id to look up
|
||||||
:rtype: :class:`Image`
|
: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)
|
meta = self._image_meta_from_headers(resp)
|
||||||
return Image(self, meta)
|
return Image(self, meta)
|
||||||
|
|
||||||
@@ -97,3 +100,14 @@ class ImageManager(base.Manager):
|
|||||||
resp, body = self.api.put(url, headers=send_meta)
|
resp, body = self.api.put(url, headers=send_meta)
|
||||||
recv_meta = self._image_meta_from_headers(resp)
|
recv_meta = self._image_meta_from_headers(resp)
|
||||||
return Image(self, recv_meta)
|
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(self.api.calls, expect)
|
||||||
self.assertEqual(image.id, '1')
|
self.assertEqual(image.id, '1')
|
||||||
self.assertEqual(image.name, 'image-2')
|
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 = {
|
fixtures = {
|
||||||
'/v1/images': {
|
'/v1/images': {
|
||||||
@@ -39,8 +52,22 @@ fixtures = {
|
|||||||
},
|
},
|
||||||
None),
|
None),
|
||||||
'DELETE': ({}, 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):
|
class FakeAPI(object):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user