Merge "Add commands for managing and unmanaging volumes"
This commit is contained in:
@@ -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})
|
||||
|
@@ -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})
|
||||
|
@@ -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})
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user