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]
|
assert 'new_type' in body[action]
|
||||||
elif action == 'os-set_bootable':
|
elif action == 'os-set_bootable':
|
||||||
assert list(body[action]) == ['bootable']
|
assert list(body[action]) == ['bootable']
|
||||||
|
elif action == 'os-unmanage':
|
||||||
|
assert body[action] is None
|
||||||
else:
|
else:
|
||||||
raise AssertionError("Unexpected action: %s" % action)
|
raise AssertionError("Unexpected action: %s" % action)
|
||||||
return (resp, {}, _body)
|
return (resp, {}, _body)
|
||||||
@@ -828,3 +830,8 @@ class FakeHTTPClient(base_client.HTTPClient):
|
|||||||
|
|
||||||
def put_snapshots_1234_metadata(self, **kw):
|
def put_snapshots_1234_metadata(self, **kw):
|
||||||
return (200, {}, {"metadata": {"key1": "val1", "key2": "val2"}})
|
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.run_command('snapshot-delete 5678')
|
||||||
self.assert_called('DELETE', '/snapshots/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')
|
v = cs.volumes.get('1234')
|
||||||
cs.volumes.set_bootable(v, True)
|
cs.volumes.set_bootable(v, True)
|
||||||
cs.assert_called('POST', '/volumes/1234/action')
|
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)
|
volume = utils.find_volume(cs, args.volume)
|
||||||
cs.volumes.set_bootable(volume,
|
cs.volumes.set_bootable(volume,
|
||||||
strutils.bool_from_string(args.bootable))
|
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)
|
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):
|
class VolumeManager(base.ManagerWithFind):
|
||||||
"""Manage :class:`Volume` resources."""
|
"""Manage :class:`Volume` resources."""
|
||||||
@@ -427,3 +440,22 @@ class VolumeManager(base.ManagerWithFind):
|
|||||||
return self._action('os-set_bootable',
|
return self._action('os-set_bootable',
|
||||||
base.getid(volume),
|
base.getid(volume),
|
||||||
{'bootable': flag})
|
{'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