From 12d162d0ee24ea0e0c696f0f11adfd6ac3bee810 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 31 May 2012 14:47:30 -0700 Subject: [PATCH] Add client work for new cinder extensions. * #5 of blueprint volume-decoupling * Fix pep8 Change-Id: Icad4eada0cf16a0f49d9a52e593bede0fc0c5289 --- cinderclient/v1/volumes.py | 113 +++++++++++++++++++++++++++++++++++++ tests/v1/fakes.py | 25 ++++++++ tests/v1/test_volumes.py | 52 +++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 tests/v1/test_volumes.py diff --git a/cinderclient/v1/volumes.py b/cinderclient/v1/volumes.py index ad3c1bc3b..64c2dfebc 100644 --- a/cinderclient/v1/volumes.py +++ b/cinderclient/v1/volumes.py @@ -33,6 +33,49 @@ class Volume(base.Resource): """ self.manager.delete(self) + def attach(self, instance_uuid, mountpoint): + """ + Set attachment metadata. + + :param instance_uuid: uuid of the attaching instance. + :param mountpoint: mountpoint on the attaching instance. + """ + return self.manager.attach(self, instance_uuid, mountpoint) + + def detach(self): + """ + Clear attachment metadata. + """ + return self.manager.detach(self) + + def reserve(self, volume): + """ + Reserve this volume. + """ + return self.manager.reserve(self) + + def unreserve(self, volume): + """ + Unreserve this volume. + """ + return self.manager.unreserve(self) + + def initialize_connection(self, volume, connector): + """ + Initialize a volume connection. + + :param connector: connector dict from nova. + """ + return self.manager.initialize_connection(self, connector) + + def terminate_connection(self, volume, connector): + """ + Terminate a volume connection. + + :param connector: connector dict from nova. + """ + return self.manager.terminate_connection(self, connector) + class VolumeManager(base.ManagerWithFind): """ @@ -133,3 +176,73 @@ class VolumeManager(base.ManagerWithFind): """ self._delete("/servers/%s/os-volume_attachments/%s" % (server_id, attachment_id,)) + + def _action(self, action, volume, info=None, **kwargs): + """ + Perform a volume "action." + """ + body = {action: info} + self.run_hooks('modify_body_for_action', body, **kwargs) + url = '/volumes/%s/action' % base.getid(volume) + return self.api.client.post(url, body=body) + + def attach(self, volume, instance_uuid, mountpoint): + """ + Set attachment metadata. + + :param server: The :class:`Volume` (or its ID) + you would like to attach. + :param instance_uuid: uuid of the attaching instance. + :param mountpoint: mountpoint on the attaching instance. + """ + return self._action('os-attach', + volume, + {'instance_uuid': instance_uuid, + 'mountpoint': mountpoint}) + + def detach(self, volume): + """ + Clear attachment metadata. + + :param server: The :class:`Volume` (or its ID) + you would like to detach. + """ + return self._action('os-detach', volume) + + def reserve(self, volume): + """ + Reserve this volume. + + :param server: The :class:`Volume` (or its ID) + you would like to reserve. + """ + return self._action('os-reserve', volume) + + def unreserve(self, volume): + """ + Unreserve this volume. + + :param server: The :class:`Volume` (or its ID) + you would like to unreserve. + """ + return self._action('os-unreserve', volume) + + def initialize_connection(self, volume, connector): + """ + Initialize a volume connection. + + :param server: The :class:`Volume` (or its ID). + :param connector: connector dict from nova. + """ + return self._action('os-initialize_connection', volume, + {'connector': connector})[1]['connection_info'] + + def terminate_connection(self, volume, connector): + """ + Terminate a volume connection. + + :param server: The :class:`Volume` (or its ID). + :param connector: connector dict from nova. + """ + self._action('os-terminate_connection', volume, + {'connector': connector}) diff --git a/tests/v1/fakes.py b/tests/v1/fakes.py index ef5e791ff..d93c9a46f 100644 --- a/tests/v1/fakes.py +++ b/tests/v1/fakes.py @@ -145,6 +145,28 @@ class FakeHTTPClient(base_client.HTTPClient): r = {'volume': self.get_volumes_detail()[1]['volumes'][0]} return (200, r) + def post_volumes_1234_action(self, body, **kw): + _body = None + resp = 202 + assert len(body.keys()) == 1 + action = body.keys()[0] + if action == 'os-attach': + assert body[action].keys() == ['instance_uuid', 'mountpoint'] + elif action == 'os-detach': + assert body[action] == None + elif action == 'os-reserve': + assert body[action] == None + elif action == 'os-unreserve': + assert body[action] == None + elif action == 'os-initialize_connection': + assert body[action].keys() == ['connector'] + return (202, {'connection_info': 'foos'}) + elif action == 'os-terminate_connection': + assert body[action].keys() == ['connector'] + else: + raise AssertionError("Unexpected server action: %s" % action) + return (resp, _body) + def post_servers(self, body, **kw): assert set(body.keys()) <= set(['server', 'os:scheduler_hints']) fakes.assert_has_keys(body['server'], @@ -168,6 +190,9 @@ class FakeHTTPClient(base_client.HTTPClient): fakes.assert_has_keys(body['server'], optional=['name', 'adminPass']) return (204, None) + def post_volumes(self, **kw): + return (202, {'volume': {}}) + def delete_servers_1234(self, **kw): return (202, None) diff --git a/tests/v1/test_volumes.py b/tests/v1/test_volumes.py new file mode 100644 index 000000000..aa46574f9 --- /dev/null +++ b/tests/v1/test_volumes.py @@ -0,0 +1,52 @@ +from cinderclient.v1 import volumes +from tests import utils +from tests.v1 import fakes + + +cs = fakes.FakeClient() + + +class VolumesTest(utils.TestCase): + + def test_delete_volume(self): + v = cs.volumes.list()[0] + v.delete() + cs.assert_called('DELETE', '/volumes/1234') + cs.volumes.delete('1234') + cs.assert_called('DELETE', '/volumes/1234') + cs.volumes.delete(v) + cs.assert_called('DELETE', '/volumes/1234') + + def test_create_keypair(self): + kp = cs.volumes.create(1) + cs.assert_called('POST', '/volumes') + + def test_attach(self): + v = cs.volumes.get('1234') + cs.volumes.attach(v, 1, '/dev/vdc') + cs.assert_called('POST', '/volumes/1234/action') + + def test_detach(self): + v = cs.volumes.get('1234') + cs.volumes.detach(v) + cs.assert_called('POST', '/volumes/1234/action') + + def test_reserve(self): + v = cs.volumes.get('1234') + cs.volumes.reserve(v) + cs.assert_called('POST', '/volumes/1234/action') + + def test_unreserve(self): + v = cs.volumes.get('1234') + cs.volumes.reserve(v) + cs.assert_called('POST', '/volumes/1234/action') + + def test_initialize_connection(self): + v = cs.volumes.get('1234') + cs.volumes.initialize_connection(v, {}) + cs.assert_called('POST', '/volumes/1234/action') + + def test_terminate_connection(self): + v = cs.volumes.get('1234') + cs.volumes.terminate_connection(v, {}) + cs.assert_called('POST', '/volumes/1234/action')