
Black used with the '-l 79 -S' flags. A future change will ignore this commit in git-blame history by adding a 'git-blame-ignore-revs' file. Change-Id: Ic318617c67ab7ce6527f9016b759a1d4b0b80802 Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
625 lines
21 KiB
Python
625 lines
21 KiB
Python
# 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 argparse
|
|
|
|
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 _
|
|
|
|
|
|
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)
|
|
# This is a bit complicated. We accept two patterns: a legacy pattern
|
|
#
|
|
# volume group create \
|
|
# <volume-group-type> <volume-type> [<volume-type>...]
|
|
#
|
|
# and the modern approach
|
|
#
|
|
# volume group create \
|
|
# --volume-group-type <volume-group-type>
|
|
# --volume-type <volume-type>
|
|
# [--volume-type <volume-type> ...]
|
|
#
|
|
# Because argparse doesn't properly support nested exclusive groups, we
|
|
# use two groups: one to ensure users don't pass <volume-group-type> as
|
|
# both a positional and an option argument and another to ensure users
|
|
# don't pass <volume-type> this way. It's a bit weird but it catches
|
|
# everything we care about.
|
|
source_parser = parser.add_mutually_exclusive_group()
|
|
# we use a different name purely so we can issue a deprecation warning
|
|
source_parser.add_argument(
|
|
'volume_group_type_legacy',
|
|
metavar='<volume_group_type>',
|
|
nargs='?',
|
|
help=argparse.SUPPRESS,
|
|
)
|
|
volume_types_parser = parser.add_mutually_exclusive_group()
|
|
# We need to use a separate dest
|
|
# https://github.com/python/cpython/issues/101990
|
|
volume_types_parser.add_argument(
|
|
'volume_types_legacy',
|
|
metavar='<volume_type>',
|
|
nargs='*',
|
|
default=[],
|
|
help=argparse.SUPPRESS,
|
|
)
|
|
source_parser.add_argument(
|
|
'--volume-group-type',
|
|
metavar='<volume_group_type>',
|
|
help=_('Volume group type to use (name or ID)'),
|
|
)
|
|
volume_types_parser.add_argument(
|
|
'--volume-type',
|
|
metavar='<volume_type>',
|
|
dest='volume_types',
|
|
action='append',
|
|
default=[],
|
|
help=_(
|
|
'Volume type(s) to use (name or ID) '
|
|
'(required with --volume-group-type)'
|
|
),
|
|
)
|
|
source_parser.add_argument(
|
|
'--source-group',
|
|
metavar='<source-group>',
|
|
help=_(
|
|
'Existing volume group to use (name or ID) '
|
|
'(supported by --os-volume-api-version 3.14 or later)'
|
|
),
|
|
)
|
|
source_parser.add_argument(
|
|
'--group-snapshot',
|
|
metavar='<group-snapshot>',
|
|
help=_(
|
|
'Existing group snapshot to use (name or ID) '
|
|
'(supported by --os-volume-api-version 3.14 or later)'
|
|
),
|
|
)
|
|
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. '
|
|
'(not available if creating group from source)'
|
|
),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
volume_client = self.app.client_manager.volume
|
|
|
|
if parsed_args.volume_group_type_legacy:
|
|
msg = _(
|
|
"Passing volume group type and volume types as positional "
|
|
"arguments is deprecated. Use the --volume-group-type and "
|
|
"--volume-type option arguments instead."
|
|
)
|
|
self.log.warning(msg)
|
|
|
|
volume_group_type = (
|
|
parsed_args.volume_group_type
|
|
or parsed_args.volume_group_type_legacy
|
|
)
|
|
volume_types = parsed_args.volume_types[:]
|
|
volume_types.extend(parsed_args.volume_types_legacy)
|
|
|
|
if volume_group_type:
|
|
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)
|
|
if not volume_types:
|
|
msg = _(
|
|
"--volume-types is a required argument when creating a "
|
|
"group from group type."
|
|
)
|
|
raise exceptions.CommandError(msg)
|
|
|
|
volume_group_type_id = utils.find_resource(
|
|
volume_client.group_types,
|
|
volume_group_type,
|
|
).id
|
|
volume_types_ids = []
|
|
for volume_type in volume_types:
|
|
volume_types_ids.append(
|
|
utils.find_resource(
|
|
volume_client.volume_types,
|
|
volume_type,
|
|
).id
|
|
)
|
|
|
|
group = volume_client.groups.create(
|
|
volume_group_type_id,
|
|
','.join(volume_types_ids),
|
|
parsed_args.name,
|
|
parsed_args.description,
|
|
availability_zone=parsed_args.availability_zone,
|
|
)
|
|
|
|
group = volume_client.groups.get(group.id)
|
|
return _format_group(group)
|
|
|
|
else:
|
|
if volume_client.api_version < api_versions.APIVersion('3.14'):
|
|
msg = _(
|
|
"--os-volume-api-version 3.14 or greater is required to "
|
|
"support the 'volume group create "
|
|
"[--source-group|--group-snapshot]' command"
|
|
)
|
|
raise exceptions.CommandError(msg)
|
|
if (
|
|
parsed_args.source_group is None
|
|
and parsed_args.group_snapshot is None
|
|
):
|
|
msg = _(
|
|
"Either --source-group <source_group> or "
|
|
"'--group-snapshot <group_snapshot>' needs to be "
|
|
"provided to run the 'volume group create "
|
|
"[--source-group|--group-snapshot]' command"
|
|
)
|
|
raise exceptions.CommandError(msg)
|
|
if parsed_args.availability_zone:
|
|
msg = _(
|
|
"'--availability-zone' option will not work "
|
|
"if creating group from source."
|
|
)
|
|
self.log.warning(msg)
|
|
|
|
source_group = None
|
|
if parsed_args.source_group:
|
|
source_group = utils.find_resource(
|
|
volume_client.groups, parsed_args.source_group
|
|
)
|
|
group_snapshot = None
|
|
if parsed_args.group_snapshot:
|
|
group_snapshot = utils.find_resource(
|
|
volume_client.group_snapshots, parsed_args.group_snapshot
|
|
)
|
|
group = volume_client.groups.create_from_src(
|
|
group_snapshot.id if group_snapshot else None,
|
|
source_group.id if source_group else None,
|
|
parsed_args.name,
|
|
parsed_args.description,
|
|
)
|
|
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,
|
|
)
|