summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-07-24 19:06:06 +0000
committerGerrit Code Review <review@openstack.org>2014-07-24 19:06:06 +0000
commitba826fbf3cc9219ad0cce0ff2d529975efc13a8d (patch)
tree8318e9ea90bf4e061841998a1225fd1ecb03d477
parent15cbd66138ba14d1bedc94a793d9ed22b97a518f (diff)
parent4d3b9bcea3d4a031d63886c39d5ae3ddb39b7bf1 (diff)
downloadpython-cinderclient-ba826fbf3cc9219ad0cce0ff2d529975efc13a8d.tar.gz
Merge "Add commands for managing and unmanaging volumes"
-rw-r--r--cinderclient/tests/v2/fakes.py7
-rw-r--r--cinderclient/tests/v2/test_shell.py97
-rw-r--r--cinderclient/tests/v2/test_volumes.py19
-rw-r--r--cinderclient/v2/shell.py88
-rw-r--r--cinderclient/v2/volumes.py32
5 files changed, 243 insertions, 0 deletions
diff --git a/cinderclient/tests/v2/fakes.py b/cinderclient/tests/v2/fakes.py
index b8dfe27..f271653 100644
--- a/cinderclient/tests/v2/fakes.py
+++ b/cinderclient/tests/v2/fakes.py
@@ -370,6 +370,8 @@ class FakeHTTPClient(base_client.HTTPClient):
assert 'new_type' in body[action]
elif action == 'os-set_bootable':
assert list(body[action]) == ['bootable']
+ elif action == 'os-unmanage':
+ assert body[action] is None
else:
raise AssertionError("Unexpected action: %s" % action)
return (resp, {}, _body)
@@ -828,3 +830,8 @@ class FakeHTTPClient(base_client.HTTPClient):
def put_snapshots_1234_metadata(self, **kw):
return (200, {}, {"metadata": {"key1": "val1", "key2": "val2"}})
+
+ def post_os_volume_manage(self, **kw):
+ volume = _stub_volume(id='1234')
+ volume.update(kw['body']['volume'])
+ return (202, {}, {'volume': volume})
diff --git a/cinderclient/tests/v2/test_shell.py b/cinderclient/tests/v2/test_shell.py
index 69b3631..f1c473c 100644
--- a/cinderclient/tests/v2/test_shell.py
+++ b/cinderclient/tests/v2/test_shell.py
@@ -521,3 +521,100 @@ class ShellTest(utils.TestCase):
self.run_command('snapshot-delete 5678')
self.assert_called('DELETE', '/snapshots/5678')
+
+ @httpretty.activate
+ def test_volume_manage(self):
+ self.register_keystone_auth_fixture()
+ self.run_command('manage host1 key1=val1 key2=val2 '
+ '--name foo --description bar '
+ '--volume-type baz --availability-zone az '
+ '--metadata k1=v1 k2=v2')
+ expected = {'volume': {'host': 'host1',
+ 'ref': {'key1': 'val1', 'key2': 'val2'},
+ 'name': 'foo',
+ 'description': 'bar',
+ 'volume_type': 'baz',
+ 'availability_zone': 'az',
+ 'metadata': {'k1': 'v1', 'k2': 'v2'},
+ 'bootable': False}}
+ self.assert_called_anytime('POST', '/os-volume-manage', body=expected)
+
+ @httpretty.activate
+ def test_volume_manage_bootable(self):
+ """
+ Tests the --bootable option
+
+ If this flag is specified, then the resulting POST should contain
+ bootable: True.
+ """
+ self.register_keystone_auth_fixture()
+ self.run_command('manage host1 key1=val1 key2=val2 '
+ '--name foo --description bar --bootable '
+ '--volume-type baz --availability-zone az '
+ '--metadata k1=v1 k2=v2')
+ expected = {'volume': {'host': 'host1',
+ 'ref': {'key1': 'val1', 'key2': 'val2'},
+ 'name': 'foo',
+ 'description': 'bar',
+ 'volume_type': 'baz',
+ 'availability_zone': 'az',
+ 'metadata': {'k1': 'v1', 'k2': 'v2'},
+ 'bootable': True}}
+ self.assert_called_anytime('POST', '/os-volume-manage', body=expected)
+
+ @httpretty.activate
+ def test_volume_manage_source_name(self):
+ """
+ Tests the --source-name option.
+
+ Checks that the --source-name option correctly updates the
+ ref structure that is passed in the HTTP POST
+ """
+ self.register_keystone_auth_fixture()
+ self.run_command('manage host1 key1=val1 key2=val2 '
+ '--source-name VolName '
+ '--name foo --description bar '
+ '--volume-type baz --availability-zone az '
+ '--metadata k1=v1 k2=v2')
+ expected = {'volume': {'host': 'host1',
+ 'ref': {'source-name': 'VolName',
+ 'key1': 'val1', 'key2': 'val2'},
+ 'name': 'foo',
+ 'description': 'bar',
+ 'volume_type': 'baz',
+ 'availability_zone': 'az',
+ 'metadata': {'k1': 'v1', 'k2': 'v2'},
+ 'bootable': False}}
+ self.assert_called_anytime('POST', '/os-volume-manage', body=expected)
+
+ @httpretty.activate
+ def test_volume_manage_source_id(self):
+ """
+ Tests the --source-id option.
+
+ Checks that the --source-id option correctly updates the
+ ref structure that is passed in the HTTP POST
+ """
+ self.register_keystone_auth_fixture()
+ self.run_command('manage host1 key1=val1 key2=val2 '
+ '--source-id 1234 '
+ '--name foo --description bar '
+ '--volume-type baz --availability-zone az '
+ '--metadata k1=v1 k2=v2')
+ expected = {'volume': {'host': 'host1',
+ 'ref': {'source-id': '1234',
+ 'key1': 'val1', 'key2': 'val2'},
+ 'name': 'foo',
+ 'description': 'bar',
+ 'volume_type': 'baz',
+ 'availability_zone': 'az',
+ 'metadata': {'k1': 'v1', 'k2': 'v2'},
+ 'bootable': False}}
+ self.assert_called_anytime('POST', '/os-volume-manage', body=expected)
+
+ @httpretty.activate
+ def test_volume_unmanage(self):
+ self.register_keystone_auth_fixture()
+ self.run_command('unmanage 1234')
+ self.assert_called('POST', '/volumes/1234/action',
+ body={'os-unmanage': None})
diff --git a/cinderclient/tests/v2/test_volumes.py b/cinderclient/tests/v2/test_volumes.py
index a4c0e90..81bd88d 100644
--- a/cinderclient/tests/v2/test_volumes.py
+++ b/cinderclient/tests/v2/test_volumes.py
@@ -139,3 +139,22 @@ class VolumesTest(utils.TestCase):
v = cs.volumes.get('1234')
cs.volumes.set_bootable(v, True)
cs.assert_called('POST', '/volumes/1234/action')
+
+ def test_volume_manage(self):
+ cs.volumes.manage('host1', {'k': 'v'})
+ expected = {'host': 'host1', 'name': None, 'availability_zone': None,
+ 'description': None, 'metadata': None, 'ref': {'k': 'v'},
+ 'volume_type': None, 'bootable': False}
+ cs.assert_called('POST', '/os-volume-manage', {'volume': expected})
+
+ def test_volume_manage_bootable(self):
+ cs.volumes.manage('host1', {'k': 'v'}, bootable=True)
+ expected = {'host': 'host1', 'name': None, 'availability_zone': None,
+ 'description': None, 'metadata': None, 'ref': {'k': 'v'},
+ 'volume_type': None, 'bootable': True}
+ cs.assert_called('POST', '/os-volume-manage', {'volume': expected})
+
+ def test_volume_unmanage(self):
+ v = cs.volumes.get('1234')
+ cs.volumes.unmanage(v)
+ cs.assert_called('POST', '/volumes/1234/action', {'os-unmanage': None})
diff --git a/cinderclient/v2/shell.py b/cinderclient/v2/shell.py
index 34283f7..10b7631 100644
--- a/cinderclient/v2/shell.py
+++ b/cinderclient/v2/shell.py
@@ -1571,3 +1571,91 @@ def do_set_bootable(cs, args):
volume = utils.find_volume(cs, args.volume)
cs.volumes.set_bootable(volume,
strutils.bool_from_string(args.bootable))
+
+
+@utils.arg('host',
+ metavar='<host>',
+ help='Cinder host on which the existing volume resides')
+@utils.arg('ref',
+ type=str,
+ nargs='*',
+ metavar='<key=value>',
+ help='Driver-specific reference to the existing volume as '
+ 'key=value pairs')
+@utils.arg('--source-name',
+ metavar='<source-name>',
+ help='Name of the volume to manage (Optional)')
+@utils.arg('--source-id',
+ metavar='<source-id>',
+ help='ID of the volume to manage (Optional)')
+@utils.arg('--name',
+ metavar='<name>',
+ help='Volume name (Optional, Default=None)')
+@utils.arg('--description',
+ metavar='<description>',
+ help='Volume description (Optional, Default=None)')
+@utils.arg('--volume-type',
+ metavar='<volume-type>',
+ help='Volume type (Optional, Default=None)')
+@utils.arg('--availability-zone',
+ metavar='<availability-zone>',
+ help='Availability zone for volume (Optional, Default=None)')
+@utils.arg('--metadata',
+ type=str,
+ nargs='*',
+ metavar='<key=value>',
+ help='Metadata key=value pairs (Optional, Default=None)')
+@utils.arg('--bootable',
+ action='store_true',
+ help='Specifies that the newly created volume should be'
+ ' marked as bootable')
+@utils.service_type('volumev2')
+def do_manage(cs, args):
+ """Manage an existing volume."""
+ volume_metadata = None
+ if args.metadata is not None:
+ volume_metadata = _extract_metadata(args)
+
+ # Build a dictionary of key/value pairs to pass to the API.
+ ref_dict = {}
+ for pair in args.ref:
+ (k, v) = pair.split('=', 1)
+ ref_dict[k] = v
+
+ # The recommended way to specify an existing volume is by ID or name, and
+ # have the Cinder driver look for 'source-name' or 'source-id' elements in
+ # the ref structure. To make things easier for the user, we have special
+ # --source-name and --source-id CLI options that add the appropriate
+ # element to the ref structure.
+ #
+ # Note how argparse converts hyphens to underscores. We use hyphens in the
+ # dictionary so that it is consistent with what the user specified on the
+ # CLI.
+ if hasattr(args, 'source_name') and \
+ args.source_name is not None:
+ ref_dict['source-name'] = args.source_name
+ if hasattr(args, 'source_id') and \
+ args.source_id is not None:
+ ref_dict['source-id'] = args.source_id
+
+ volume = cs.volumes.manage(host=args.host,
+ ref=ref_dict,
+ name=args.name,
+ description=args.description,
+ volume_type=args.volume_type,
+ availability_zone=args.availability_zone,
+ metadata=volume_metadata,
+ bootable=args.bootable)
+
+ info = {}
+ volume = cs.volumes.get(volume.id)
+ info.update(volume._info)
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('volume', metavar='<volume>',
+ help='Name or ID of the volume to unmanage.')
+@utils.service_type('volumev2')
+def do_unmanage(cs, args):
+ utils.find_volume(cs, args.volume).unmanage(args.volume)
diff --git a/cinderclient/v2/volumes.py b/cinderclient/v2/volumes.py
index ce2b06f..83a48c0 100644
--- a/cinderclient/v2/volumes.py
+++ b/cinderclient/v2/volumes.py
@@ -134,6 +134,19 @@ class Volume(base.Resource):
"""
self.manager.update_readonly_flag(self, read_only)
+ def manage(self, host, ref, name=None, description=None,
+ volume_type=None, availability_zone=None, metadata=None,
+ bootable=False):
+ """Manage an existing volume."""
+ self.manager.manage(host=host, ref=ref, name=name,
+ description=description, volume_type=volume_type,
+ availability_zone=availability_zone,
+ metadata=metadata, bootable=bootable)
+
+ def unmanage(self, volume):
+ """Unmanage a volume."""
+ self.manager.unmanage(volume)
+
class VolumeManager(base.ManagerWithFind):
"""Manage :class:`Volume` resources."""
@@ -427,3 +440,22 @@ class VolumeManager(base.ManagerWithFind):
return self._action('os-set_bootable',
base.getid(volume),
{'bootable': flag})
+
+ def manage(self, host, ref, name=None, description=None,
+ volume_type=None, availability_zone=None, metadata=None,
+ bootable=False):
+ """Manage an existing volume."""
+ body = {'volume': {'host': host,
+ 'ref': ref,
+ 'name': name,
+ 'description': description,
+ 'volume_type': volume_type,
+ 'availability_zone': availability_zone,
+ 'metadata': metadata,
+ 'bootable': bootable
+ }}
+ return self._create('/os-volume-manage', body, 'volume')
+
+ def unmanage(self, volume):
+ """Unmanage a volume."""
+ return self._action('os-unmanage', volume, None)