Merge "Add commands for managing and unmanaging volumes"

This commit is contained in:
Jenkins
2014-07-24 19:06:06 +00:00
committed by Gerrit Code Review
5 changed files with 243 additions and 0 deletions

View File

@@ -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})

View File

@@ -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})

View File

@@ -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})

View File

@@ -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)

View File

@@ -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)