summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXing Yang <xing.yang@emc.com>2014-07-03 22:37:40 -0400
committerXing Yang <xing.yang@emc.com>2014-09-08 10:37:04 -0400
commit9fc64a52b19cb3994df6d16af10ba5234947216f (patch)
tree56df47dbf0d1170d2e780ddeacdbae45737e6a2d
parent4162c9ff5c68ff3078137f47cceb070e66f8ebf6 (diff)
downloadpython-cinderclient-9fc64a52b19cb3994df6d16af10ba5234947216f.tar.gz
Cinder Client for Consistency Groups
This patch implements CLI commands for the Consistency Groups feature. Only snapshots for CGs will be implemented in phase 1. Change-Id: I447555fd8a92bceecf6f40be59030d65461e4cbb Implements: blueprint consistency-groups
-rw-r--r--cinderclient/tests/v2/fakes.py63
-rw-r--r--cinderclient/tests/v2/test_cgsnapshots.py56
-rw-r--r--cinderclient/tests/v2/test_consistencygroups.py52
-rw-r--r--cinderclient/tests/v2/test_volumes.py3
-rw-r--r--cinderclient/v2/cgsnapshots.py124
-rw-r--r--cinderclient/v2/client.py5
-rw-r--r--cinderclient/v2/consistencygroups.py131
-rw-r--r--cinderclient/v2/shell.py204
-rw-r--r--cinderclient/v2/volumes.py6
9 files changed, 641 insertions, 3 deletions
diff --git a/cinderclient/tests/v2/fakes.py b/cinderclient/tests/v2/fakes.py
index 5439efb..dbb50f2 100644
--- a/cinderclient/tests/v2/fakes.py
+++ b/cinderclient/tests/v2/fakes.py
@@ -69,6 +69,32 @@ def _stub_snapshot(**kwargs):
return snapshot
+def _stub_consistencygroup(**kwargs):
+ consistencygroup = {
+ "created_at": "2012-08-28T16:30:31.000000",
+ "description": None,
+ "name": "cg",
+ "id": "11111111-1111-1111-1111-111111111111",
+ "availability_zone": "myzone",
+ "status": "available",
+ }
+ consistencygroup.update(kwargs)
+ return consistencygroup
+
+
+def _stub_cgsnapshot(**kwargs):
+ cgsnapshot = {
+ "created_at": "2012-08-28T16:30:31.000000",
+ "description": None,
+ "name": None,
+ "id": "11111111-1111-1111-1111-111111111111",
+ "status": "available",
+ "consistencygroup_id": "00000000-0000-0000-0000-000000000000",
+ }
+ cgsnapshot.update(kwargs)
+ return cgsnapshot
+
+
def _self_href(base_uri, tenant_id, backup_id):
return '%s/v2/%s/backups/%s' % (base_uri, tenant_id, backup_id)
@@ -395,6 +421,43 @@ class FakeHTTPClient(base_client.HTTPClient):
return (202, {}, None)
#
+ # Consistencygroups
+ #
+
+ def get_consistencygroups_detail(self, **kw):
+ return (200, {}, {"consistencygroups": [
+ _stub_consistencygroup(id='1234'),
+ _stub_consistencygroup(id='4567')]})
+
+ def get_consistencygroups_1234(self, **kw):
+ return (200, {}, {'consistencygroup':
+ _stub_consistencygroup(id='1234')})
+
+ def post_consistencygroups(self, **kw):
+ return (202, {}, {'consistencygroup': {}})
+
+ def post_consistencygroups_1234_delete(self, **kw):
+ return (202, {}, {})
+
+ #
+ # Cgsnapshots
+ #
+
+ def get_cgsnapshots_detail(self, **kw):
+ return (200, {}, {"cgsnapshots": [
+ _stub_cgsnapshot(id='1234'),
+ _stub_cgsnapshot(id='4567')]})
+
+ def get_cgsnapshots_1234(self, **kw):
+ return (200, {}, {'cgsnapshot': _stub_cgsnapshot(id='1234')})
+
+ def post_cgsnapshots(self, **kw):
+ return (202, {}, {'cgsnapshot': {}})
+
+ def delete_cgsnapshots_1234(self, **kw):
+ return (202, {}, {})
+
+ #
# Quotas
#
diff --git a/cinderclient/tests/v2/test_cgsnapshots.py b/cinderclient/tests/v2/test_cgsnapshots.py
new file mode 100644
index 0000000..0d5fad3
--- /dev/null
+++ b/cinderclient/tests/v2/test_cgsnapshots.py
@@ -0,0 +1,56 @@
+# Copyright (C) 2012 - 2014 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.
+
+from cinderclient.tests import utils
+from cinderclient.tests.v2 import fakes
+
+
+cs = fakes.FakeClient()
+
+
+class cgsnapshotsTest(utils.TestCase):
+
+ def test_delete_cgsnapshot(self):
+ v = cs.cgsnapshots.list()[0]
+ v.delete()
+ cs.assert_called('DELETE', '/cgsnapshots/1234')
+ cs.cgsnapshots.delete('1234')
+ cs.assert_called('DELETE', '/cgsnapshots/1234')
+ cs.cgsnapshots.delete(v)
+ cs.assert_called('DELETE', '/cgsnapshots/1234')
+
+ def test_create_cgsnapshot(self):
+ cs.cgsnapshots.create('cgsnap')
+ cs.assert_called('POST', '/cgsnapshots')
+
+ def test_create_cgsnapshot_with_cg_id(self):
+ cs.cgsnapshots.create('1234')
+ expected = {'cgsnapshot': {'status': 'creating',
+ 'description': None,
+ 'user_id': None,
+ 'name': None,
+ 'consistencygroup_id': '1234',
+ 'project_id': None}}
+ cs.assert_called('POST', '/cgsnapshots', body=expected)
+
+ def test_list_cgsnapshot(self):
+ cs.cgsnapshots.list()
+ cs.assert_called('GET', '/cgsnapshots/detail')
+
+ def test_get_cgsnapshot(self):
+ cgsnapshot_id = '1234'
+ cs.cgsnapshots.get(cgsnapshot_id)
+ cs.assert_called('GET', '/cgsnapshots/%s' % cgsnapshot_id)
diff --git a/cinderclient/tests/v2/test_consistencygroups.py b/cinderclient/tests/v2/test_consistencygroups.py
new file mode 100644
index 0000000..d9b0107
--- /dev/null
+++ b/cinderclient/tests/v2/test_consistencygroups.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2012 - 2014 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.
+
+from cinderclient.tests import utils
+from cinderclient.tests.v2 import fakes
+
+
+cs = fakes.FakeClient()
+
+
+class ConsistencygroupsTest(utils.TestCase):
+
+ def test_delete_consistencygroup(self):
+ v = cs.consistencygroups.list()[0]
+ v.delete(force='True')
+ cs.assert_called('POST', '/consistencygroups/1234/delete')
+ cs.consistencygroups.delete('1234', force=True)
+ cs.assert_called('POST', '/consistencygroups/1234/delete')
+ cs.consistencygroups.delete(v, force=True)
+ cs.assert_called('POST', '/consistencygroups/1234/delete')
+
+ def test_create_consistencygroup(self):
+ cs.consistencygroups.create('cg')
+ cs.assert_called('POST', '/consistencygroups')
+
+ def test_create_consistencygroup_with_volume_types(self):
+ cs.consistencygroups.create('cg', volume_types='type1,type2')
+ expected = {'consistencygroup': {'status': 'creating',
+ 'description': None,
+ 'availability_zone': None,
+ 'user_id': None,
+ 'name': 'cg',
+ 'volume_types': 'type1,type2',
+ 'project_id': None}}
+ cs.assert_called('POST', '/consistencygroups', body=expected)
+
+ def test_list_consistencygroup(self):
+ cs.consistencygroups.list()
+ cs.assert_called('GET', '/consistencygroups/detail')
diff --git a/cinderclient/tests/v2/test_volumes.py b/cinderclient/tests/v2/test_volumes.py
index 3b3bbcb..165742d 100644
--- a/cinderclient/tests/v2/test_volumes.py
+++ b/cinderclient/tests/v2/test_volumes.py
@@ -67,7 +67,8 @@ class VolumesTest(utils.TestCase):
'volume_type': None,
'project_id': None,
'metadata': {},
- 'source_replica': None},
+ 'source_replica': None,
+ 'consistencygroup_id': None},
'OS-SCH-HNT:scheduler_hints': 'uuid'}
cs.assert_called('POST', '/volumes', body=expected)
diff --git a/cinderclient/v2/cgsnapshots.py b/cinderclient/v2/cgsnapshots.py
new file mode 100644
index 0000000..29513a9
--- /dev/null
+++ b/cinderclient/v2/cgsnapshots.py
@@ -0,0 +1,124 @@
+# Copyright (C) 2012 - 2014 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.
+
+"""cgsnapshot interface (v2 extension)."""
+
+import six
+try:
+ from urllib import urlencode
+except ImportError:
+ from urllib.parse import urlencode
+
+from cinderclient import base
+
+
+class Cgsnapshot(base.Resource):
+ """A cgsnapshot is snapshot of a consistency group."""
+ def __repr__(self):
+ return "<cgsnapshot: %s>" % self.id
+
+ def delete(self):
+ """Delete this cgsnapshot."""
+ self.manager.delete(self)
+
+ def update(self, **kwargs):
+ """Update the name or description for this cgsnapshot."""
+ self.manager.update(self, **kwargs)
+
+
+class CgsnapshotManager(base.ManagerWithFind):
+ """Manage :class:`Cgsnapshot` resources."""
+ resource_class = Cgsnapshot
+
+ def create(self, consistencygroup_id, name=None, description=None,
+ user_id=None,
+ project_id=None):
+ """Creates a cgsnapshot.
+
+ :param consistencygroup: Name or uuid of a consistencygroup
+ :param name: Name of the cgsnapshot
+ :param description: Description of the cgsnapshot
+ :param user_id: User id derived from context
+ :param project_id: Project id derived from context
+ :rtype: :class:`Cgsnapshot`
+ """
+
+ body = {'cgsnapshot': {'consistencygroup_id': consistencygroup_id,
+ 'name': name,
+ 'description': description,
+ 'user_id': user_id,
+ 'project_id': project_id,
+ 'status': "creating",
+ }}
+
+ return self._create('/cgsnapshots', body, 'cgsnapshot')
+
+ def get(self, cgsnapshot_id):
+ """Get a cgsnapshot.
+
+ :param cgsnapshot_id: The ID of the cgsnapshot to get.
+ :rtype: :class:`Cgsnapshot`
+ """
+ return self._get("/cgsnapshots/%s" % cgsnapshot_id, "cgsnapshot")
+
+ def list(self, detailed=True, search_opts=None):
+ """Lists all cgsnapshots.
+
+ :rtype: list of :class:`Cgsnapshot`
+ """
+ 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("/cgsnapshots%s%s" % (detail, query_string),
+ "cgsnapshots")
+
+ def delete(self, cgsnapshot):
+ """Delete a cgsnapshot.
+
+ :param cgsnapshot: The :class:`Cgsnapshot` to delete.
+ """
+ self._delete("/cgsnapshots/%s" % base.getid(cgsnapshot))
+
+ def update(self, cgsnapshot, **kwargs):
+ """Update the name or description for a cgsnapshot.
+
+ :param cgsnapshot: The :class:`Cgsnapshot` to update.
+ """
+ if not kwargs:
+ return
+
+ body = {"cgsnapshot": kwargs}
+
+ self._update("/cgsnapshots/%s" % base.getid(cgsnapshot), body)
+
+ def _action(self, action, cgsnapshot, info=None, **kwargs):
+ """Perform a cgsnapshot "action."
+ """
+ body = {action: info}
+ self.run_hooks('modify_body_for_action', body, **kwargs)
+ url = '/cgsnapshots/%s/action' % base.getid(cgsnapshot)
+ return self.api.client.post(url, body=body)
diff --git a/cinderclient/v2/client.py b/cinderclient/v2/client.py
index a4016db..143146d 100644
--- a/cinderclient/v2/client.py
+++ b/cinderclient/v2/client.py
@@ -15,6 +15,8 @@
from cinderclient import client
from cinderclient.v2 import availability_zones
+from cinderclient.v2 import cgsnapshots
+from cinderclient.v2 import consistencygroups
from cinderclient.v2 import limits
from cinderclient.v2 import qos_specs
from cinderclient.v2 import quota_classes
@@ -69,6 +71,9 @@ class Client(object):
self.restores = volume_backups_restore.VolumeBackupRestoreManager(self)
self.transfers = volume_transfers.VolumeTransferManager(self)
self.services = services.ServiceManager(self)
+ self.consistencygroups = consistencygroups.\
+ ConsistencygroupManager(self)
+ self.cgsnapshots = cgsnapshots.CgsnapshotManager(self)
self.availability_zones = \
availability_zones.AvailabilityZoneManager(self)
diff --git a/cinderclient/v2/consistencygroups.py b/cinderclient/v2/consistencygroups.py
new file mode 100644
index 0000000..cbf911b
--- /dev/null
+++ b/cinderclient/v2/consistencygroups.py
@@ -0,0 +1,131 @@
+# Copyright (C) 2012 - 2014 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.
+
+"""Consistencygroup interface (v2 extension)."""
+
+import six
+try:
+ from urllib import urlencode
+except ImportError:
+ from urllib.parse import urlencode
+
+from cinderclient import base
+
+
+class Consistencygroup(base.Resource):
+ """A Consistencygroup of volumes."""
+ def __repr__(self):
+ return "<Consistencygroup: %s>" % self.id
+
+ def delete(self, force='False'):
+ """Delete this consistencygroup."""
+ self.manager.delete(self, force)
+
+ def update(self, **kwargs):
+ """Update the name or description for this consistencygroup."""
+ self.manager.update(self, **kwargs)
+
+
+class ConsistencygroupManager(base.ManagerWithFind):
+ """Manage :class:`Consistencygroup` resources."""
+ resource_class = Consistencygroup
+
+ def create(self, name=None, description=None,
+ volume_types=None, user_id=None,
+ project_id=None, availability_zone=None):
+ """Creates a consistencygroup.
+
+ :param name: Name of the ConsistencyGroup
+ :param description: Description of the ConsistencyGroup
+ :param volume_types: Types of volume
+ :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:`Consistencygroup`
+ """
+
+ body = {'consistencygroup': {'name': name,
+ 'description': description,
+ 'volume_types': volume_types,
+ 'user_id': user_id,
+ 'project_id': project_id,
+ 'availability_zone': availability_zone,
+ 'status': "creating",
+ }}
+
+ return self._create('/consistencygroups', body, 'consistencygroup')
+
+ def get(self, group_id):
+ """Get a consistencygroup.
+
+ :param group_id: The ID of the consistencygroup to get.
+ :rtype: :class:`Consistencygroup`
+ """
+ return self._get("/consistencygroups/%s" % group_id,
+ "consistencygroup")
+
+ def list(self, detailed=True, search_opts=None):
+ """Lists all consistencygroups.
+
+ :rtype: list of :class:`Consistencygroup`
+ """
+ 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("/consistencygroups%s%s" % (detail, query_string),
+ "consistencygroups")
+
+ def delete(self, consistencygroup, force=False):
+ """Delete a consistencygroup.
+
+ :param Consistencygroup: The :class:`Consistencygroup` to delete.
+ """
+ body = {'consistencygroup': {'force': force}}
+ self.run_hooks('modify_body_for_action', body, 'consistencygroup')
+ url = '/consistencygroups/%s/delete' % base.getid(consistencygroup)
+ return self.api.client.post(url, body=body)
+
+ def update(self, consistencygroup, **kwargs):
+ """Update the name or description for a consistencygroup.
+
+ :param Consistencygroup: The :class:`Consistencygroup` to update.
+ """
+ if not kwargs:
+ return
+
+ body = {"consistencygroup": kwargs}
+
+ self._update("/consistencygroups/%s" % base.getid(consistencygroup),
+ body)
+
+ def _action(self, action, consistencygroup, info=None, **kwargs):
+ """Perform a consistencygroup "action."
+ """
+ body = {action: info}
+ self.run_hooks('modify_body_for_action', body, **kwargs)
+ url = '/consistencygroups/%s/action' % base.getid(consistencygroup)
+ return self.api.client.post(url, body=body)
diff --git a/cinderclient/v2/shell.py b/cinderclient/v2/shell.py
index 2405dd5..2ff4a82 100644
--- a/cinderclient/v2/shell.py
+++ b/cinderclient/v2/shell.py
@@ -70,6 +70,16 @@ def _find_backup(cs, backup):
return utils.find_resource(cs.backups, backup)
+def _find_consistencygroup(cs, consistencygroup):
+ """Gets a consistencygroup by name or ID."""
+ return utils.find_resource(cs.consistencygroups, consistencygroup)
+
+
+def _find_cgsnapshot(cs, cgsnapshot):
+ """Gets a cgsnapshot by name or ID."""
+ return utils.find_resource(cs.cgsnapshots, cgsnapshot)
+
+
def _find_transfer(cs, transfer):
"""Gets a transfer by name or ID."""
return utils.find_resource(cs.transfers, transfer)
@@ -240,6 +250,11 @@ class CheckSizeArgForCreate(argparse.Action):
action=CheckSizeArgForCreate,
help='Size of volume, in GBs. (Required unless '
'snapshot-id/source-volid is specified).')
+@utils.arg('--consisgroup-id',
+ metavar='<consistencygroup-id>',
+ default=None,
+ help='ID of a consistency group where the new volume belongs to. '
+ 'Default=None.')
@utils.arg('--snapshot-id',
metavar='<snapshot-id>',
default=None,
@@ -332,6 +347,7 @@ def do_create(cs, args):
#NOTE(N.S.): end of taken piece
volume = cs.volumes.create(args.size,
+ args.consisgroup_id,
args.snapshot_id,
args.source_volid,
args.name,
@@ -1694,3 +1710,191 @@ def do_replication_promote(cs, args):
def do_replication_reenable(cs, args):
"""Sync the secondary volume with primary for a relationship."""
utils.find_volume(cs, args.volume).reenable(args.volume)
+
+
+@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.')
+@utils.service_type('volumev2')
+def do_consisgroup_list(cs, args):
+ """Lists all consistencygroups."""
+ consistencygroups = cs.consistencygroups.list()
+
+ columns = ['ID', 'Status', 'Name']
+ utils.print_list(consistencygroups, columns)
+
+
+@utils.arg('consistencygroup',
+ metavar='<consistencygroup>',
+ help='Name or ID of a consistency group.')
+@utils.service_type('volumev2')
+def do_consisgroup_show(cs, args):
+ """Shows details of a consistency group."""
+ info = dict()
+ consistencygroup = _find_consistencygroup(cs, args.consistencygroup)
+ info.update(consistencygroup._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('--name',
+ metavar='<name>',
+ help='Name of a consistency group.')
+@utils.arg('--description',
+ metavar='<description>',
+ default=None,
+ help='Description of a consistency group. Default=None.')
+@utils.arg('--volume-types',
+ metavar='<volume-types>',
+ default=None,
+ help='Volume types. If not provided, default_volume_type '
+ 'in cinder.conf must be specified. Default=None.')
+@utils.arg('--availability-zone',
+ metavar='<availability-zone>',
+ default=None,
+ help='Availability zone for volume. Default=None.')
+@utils.service_type('volumev2')
+def do_consisgroup_create(cs, args):
+ """Creates a consistency group."""
+
+ consistencygroup = cs.consistencygroups.create(
+ args.name,
+ args.description,
+ args.volume_types,
+ availability_zone=args.availability_zone)
+
+ info = dict()
+ consistencygroup = cs.consistencygroups.get(consistencygroup.id)
+ info.update(consistencygroup._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('consistencygroup',
+ metavar='<consistencygroup>', nargs='+',
+ help='Name or ID of one or more consistency groups '
+ 'to be deleted.')
+@utils.arg('--force',
+ action='store_true',
+ help='Allows or disallows consistency groups '
+ 'to be deleted. If the consistency group is empty, '
+ 'it can be deleted without the force flag. '
+ 'If the consistency group is not empty, the force '
+ 'flag is required for it to be deleted.',
+ default=False)
+@utils.service_type('volumev2')
+def do_consisgroup_delete(cs, args):
+ """Removes one or more consistency groups."""
+ failure_count = 0
+ for consistencygroup in args.consistencygroup:
+ try:
+ _find_consistencygroup(cs, consistencygroup).delete(args.force)
+ except Exception as e:
+ failure_count += 1
+ print("Delete for consistency group %s failed: %s" %
+ (consistencygroup, e))
+ if failure_count == len(args.consistencygroup):
+ raise exceptions.CommandError("Unable to delete any of specified "
+ "consistency groups.")
+
+
+@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.')
+@utils.arg('--status',
+ metavar='<status>',
+ default=None,
+ help='Filters results by a status. Default=None.')
+@utils.arg('--consistencygroup-id',
+ metavar='<consistencygroup_id>',
+ default=None,
+ help='Filters results by a consistency group ID. Default=None.')
+@utils.service_type('volumev2')
+def do_cgsnapshot_list(cs, args):
+ """Lists all cgsnapshots."""
+ cgsnapshots = cs.cgsnapshots.list()
+
+ all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants))
+
+ search_opts = {
+ 'all_tenants': all_tenants,
+ 'status': args.status,
+ 'consistencygroup_id': args.consistencygroup_id,
+ }
+
+ cgsnapshots = cs.cgsnapshots.list(search_opts=search_opts)
+
+ columns = ['ID', 'Status', 'Name']
+ utils.print_list(cgsnapshots, columns)
+
+
+@utils.arg('cgsnapshot',
+ metavar='<cgsnapshot>',
+ help='Name or ID of cgsnapshot.')
+@utils.service_type('volumev2')
+def do_cgsnapshot_show(cs, args):
+ """Shows cgsnapshot details."""
+ info = dict()
+ cgsnapshot = _find_cgsnapshot(cs, args.cgsnapshot)
+ info.update(cgsnapshot._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('consistencygroup',
+ metavar='<consistencygroup>',
+ help='Name or ID of a consistency group.')
+@utils.arg('--name',
+ metavar='<name>',
+ default=None,
+ help='Cgsnapshot name. Default=None.')
+@utils.arg('--description',
+ metavar='<description>',
+ default=None,
+ help='Cgsnapshot description. Default=None.')
+@utils.service_type('volumev2')
+def do_cgsnapshot_create(cs, args):
+ """Creates a cgsnapshot."""
+ consistencygroup = _find_consistencygroup(cs, args.consistencygroup)
+ cgsnapshot = cs.cgsnapshots.create(
+ consistencygroup.id,
+ args.name,
+ args.description)
+
+ info = dict()
+ cgsnapshot = cs.cgsnapshots.get(cgsnapshot.id)
+ info.update(cgsnapshot._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('cgsnapshot',
+ metavar='<cgsnapshot>', nargs='+',
+ help='Name or ID of one or more cgsnapshots to be deleted.')
+@utils.service_type('volumev2')
+def do_cgsnapshot_delete(cs, args):
+ """Removes one or more cgsnapshots."""
+ failure_count = 0
+ for cgsnapshot in args.cgsnapshot:
+ try:
+ _find_cgsnapshot(cs, cgsnapshot).delete()
+ except Exception as e:
+ failure_count += 1
+ print("Delete for cgsnapshot %s failed: %s" % (cgsnapshot, e))
+ if failure_count == len(args.cgsnapshot):
+ raise exceptions.CommandError("Unable to delete any of specified "
+ "cgsnapshots.")
diff --git a/cinderclient/v2/volumes.py b/cinderclient/v2/volumes.py
index 98afd93..ae8ed29 100644
--- a/cinderclient/v2/volumes.py
+++ b/cinderclient/v2/volumes.py
@@ -165,8 +165,8 @@ class VolumeManager(base.ManagerWithFind):
"""Manage :class:`Volume` resources."""
resource_class = Volume
- def create(self, size, snapshot_id=None, source_volid=None,
- name=None, description=None,
+ def create(self, size, consistencygroup_id=None, snapshot_id=None,
+ source_volid=None, name=None, description=None,
volume_type=None, user_id=None,
project_id=None, availability_zone=None,
metadata=None, imageRef=None, scheduler_hints=None,
@@ -174,6 +174,7 @@ class VolumeManager(base.ManagerWithFind):
"""Creates a volume.
:param size: Size of volume in GB
+ :param consistencygroup_id: ID of the consistencygroup
:param snapshot_id: ID of the snapshot
:param name: Name of the volume
:param description: Description of the volume
@@ -196,6 +197,7 @@ class VolumeManager(base.ManagerWithFind):
volume_metadata = metadata
body = {'volume': {'size': size,
+ 'consistencygroup_id': consistencygroup_id,
'snapshot_id': snapshot_id,
'name': name,
'description': description,