Repository: libcloud Updated Branches: refs/heads/trunk a53974511 -> 5231cecd2
add image member methods to openstackv2 Signed-off-by: Quentin Pradet <quent...@apache.org> Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/d5af528a Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/d5af528a Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/d5af528a Branch: refs/heads/trunk Commit: d5af528a6e22b51c9703048f727d6691da306144 Parents: a539745 Author: Rick van de Loo <rickvande...@gmail.com> Authored: Sun Feb 11 10:47:22 2018 +0100 Committer: Quentin Pradet <quent...@apache.org> Committed: Fri Mar 2 12:46:03 2018 +0400 ---------------------------------------------------------------------- libcloud/compute/base.py | 52 ++++++++++++++++ libcloud/compute/drivers/openstack.py | 63 ++++++++++++++++++-- libcloud/compute/types.py | 9 +++ ...d9a_278a_444c_90a6_d24b8c688a63_members.json | 1 + ...embers_016926dff12345e8b10329f24c99745b.json | 2 + libcloud/test/compute/test_openstack.py | 46 +++++++++++++- 6 files changed, 166 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/d5af528a/libcloud/compute/base.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/base.py b/libcloud/compute/base.py index 7d04509..f89ca7e 100644 --- a/libcloud/compute/base.py +++ b/libcloud/compute/base.py @@ -64,6 +64,7 @@ __all__ = [ 'NodeState', 'NodeSize', 'NodeImage', + 'NodeImageMember', 'NodeLocation', 'NodeAuthSSHKey', 'NodeAuthPassword', @@ -383,6 +384,57 @@ class NodeImage(UuidMixin): % (self.id, self.name, self.driver.name)) +class NodeImageMember(UuidMixin): + """ + A member of an image. At some cloud providers there is a mechanism + to share images. Once an image is shared with another account that + user will be a 'member' of the image. + + For example, see the image members schema in the OpenStack Image + Service API v2 documentation. https://developer.openstack.org/ + api-ref/image/v2/index.html#image-members-schema + + NodeImageMember objects are typically returned by the driver for the + cloud provider in response to the list_image_members method + """ + + def __init__(self, id, image_id, state, driver, created=None, extra=None): + """ + :param id: Image member ID. + :type id: ``str`` + + :param id: The associated image ID. + :type id: ``str`` + + :param state: State of the NodeImageMember. If not + provided, will default to UNKNOWN. + :type state: :class:`.NodeImageMemberState` + + :param driver: Driver this image belongs to. + :type driver: :class:`.NodeDriver` + + :param created: A datetime object that represents when the + image member was created + :type created: ``datetime.datetime`` + + :param extra: Optional provided specific attributes associated with + this image. + :type extra: ``dict`` + """ + self.id = str(id) + self.image_id = str(image_id) + self.state = state + self.driver = driver + self.created = created + self.extra = extra or {} + UuidMixin.__init__(self) + + def __repr__(self): + return (('<NodeImageMember: id=%s, image_id=%s, ' + 'state=%s, driver=%s ...>') + % (self.id, self.image_id, self.state, self.driver.name)) + + class NodeLocation(object): """ A physical location where nodes can be. http://git-wip-us.apache.org/repos/asf/libcloud/blob/d5af528a/libcloud/compute/drivers/openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index 020b657..527dce9 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -38,7 +38,7 @@ from libcloud.common.openstack import OpenStackDriverMixin from libcloud.common.openstack import OpenStackException from libcloud.common.openstack import OpenStackResponse from libcloud.utils.networking import is_public_subnet -from libcloud.compute.base import NodeSize, NodeImage +from libcloud.compute.base import NodeSize, NodeImage, NodeImageMember from libcloud.compute.base import (NodeDriver, Node, NodeLocation, StorageVolume, VolumeSnapshot) from libcloud.compute.base import KeyPair @@ -1320,6 +1320,21 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): ) ) + def _to_image_member(self, api_image_member): + created = api_image_member['created_at'] + updated = api_image_member.get('updated_at') + return NodeImageMember( + id=api_image_member['member_id'], + image_id=api_image_member['image_id'], + state=api_image_member['status'], + created=created, + driver=self, + extra=dict( + schema=api_image_member.get('schema'), + updated=updated, + ) + ) + def _to_nodes(self, obj): servers = obj['servers'] return [self._to_node(server) for server in servers] @@ -2545,12 +2560,10 @@ class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver): :param location: Which data center to list the images in. If empty, undefined behavior will be selected. (optional) - :type location: :class:`.NodeLocation` - :param ex_only_active: True if list only active (optional) + :param ex_only_active: True if list only active (optional) :type ex_only_active: ``bool`` - """ if location is not None: raise NotImplementedError( @@ -2569,15 +2582,16 @@ class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver): def ex_update_image(self, image_id, data): """ Patch a NodeImage. Can be used to set visibility - :param image_id: ID of the image which should be used + :param image_id: ID of the image which should be used :type image_id: ``str`` + :param data: The data to PATCH, either a dict or a list for example: [ {'op': 'replace', 'path': '/visibility', 'value': 'shared'} ] - :type data: ``dict|list`` + :rtype: :class:`NodeImage` """ response = self.image_connection.request( @@ -2589,6 +2603,43 @@ class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver): ) return self._to_image(response.object) + def ex_list_image_members(self, image_id): + """ + List all members of an image. See + https://developer.openstack.org/api-ref/image/v2/index.html#sharing + + :param image_id: ID of the image of which the members should + be listed + :type image_id: ``str`` + + :rtype: ``list`` of :class:`NodeImageMember` + """ + response = self.image_connection.request( + '/v2/images/%s/members' % (image_id,) + ) + image_members = [] + for image_member in response.object['members']: + image_members.append(self._to_image_member(image_member)) + return image_members + + def ex_get_image_member(self, image_id, member_id): + """ + Get a member of an image by id + + :param image_id: ID of the image of which the member should + be listed + :type image_id: ``str`` + + :param member_id: ID of the member to list + :type image_id: ``str`` + + :rtype: ``list`` of :class:`NodeImageMember` + """ + response = self.image_connection.request( + '/v2/images/%s/members/%s' % (image_id, member_id) + ) + return self._to_image_member(response.object) + class OpenStack_1_1_FloatingIpPool(object): """ http://git-wip-us.apache.org/repos/asf/libcloud/blob/d5af528a/libcloud/compute/types.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/types.py b/libcloud/compute/types.py index 0de0497..89d7d06 100644 --- a/libcloud/compute/types.py +++ b/libcloud/compute/types.py @@ -321,6 +321,15 @@ class VolumeSnapshotState(Type): UPDATING = 'updating' +class NodeImageMemberState(Type): + """ + Standard states of VolumeSnapshots + """ + ACCEPTED = 'accepted' + PENDING = 'pending' + REJECTED = 'rejected' + + class Architecture(object): """ Image and size architectures. http://git-wip-us.apache.org/repos/asf/libcloud/blob/d5af528a/libcloud/test/compute/fixtures/openstack_v1.1/_images_d9a9cd9a_278a_444c_90a6_d24b8c688a63_members.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_images_d9a9cd9a_278a_444c_90a6_d24b8c688a63_members.json b/libcloud/test/compute/fixtures/openstack_v1.1/_images_d9a9cd9a_278a_444c_90a6_d24b8c688a63_members.json new file mode 100644 index 0000000..b81f48d --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1.1/_images_d9a9cd9a_278a_444c_90a6_d24b8c688a63_members.json @@ -0,0 +1 @@ +{"members": [{"status": "accepted", "created_at": "2017-01-12T12:31:50Z", "updated_at": "2017-01-12T12:31:54Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "016926dff12345e8b10329f24c99745b", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T12:43:24Z", "updated_at": "2017-01-12T12:43:28Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "0cb96fe6d1a749a3b32733bf1fc5e3d8", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-19T08:06:33Z", "updated_at": "2017-01-19T08:06:58Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "0d6aa4aaa8624fa38621e23b84a583f6", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T12:19:38Z", "updated_at": "2017-01-12T12:19:41Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "0e3c876745274d13b6c2ab70f82f4503", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T12: 15:44Z", "updated_at": "2017-01-12T12:15:47Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "13c07d734435bb1ea2faeeb23f8574c6", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-02-09T14:08:46Z", "updated_at": "2017-02-09T14:08:33Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "1a0ba874fcaf4a0c814dc85d2a2ef9bc", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-27T13:20:08Z", "updated_at": "2017-01-27T13:20:11Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "1ce2f38e23a44ab8bb6a2cc2b409de06", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T12:31:48Z", "updated_at": "2017-01-12T12:31:52Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "2b269ec4335745ef84562d4f1fabbb50", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T10:33:34Z", "updated_at": "2017-01-12T10:33:37Z", "image_id": "d9a9c d9a-278a-444c-90a6-d24b8c688a63", "member_id": "31eb590cf2014726bd3aa372f1e21383", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T12:27:14Z", "updated_at": "2017-01-12T12:27:17Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "32ab6a1cfa6f4c2da7233a27c7ab624c", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-19T08:24:39Z", "updated_at": "2017-01-19T08:24:42Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "3865bd651ee141c5b37b81e2333daaef", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-16T14:12:12Z", "updated_at": "2017-01-16T14:12:15Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "33a2b8475b3443738c443b94c3be6852", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-27T13:05:16Z", "updated_at": "2017-01-27T13:05:20Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "4b9aa0de831245e2b 26f0b1b53d2b70b", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T12:21:31Z", "updated_at": "2017-01-12T12:21:34Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "4f1568969ad84c939cc9c396a43ad757", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T12:13:10Z", "updated_at": "2017-01-12T12:13:13Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "5a21d13639bc4141a3030c46e95e8ed4", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T10:13:37Z", "updated_at": "2017-01-12T10:13:40Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "679d169d15a8448a91cd66b1d34367d6", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-27T15:31:29Z", "updated_at": "2017-01-27T15:31:33Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "6c469adc3d464275abe13aabdd28b19a", "schema": "/v2/schemas/member"}, {"status": "ac cepted", "created_at": "2017-01-12T12:30:29Z", "updated_at": "2017-01-12T12:30:32Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "6fbabd8cba81429cb35ff1df4bd2a62e", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-27T12:54:40Z", "updated_at": "2017-01-27T12:54:44Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "77a940af30ac444ea6734fd809138906", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-02-09T14:16:59Z", "updated_at": "2017-02-09T14:17:02Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "796deab733b14a3781f3f9cb07c834a5", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T12:12:41Z", "updated_at": "2017-01-12T12:12:44Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "84ee139deb75b133b5616571b989e780", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-02-09T14:01:22Z", "updated_at": "201 7-02-09T14:01:26Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "88af7921eeda432ca47b09c0e6bd4aa3", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T10:56:07Z", "updated_at": "2017-01-12T10:56:10Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "ae394c8ecde5b09a8ba04aafefdac1e6", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T12:12:04Z", "updated_at": "2017-01-12T12:12:07Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "d146a26974104aa1ba3ba56bed8699dc", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-02-07T27:17:35Z", "updated_at": "2017-02-07T27:17:38Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "da1fe8eafd794ef8bda390dcf78c3927", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T12:21:27Z", "updated_at": "2017-01-12T12:21:21Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c68 8a63", "member_id": "da52660bdabf4adeab261a0fde666e80", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-19T08:22:21Z", "updated_at": "2017-01-19T08:22:24Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "e883313f4a864ef9933b3574f4ea3c42", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T12:31:51Z", "updated_at": "2017-01-12T12:31:54Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "ec9b3bcb5bac4f4ea3dd42afff28713a", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-12T12:15:08Z", "updated_at": "2017-01-12T12:15:12Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "fdfb81a8ffaa4f1d9d7b712ac3369332", "schema": "/v2/schemas/member"}, {"status": "accepted", "created_at": "2017-01-19T08:13:03Z", "updated_at": "2017-01-19T08:13:07Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "ffad303302884e96998c8d4564a263c2", "schema": "/v2/schemas/member"}], "schema": "/v2/schemas/members"} http://git-wip-us.apache.org/repos/asf/libcloud/blob/d5af528a/libcloud/test/compute/fixtures/openstack_v1.1/_images_d9a9cd9a_278a_444c_90a6_d24b8c688a63_members_016926dff12345e8b10329f24c99745b.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_images_d9a9cd9a_278a_444c_90a6_d24b8c688a63_members_016926dff12345e8b10329f24c99745b.json b/libcloud/test/compute/fixtures/openstack_v1.1/_images_d9a9cd9a_278a_444c_90a6_d24b8c688a63_members_016926dff12345e8b10329f24c99745b.json new file mode 100644 index 0000000..2e1024b --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1.1/_images_d9a9cd9a_278a_444c_90a6_d24b8c688a63_members_016926dff12345e8b10329f24c99745b.json @@ -0,0 +1,2 @@ +{"status": "accepted", "created_at": "2017-01-12T12:31:50Z", "updated_at": "2017-01-12T12:31:54Z", "image_id": "d9a9cd9a-278a-444c-90a6-d24b8c688a63", "member_id": "016926dff12345e8b10329f24c99745b", "schema": "/v2/schemas/member"} + http://git-wip-us.apache.org/repos/asf/libcloud/blob/d5af528a/libcloud/test/compute/test_openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py index de04c42..02ed3f3 100644 --- a/libcloud/test/compute/test_openstack.py +++ b/libcloud/test/compute/test_openstack.py @@ -38,7 +38,7 @@ from libcloud.common.base import LibcloudConnection from libcloud.common.types import InvalidCredsError, MalformedResponseError, \ LibcloudError from libcloud.compute.types import Provider, KeyPairDoesNotExistError, StorageVolumeState, \ - VolumeSnapshotState + VolumeSnapshotState, NodeImageMemberState from libcloud.compute.providers import get_driver from libcloud.compute.drivers.openstack import ( OpenStack_1_0_NodeDriver, @@ -1636,6 +1636,34 @@ class OpenStack_2_Tests(OpenStack_1_1_Tests): self.assertEqual(image.extra['minRam'], 0) self.assertEqual(image.extra['visibility'], "shared") + def test_ex_list_image_members(self): + image_id = 'd9a9cd9a-278a-444c-90a6-d24b8c688a63' + image_member_id = '016926dff12345e8b10329f24c99745b' + image_members = self.driver.ex_list_image_members(image_id) + self.assertEqual(len(image_members), 30, 'Wrong image member count') + + image_member = image_members[0] + self.assertEqual(image_member.id, image_member_id) + self.assertEqual(image_member.image_id, image_id) + self.assertEqual(image_member.state, NodeImageMemberState.ACCEPTED) + self.assertEqual(image_member.created, '2017-01-12T12:31:50Z') + self.assertEqual(image_member.extra['updated'], '2017-01-12T12:31:54Z') + self.assertEqual(image_member.extra['schema'], '/v2/schemas/member') + + def test_ex_get_image_member(self): + image_id = 'd9a9cd9a-278a-444c-90a6-d24b8c688a63' + image_member_id = '016926dff12345e8b10329f24c99745b' + image_member = self.driver.ex_get_image_member( + image_id, image_member_id + ) + + self.assertEqual(image_member.id, image_member_id) + self.assertEqual(image_member.image_id, image_id) + self.assertEqual(image_member.state, NodeImageMemberState.ACCEPTED) + self.assertEqual(image_member.created, '2017-01-12T12:31:50Z') + self.assertEqual(image_member.extra['updated'], '2017-01-12T12:31:54Z') + self.assertEqual(image_member.extra['schema'], '/v2/schemas/member') + class OpenStack_1_1_FactoryMethodTests(OpenStack_1_1_Tests): should_list_locations = False @@ -1790,6 +1818,22 @@ class OpenStack_1_1_MockHttp(MockHttp, unittest.TestCase): else: raise NotImplementedError() + def _v2_1337_v2_images_d9a9cd9a_278a_444c_90a6_d24b8c688a63_members(self, method, url, body, headers): + if method == "GET": + body = self.fixtures.load('_images_d9a9cd9a_278a_444c_90a6_d24b8c688a63_members.json') + return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + else: + raise NotImplementedError() + + def _v2_1337_v2_images_d9a9cd9a_278a_444c_90a6_d24b8c688a63_members_016926dff12345e8b10329f24c99745b(self, method, url, body, headers): + if method == "GET": + body = self.fixtures.load( + '_images_d9a9cd9a_278a_444c_90a6_d24b8c688a63_members_016926dff12345e8b10329f24c99745b.json' + ) + return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + else: + raise NotImplementedError() + def _v2_1337_v2_images(self, method, url, body, headers): if method == "GET": body = self.fixtures.load('_images_v2.json')