Add generic volume groups
This patch adds support to generic volume groups. Server patch is here: https://review.openstack.org/#/c/322459/ Current microversion is 3.13. The following CLI's are supported: cinder --os-volume-api-version 3.13 group-create --name my_group <group type uuid> <volume type uuid> cinder --os-volume-api-version 3.13 group-list cinder --os-volume-api-version 3.13 create --group-id <group uuid> --volume-type <volume type uuid> <size> cinder --os-volume-api-version 3.13 group-update <group uuid> --name new_name description new_description --add-volumes <uuid of volume to add> --remove-volumes <uuid of volume to remove> cinder --os-volume-api-version 3.13 group-show <group uuid> cinder --os-volume-api-version 3.13 group-delete --delete-volumes <group uuid> Depends-on: I35157439071786872bc9976741c4ef75698f7cb7 Change-Id: Icff2d7385bde0a7c023c2ca38fffcd4bc5460af9 Partial-Implements: blueprint generic-volume-group
This commit is contained in:
parent
1610c3d95c
commit
6c5a764c77
cinderclient
@ -32,7 +32,7 @@ if not LOG.handlers:
|
||||
|
||||
# key is a deprecated version and value is an alternative version.
|
||||
DEPRECATED_VERSIONS = {"1": "2"}
|
||||
MAX_VERSION = "3.11"
|
||||
MAX_VERSION = "3.13"
|
||||
|
||||
_SUBSTITUTIONS = {}
|
||||
|
||||
|
@ -19,6 +19,24 @@ from cinderclient.v3 import client
|
||||
from cinderclient.tests.unit.v2 import fakes as fake_v2
|
||||
|
||||
|
||||
def _stub_group(detailed=True, **kwargs):
|
||||
group = {
|
||||
"name": "test-1",
|
||||
"id": "1234",
|
||||
}
|
||||
if detailed:
|
||||
details = {
|
||||
"created_at": "2012-08-28T16:30:31.000000",
|
||||
"description": "test-1-desc",
|
||||
"availability_zone": "zone1",
|
||||
"status": "available",
|
||||
"group_type": "my_group_type",
|
||||
}
|
||||
group.update(details)
|
||||
group.update(kwargs)
|
||||
return group
|
||||
|
||||
|
||||
class FakeClient(fakes.FakeClient, client.Client):
|
||||
|
||||
def __init__(self, api_version=None, *args, **kwargs):
|
||||
@ -248,3 +266,39 @@ class FakeHTTPClient(fake_v2.FakeHTTPClient):
|
||||
|
||||
def put_group_types_1(self, **kw):
|
||||
return self.get_group_types_1()
|
||||
|
||||
#
|
||||
# Groups
|
||||
#
|
||||
|
||||
def get_groups_detail(self, **kw):
|
||||
return (200, {}, {"groups": [
|
||||
_stub_group(id='1234'),
|
||||
_stub_group(id='4567')]})
|
||||
|
||||
def get_groups(self, **kw):
|
||||
return (200, {}, {"groups": [
|
||||
_stub_group(detailed=False, id='1234'),
|
||||
_stub_group(detailed=False, id='4567')]})
|
||||
|
||||
def get_groups_1234(self, **kw):
|
||||
return (200, {}, {'group':
|
||||
_stub_group(id='1234')})
|
||||
|
||||
def post_groups(self, **kw):
|
||||
group = _stub_group(id='1234', group_type='my_group_type',
|
||||
volume_types=['type1', 'type2'])
|
||||
return (202, {}, {'group': group})
|
||||
|
||||
def put_groups_1234(self, **kw):
|
||||
return (200, {}, {'group': {}})
|
||||
|
||||
def post_groups_1234_action(self, body, **kw):
|
||||
resp = 202
|
||||
assert len(list(body)) == 1
|
||||
action = list(body)[0]
|
||||
if action == 'delete':
|
||||
assert 'delete-volumes' in body[action]
|
||||
else:
|
||||
raise AssertionError("Unexpected action: %s" % action)
|
||||
return (resp, {}, {})
|
||||
|
115
cinderclient/tests/unit/v3/test_groups.py
Normal file
115
cinderclient/tests/unit/v3/test_groups.py
Normal file
@ -0,0 +1,115 @@
|
||||
# Copyright (C) 2016 EMC Corporation.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ddt
|
||||
|
||||
from cinderclient.tests.unit import utils
|
||||
from cinderclient.tests.unit.v3 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class GroupsTest(utils.TestCase):
|
||||
|
||||
def test_delete_group(self):
|
||||
expected = {'delete': {'delete-volumes': True}}
|
||||
v = cs.groups.list()[0]
|
||||
grp = v.delete(delete_volumes=True)
|
||||
self._assert_request_id(grp)
|
||||
cs.assert_called('POST', '/groups/1234/action', body=expected)
|
||||
grp = cs.groups.delete('1234', delete_volumes=True)
|
||||
self._assert_request_id(grp)
|
||||
cs.assert_called('POST', '/groups/1234/action', body=expected)
|
||||
grp = cs.groups.delete(v, delete_volumes=True)
|
||||
self._assert_request_id(grp)
|
||||
cs.assert_called('POST', '/groups/1234/action', body=expected)
|
||||
|
||||
def test_create_group(self):
|
||||
grp = cs.groups.create('my_group_type', 'type1,type2', name='group')
|
||||
cs.assert_called('POST', '/groups')
|
||||
self._assert_request_id(grp)
|
||||
|
||||
def test_create_group_with_volume_types(self):
|
||||
grp = cs.groups.create('my_group_type', 'type1,type2', name='group')
|
||||
expected = {'group': {'status': 'creating',
|
||||
'description': None,
|
||||
'availability_zone': None,
|
||||
'user_id': None,
|
||||
'name': 'group',
|
||||
'group_type': 'my_group_type',
|
||||
'volume_types': ['type1', 'type2'],
|
||||
'project_id': None}}
|
||||
cs.assert_called('POST', '/groups', body=expected)
|
||||
self._assert_request_id(grp)
|
||||
|
||||
@ddt.data(
|
||||
{'name': 'group2', 'desc': None, 'add': None, 'remove': None},
|
||||
{'name': None, 'desc': 'group2 desc', 'add': None, 'remove': None},
|
||||
{'name': None, 'desc': None, 'add': 'uuid1,uuid2', 'remove': None},
|
||||
{'name': None, 'desc': None, 'add': None, 'remove': 'uuid3,uuid4'},
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_update_group_name(self, name, desc, add, remove):
|
||||
v = cs.groups.list()[0]
|
||||
expected = {'group': {'name': name, 'description': desc,
|
||||
'add_volumes': add, 'remove_volumes': remove}}
|
||||
grp = v.update(name=name, description=desc,
|
||||
add_volumes=add, remove_volumes=remove)
|
||||
cs.assert_called('PUT', '/groups/1234', body=expected)
|
||||
self._assert_request_id(grp)
|
||||
grp = cs.groups.update('1234', name=name, description=desc,
|
||||
add_volumes=add, remove_volumes=remove)
|
||||
cs.assert_called('PUT', '/groups/1234', body=expected)
|
||||
self._assert_request_id(grp)
|
||||
grp = cs.groups.update(v, name=name, description=desc,
|
||||
add_volumes=add, remove_volumes=remove)
|
||||
cs.assert_called('PUT', '/groups/1234', body=expected)
|
||||
self._assert_request_id(grp)
|
||||
|
||||
def test_update_group_none(self):
|
||||
self.assertIsNone(cs.groups.update('1234'))
|
||||
|
||||
def test_update_group_no_props(self):
|
||||
cs.groups.update('1234')
|
||||
|
||||
def test_list_group(self):
|
||||
lst = cs.groups.list()
|
||||
cs.assert_called('GET', '/groups/detail')
|
||||
self._assert_request_id(lst)
|
||||
|
||||
def test_list_group_detailed_false(self):
|
||||
lst = cs.groups.list(detailed=False)
|
||||
cs.assert_called('GET', '/groups')
|
||||
self._assert_request_id(lst)
|
||||
|
||||
def test_list_group_with_search_opts(self):
|
||||
lst = cs.groups.list(search_opts={'foo': 'bar'})
|
||||
cs.assert_called('GET', '/groups/detail?foo=bar')
|
||||
self._assert_request_id(lst)
|
||||
|
||||
def test_list_group_with_empty_search_opt(self):
|
||||
lst = cs.groups.list(
|
||||
search_opts={'foo': 'bar', 'abc': None}
|
||||
)
|
||||
cs.assert_called('GET', '/groups/detail?foo=bar')
|
||||
self._assert_request_id(lst)
|
||||
|
||||
def test_get_group(self):
|
||||
group_id = '1234'
|
||||
grp = cs.groups.get(group_id)
|
||||
cs.assert_called('GET', '/groups/%s' % group_id)
|
||||
self._assert_request_id(grp)
|
@ -13,6 +13,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ddt
|
||||
|
||||
import fixtures
|
||||
import mock
|
||||
from requests_mock.contrib import fixture as requests_mock_fixture
|
||||
@ -25,6 +27,7 @@ from cinderclient.tests.unit.v3 import fakes
|
||||
from cinderclient.tests.unit.fixture_data import keystone_client
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@mock.patch.object(client, 'Client', fakes.FakeClient)
|
||||
class ShellTest(utils.TestCase):
|
||||
|
||||
@ -183,3 +186,78 @@ class ShellTest(utils.TestCase):
|
||||
def test_group_specs_list(self):
|
||||
self.run_command('--os-volume-api-version 3.11 group-specs-list')
|
||||
self.assert_called('GET', '/group_types?is_public=None')
|
||||
|
||||
def test_create_volume_with_group(self):
|
||||
self.run_command('--os-volume-api-version 3.13 create --group-id 5678 '
|
||||
'--volume-type 4321 1')
|
||||
self.assert_called('GET', '/volumes/1234')
|
||||
expected = {'volume': {'imageRef': None,
|
||||
'project_id': None,
|
||||
'status': 'creating',
|
||||
'size': 1,
|
||||
'user_id': None,
|
||||
'availability_zone': None,
|
||||
'source_replica': None,
|
||||
'attach_status': 'detached',
|
||||
'source_volid': None,
|
||||
'consistencygroup_id': None,
|
||||
'group_id': '5678',
|
||||
'name': None,
|
||||
'snapshot_id': None,
|
||||
'metadata': {},
|
||||
'volume_type': '4321',
|
||||
'description': None,
|
||||
'multiattach': False}}
|
||||
self.assert_called_anytime('POST', '/volumes', expected)
|
||||
|
||||
def test_group_list(self):
|
||||
self.run_command('--os-volume-api-version 3.13 group-list')
|
||||
self.assert_called_anytime('GET', '/groups/detail')
|
||||
|
||||
def test_group_show(self):
|
||||
self.run_command('--os-volume-api-version 3.13 '
|
||||
'group-show 1234')
|
||||
self.assert_called('GET', '/groups/1234')
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_group_delete(self, delete_vol):
|
||||
cmd = '--os-volume-api-version 3.13 group-delete 1234'
|
||||
if delete_vol:
|
||||
cmd += ' --delete-volumes'
|
||||
self.run_command(cmd)
|
||||
expected = {'delete': {'delete-volumes': delete_vol}}
|
||||
self.assert_called('POST', '/groups/1234/action', expected)
|
||||
|
||||
def test_group_create(self):
|
||||
expected = {'group': {'name': 'test-1',
|
||||
'description': 'test-1-desc',
|
||||
'user_id': None,
|
||||
'project_id': None,
|
||||
'status': 'creating',
|
||||
'group_type': 'my_group_type',
|
||||
'volume_types': ['type1', 'type2'],
|
||||
'availability_zone': 'zone1'}}
|
||||
self.run_command('--os-volume-api-version 3.13 '
|
||||
'group-create --name test-1 '
|
||||
'--description test-1-desc '
|
||||
'--availability-zone zone1 '
|
||||
'my_group_type type1,type2')
|
||||
self.assert_called_anytime('POST', '/groups', body=expected)
|
||||
|
||||
def test_group_update(self):
|
||||
self.run_command('--os-volume-api-version 3.13 group-update '
|
||||
'--name group2 --description desc2 '
|
||||
'--add-volumes uuid1,uuid2 '
|
||||
'--remove-volumes uuid3,uuid4 '
|
||||
'1234')
|
||||
expected = {'group': {'name': 'group2',
|
||||
'description': 'desc2',
|
||||
'add_volumes': 'uuid1,uuid2',
|
||||
'remove_volumes': 'uuid3,uuid4'}}
|
||||
self.assert_called('PUT', '/groups/1234',
|
||||
body=expected)
|
||||
|
||||
def test_group_update_invalid_args(self):
|
||||
self.assertRaises(exceptions.ClientException,
|
||||
self.run_command,
|
||||
'--os-volume-api-version 3.13 group-update 1234')
|
||||
|
@ -1,4 +1,5 @@
|
||||
# Copyright 2016 FUJITSU LIMITED
|
||||
# Copyright (c) 2016 EMC Corporation
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
@ -20,6 +21,8 @@ from cinderclient.tests.unit.v3 import fakes
|
||||
from cinderclient.v3.volumes import Volume
|
||||
from cinderclient.v3.volumes import VolumeManager
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
|
||||
|
||||
class VolumesTest(utils.TestCase):
|
||||
|
||||
@ -40,3 +43,25 @@ class VolumesTest(utils.TestCase):
|
||||
fake_volume.upload_to_image(False, 'name', 'bare', 'raw',
|
||||
visibility='public', protected=True)
|
||||
cs.assert_called_anytime('POST', '/volumes/1234/action', body=expected)
|
||||
|
||||
def test_create_volume(self):
|
||||
vol = cs.volumes.create(1, group_id='1234', volume_type='5678')
|
||||
expected = {'volume': {'status': 'creating',
|
||||
'description': None,
|
||||
'availability_zone': None,
|
||||
'source_volid': None,
|
||||
'snapshot_id': None,
|
||||
'size': 1,
|
||||
'user_id': None,
|
||||
'name': None,
|
||||
'imageRef': None,
|
||||
'attach_status': 'detached',
|
||||
'volume_type': '5678',
|
||||
'project_id': None,
|
||||
'metadata': {},
|
||||
'source_replica': None,
|
||||
'consistencygroup_id': None,
|
||||
'multiattach': False,
|
||||
'group_id': '1234'}}
|
||||
cs.assert_called('POST', '/volumes', body=expected)
|
||||
self._assert_request_id(vol)
|
||||
|
@ -22,6 +22,7 @@ from cinderclient.v3 import cgsnapshots
|
||||
from cinderclient.v3 import clusters
|
||||
from cinderclient.v3 import consistencygroups
|
||||
from cinderclient.v3 import capabilities
|
||||
from cinderclient.v3 import groups
|
||||
from cinderclient.v3 import group_types
|
||||
from cinderclient.v3 import limits
|
||||
from cinderclient.v3 import pools
|
||||
@ -86,6 +87,7 @@ class Client(object):
|
||||
self.clusters = clusters.ClusterManager(self)
|
||||
self.consistencygroups = consistencygroups.\
|
||||
ConsistencygroupManager(self)
|
||||
self.groups = groups.GroupManager(self)
|
||||
self.cgsnapshots = cgsnapshots.CgsnapshotManager(self)
|
||||
self.availability_zones = \
|
||||
availability_zones.AvailabilityZoneManager(self)
|
||||
|
139
cinderclient/v3/groups.py
Normal file
139
cinderclient/v3/groups.py
Normal file
@ -0,0 +1,139 @@
|
||||
# Copyright (C) 2016 EMC Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Group interface (v3 extension)."""
|
||||
|
||||
import six
|
||||
from six.moves.urllib.parse import urlencode
|
||||
|
||||
from cinderclient import base
|
||||
from cinderclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
|
||||
class Group(base.Resource):
|
||||
"""A Group of volumes."""
|
||||
def __repr__(self):
|
||||
return "<Group: %s>" % self.id
|
||||
|
||||
def delete(self, delete_volumes=False):
|
||||
"""Delete this group."""
|
||||
return self.manager.delete(self, delete_volumes)
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update the name or description for this group."""
|
||||
return self.manager.update(self, **kwargs)
|
||||
|
||||
|
||||
class GroupManager(base.ManagerWithFind):
|
||||
"""Manage :class:`Group` resources."""
|
||||
resource_class = Group
|
||||
|
||||
def create(self, group_type, volume_types, name=None,
|
||||
description=None, user_id=None,
|
||||
project_id=None, availability_zone=None):
|
||||
"""Creates a group.
|
||||
|
||||
:param group_type: Type of the Group
|
||||
:param volume_types: Types of volume
|
||||
:param name: Name of the Group
|
||||
:param description: Description of the Group
|
||||
:param user_id: User id derived from context
|
||||
:param project_id: Project id derived from context
|
||||
:param availability_zone: Availability Zone to use
|
||||
:rtype: :class:`Group`
|
||||
"""
|
||||
body = {'group': {'name': name,
|
||||
'description': description,
|
||||
'group_type': group_type,
|
||||
'volume_types': volume_types.split(','),
|
||||
'user_id': user_id,
|
||||
'project_id': project_id,
|
||||
'availability_zone': availability_zone,
|
||||
'status': "creating",
|
||||
}}
|
||||
|
||||
return self._create('/groups', body, 'group')
|
||||
|
||||
def get(self, group_id):
|
||||
"""Get a group.
|
||||
|
||||
:param group_id: The ID of the group to get.
|
||||
:rtype: :class:`Group`
|
||||
"""
|
||||
return self._get("/groups/%s" % group_id,
|
||||
"group")
|
||||
|
||||
def list(self, detailed=True, search_opts=None):
|
||||
"""Lists all groups.
|
||||
|
||||
:rtype: list of :class:`Group`
|
||||
"""
|
||||
if search_opts is None:
|
||||
search_opts = {}
|
||||
|
||||
qparams = {}
|
||||
|
||||
for opt, val in six.iteritems(search_opts):
|
||||
if val:
|
||||
qparams[opt] = val
|
||||
|
||||
query_string = "?%s" % urlencode(qparams) if qparams else ""
|
||||
|
||||
detail = ""
|
||||
if detailed:
|
||||
detail = "/detail"
|
||||
|
||||
return self._list("/groups%s%s" % (detail, query_string),
|
||||
"groups")
|
||||
|
||||
def delete(self, group, delete_volumes=False):
|
||||
"""Delete a group.
|
||||
|
||||
:param group: the :class:`Group` to delete.
|
||||
:param delete_volumes: delete volumes in the group.
|
||||
"""
|
||||
body = {'delete': {'delete-volumes': delete_volumes}}
|
||||
self.run_hooks('modify_body_for_action', body, 'group')
|
||||
url = '/groups/%s/action' % base.getid(group)
|
||||
resp, body = self.api.client.post(url, body=body)
|
||||
return common_base.TupleWithMeta((resp, body), resp)
|
||||
|
||||
def update(self, group, **kwargs):
|
||||
"""Update the name or description for a group.
|
||||
|
||||
:param Group: The :class:`Group` to update.
|
||||
"""
|
||||
if not kwargs:
|
||||
return
|
||||
|
||||
body = {"group": kwargs}
|
||||
|
||||
return self._update("/groups/%s" %
|
||||
base.getid(group), body)
|
||||
|
||||
def _action(self, action, group, info=None, **kwargs):
|
||||
"""Perform a group "action."
|
||||
|
||||
:param action: an action to be performed on the group
|
||||
:param group: a group to perform the action on
|
||||
:param info: details of the action
|
||||
:param **kwargs: other parameters
|
||||
"""
|
||||
|
||||
body = {action: info}
|
||||
self.run_hooks('modify_body_for_action', body, **kwargs)
|
||||
url = '/groups/%s/action' % base.getid(group)
|
||||
resp, body = self.api.client.post(url, body=body)
|
||||
return common_base.TupleWithMeta((resp, body), resp)
|
@ -88,6 +88,11 @@ def _find_consistencygroup(cs, consistencygroup):
|
||||
return utils.find_resource(cs.consistencygroups, consistencygroup)
|
||||
|
||||
|
||||
def _find_group(cs, group):
|
||||
"""Gets a group by name or ID."""
|
||||
return utils.find_resource(cs.groups, group)
|
||||
|
||||
|
||||
def _find_cgsnapshot(cs, cgsnapshot):
|
||||
"""Gets a cgsnapshot by name or ID."""
|
||||
return utils.find_resource(cs.cgsnapshots, cgsnapshot)
|
||||
@ -313,6 +318,7 @@ class CheckSizeArgForCreate(argparse.Action):
|
||||
setattr(args, self.dest, values)
|
||||
|
||||
|
||||
@utils.service_type('volumev3')
|
||||
@utils.arg('size',
|
||||
metavar='<size>',
|
||||
nargs='?',
|
||||
@ -325,6 +331,12 @@ class CheckSizeArgForCreate(argparse.Action):
|
||||
default=None,
|
||||
help='ID of a consistency group where the new volume belongs to. '
|
||||
'Default=None.')
|
||||
@utils.arg('--group-id',
|
||||
metavar='<group-id>',
|
||||
default=None,
|
||||
help='ID of a group where the new volume belongs to. '
|
||||
'Default=None.',
|
||||
start_version='3.13')
|
||||
@utils.arg('--snapshot-id',
|
||||
metavar='<snapshot-id>',
|
||||
default=None,
|
||||
@ -399,9 +411,9 @@ class CheckSizeArgForCreate(argparse.Action):
|
||||
help=('Allow volume to be attached more than once.'
|
||||
' Default=False'),
|
||||
default=False)
|
||||
@utils.service_type('volumev3')
|
||||
def do_create(cs, args):
|
||||
"""Creates a volume."""
|
||||
|
||||
# NOTE(thingee): Backwards-compatibility with v1 args
|
||||
if args.display_name is not None:
|
||||
args.name = args.display_name
|
||||
@ -431,8 +443,13 @@ def do_create(cs, args):
|
||||
# Keep backward compatibility with image_id, favoring explicit ID
|
||||
image_ref = args.image_id or args.image or args.image_ref
|
||||
|
||||
try:
|
||||
group_id = args.group_id
|
||||
except AttributeError:
|
||||
group_id = None
|
||||
volume = cs.volumes.create(args.size,
|
||||
args.consisgroup_id,
|
||||
group_id,
|
||||
args.snapshot_id,
|
||||
args.source_volid,
|
||||
args.name,
|
||||
@ -1194,7 +1211,8 @@ def do_credentials(cs, args):
|
||||
|
||||
_quota_resources = ['volumes', 'snapshots', 'gigabytes',
|
||||
'backups', 'backup_gigabytes',
|
||||
'consistencygroups', 'per_volume_gigabytes']
|
||||
'consistencygroups', 'per_volume_gigabytes',
|
||||
'groups', ]
|
||||
_quota_infos = ['Type', 'In_use', 'Reserved', 'Limit']
|
||||
|
||||
|
||||
@ -1296,6 +1314,11 @@ def do_quota_defaults(cs, args):
|
||||
metavar='<consistencygroups>',
|
||||
type=int, default=None,
|
||||
help='The new "consistencygroups" quota value. Default=None.')
|
||||
@utils.arg('--groups',
|
||||
metavar='<groups>',
|
||||
type=int, default=None,
|
||||
help='The new "groups" quota value. Default=None.',
|
||||
start_version='3.13')
|
||||
@utils.arg('--volume-type',
|
||||
metavar='<volume_type_name>',
|
||||
default=None,
|
||||
@ -2596,10 +2619,28 @@ def do_consisgroup_list(cs, args):
|
||||
utils.print_list(consistencygroups, columns)
|
||||
|
||||
|
||||
@utils.service_type('volumev3')
|
||||
@api_versions.wraps('3.13')
|
||||
@utils.arg('--all-tenants',
|
||||
dest='all_tenants',
|
||||
metavar='<0|1>',
|
||||
nargs='?',
|
||||
type=int,
|
||||
const=1,
|
||||
default=0,
|
||||
help='Shows details for all tenants. Admin only.')
|
||||
def do_group_list(cs, args):
|
||||
"""Lists all groups."""
|
||||
groups = cs.groups.list()
|
||||
|
||||
columns = ['ID', 'Status', 'Name']
|
||||
utils.print_list(groups, columns)
|
||||
|
||||
|
||||
@utils.service_type('volumev3')
|
||||
@utils.arg('consistencygroup',
|
||||
metavar='<consistencygroup>',
|
||||
help='Name or ID of a consistency group.')
|
||||
@utils.service_type('volumev3')
|
||||
def do_consisgroup_show(cs, args):
|
||||
"""Shows details of a consistency group."""
|
||||
info = dict()
|
||||
@ -2610,6 +2651,20 @@ def do_consisgroup_show(cs, args):
|
||||
utils.print_dict(info)
|
||||
|
||||
|
||||
@utils.arg('group',
|
||||
metavar='<group>',
|
||||
help='Name or ID of a group.')
|
||||
@utils.service_type('volumev3')
|
||||
def do_group_show(cs, args):
|
||||
"""Shows details of a group."""
|
||||
info = dict()
|
||||
group = _find_group(cs, args.group)
|
||||
info.update(group._info)
|
||||
|
||||
info.pop('links', None)
|
||||
utils.print_dict(info)
|
||||
|
||||
|
||||
@utils.arg('volumetypes',
|
||||
metavar='<volume-types>',
|
||||
help='Volume types.')
|
||||
@ -2642,6 +2697,43 @@ def do_consisgroup_create(cs, args):
|
||||
utils.print_dict(info)
|
||||
|
||||
|
||||
@utils.service_type('volumev3')
|
||||
@api_versions.wraps('3.13')
|
||||
@utils.arg('grouptype',
|
||||
metavar='<group-type>',
|
||||
help='Group type.')
|
||||
@utils.arg('volumetypes',
|
||||
metavar='<volume-types>',
|
||||
help='Comma-separated list of volume types.')
|
||||
@utils.arg('--name',
|
||||
metavar='<name>',
|
||||
help='Name of a group.')
|
||||
@utils.arg('--description',
|
||||
metavar='<description>',
|
||||
default=None,
|
||||
help='Description of a group. Default=None.')
|
||||
@utils.arg('--availability-zone',
|
||||
metavar='<availability-zone>',
|
||||
default=None,
|
||||
help='Availability zone for group. Default=None.')
|
||||
def do_group_create(cs, args):
|
||||
"""Creates a group."""
|
||||
|
||||
group = cs.groups.create(
|
||||
args.grouptype,
|
||||
args.volumetypes,
|
||||
args.name,
|
||||
args.description,
|
||||
availability_zone=args.availability_zone)
|
||||
|
||||
info = dict()
|
||||
group = cs.groups.get(group.id)
|
||||
info.update(group._info)
|
||||
|
||||
info.pop('links', None)
|
||||
utils.print_dict(info)
|
||||
|
||||
|
||||
@utils.arg('--cgsnapshot',
|
||||
metavar='<cgsnapshot>',
|
||||
help='Name or ID of a cgsnapshot. Default=None.')
|
||||
@ -2709,6 +2801,36 @@ def do_consisgroup_delete(cs, args):
|
||||
"consistency groups.")
|
||||
|
||||
|
||||
@utils.service_type('volumev3')
|
||||
@api_versions.wraps('3.13')
|
||||
@utils.arg('group',
|
||||
metavar='<group>', nargs='+',
|
||||
help='Name or ID of one or more groups '
|
||||
'to be deleted.')
|
||||
@utils.arg('--delete-volumes',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Allows or disallows groups to be deleted '
|
||||
'if they are not empty. If the group is empty, '
|
||||
'it can be deleted without the delete-volumes flag. '
|
||||
'If the group is not empty, the delete-volumes '
|
||||
'flag is required for it to be deleted. If True, '
|
||||
'all volumes in the group will also be deleted.')
|
||||
def do_group_delete(cs, args):
|
||||
"""Removes one or more groups."""
|
||||
failure_count = 0
|
||||
for group in args.group:
|
||||
try:
|
||||
_find_group(cs, group).delete(args.delete_volumes)
|
||||
except Exception as e:
|
||||
failure_count += 1
|
||||
print("Delete for group %s failed: %s" %
|
||||
(group, e))
|
||||
if failure_count == len(args.group):
|
||||
raise exceptions.CommandError("Unable to delete any of the specified "
|
||||
"groups.")
|
||||
|
||||
|
||||
@utils.arg('consistencygroup',
|
||||
metavar='<consistencygroup>',
|
||||
help='Name or ID of a consistency group.')
|
||||
@ -2751,6 +2873,49 @@ def do_consisgroup_update(cs, args):
|
||||
_find_consistencygroup(cs, args.consistencygroup).update(**kwargs)
|
||||
|
||||
|
||||
@utils.service_type('volumev3')
|
||||
@api_versions.wraps('3.13')
|
||||
@utils.arg('group',
|
||||
metavar='<group>',
|
||||
help='Name or ID of a group.')
|
||||
@utils.arg('--name', metavar='<name>',
|
||||
help='New name for group. Default=None.')
|
||||
@utils.arg('--description', metavar='<description>',
|
||||
help='New description for group. Default=None.')
|
||||
@utils.arg('--add-volumes',
|
||||
metavar='<uuid1,uuid2,......>',
|
||||
help='UUID of one or more volumes '
|
||||
'to be added to the group, '
|
||||
'separated by commas. Default=None.')
|
||||
@utils.arg('--remove-volumes',
|
||||
metavar='<uuid3,uuid4,......>',
|
||||
help='UUID of one or more volumes '
|
||||
'to be removed from the group, '
|
||||
'separated by commas. Default=None.')
|
||||
def do_group_update(cs, args):
|
||||
"""Updates a group."""
|
||||
kwargs = {}
|
||||
|
||||
if args.name is not None:
|
||||
kwargs['name'] = args.name
|
||||
|
||||
if args.description is not None:
|
||||
kwargs['description'] = args.description
|
||||
|
||||
if args.add_volumes is not None:
|
||||
kwargs['add_volumes'] = args.add_volumes
|
||||
|
||||
if args.remove_volumes is not None:
|
||||
kwargs['remove_volumes'] = args.remove_volumes
|
||||
|
||||
if not kwargs:
|
||||
msg = ('At least one of the following args must be supplied: '
|
||||
'name, description, add-volumes, remove-volumes.')
|
||||
raise exceptions.ClientException(code=1, message=msg)
|
||||
|
||||
_find_group(cs, args.group).update(**kwargs)
|
||||
|
||||
|
||||
@utils.arg('--all-tenants',
|
||||
dest='all_tenants',
|
||||
metavar='<0|1>',
|
||||
|
@ -214,7 +214,8 @@ class VolumeManager(base.ManagerWithFind):
|
||||
"""Manage :class:`Volume` resources."""
|
||||
resource_class = Volume
|
||||
|
||||
def create(self, size, consistencygroup_id=None, snapshot_id=None,
|
||||
def create(self, size, consistencygroup_id=None,
|
||||
group_id=None, snapshot_id=None,
|
||||
source_volid=None, name=None, description=None,
|
||||
volume_type=None, user_id=None,
|
||||
project_id=None, availability_zone=None,
|
||||
@ -224,6 +225,7 @@ class VolumeManager(base.ManagerWithFind):
|
||||
|
||||
:param size: Size of volume in GB
|
||||
:param consistencygroup_id: ID of the consistencygroup
|
||||
:param group_id: ID of the group
|
||||
:param snapshot_id: ID of the snapshot
|
||||
:param name: Name of the volume
|
||||
:param description: Description of the volume
|
||||
@ -264,6 +266,9 @@ class VolumeManager(base.ManagerWithFind):
|
||||
'multiattach': multiattach,
|
||||
}}
|
||||
|
||||
if group_id:
|
||||
body['volume']['group_id'] = group_id
|
||||
|
||||
if scheduler_hints:
|
||||
body['OS-SCH-HNT:scheduler_hints'] = scheduler_hints
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user