volume: Add 'volume group *' commands

These mirror the 'cinder group-*' commands, with arguments copied across
essentially verbatim. The only significant departures are the
replacement of "tenant" terminology with "project" and the merging of
the various volume group replication action commands into the parent
volume group (e.g. 'openstack volume group set --enable-replication'
instead of 'cinder group enable-replication')

  volume group create
  volume group delete
  volume group list
  volume group show
  volume group set
  volume group failover

Change-Id: I3b2c0cb92b8a53cc1c0cefa3313b80f59c9e5835
Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
This commit is contained in:
Stephen Finucane 2021-06-03 10:21:20 +01:00
parent 5faa9ef805
commit 4c2e8523a9
8 changed files with 1162 additions and 9 deletions

View File

@ -0,0 +1,23 @@
============
volume group
============
Block Storage v3
.. autoprogram-cliff:: openstack.volume.v3
:command: volume group create
.. autoprogram-cliff:: openstack.volume.v3
:command: volume group delete
.. autoprogram-cliff:: openstack.volume.v3
:command: volume group list
.. autoprogram-cliff:: openstack.volume.v3
:command: volume group failover
.. autoprogram-cliff:: openstack.volume.v3
:command: volume group set
.. autoprogram-cliff:: openstack.volume.v3
:command: volume group show

View File

@ -159,6 +159,7 @@ referring to both Compute and Volume quotas.
* ``volume backend pool``: (**Volume**) volume backend storage pools
* ``volume backup record``: (**Volume**) volume record that can be imported or exported
* ``volume backend``: (**Volume**) volume backend storage
* ``volume group``: (**Volume**) group of volumes
* ``volume host``: (**Volume**) the physical computer for volumes
* ``volume message``: (**Volume**) volume API internal messages detailing volume failure messages
* ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes

View File

