
Add some pagination helpers to configure pagination parameters for various commands. Two pagination schemes are supported, based on what we currently support across OSC commands: marker-based pagination and offset-based pagination. Change-Id: I551bb4c3ff0568c6df5244a1d0f0669497bee58f Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
511 lines
16 KiB
Python
511 lines
16 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 logging
|
|
|
|
from cinderclient import api_versions
|
|
from osc_lib.cli import format_columns
|
|
from osc_lib.command import command
|
|
from osc_lib import exceptions
|
|
from osc_lib import utils
|
|
|
|
from openstackclient.common import pagination
|
|
from openstackclient.i18n import _
|
|
from openstackclient.identity import common as identity_common
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
_FILTER_DEPRECATED = _(
|
|
"This option is deprecated. Consider using the '--filters' option which "
|
|
"was introduced in microversion 3.33 instead."
|
|
)
|
|
|
|
|
|
def _format_attachment(attachment):
|
|
columns = (
|
|
'id',
|
|
'volume_id',
|
|
'instance',
|
|
'status',
|
|
'attach_mode',
|
|
'attached_at',
|
|
'detached_at',
|
|
'connection_info',
|
|
)
|
|
column_headers = (
|
|
'ID',
|
|
'Volume ID',
|
|
'Instance ID',
|
|
'Status',
|
|
'Attach Mode',
|
|
'Attached At',
|
|
'Detached At',
|
|
'Properties',
|
|
)
|
|
|
|
# VolumeAttachmentManager.create returns a dict while everything else
|
|
# returns a VolumeAttachment object
|
|
if isinstance(attachment, dict):
|
|
data = []
|
|
for column in columns:
|
|
if column == 'connection_info':
|
|
data.append(format_columns.DictColumn(attachment[column]))
|
|
continue
|
|
data.append(attachment[column])
|
|
else:
|
|
data = utils.get_item_properties(
|
|
attachment,
|
|
columns,
|
|
formatters={
|
|
'connection_info': format_columns.DictColumn,
|
|
},
|
|
)
|
|
|
|
# TODO(stephenfin): Improve output with the nested connection_info
|
|
# field - cinderclient printed two things but that's equally ugly
|
|
return (column_headers, data)
|
|
|
|
|
|
class CreateVolumeAttachment(command.ShowOne):
|
|
"""Create an attachment for a volume.
|
|
|
|
This command will only create a volume attachment in the Volume service. It
|
|
will not invoke the necessary Compute service actions to actually attach
|
|
the volume to the server at the hypervisor level. As a result, it should
|
|
typically only be used for troubleshooting issues with an existing server
|
|
in combination with other tooling. For all other use cases, the 'server
|
|
add volume' command should be preferred.
|
|
"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super().get_parser(prog_name)
|
|
parser.add_argument(
|
|
'volume',
|
|
metavar='<volume>',
|
|
help=_('Name or ID of volume to attach to server.'),
|
|
)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Name or ID of server to attach volume to.'),
|
|
)
|
|
parser.add_argument(
|
|
'--connect',
|
|
action='store_true',
|
|
dest='connect',
|
|
default=False,
|
|
help=_('Make an active connection using provided connector info'),
|
|
)
|
|
parser.add_argument(
|
|
'--no-connect',
|
|
action='store_false',
|
|
dest='connect',
|
|
help=_(
|
|
'Do not make an active connection using provided connector '
|
|
'info'
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
'--initiator',
|
|
metavar='<initiator>',
|
|
help=_('IQN of the initiator attaching to'),
|
|
)
|
|
parser.add_argument(
|
|
'--ip',
|
|
metavar='<ip>',
|
|
help=_('IP of the system attaching to'),
|
|
)
|
|
parser.add_argument(
|
|
'--host',
|
|
metavar='<host>',
|
|
help=_('Name of the host attaching to'),
|
|
)
|
|
parser.add_argument(
|
|
'--platform',
|
|
metavar='<platform>',
|
|
help=_('Platform type'),
|
|
)
|
|
parser.add_argument(
|
|
'--os-type',
|
|
metavar='<ostype>',
|
|
help=_('OS type'),
|
|
)
|
|
parser.add_argument(
|
|
'--multipath',
|
|
action='store_true',
|
|
dest='multipath',
|
|
default=False,
|
|
help=_('Use multipath'),
|
|
)
|
|
parser.add_argument(
|
|
'--no-multipath',
|
|
action='store_false',
|
|
dest='multipath',
|
|
help=_('Use multipath'),
|
|
)
|
|
parser.add_argument(
|
|
'--mountpoint',
|
|
metavar='<mountpoint>',
|
|
help=_('Mountpoint volume will be attached at'),
|
|
)
|
|
parser.add_argument(
|
|
'--mode',
|
|
metavar='<mode>',
|
|
help=_(
|
|
'Mode of volume attachment, rw, ro and null, where null '
|
|
'indicates we want to honor any existing admin-metadata '
|
|
'settings '
|
|
'(supported by --os-volume-api-version 3.54 or later)'
|
|
),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
volume_client = self.app.client_manager.volume
|
|
compute_client = self.app.client_manager.compute
|
|
|
|
if volume_client.api_version < api_versions.APIVersion('3.27'):
|
|
msg = _(
|
|
"--os-volume-api-version 3.27 or greater is required to "
|
|
"support the 'volume attachment create' command"
|
|
)
|
|
raise exceptions.CommandError(msg)
|
|
|
|
if parsed_args.mode:
|
|
if volume_client.api_version < api_versions.APIVersion('3.54'):
|
|
msg = _(
|
|
"--os-volume-api-version 3.54 or greater is required to "
|
|
"support the '--mode' option"
|
|
)
|
|
raise exceptions.CommandError(msg)
|
|
|
|
connector = {}
|
|
if parsed_args.connect:
|
|
connector = {
|
|
'initiator': parsed_args.initiator,
|
|
'ip': parsed_args.ip,
|
|
'platform': parsed_args.platform,
|
|
'host': parsed_args.host,
|
|
'os_type': parsed_args.os_type,
|
|
'multipath': parsed_args.multipath,
|
|
'mountpoint': parsed_args.mountpoint,
|
|
}
|
|
else:
|
|
if any(
|
|
{
|
|
parsed_args.initiator,
|
|
parsed_args.ip,
|
|
parsed_args.platform,
|
|
parsed_args.host,
|
|
parsed_args.host,
|
|
parsed_args.multipath,
|
|
parsed_args.mountpoint,
|
|
}
|
|
):
|
|
msg = _(
|
|
'You must specify the --connect option for any of the '
|
|
'connection-specific options such as --initiator to be '
|
|
'valid'
|
|
)
|
|
raise exceptions.CommandError(msg)
|
|
|
|
volume = utils.find_resource(
|
|
volume_client.volumes,
|
|
parsed_args.volume,
|
|
)
|
|
server = utils.find_resource(
|
|
compute_client.servers,
|
|
parsed_args.server,
|
|
)
|
|
|
|
attachment = volume_client.attachments.create(
|
|
volume.id, connector, server.id, parsed_args.mode
|
|
)
|
|
|
|
return _format_attachment(attachment)
|
|
|
|
|
|
class DeleteVolumeAttachment(command.Command):
|
|
"""Delete an attachment for a volume.
|
|
|
|
Similarly to the 'volume attachment create' command, this command will only
|
|
delete the volume attachment record in the Volume service. It will not
|
|
invoke the necessary Compute service actions to actually attach the volume
|
|
to the server at the hypervisor level. As a result, it should typically
|
|
only be used for troubleshooting issues with an existing server in
|
|
combination with other tooling. For all other use cases, the 'server volume
|
|
remove' command should be preferred.
|
|
"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super().get_parser(prog_name)
|
|
parser.add_argument(
|
|
'attachment',
|
|
metavar='<attachment>',
|
|
help=_('ID of volume attachment to delete'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
volume_client = self.app.client_manager.volume
|
|
|
|
if volume_client.api_version < api_versions.APIVersion('3.27'):
|
|
msg = _(
|
|
"--os-volume-api-version 3.27 or greater is required to "
|
|
"support the 'volume attachment delete' command"
|
|
)
|
|
raise exceptions.CommandError(msg)
|
|
|
|
volume_client.attachments.delete(parsed_args.attachment)
|
|
|
|
|
|
class SetVolumeAttachment(command.ShowOne):
|
|
"""Update an attachment for a volume.
|
|
|
|
This call is designed to be more of an volume attachment completion than
|
|
anything else. It expects the value of a connector object to notify the
|
|
driver that the volume is going to be connected and where it's being
|
|
connected to.
|
|
"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super().get_parser(prog_name)
|
|
parser.add_argument(
|
|
'attachment',
|
|
metavar='<attachment>',
|
|
help=_('ID of volume attachment.'),
|
|
)
|
|
parser.add_argument(
|
|
'--initiator',
|
|
metavar='<initiator>',
|
|
help=_('IQN of the initiator attaching to'),
|
|
)
|
|
parser.add_argument(
|
|
'--ip',
|
|
metavar='<ip>',
|
|
help=_('IP of the system attaching to'),
|
|
)
|
|
parser.add_argument(
|
|
'--host',
|
|
metavar='<host>',
|
|
help=_('Name of the host attaching to'),
|
|
)
|
|
parser.add_argument(
|
|
'--platform',
|
|
metavar='<platform>',
|
|
help=_('Platform type'),
|
|
)
|
|
parser.add_argument(
|
|
'--os-type',
|
|
metavar='<ostype>',
|
|
help=_('OS type'),
|
|
)
|
|
parser.add_argument(
|
|
'--multipath',
|
|
action='store_true',
|
|
dest='multipath',
|
|
default=False,
|
|
help=_('Use multipath'),
|
|
)
|
|
parser.add_argument(
|
|
'--no-multipath',
|
|
action='store_false',
|
|
dest='multipath',
|
|
help=_('Use multipath'),
|
|
)
|
|
parser.add_argument(
|
|
'--mountpoint',
|
|
metavar='<mountpoint>',
|
|
help=_('Mountpoint volume will be attached at'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
volume_client = self.app.client_manager.volume
|
|
|
|
if volume_client.api_version < api_versions.APIVersion('3.27'):
|
|
msg = _(
|
|
"--os-volume-api-version 3.27 or greater is required to "
|
|
"support the 'volume attachment set' command"
|
|
)
|
|
raise exceptions.CommandError(msg)
|
|
|
|
connector = {
|
|
'initiator': parsed_args.initiator,
|
|
'ip': parsed_args.ip,
|
|
'platform': parsed_args.platform,
|
|
'host': parsed_args.host,
|
|
'os_type': parsed_args.os_type,
|
|
'multipath': parsed_args.multipath,
|
|
'mountpoint': parsed_args.mountpoint,
|
|
}
|
|
|
|
attachment = volume_client.attachments.update(
|
|
parsed_args.attachment, connector
|
|
)
|
|
|
|
return _format_attachment(attachment)
|
|
|
|
|
|
class CompleteVolumeAttachment(command.Command):
|
|
"""Complete an attachment for a volume."""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super().get_parser(prog_name)
|
|
parser.add_argument(
|
|
'attachment',
|
|
metavar='<attachment>',
|
|
help=_('ID of volume attachment to mark as completed'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
volume_client = self.app.client_manager.volume
|
|
|
|
if volume_client.api_version < api_versions.APIVersion('3.44'):
|
|
msg = _(
|
|
"--os-volume-api-version 3.44 or greater is required to "
|
|
"support the 'volume attachment complete' command"
|
|
)
|
|
raise exceptions.CommandError(msg)
|
|
|
|
volume_client.attachments.complete(parsed_args.attachment)
|
|
|
|
|
|
class ListVolumeAttachment(command.Lister):
|
|
"""Lists all volume attachments."""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super().get_parser(prog_name)
|
|
parser.add_argument(
|
|
'--project',
|
|
dest='project',
|
|
metavar='<project>',
|
|
help=_('Filter results by project (name or ID) (admin only)'),
|
|
)
|
|
identity_common.add_project_domain_option_to_parser(parser)
|
|
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).'),
|
|
)
|
|
parser.add_argument(
|
|
'--volume-id',
|
|
metavar='<volume-id>',
|
|
default=None,
|
|
help=_('Filters results by a volume ID. ') + _FILTER_DEPRECATED,
|
|
)
|
|
parser.add_argument(
|
|
'--status',
|
|
metavar='<status>',
|
|
help=_('Filters results by a status. ') + _FILTER_DEPRECATED,
|
|
)
|
|
pagination.add_marker_pagination_option_to_parser(parser)
|
|
# 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
|
|
identity_client = self.app.client_manager.identity
|
|
|
|
if volume_client.api_version < api_versions.APIVersion('3.27'):
|
|
msg = _(
|
|
"--os-volume-api-version 3.27 or greater is required to "
|
|
"support the 'volume attachment list' command"
|
|
)
|
|
raise exceptions.CommandError(msg)
|
|
|
|
project_id = None
|
|
if parsed_args.project:
|
|
project_id = identity_common.find_project(
|
|
identity_client,
|
|
parsed_args.project,
|
|
parsed_args.project_domain,
|
|
).id
|
|
|
|
search_opts = {
|
|
'all_tenants': True if project_id else parsed_args.all_projects,
|
|
'project_id': project_id,
|
|
'status': parsed_args.status,
|
|
'volume_id': parsed_args.volume_id,
|
|
}
|
|
# Update search option with `filters`
|
|
# if AppendFilters.filters:
|
|
# search_opts.update(shell_utils.extract_filters(AppendFilters.filters))
|
|
|
|
# TODO(stephenfin): Implement sorting
|
|
attachments = volume_client.attachments.list(
|
|
search_opts=search_opts,
|
|
marker=parsed_args.marker,
|
|
limit=parsed_args.limit,
|
|
)
|
|
|
|
column_headers = (
|
|
'ID',
|
|
'Volume ID',
|
|
'Server ID',
|
|
'Status',
|
|
)
|
|
columns = (
|
|
'id',
|
|
'volume_id',
|
|
'instance',
|
|
'status',
|
|
)
|
|
|
|
return (
|
|
column_headers,
|
|
(utils.get_item_properties(a, columns) for a in attachments),
|
|
)
|
|
|
|
|
|
class ShowVolumeAttachment(command.ShowOne):
|
|
"""Show detailed information for a volume attachment."""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super().get_parser(prog_name)
|
|
parser.add_argument(
|
|
'attachment',
|
|
metavar='<attachment>',
|
|
help=_('ID of volume attachment.'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
volume_client = self.app.client_manager.volume
|
|
|
|
if volume_client.api_version < api_versions.APIVersion('3.27'):
|
|
msg = _(
|
|
"--os-volume-api-version 3.27 or greater is required to "
|
|
"support the 'volume attachment show' command"
|
|
)
|
|
raise exceptions.CommandError(msg)
|
|
|
|
attachment = volume_client.attachments.show(parsed_args.attachment)
|
|
|
|
return _format_attachment(attachment)
|