diff options
author | Geraint North <geraint.north@uk.ibm.com> | 2014-02-25 16:21:08 +0200 |
---|---|---|
committer | Geraint North <geraint.north@uk.ibm.com> | 2014-07-24 11:16:57 +0100 |
commit | 4d3b9bcea3d4a031d63886c39d5ae3ddb39b7bf1 (patch) | |
tree | 0d475729ef59a7d3b3c0189a236709922fae3141 | |
parent | 0c4010e7fbc9939905e8647afbe1358c384dc658 (diff) | |
download | python-cinderclient-4d3b9bcea3d4a031d63886c39d5ae3ddb39b7bf1.tar.gz |
Add commands for managing and unmanaging volumes
Add manage and unmanage commands.
Cinder code: https://review.openstack.org/#/c/72501
See also adding support for "bootable" flag for volume manage,
and changing the LVM backend to use the source-volume-name key
as part of the existing-ref structure, to make the CLI easier
to use:
https://review.openstack.org/#/c/108488/
Implements: blueprint add-export-import-volumes
Change-Id: I27d0d3396d80063a51b0fe56d2d3c92931fa9c6c
-rw-r--r-- | cinderclient/tests/v2/fakes.py | 7 | ||||
-rw-r--r-- | cinderclient/tests/v2/test_shell.py | 97 | ||||
-rw-r--r-- | cinderclient/tests/v2/test_volumes.py | 19 | ||||
-rw-r--r-- | cinderclient/v2/shell.py | 88 | ||||
-rw-r--r-- | cinderclient/v2/volumes.py | 32 |
5 files changed, 243 insertions, 0 deletions
diff --git a/cinderclient/tests/v2/fakes.py b/cinderclient/tests/v2/fakes.py index 98f3500..696f598 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) @@ -826,3 +828,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 a52bd10..b8204a6 100644 --- a/cinderclient/tests/v2/test_shell.py +++ b/cinderclient/tests/v2/test_shell.py @@ -482,3 +482,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 4842709..dfaa3a9 100644 --- a/cinderclient/v2/shell.py +++ b/cinderclient/v2/shell.py @@ -1560,3 +1560,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) |