@ -44,15 +44,15 @@ force-delete,volume delete --force,"Attempts force-delete of volume regardless o
freeze-host,volume host set --disable,Freeze and disable the specified cinder-volume host.
get-capabilities,volume backend capability show,Show capabilities of a volume backend. Admin only.
get-pools,volume backend pool list,Show pool information for backends. Admin only.
group-create,,Creates a group. (Supported by API versions 3.13 - 3.latest)
group-create,volume group create,Creates a group. (Supported by API versions 3.13 - 3.latest)
group-create-from-src,,Creates a group from a group snapshot or a source group. (Supported by API versions 3.14 - 3.latest)
group-delete,,Removes one or more groups. (Supported by API versions 3.13 - 3.latest)
group-disable-replication,,Disables replication for group. (Supported by API versions 3.38 - 3.latest)
group-enable-replication,,Enables replication for group. (Supported by API versions 3.38 - 3.latest)
group-failover-replication,,Fails over replication for group. (Supported by API versions 3.38 - 3.latest)
group-list,,Lists all groups. (Supported by API versions 3.13 - 3.latest)
group-list-replication-targets,,Lists replication targets for group. (Supported by API versions 3.38 - 3.latest)
group-show,,Shows details of a group. (Supported by API versions 3.13 - 3.latest)
group-delete,volume group delete,Removes one or more groups. (Supported by API versions 3.13 - 3.latest)
group-disable-replication,volume group set --disable-replication,Disables replication for group. (Supported by API versions 3.38 - 3.latest)
group-enable-replication,volume group set --enable-replication,Enables replication for group. (Supported by API versions 3.38 - 3.latest)
group-failover-replication,volume group failover,Fails over replication for group. (Supported by API versions 3.38 - 3.latest)
group-list,volume group list,Lists all groups. (Supported by API versions 3.13 - 3.latest)
group-list-replication-targets,volume group list --replication-targets,Lists replication targets for group. (Supported by API versions 3.38 - 3.latest)
group-show,volume group show,Shows details of a group. (Supported by API versions 3.13 - 3.latest)
group-snapshot-create,,Creates a group snapshot. (Supported by API versions 3.14 - 3.latest)
group-snapshot-delete,,Removes one or more group snapshots. (Supported by API versions 3.14 - 3.latest)
group-snapshot-list,,Lists all group snapshots. (Supported by API versions 3.14 - 3.latest)
@ -65,7 +65,7 @@ group-type-key,,Sets or unsets group_spec for a group type. (Supported by API ve
group-type-list,,Lists available 'group types'. (Admin only will see private types) (Supported by API versions 3.11 - 3.latest)
group-type-show,,Show group type details. (Supported by API versions 3.11 - 3.latest)
group-type-update,,Updates group type name description and/or is_public. (Supported by API versions 3.11 - 3.latest)
group-update,,Updates a group. (Supported by API versions 3.13 - 3.latest)
group-update,volume group set,Updates a group. (Supported by API versions 3.13 - 3.latest)
image-metadata,volume set --image-property,Sets or deletes volume image metadata.
image-metadata-show,volume show,Shows volume image metadata.
list,volume list,Lists all volumes.

1 absolute-limits limits show --absolute Lists absolute limits for a user.
44 freeze-host volume host set --disable Freeze and disable the specified cinder-volume host.
45 get-capabilities volume backend capability show Show capabilities of a volume backend. Admin only.
46 get-pools volume backend pool list Show pool information for backends. Admin only.
47 group-create volume group create Creates a group. (Supported by API versions 3.13 - 3.latest)
48 group-create-from-src Creates a group from a group snapshot or a source group. (Supported by API versions 3.14 - 3.latest)
49 group-delete volume group delete Removes one or more groups. (Supported by API versions 3.13 - 3.latest)
50 group-disable-replication volume group set --disable-replication Disables replication for group. (Supported by API versions 3.38 - 3.latest)
51 group-enable-replication volume group set --enable-replication Enables replication for group. (Supported by API versions 3.38 - 3.latest)
52 group-failover-replication volume group failover Fails over replication for group. (Supported by API versions 3.38 - 3.latest)
53 group-list volume group list Lists all groups. (Supported by API versions 3.13 - 3.latest)
54 group-list-replication-targets volume group list --replication-targets Lists replication targets for group. (Supported by API versions 3.38 - 3.latest)
55 group-show volume group show Shows details of a group. (Supported by API versions 3.13 - 3.latest)
56 group-snapshot-create Creates a group snapshot. (Supported by API versions 3.14 - 3.latest)
57 group-snapshot-delete Removes one or more group snapshots. (Supported by API versions 3.14 - 3.latest)
58 group-snapshot-list Lists all group snapshots. (Supported by API versions 3.14 - 3.latest)
65 group-type-list Lists available 'group types'. (Admin only will see private types) (Supported by API versions 3.11 - 3.latest)
66 group-type-show Show group type details. (Supported by API versions 3.11 - 3.latest)
67 group-type-update Updates group type name description and/or is_public. (Supported by API versions 3.11 - 3.latest)
68 group-update volume group set Updates a group. (Supported by API versions 3.13 - 3.latest)
69 image-metadata volume set --image-property Sets or deletes volume image metadata.
70 image-metadata-show volume show Shows volume image metadata.
71 list volume list Lists all volumes.

View File

@ -32,10 +32,16 @@ class FakeVolumeClient(object):
self.attachments = mock.Mock()
self.attachments.resource_class = fakes.FakeResource(None, {})
self.groups = mock.Mock()
self.groups.resource_class = fakes.FakeResource(None, {})
self.group_types = mock.Mock()
self.group_types.resource_class = fakes.FakeResource(None, {})
self.messages = mock.Mock()
self.messages.resource_class = fakes.FakeResource(None, {})
self.volumes = mock.Mock()
self.volumes.resource_class = fakes.FakeResource(None, {})
self.volume_types = mock.Mock()
self.volume_types.resource_class = fakes.FakeResource(None, {})
class TestVolume(utils.TestCommand):
@ -59,6 +65,111 @@ class TestVolume(utils.TestCommand):
# TODO(stephenfin): Check if the responses are actually the same
FakeVolume = volume_v2_fakes.FakeVolume
FakeVolumeType = volume_v2_fakes.FakeVolumeType
class FakeVolumeGroup:
"""Fake one or more volume groups."""
@staticmethod
def create_one_volume_group(attrs=None):
"""Create a fake group.
:param attrs: A dictionary with all attributes of group
:return: A FakeResource object with id, name, status, etc.
"""
attrs = attrs or {}
group_type = attrs.pop('group_type', None) or uuid.uuid4().hex
volume_types = attrs.pop('volume_types', None) or [uuid.uuid4().hex]
# Set default attribute
group_info = {
'id': uuid.uuid4().hex,
'status': random.choice([
'available',
]),
'availability_zone': f'az-{uuid.uuid4().hex}',
'created_at': '2015-09-16T09:28:52.000000',
'name': 'first_group',
'description': f'description-{uuid.uuid4().hex}',
'group_type': group_type,
'volume_types': volume_types,
'volumes': [f'volume-{uuid.uuid4().hex}'],
'group_snapshot_id': None,
'source_group_id': None,
'project_id': f'project-{uuid.uuid4().hex}',
}
# Overwrite default attributes if there are some attributes set
group_info.update(attrs)
group = fakes.FakeResource(
None,
group_info,
loaded=True)
return group
@staticmethod
def create_volume_groups(attrs=None, count=2):
"""Create multiple fake groups.
:param attrs: A dictionary with all attributes of group
:param count: The number of groups to be faked
:return: A list of FakeResource objects
"""
groups = []
for n in range(0, count):
groups.append(FakeVolumeGroup.create_one_volume_group(attrs))
return groups
class FakeVolumeGroupType:
"""Fake one or more volume group types."""
@staticmethod
def create_one_volume_group_type(attrs=None):
"""Create a fake group type.
:param attrs: A dictionary with all attributes of group type
:return: A FakeResource object with id, name, description, etc.
"""
attrs = attrs or {}
# Set default attribute
group_type_info = {
'id': uuid.uuid4().hex,
'name': f'group-type-{uuid.uuid4().hex}',
'description': f'description-{uuid.uuid4().hex}',
'is_public': random.choice([True, False]),
'group_specs': {},
}
# Overwrite default attributes if there are some attributes set
group_type_info.update(attrs)
group_type = fakes.FakeResource(
None,
group_type_info,
loaded=True)
return group_type
@staticmethod
def create_volume_group_types(attrs=None, count=2):
"""Create multiple fake group types.
:param attrs: A dictionary with all attributes of group type
:param count: The number of group types to be faked
:return: A list of FakeResource objects
"""
group_types = []
for n in range(0, count):
group_types.append(
FakeVolumeGroupType.create_one_volume_group_type(attrs)
)
return group_types
class FakeVolumeMessage:

View File

@ -0,0 +1,497 @@
# 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 import api_versions
from osc_lib import exceptions
from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes
from openstackclient.volume.v3 import volume_group
class TestVolumeGroup(volume_fakes.TestVolume):
def setUp(self):
super().setUp()
self.volume_groups_mock = self.app.client_manager.volume.groups
self.volume_groups_mock.reset_mock()
self.volume_group_types_mock = \
self.app.client_manager.volume.group_types
self.volume_group_types_mock.reset_mock()
self.volume_types_mock = self.app.client_manager.volume.volume_types
self.volume_types_mock.reset_mock()
class TestVolumeGroupCreate(TestVolumeGroup):
fake_volume_type = volume_fakes.FakeVolumeType.create_one_volume_type()
fake_volume_group_type = \
volume_fakes.FakeVolumeGroupType.create_one_volume_group_type()
fake_volume_group = volume_fakes.FakeVolumeGroup.create_one_volume_group(
attrs={
'group_type': fake_volume_group_type.id,
'volume_types': [fake_volume_type.id],
},
)
columns = (
'ID',
'Status',
'Name',
'Description',
'Group Type',
'Volume Types',
'Availability Zone',
'Created At',
'Volumes',
'Group Snapshot ID',
'Source Group ID',
)
data = (
fake_volume_group.id,
fake_volume_group.status,
fake_volume_group.name,
fake_volume_group.description,
fake_volume_group.group_type,
fake_volume_group.volume_types,
fake_volume_group.availability_zone,
fake_volume_group.created_at,
fake_volume_group.volumes,
fake_volume_group.group_snapshot_id,
fake_volume_group.source_group_id,
)
def setUp(self):
super().setUp()
self.volume_types_mock.get.return_value = self.fake_volume_type
self.volume_group_types_mock.get.return_value = \
self.fake_volume_group_type
self.volume_groups_mock.create.return_value = self.fake_volume_group
self.volume_groups_mock.get.return_value = self.fake_volume_group
self.cmd = volume_group.CreateVolumeGroup(self.app, None)
def test_volume_group_create(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.13')
arglist = [
self.fake_volume_group_type.id,
self.fake_volume_type.id,
]
verifylist = [
('volume_group_type', self.fake_volume_group_type.id),
('volume_types', [self.fake_volume_type.id]),
('name', None),
('description', None),
('availability_zone', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.volume_group_types_mock.get.assert_called_once_with(
self.fake_volume_group_type.id)
self.volume_types_mock.get.assert_called_once_with(
self.fake_volume_type.id)
self.volume_groups_mock.create.assert_called_once_with(
self.fake_volume_group_type.id,
self.fake_volume_type.id,
None,
None,
availability_zone=None,
)
self.assertEqual(self.columns, columns)
self.assertCountEqual(self.data, data)
def test_volume_group_create_with_options(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.13')
arglist = [
self.fake_volume_group_type.id,
self.fake_volume_type.id,
'--name', 'foo',
'--description', 'hello, world',
'--availability-zone', 'bar',
]
verifylist = [
('volume_group_type', self.fake_volume_group_type.id),
('volume_types', [self.fake_volume_type.id]),
('name', 'foo'),
('description', 'hello, world'),
('availability_zone', 'bar'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.volume_group_types_mock.get.assert_called_once_with(
self.fake_volume_group_type.id)
self.volume_types_mock.get.assert_called_once_with(
self.fake_volume_type.id)
self.volume_groups_mock.create.assert_called_once_with(
self.fake_volume_group_type.id,
self.fake_volume_type.id,
'foo',
'hello, world',
availability_zone='bar',
)
self.assertEqual(self.columns, columns)
self.assertCountEqual(self.data, data)
def test_volume_group_create_pre_v313(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.12')
arglist = [
self.fake_volume_group_type.id,
self.fake_volume_type.id,
]
verifylist = [
('volume_group_type', self.fake_volume_group_type.id),
('volume_types', [self.fake_volume_type.id]),
('name', None),
('description', None),
('availability_zone', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
exc = self.assertRaises(
exceptions.CommandError,
self.cmd.take_action,
parsed_args)
self.assertIn(
'--os-volume-api-version 3.13 or greater is required',
str(exc))
class TestVolumeGroupDelete(TestVolumeGroup):
fake_volume_group = \
volume_fakes.FakeVolumeGroup.create_one_volume_group()
def setUp(self):
super().setUp()
self.volume_groups_mock.get.return_value = self.fake_volume_group
self.volume_groups_mock.delete.return_value = None
self.cmd = volume_group.DeleteVolumeGroup(self.app, None)
def test_volume_group_delete(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.13')
arglist = [
self.fake_volume_group.id,
'--force',
]
verifylist = [
('group', self.fake_volume_group.id),
('force', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.volume_groups_mock.delete.assert_called_once_with(
self.fake_volume_group.id, delete_volumes=True,
)
self.assertIsNone(result)
def test_volume_group_delete_pre_v313(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.12')
arglist = [
self.fake_volume_group.id,
]
verifylist = [
('group', self.fake_volume_group.id),
('force', False),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
exc = self.assertRaises(
exceptions.CommandError,
self.cmd.take_action,
parsed_args)
self.assertIn(
'--os-volume-api-version 3.13 or greater is required',
str(exc))
class TestVolumeGroupSet(TestVolumeGroup):
fake_volume_group = \
volume_fakes.FakeVolumeGroup.create_one_volume_group()
columns = (
'ID',
'Status',
'Name',
'Description',
'Group Type',
'Volume Types',
'Availability Zone',
'Created At',
'Volumes',
'Group Snapshot ID',
'Source Group ID',
)
data = (
fake_volume_group.id,
fake_volume_group.status,
fake_volume_group.name,
fake_volume_group.description,
fake_volume_group.group_type,
fake_volume_group.volume_types,
fake_volume_group.availability_zone,
fake_volume_group.created_at,
fake_volume_group.volumes,
fake_volume_group.group_snapshot_id,
fake_volume_group.source_group_id,
)
def setUp(self):
super().setUp()
self.volume_groups_mock.get.return_value = self.fake_volume_group
self.volume_groups_mock.update.return_value = self.fake_volume_group
self.cmd = volume_group.SetVolumeGroup(self.app, None)
def test_volume_group_set(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.13')
arglist = [
self.fake_volume_group.id,
'--name', 'foo',
'--description', 'hello, world',
]
verifylist = [
('group', self.fake_volume_group.id),
('name', 'foo'),
('description', 'hello, world'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.volume_groups_mock.update.assert_called_once_with(
self.fake_volume_group.id, name='foo', description='hello, world',
)
self.assertEqual(self.columns, columns)
self.assertCountEqual(self.data, data)
def test_volume_group_with_enable_replication_option(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.38')
arglist = [
self.fake_volume_group.id,
'--enable-replication',
]
verifylist = [
('group', self.fake_volume_group.id),
('enable_replication', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.volume_groups_mock.enable_replication.assert_called_once_with(
self.fake_volume_group.id)
self.assertEqual(self.columns, columns)
self.assertCountEqual(self.data, data)
def test_volume_group_set_pre_v313(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.12')
arglist = [
self.fake_volume_group.id,
'--name', 'foo',
'--description', 'hello, world',
]
verifylist = [
('group', self.fake_volume_group.id),
('name', 'foo'),
('description', 'hello, world'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
exc = self.assertRaises(
exceptions.CommandError,
self.cmd.take_action,
parsed_args)
self.assertIn(
'--os-volume-api-version 3.13 or greater is required',
str(exc))
def test_volume_group_with_enable_replication_option_pre_v338(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.37')
arglist = [
self.fake_volume_group.id,
'--enable-replication',
]
verifylist = [
('group', self.fake_volume_group.id),
('enable_replication', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
exc = self.assertRaises(
exceptions.CommandError,
self.cmd.take_action,
parsed_args)
self.assertIn(
'--os-volume-api-version 3.38 or greater is required',
str(exc))
class TestVolumeGroupList(TestVolumeGroup):
fake_volume_groups = \
volume_fakes.FakeVolumeGroup.create_volume_groups()
columns = (
'ID',
'Status',
'Name',
)
data = [
(
fake_volume_group.id,
fake_volume_group.status,
fake_volume_group.name,
) for fake_volume_group in fake_volume_groups
]
def setUp(self):
super().setUp()
self.volume_groups_mock.list.return_value = self.fake_volume_groups
self.cmd = volume_group.ListVolumeGroup(self.app, None)
def test_volume_group_list(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.13')
arglist = [
'--all-projects',
]
verifylist = [
('all_projects', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.volume_groups_mock.list.assert_called_once_with(
search_opts={
'all_tenants': True,
},
)
self.assertEqual(self.columns, columns)
self.assertCountEqual(tuple(self.data), data)
def test_volume_group_list_pre_v313(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.12')
arglist = [
'--all-projects',
]
verifylist = [
('all_projects', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
exc = self.assertRaises(
exceptions.CommandError,
self.cmd.take_action,
parsed_args)
self.assertIn(
'--os-volume-api-version 3.13 or greater is required',
str(exc))
class TestVolumeGroupFailover(TestVolumeGroup):
fake_volume_group = \
volume_fakes.FakeVolumeGroup.create_one_volume_group()
def setUp(self):
super().setUp()
self.volume_groups_mock.get.return_value = self.fake_volume_group
self.volume_groups_mock.failover_replication.return_value = None
self.cmd = volume_group.FailoverVolumeGroup(self.app, None)
def test_volume_group_failover(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.38')
arglist = [
self.fake_volume_group.id,
'--allow-attached-volume',
'--secondary-backend-id', 'foo',
]
verifylist = [
('group', self.fake_volume_group.id),
('allow_attached_volume', True),
('secondary_backend_id', 'foo'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.volume_groups_mock.failover_replication.assert_called_once_with(
self.fake_volume_group.id,
allow_attached_volume=True,
secondary_backend_id='foo',
)
self.assertIsNone(result)
def test_volume_group_failover_pre_v338(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.37')
arglist = [
self.fake_volume_group.id,
'--allow-attached-volume',
'--secondary-backend-id', 'foo',
]
verifylist = [
('group', self.fake_volume_group.id),
('allow_attached_volume', True),
('secondary_backend_id', 'foo'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
exc = self.assertRaises(
exceptions.CommandError,
self.cmd.take_action,
parsed_args)
self.assertIn(
'--os-volume-api-version 3.38 or greater is required',
str(exc))

View File

@ -0,0 +1,506 @@
# 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 logging
from cinderclient import api_versions
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
from openstackclient.i18n import _
LOG = logging.getLogger(__name__)
def _format_group(group):
columns = (
'id',
'status',
'name',
'description',
'group_type',
'volume_types',
'availability_zone',
'created_at',
'volumes',
'group_snapshot_id',
'source_group_id',
)
column_headers = (
'ID',
'Status',
'Name',
'Description',
'Group Type',
'Volume Types',
'Availability Zone',
'Created At',
'Volumes',
'Group Snapshot ID',
'Source Group ID',
)
# TODO(stephenfin): Consider using a formatter for volume_types since it's
# a list
return (
column_headers,
utils.get_item_properties(
group,
columns,
),
)
class CreateVolumeGroup(command.ShowOne):
"""Create a volume group.
Generic volume groups enable you to create a group of volumes and manage
them together.
Generic volume groups are more flexible than consistency groups. Currently
volume consistency groups only support consistent group snapshot. It
cannot be extended easily to serve other purposes. A project may want to
put volumes used in the same application together in a group so that it is
easier to manage them together, and this group of volumes may or may not
support consistent group snapshot. Generic volume group solve this problem.
By decoupling the tight relationship between the group construct and the
consistency concept, generic volume groups can be extended to support other
features in the future.
This command requires ``--os-volume-api-version`` 3.13 or greater.
"""
def get_parser(self, prog_name):
parser = super().get_parser(prog_name)
parser.add_argument(
'volume_group_type',
metavar='<volume_group_type>',
help=_('Name or ID of volume group type to use.'),
)
parser.add_argument(
'volume_types',
metavar='<volume_type>',
nargs='+',
default=[],
help=_('Name or ID of volume type(s) to use.'),
)
parser.add_argument(
'--name',
metavar='<name>',
help=_('Name of the volume group.'),
)
parser.add_argument(
'--description',
metavar='<description>',
help=_('Description of a volume group.')
)
parser.add_argument(
'--availability-zone',
metavar='<availability-zone>',
help=_('Availability zone for volume group.'),
)
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
if volume_client.api_version < api_versions.APIVersion('3.13'):
msg = _(
"--os-volume-api-version 3.13 or greater is required to "
"support the 'volume group create' command"
)
raise exceptions.CommandError(msg)
volume_group_type = utils.find_resource(
volume_client.group_types,
parsed_args.volume_group_type,
)
volume_types = []
for volume_type in parsed_args.volume_types:
volume_types.append(
utils.find_resource(
volume_client.volume_types,
volume_type,
)
)
group = volume_client.groups.create(
volume_group_type.id,
','.join(x.id for x in volume_types),
parsed_args.name,
parsed_args.description,
availability_zone=parsed_args.availability_zone)
group = volume_client.groups.get(group.id)
return _format_group(group)
class DeleteVolumeGroup(command.Command):
"""Delete a volume group.
This command requires ``--os-volume-api-version`` 3.13 or greater.
"""
def get_parser(self, prog_name):
parser = super().get_parser(prog_name)
parser.add_argument(
'group',
metavar='<group>',
help=_('Name or ID of volume group to delete'),
)
parser.add_argument(
'--force',
action='store_true',
default=False,
help=_(
'Delete the volume group even if it contains volumes. '
'This will delete any remaining volumes in the group.',
)
)
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
if volume_client.api_version < api_versions.APIVersion('3.13'):
msg = _(
"--os-volume-api-version 3.13 or greater is required to "
"support the 'volume group delete' command"
)
raise exceptions.CommandError(msg)
group = utils.find_resource(
volume_client.groups,
parsed_args.group,
)
volume_client.groups.delete(
group.id, delete_volumes=parsed_args.force)
class SetVolumeGroup(command.ShowOne):
"""Update a volume group.
This command requires ``--os-volume-api-version`` 3.13 or greater.
"""
def get_parser(self, prog_name):
parser = super().get_parser(prog_name)
parser.add_argument(
'group',
metavar='<group>',
help=_('Name or ID of volume group.'),
)
parser.add_argument(
'--name',
metavar='<name>',
help=_('New name for group.'),
)
parser.add_argument(
'--description',
metavar='<description>',
help=_('New description for group.'),
)
parser.add_argument(
'--enable-replication',
action='store_true',
dest='enable_replication',
default=None,
help=_(
'Enable replication for group. '
'(supported by --os-volume-api-version 3.38 or above)'
),
)
parser.add_argument(
'--disable-replication',
action='store_false',
dest='enable_replication',
help=_(
'Disable replication for group. '
'(supported by --os-volume-api-version 3.38 or above)'
),
)
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
if volume_client.api_version < api_versions.APIVersion('3.13'):
msg = _(
"--os-volume-api-version 3.13 or greater is required to "
"support the 'volume group set' command"
)
raise exceptions.CommandError(msg)
group = utils.find_resource(
volume_client.groups,
parsed_args.group,
)
if parsed_args.enable_replication is not None:
if volume_client.api_version < api_versions.APIVersion('3.38'):
msg = _(
"--os-volume-api-version 3.38 or greater is required to "
"support the '--enable-replication' or "
"'--disable-replication' options"
)
raise exceptions.CommandError(msg)
if parsed_args.enable_replication:
volume_client.groups.enable_replication(group.id)
else:
volume_client.groups.disable_replication(group.id)
kwargs = {}
if parsed_args.name is not None:
kwargs['name'] = parsed_args.name
if parsed_args.description is not None:
kwargs['description'] = parsed_args.description
if kwargs:
group = volume_client.groups.update(group.id, **kwargs)
return _format_group(group)
class ListVolumeGroup(command.Lister):
"""Lists all volume groups.
This command requires ``--os-volume-api-version`` 3.13 or greater.
"""
def get_parser(self, prog_name):
parser = super().get_parser(prog_name)
parser.add_argument(
'--all-projects',
dest='all_projects',
action='store_true',
default=utils.env('ALL_PROJECTS', default=False),
help=_('Shows details for all projects (admin only).'),
)
# TODO(stephenfin): Add once we have an equivalent command for
# 'cinder list-filters'
# parser.add_argument(
# '--filter',
# metavar='<key=value>',
# action=parseractions.KeyValueAction,
# dest='filters',
# help=_(
# "Filter key and value pairs. Use 'foo' to "
# "check enabled filters from server. Use 'key~=value' for "
# "inexact filtering if the key supports "
# "(supported by --os-volume-api-version 3.33 or above)"
# ),
# )
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
if volume_client.api_version < api_versions.APIVersion('3.13'):
msg = _(
"--os-volume-api-version 3.13 or greater is required to "
"support the 'volume group list' command"
)
raise exceptions.CommandError(msg)
search_opts = {
'all_tenants': parsed_args.all_projects,
}
groups = volume_client.groups.list(
search_opts=search_opts)
column_headers = (
'ID',
'Status',
'Name',
)
columns = (
'id',
'status',
'name',
)
return (
column_headers,
(
utils.get_item_properties(a, columns)
for a in groups
),
)
class ShowVolumeGroup(command.ShowOne):
"""Show detailed information for a volume group.
This command requires ``--os-volume-api-version`` 3.13 or greater.
"""
def get_parser(self, prog_name):
parser = super().get_parser(prog_name)
parser.add_argument(
'group',
metavar='<group>',
help=_('Name or ID of volume group.'),
)
parser.add_argument(
'--volumes',
action='store_true',
dest='show_volumes',
default=None,
help=_(
'Show volumes included in the group. '
'(supported by --os-volume-api-version 3.25 or above)'
),
)
parser.add_argument(
'--no-volumes',
action='store_false',
dest='show_volumes',
help=_(
'Do not show volumes included in the group. '
'(supported by --os-volume-api-version 3.25 or above)'
),
)
parser.add_argument(
'--replication-targets',
action='store_true',
dest='show_replication_targets',
default=None,
help=_(
'Show replication targets for the group. '
'(supported by --os-volume-api-version 3.38 or above)'
),
)
parser.add_argument(
'--no-replication-targets',
action='store_false',
dest='show_replication_targets',
help=_(
'Do not show replication targets for the group. '
'(supported by --os-volume-api-version 3.38 or above)'
),
)
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
if volume_client.api_version < api_versions.APIVersion('3.13'):
msg = _(
"--os-volume-api-version 3.13 or greater is required to "
"support the 'volume group show' command"
)
raise exceptions.CommandError(msg)
kwargs = {}
if parsed_args.show_volumes is not None:
if volume_client.api_version < api_versions.APIVersion('3.25'):
msg = _(
"--os-volume-api-version 3.25 or greater is required to "
"support the '--(no-)volumes' option"
)
raise exceptions.CommandError(msg)
kwargs['list_volume'] = parsed_args.show_volumes
if parsed_args.show_replication_targets is not None:
if volume_client.api_version < api_versions.APIVersion('3.38'):
msg = _(
"--os-volume-api-version 3.38 or greater is required to "
"support the '--(no-)replication-targets' option"
)
raise exceptions.CommandError(msg)
group = utils.find_resource(
volume_client.groups,
parsed_args.group,
)
group = volume_client.groups.show(group.id, **kwargs)
if parsed_args.show_replication_targets:
replication_targets = \
volume_client.groups.list_replication_targets(group.id)
group.replication_targets = replication_targets
# TODO(stephenfin): Show replication targets
return _format_group(group)
class FailoverVolumeGroup(command.Command):
"""Failover replication for a volume group.
This command requires ``--os-volume-api-version`` 3.38 or greater.
"""
def get_parser(self, prog_name):
parser = super().get_parser(prog_name)
parser.add_argument(
'group',
metavar='<group>',
help=_('Name or ID of volume group to failover replication for.'),
)
parser.add_argument(
'--allow-attached-volume',
action='store_true',
dest='allow_attached_volume',
default=False,
help=_(
'Allow group with attached volumes to be failed over.',
)
)
parser.add_argument(
'--disallow-attached-volume',
action='store_false',
dest='allow_attached_volume',
default=False,
help=_(
'Disallow group with attached volumes to be failed over.',
)
)
parser.add_argument(
'--secondary-backend-id',
metavar='<backend_id>',
help=_('Secondary backend ID.'),
)
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
if volume_client.api_version < api_versions.APIVersion('3.38'):
msg = _(
"--os-volume-api-version 3.38 or greater is required to "
"support the 'volume group failover' command"
)
raise exceptions.CommandError(msg)
group = utils.find_resource(
volume_client.groups,
parsed_args.group,
)
volume_client.groups.failover_replication(
group.id,
allow_attached_volume=parsed_args.allow_attached_volume,
secondary_backend_id=parsed_args.secondary_backend_id,
)

View File

@ -0,0 +1,8 @@
---
features:
- |
Add ``volume group create``, ``volume group delete``,
``volume group list``, ``volume group failover``,
``volume group set/unset`` and ``volume attachment show``
commands to create, delete, list, failover, update and show volume groups,
respectively.

View File

@ -714,6 +714,13 @@ openstack.volume.v3 =
volume_backup_record_export = openstackclient.volume.v2.backup_record:ExportBackupRecord
volume_backup_record_import = openstackclient.volume.v2.backup_record:ImportBackupRecord
volume_group_create = openstackclient.volume.v3.volume_group:CreateVolumeGroup
volume_group_delete = openstackclient.volume.v3.volume_group:DeleteVolumeGroup
volume_group_list = openstackclient.volume.v3.volume_group:ListVolumeGroup
volume_group_failover = openstackclient.volume.v3.volume_group:FailoverVolumeGroup
volume_group_set = openstackclient.volume.v3.volume_group:SetVolumeGroup
volume_group_show = openstackclient.volume.v3.volume_group:ShowVolumeGroup
volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost
volume_message_delete = openstackclient.volume.v3.volume_message:DeleteMessage