# Copyright 2013 OpenStack Foundation
# 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 operator import xor
import os
import re
import sys
import time

from oslo_utils import strutils

from manilaclient import api_versions
from manilaclient.common.apiclient import utils as apiclient_utils
from manilaclient.common import cliutils
from manilaclient.common import constants
from manilaclient import exceptions
import manilaclient.v2.shares


def _wait_for_resource_status(cs,
                              resource,
                              expected_status,
                              resource_type='share',
                              status_attr='status',
                              poll_timeout=900,
                              poll_interval=2):
    """Waiter for resource status changes

    :param cs: command shell control
    :param expected_status: a string or a list of strings containing expected
       states to wait for
    :param resource_type: 'share', 'snapshot', 'share_replica', 'share_group',
       or 'share_group_snapshot'
    :param status_attr: 'status', 'task_state', 'access_rules_status' or any
       other status field that is expected to have the "expected_status"
    :param poll_timeout: how long to wait for in seconds
    :param poll_interval: how often to try in seconds
    """
    find_resource = {
        'share': _find_share,
        'snapshot': _find_share_snapshot,
        'share_replica': _find_share_replica,
        'share_group': _find_share_group,
        'share_group_snapshot': _find_share_group_snapshot,
        'share_instance': _find_share_instance,
        'share_server': _find_share_server,
        'share_access_rule': _find_share_access_rule,
    }

    print_resource = {
        'share': _print_share,
        'snapshot': _print_share_snapshot,
        'share_replica': _print_share_replica,
        'share_group': _print_share_group,
        'share_group_snapshot': _print_share_group_snapshot,
        'share_instance': _print_share_instance,
        'share_access_rule': _print_share_access_rule,
    }

    expected_status = expected_status or ('available', )
    if not isinstance(expected_status, (list, tuple, set)):
        expected_status = (expected_status, )

    time_elapsed = 0
    timeout_message = ("%(resource_type)s %(resource)s did not reach "
                       "%(expected_states)s within %(seconds)d seconds.")
    error_message = ("%(resource_type)s %(resource)s has reached a failed "
                     "state.")
    deleted_message = ("%(resource_type)s %(resource)s has been successfully "
                       "deleted.")
    unmanaged_message = ("%(resource_type)s %(resource)s has been "
                         "successfully unmanaged.")
    message_payload = {
        'resource_type': resource_type.capitalize(),
        'resource': resource.id,
    }
    not_found_regex = "no .* exists"
    while True:
        if time_elapsed > poll_timeout:
            print_resource[resource_type](cs, resource)
            message_payload.update({'expected_states': expected_status,
                                    'seconds': poll_timeout})
            raise exceptions.TimeoutException(
                message=timeout_message % message_payload)
        try:
            resource = find_resource[resource_type](cs, resource.id)
        except exceptions.CommandError as e:
            if (re.search(not_found_regex, str(e), flags=re.IGNORECASE)):
                if 'deleted' in expected_status:
                    print(deleted_message % message_payload)
                    break
                if 'unmanaged' in expected_status:
                    print(unmanaged_message % message_payload)
                    break
            else:
                raise e

        if getattr(resource, status_attr) in expected_status:
            break
        elif 'error' in getattr(resource, status_attr):
            print_resource[resource_type](cs, resource)
            raise exceptions.ResourceInErrorState(
                message=error_message % message_payload)
        time.sleep(poll_interval)
        time_elapsed += poll_interval

    return resource


def _find_share(cs, share):
    """Get a share by ID."""
    return apiclient_utils.find_resource(cs.shares, share)


@api_versions.wraps("1.0", "2.8")
def _print_share(cs, share):
    info = share._info.copy()
    info.pop('links', None)

    # NOTE(vponomaryov): remove deprecated single field 'export_location' and
    # leave only list field 'export_locations'. Also, transform the latter to
    # text with new line separators to make it pretty in CLI.
    # It will look like following:
    # +-------------------+--------------------------------------------+
    # | Property          | Value                                      |
    # +-------------------+--------------------------------------------+
    # | status            | available                                  |
    # | export_locations  | 1.2.3.4:/f/o/o                             |
    # |                   | 5.6.7.8:/b/a/r                             |
    # |                   | 9.10.11.12:/q/u/u/z                        |
    # | id                | d778d2ee-b6bb-4c5f-9f5d-6f3057d549b1       |
    # | size              | 1                                          |
    # | share_proto       | NFS                                        |
    # +-------------------+--------------------------------------------+
    if info.get('export_locations'):
        info.pop('export_location', None)
        info['export_locations'] = "\n".join(info['export_locations'])

    # No need to print both volume_type and share_type to CLI
    if 'volume_type' in info and 'share_type' in info:
        info.pop('volume_type', None)

    cliutils.print_dict(info)


@api_versions.wraps("2.9")  # noqa
def _print_share(cs, share):  # noqa
    info = share._info.copy()
    info.pop('links', None)

    # NOTE(vponomaryov): remove deprecated single field 'export_location' and
    # leave only list field 'export_locations'. Also, transform the latter to
    # text with new line separators to make it pretty in CLI.
    # It will look like following:
    # +-------------------+--------------------------------------------+
    # | Property          | Value                                      |
    # +-------------------+--------------------------------------------+
    # | status            | available                                  |
    # | export_locations  |                                            |
    # |                   | uuid = FOO-UUID                            |
    # |                   | path = 5.6.7.8:/foo/export/location/path   |
    # |                   |                                            |
    # |                   | uuid = BAR-UUID                            |
    # |                   | path = 5.6.7.8:/bar/export/location/path   |
    # |                   |                                            |
    # | id                | d778d2ee-b6bb-4c5f-9f5d-6f3057d549b1       |
    # | size              | 1                                          |
    # | share_proto       | NFS                                        |
    # +-------------------+--------------------------------------------+
    if info.get('export_locations'):
        info['export_locations'] = (
            cliutils.convert_dict_list_to_string(
                info['export_locations'],
                ignored_keys=['replica_state',
                              'availability_zone',
                              'share_replica_id'])
        )

    # No need to print both volume_type and share_type to CLI
    if 'volume_type' in info and 'share_type' in info:
        info.pop('volume_type', None)

    cliutils.print_dict(info)


def _wait_for_share_status(cs, share, expected_status='available'):
    return _wait_for_resource_status(
        cs, share, expected_status, resource_type='share')


def _find_share_instance(cs, instance):
    """Get a share instance by ID."""
    return apiclient_utils.find_resource(cs.share_instances, instance)


def _print_type_show(stype, default_share_type=None):

    if hasattr(stype, 'is_default'):
        is_default = 'YES' if stype.is_default else 'NO'
    elif default_share_type:
        is_default = 'YES' if stype.id == default_share_type.id else 'NO'
    else:
        is_default = 'NO'

    stype_dict = {
        'id': stype.id,
        'name': stype.name,
        'visibility': _is_share_type_public(stype),
        'is_default': is_default,
        'description': stype.description,
        'required_extra_specs': _print_type_required_extra_specs(stype),
        'optional_extra_specs': _print_type_optional_extra_specs(stype),
    }
    cliutils.print_dict(stype_dict)


@api_versions.wraps("1.0", "2.8")
def _print_share_instance(cs, instance):
    info = instance._info.copy()
    info.pop('links', None)
    cliutils.print_dict(info)


@api_versions.wraps("2.9")  # noqa
def _print_share_instance(cs, instance):  # noqa
    info = instance._info.copy()
    info.pop('links', None)
    if info.get('export_locations'):
        info['export_locations'] = (
            cliutils.convert_dict_list_to_string(
                info['export_locations'],
                ignored_keys=['replica_state',
                              'availability_zone',
                              'share_replica_id'])
        )

    cliutils.print_dict(info)


def _find_share_access_rule(cs, access_rule):
    """Get share access rule state"""
    return apiclient_utils.find_resource(cs.share_access_rules, access_rule)


def _print_share_access_rule(cs, access_rule):
    info = access_rule._info.copy()

    cliutils.print_dict(info)


def _find_share_replica(cs, replica):
    """Get a replica by ID."""
    return apiclient_utils.find_resource(cs.share_replicas, replica)


@api_versions.wraps("2.11", "2.46")
def _print_share_replica(cs, replica):
    info = replica._info.copy()
    info.pop('links', None)
    cliutils.print_dict(info)


@api_versions.wraps("2.47")  # noqa
def _print_share_replica(cs, replica):  # noqa
    info = replica._info.copy()
    info.pop('links', None)
    if info.get('export_locations'):
        info['export_locations'] = (
            cliutils.convert_dict_list_to_string(
                info['export_locations'],
                ignored_keys=['replica_state', 'availability_zone',
                              'share_replica_id']))
    cliutils.print_dict(info)


@api_versions.wraps("2.31")
def _find_share_group(cs, share_group):
    """Get a share group ID."""
    return apiclient_utils.find_resource(cs.share_groups, share_group)


def _print_share_group(cs, share_group):
    info = share_group._info.copy()
    info.pop('links', None)

    if info.get('share_types'):
        info['share_types'] = "\n".join(info['share_types'])

    cliutils.print_dict(info)


@api_versions.wraps("2.31")
def _find_share_group_snapshot(cs, share_group_snapshot):
    """Get a share group snapshot by name or ID."""
    return apiclient_utils.find_resource(
        cs.share_group_snapshots, share_group_snapshot)


def _print_share_group_snapshot(cs, share_group_snapshot):
    info = share_group_snapshot._info.copy()
    info.pop('links', None)
    info.pop('members', None)
    cliutils.print_dict(info)


def _print_share_group_snapshot_members(cs, share_group_snapshot):
    info = share_group_snapshot._info.copy()
    cliutils.print_dict(info.get('members', {}))


def _find_share_snapshot(cs, snapshot):
    """Get a snapshot by ID."""
    return apiclient_utils.find_resource(cs.share_snapshots, snapshot)


def _print_share_snapshot(cs, snapshot):
    info = snapshot._info.copy()
    info.pop('links', None)

    if info.get('export_locations'):
        info['export_locations'] = (
            cliutils.convert_dict_list_to_string(
                info['export_locations']))

    cliutils.print_dict(info)


def _quota_set_pretty_show(quotas):
    """Convert quotas object to dict and display."""

    new_quotas = {}
    for quota_k, quota_v in sorted(quotas.to_dict().items()):
        if isinstance(quota_v, dict):
            quota_v = '\n'.join(
                ['%s = %s' % (k, v) for k, v in sorted(quota_v.items())])
        new_quotas[quota_k] = quota_v

    cliutils.print_dict(new_quotas)


def _find_share_snapshot_instance(cs, snapshot_instance):
    """Get a share snapshot instance by ID."""
    return apiclient_utils.find_resource(
        cs.share_snapshot_instances, snapshot_instance)


def _find_share_network(cs, share_network):
    """Get a share network by ID or name."""
    return apiclient_utils.find_resource(cs.share_networks, share_network)


def _find_security_service(cs, security_service):
    """Get a security service by ID or name."""
    return apiclient_utils.find_resource(cs.security_services,
                                         security_service)


def _find_share_server(cs, share_server):
    """Get a share server by ID."""
    return apiclient_utils.find_resource(cs.share_servers, share_server)


def _find_message(cs, message):
    """Get a message by ID."""
    return apiclient_utils.find_resource(cs.messages, message)


def _translate_keys(collection, convert):
    for item in collection:
        keys = item.__dict__
        for from_key, to_key in convert:
            if from_key in keys and to_key not in keys:
                setattr(item, to_key, item._info[from_key])


def _extract_metadata(args):
    return _extract_key_value_options(args, 'metadata')


def _extract_extra_specs(args):
    return _extract_key_value_options(args, 'extra_specs')


def _extract_group_specs(args):
    return _extract_key_value_options(args, 'group_specs')


def _extract_key_value_options(args, option_name):
    result_dict = {}
    duplicate_options = []

    options = getattr(args, option_name, None)

    if options:
        for option in options:
            # unset doesn't require a val, so we have the if/else
            if '=' in option:
                (key, value) = option.split('=', 1)
            else:
                key = option
                value = None

            if key not in result_dict:
                result_dict[key] = value
            else:
                duplicate_options.append(key)

        if len(duplicate_options) > 0:
            duplicate_str = ', '.join(duplicate_options)
            msg = "Following options were duplicated: %s" % duplicate_str
            raise exceptions.CommandError(msg)
    return result_dict


def _split_columns(columns, title=True):
    if title:
        list_of_keys = list(map(lambda x: x.strip().title(),
                                columns.split(",")))
    else:
        list_of_keys = list(map(lambda x: x.strip().lower(),
                                columns.split(",")))
    return list_of_keys


@api_versions.wraps("2.0")
def do_api_version(cs, args):
    """Display the API version information."""
    columns = ['ID', 'Status', 'Version', 'Min_version']
    column_labels = ['ID', 'Status', 'Version', 'Minimum Version']
    response = cs.services.server_api_version()
    cliutils.print_list(response, columns, field_labels=column_labels)


def do_endpoints(cs, args):
    """Discover endpoints that get returned from the authenticate services."""
    catalog = cs.keystone_client.service_catalog.catalog
    for e in catalog.get('serviceCatalog', catalog.get('catalog')):
        cliutils.print_dict(e['endpoints'][0], e['name'])


def do_credentials(cs, args):
    """Show user credentials returned from auth."""
    catalog = cs.keystone_client.service_catalog.catalog
    cliutils.print_dict(catalog['user'], "User Credentials")
    if not catalog['version'] == 'v3':
        data = catalog['token']
    else:
        data = {
            'issued_at': catalog['issued_at'],
            'expires': catalog['expires_at'],
            'id': catalog['auth_token'],
            'audit_ids': catalog['audit_ids'],
            'tenant': catalog['project'],
        }
    cliutils.print_dict(data, "Token")


_quota_resources = [
    'shares',
    'snapshots',
    'gigabytes',
    'snapshot_gigabytes',
    'share_networks',
    'share_replicas',
    'replica_gigabytes',
    'per_share_gigabytes',
    'share_groups',
    'share_group_snapshots'
]


def _quota_class_update(manager, identifier, args):
    updates = {}
    for resource in _quota_resources:
        val = getattr(args, resource, None)
        if val is not None:
            updates[resource] = val

    if updates:
        manager.update(identifier, **updates)


@cliutils.arg(
    '--tenant-id', '--tenant',
    '--project', '--project-id',
    action='single_alias',
    dest='project_id',
    metavar='<project-id>',
    default=None,
    help='ID of project to list the quotas for.')
@cliutils.arg(
    '--user-id',
    metavar='<user-id>',
    default=None,
    help="ID of user to list the quotas for. Optional. "
         "Mutually exclusive with '--share-type'.")
@cliutils.arg(
    '--share-type',
    '--share_type',
    metavar='<share-type>',
    type=str,
    default=None,
    action='single_alias',
    help="UUID or name of a share type to set the quotas for. Optional. "
         "Mutually exclusive with '--user-id'. "
         "Available only for microversion >= 2.39")
@cliutils.arg(
    '--detail',
    action='store_true',
    help='Optional flag to indicate whether to show quota in detail. '
         'Default false, available only for microversion >= 2.25.')
@api_versions.wraps("1.0")
def do_quota_show(cs, args):
    """List the quotas for a project, user or share type."""
    project_id = args.project_id or cs.keystone_client.project_id
    kwargs = {
        "tenant_id": project_id,
        "user_id": args.user_id,
        "detail": args.detail,
    }
    if args.share_type is not None:
        if cs.api_version < api_versions.APIVersion("2.39"):
            raise exceptions.CommandError(
                "'share type' quotas are available only starting with "
                "'2.39' API microversion.")
        kwargs["share_type"] = args.share_type
    _quota_set_pretty_show(cs.quotas.get(**kwargs))


@cliutils.arg(
    '--tenant-id', '--tenant',
    '--project', '--project-id',
    action='single_alias',
    dest='project_id',
    metavar='<project-id>',
    default=None,
    help='ID of the project to list the default quotas for.')
def do_quota_defaults(cs, args):
    """List the default quotas for a project."""
    project = args.project_id or cs.keystone_client.project_id
    _quota_set_pretty_show(cs.quotas.defaults(project))


@cliutils.arg(
    'project_id',
    metavar='<project-id>',
    help='UUID of project to set the quotas for.')
@cliutils.arg(
    '--user-id',
    metavar='<user-id>',
    default=None,
    help="ID of a user to set the quotas for. Optional. "
         "Mutually exclusive with '--share-type'.")
@cliutils.arg(
    '--shares',
    metavar='<shares>',
    type=int,
    default=None,
    help='New value for the "shares" quota.')
@cliutils.arg(
    '--snapshots',
    metavar='<snapshots>',
    type=int,
    default=None,
    help='New value for the "snapshots" quota.')
@cliutils.arg(
    '--gigabytes',
    metavar='<gigabytes>',
    type=int,
    default=None,
    help='New value for the "gigabytes" quota.')
@cliutils.arg(
    '--snapshot-gigabytes',
    '--snapshot_gigabytes',  # alias
    metavar='<snapshot_gigabytes>',
    type=int,
    default=None,
    action='single_alias',
    help='New value for the "snapshot_gigabytes" quota.')
@cliutils.arg(
    '--share-networks',
    '--share_networks',
    metavar='<share-networks>',
    type=int,
    default=None,
    action='single_alias',
    help='New value for the "share_networks" quota.')
@cliutils.arg(
    '--share-groups', '--share_groups', '--groups',
    metavar='<share_groups>',
    type=int,
    default=None,
    action='single_alias',
    help='New value for the "share_groups" quota.')
@cliutils.arg(
    '--share-group-snapshots', '--share_group_snapshots',
    '--group-snapshots', '--group_snapshots',
    metavar='<share_group_snapshots>',
    type=int,
    default=None,
    action='single_alias',
    help='New value for the "share_group_snapshots" quota.')
@cliutils.arg(
    '--share-type',
    '--share_type',
    metavar='<share-type>',
    type=str,
    default=None,
    action='single_alias',
    help="UUID or name of a share type to set the quotas for. Optional. "
         "Mutually exclusive with '--user-id'. "
         "Available only for microversion >= 2.39")
@cliutils.arg(
    '--share-replicas',
    '--share_replicas',
    '--replicas',
    metavar='<share-replicas>',
    type=int,
    default=None,
    help='New value for the "share_replicas" quota. Available only for '
         'microversion >= 2.53')
@cliutils.arg(
    '--replica-gigabytes',
    '--replica_gigabytes',
    metavar='<replica-gigabytes>',
    type=int,
    default=None,
    help='New value for the "replica_gigabytes" quota. Available only for '
         'microversion >= 2.53')
@cliutils.arg(
    '--per-share-gigabytes',
    '--per_share_gigabytes',
    metavar='<per-share-gigabytes>',
    type=int,
    default=None,
    help='New value for the "per_share_gigabytes" quota. Available only for '
         'microversion >= 2.62')
@cliutils.arg(
    '--force',
    dest='force',
    action="store_true",
    default=None,
    help='Whether force update the quota even if the already used '
         'and reserved exceeds the new quota.')
@api_versions.wraps("1.0")
def do_quota_update(cs, args):
    """Update the quotas for a project/user and/or share type (Admin only)."""
    kwargs = {
        "tenant_id": args.project_id,
        "user_id": args.user_id,
        "shares": args.shares,
        "gigabytes": args.gigabytes,
        "snapshots": args.snapshots,
        "snapshot_gigabytes": args.snapshot_gigabytes,
        "share_networks": args.share_networks,
        "force": args.force,
    }
    if args.share_type is not None:
        if cs.api_version < api_versions.APIVersion("2.39"):
            raise exceptions.CommandError(
                "'share type' quotas are available only starting with "
                "'2.39' API microversion.")
        kwargs["share_type"] = args.share_type
    if args.share_groups is not None or args.share_group_snapshots is not None:
        if cs.api_version < api_versions.APIVersion("2.40"):
            raise exceptions.CommandError(
                "'share group' quotas are available only starting with "
                "'2.40' API microversion.")
        elif args.share_type is not None:
            raise exceptions.CommandError(
                "Share type quotas cannot be used to constrain share groups.")
        kwargs["share_groups"] = args.share_groups
        kwargs["share_group_snapshots"] = args.share_group_snapshots
    if args.share_replicas is not None or args.replica_gigabytes is not None:
        if cs.api_version < api_versions.APIVersion("2.53"):
            raise exceptions.CommandError(
                "'share replica' quotas are available only starting with "
                "'2.53' API microversion.")
        kwargs["share_replicas"] = args.share_replicas
        kwargs["replica_gigabytes"] = args.replica_gigabytes
    if args.per_share_gigabytes is not None:
        if cs.api_version < api_versions.APIVersion("2.62"):
            raise exceptions.CommandError(
                "'per share gigabytes' quotas are available only starting "
                "with '2.62' API microversion.")
        kwargs["per_share_gigabytes"] = args.per_share_gigabytes

    cs.quotas.update(**kwargs)


@cliutils.arg(
    '--tenant-id', '--tenant',
    '--project', '--project-id',
    action='single_alias',
    dest='project_id',
    metavar='<project-id>',
    help='ID of the project to delete quota for.')
@cliutils.arg(
    '--user-id',
    metavar='<user-id>',
    help="ID of user to delete quota for. Optional."
         "Mutually exclusive with '--share-type'.")
@cliutils.arg(
    '--share-type',
    '--share_type',
    metavar='<share-type>',
    type=str,
    default=None,
    action='single_alias',
    help="UUID or name of a share type to set the quotas for. Optional. "
         "Mutually exclusive with '--user-id'. "
         "Available only for microversion >= 2.39")
@api_versions.wraps("1.0")
def do_quota_delete(cs, args):
    """Delete quota for a project, or project/user or project/share-type.

    The quota will revert back to default (Admin only).
    """
    project_id = args.project_id or cs.keystone_client.project_id
    kwargs = {
        "tenant_id": project_id,
        "user_id": args.user_id,
    }
    if args.share_type is not None:
        if cs.api_version < api_versions.APIVersion("2.39"):
            raise exceptions.CommandError(
                "'share type' quotas are available only starting with "
                "'2.39' API microversion.")
        kwargs["share_type"] = args.share_type

    cs.quotas.delete(**kwargs)


@cliutils.arg(
    'class_name',
    metavar='<class>',
    help='Name of quota class to list the quotas for.')
def do_quota_class_show(cs, args):
    """List the quotas for a quota class."""

    _quota_set_pretty_show(cs.quota_classes.get(args.class_name))


@cliutils.arg(
    'class_name',
    metavar='<class-name>',
    help='Name of quota class to set the quotas for.')
@cliutils.arg(
    '--shares',
    metavar='<shares>',
    type=int,
    default=None,
    help='New value for the "shares" quota.')
@cliutils.arg(
    '--snapshots',
    metavar='<snapshots>',
    type=int,
    default=None,
    help='New value for the "snapshots" quota.')
@cliutils.arg(
    '--gigabytes',
    metavar='<gigabytes>',
    type=int,
    default=None,
    help='New value for the "gigabytes" quota.')
@cliutils.arg(
    '--snapshot-gigabytes',
    '--snapshot_gigabytes',  # alias
    metavar='<snapshot_gigabytes>',
    type=int,
    default=None,
    action='single_alias',
    help='New value for the "snapshot_gigabytes" quota.')
@cliutils.arg(
    '--share-networks',
    '--share_networks',  # alias
    metavar='<share-networks>',
    type=int,
    default=None,
    action='single_alias',
    help='New value for the "share_networks" quota.')
@cliutils.arg(
    '--share-groups',
    '--share_groups',  # alias
    metavar='<share-groups>',
    type=int,
    default=None,
    action='single_alias',
    help='New value for the "share_groups" quota. Available only for '
         'microversion >= 2.40')
@cliutils.arg(
    '--share-group-snapshots',
    '--share_group_snapshots',
    metavar='<share-group-snapshots>',
    type=int,
    default=None,
    action='single_alias',
    help='New value for the "share_group_snapshots" quota. Available only for '
         'microversion >= 2.40')
@cliutils.arg(
    '--share-replicas',
    '--share_replicas',  # alias
    '--replicas',  # alias
    metavar='<share-replicas>',
    type=int,
    default=None,
    action='single_alias',
    help='New value for the "share_replicas" quota. Available only for '
         'microversion >= 2.53')
@cliutils.arg(
    '--replica-gigabytes',
    '--replica_gigabytes',  # alias
    metavar='<replica-gigabytes>',
    type=int,
    default=None,
    action='single_alias',
    help='New value for the "replica_gigabytes" quota. Available only for '
         'microversion >= 2.53')
@cliutils.arg(
    '--per-share-gigabytes',
    '--per_share_gigabytes',  # alias
    metavar='<per-share-gigabytes>',
    type=int,
    default=None,
    action='single_alias',
    help='New value for the "per_share_gigabytes" quota. Available only for '
         'microversion >= 2.62')
def do_quota_class_update(cs, args):
    """Update the quotas for a quota class (Admin only)."""
    if args.share_groups is not None or args.share_group_snapshots is not None:
        if cs.api_version < api_versions.APIVersion("2.40"):
            raise exceptions.CommandError(
                "'share groups' quotas are available only starting with "
                "'2.40' API microversion.")
    if args.share_replicas is not None or args.replica_gigabytes is not None:
        if cs.api_version < api_versions.APIVersion("2.53"):
            raise exceptions.CommandError(
                "'share replica' quotas are available only starting with "
                "'2.53' API microversion.")
    if args.per_share_gigabytes is not None:
        if cs.api_version < api_versions.APIVersion("2.62"):
            raise exceptions.CommandError(
                "'per_share_gigabytes' quota is available only starting "
                "with '2.62' API microversion.")

    _quota_class_update(cs.quota_classes, args.class_name, args)


def do_absolute_limits(cs, args):
    """Print a list of absolute limits for a user."""
    limits = cs.limits.get().absolute
    columns = ['Name', 'Value']
    cliutils.print_list(limits, columns)


@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "verb,uri,value".')
def do_rate_limits(cs, args):
    """Print a list of rate limits for a user."""
    limits = cs.limits.get().rate
    columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available']

    if args.columns is not None:
        columns = _split_columns(columns=args.columns)

    cliutils.print_list(limits, columns)


@cliutils.arg(
    'share_protocol',
    metavar='<share_protocol>',
    type=str,
    help='Share protocol (NFS, CIFS, CephFS, GlusterFS, HDFS or MAPRFS).')
@cliutils.arg(
    'size',
    metavar='<size>',
    type=int,
    help='Share size in GiB.')
@cliutils.arg(
    '--snapshot-id',
    '--snapshot_id',
    '--snapshot',
    metavar='<snapshot>',
    action='single_alias',
    help='Optional snapshot ID or name to create the share from.'
         ' (Default=None)',
    default=None)
@cliutils.arg(
    '--name',
    metavar='<name>',
    help='Optional share name. (Default=None)',
    default=None)
@cliutils.arg(
    '--metadata',
    type=str,
    nargs='*',
    metavar='<key=value>',
    help='Metadata key=value pairs (Optional, Default=None).',
    default=None)
@cliutils.arg(
    '--share-network',
    '--share_network',
    metavar='<network-info>',
    action='single_alias',
    help='Optional network info ID or name.',
    default=None)
@cliutils.arg(
    '--description',
    metavar='<description>',
    help='Optional share description. (Default=None)',
    default=None)
@cliutils.arg(
    '--share-type', '--share_type', '--volume-type', '--volume_type',
    metavar='<share-type>',
    default=None,
    action='single_alias',
    help='Optional share type. Use of optional volume type is deprecated. '
         '(Default=None)')
@cliutils.arg(
    '--public',
    dest='public',
    action='store_true',
    default=False,
    help="Level of visibility for share. Defines whether other projects are "
         "able to see it or not. (Default=False)")
@cliutils.arg(
    '--availability-zone', '--availability_zone', '--az',
    metavar='<availability-zone>',
    default=None,
    action='single_alias',
    help='Availability zone in which share should be created.')
@cliutils.arg(
    '--share-group', '--share_group', '--group',
    metavar='<share-group>',
    action='single_alias',
    help='Optional share group name or ID in which to create the share '
         '(Default=None).',
    default=None)
@cliutils.arg(
    '--wait',
    action='store_true',
    help='Wait for share creation')
@cliutils.arg(
    '--scheduler-hints', '--scheduler_hints', '--sh',
    metavar='<key=value>',
    nargs='*',
    help='Scheduler hints for the share as key=value pairs, '
         'possible keys are same_host, different_host, '
         'value must be share_name or share_id.',
    default=None)
@cliutils.service_type('sharev2')
def do_create(cs, args):
    """Creates a new share (NFS, CIFS, CephFS, GlusterFS, HDFS or MAPRFS)."""

    share_metadata = None
    if args.metadata is not None:
        share_metadata = _extract_metadata(args)

    share_group = None
    if args.share_group:
        share_group = _find_share_group(cs, args.share_group).id

    share_network = None
    if args.share_network:
        share_network = _find_share_network(cs, args.share_network)

    snapshot = None
    if args.snapshot_id:
        snapshot = _find_share_snapshot(cs, args.snapshot_id).id

    if args.name:
        if args.name.capitalize() == 'None':
            raise exceptions.CommandError(
                "Share name cannot be with the value 'None'")

    if not args.share_type:
        try:
            _find_share_type(cs, "default")
        except exceptions.CommandError:
            msg = ("There is no default share type available. You must pick "
                   "a valid share type to create a share.")
            raise exceptions.CommandError(msg)

    scheduler_hints = {}
    if args.scheduler_hints:
        scheduler_hints = _extract_key_value_options(args, 'scheduler_hints')
        same_host_hint_shares = scheduler_hints.get('same_host')
        different_host_hint_shares = scheduler_hints.get('different_host')
        if same_host_hint_shares:
            same_host_hint_shares = [
                _find_share(cs, sh).id
                for sh in same_host_hint_shares.split(',')
            ]
            scheduler_hints['same_host'] = ','.join(same_host_hint_shares)
        if different_host_hint_shares:
            different_host_hint_shares = [
                _find_share(cs, sh).id
                for sh in different_host_hint_shares.split(',')
            ]
            scheduler_hints['different_host'] = ','.join(
                different_host_hint_shares)

    share = cs.shares.create(args.share_protocol, args.size, snapshot,
                             args.name, args.description,
                             metadata=share_metadata,
                             share_network=share_network,
                             share_type=args.share_type,
                             is_public=args.public,
                             availability_zone=args.availability_zone,
                             share_group_id=share_group,
                             scheduler_hints=scheduler_hints)

    if args.wait:
        share = _wait_for_share_status(cs, share)

    _print_share(cs, share)


@api_versions.wraps("2.29")
@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of share to migrate.')
@cliutils.arg(
    'host',
    metavar='<host@backend#pool>',
    help="Destination host where share will be migrated to. Use the "
         "format 'host@backend#pool'.")
@cliutils.arg(
    '--force_host_assisted_migration',
    '--force-host-assisted-migration',
    metavar='<True|False>',
    choices=['True', 'False'],
    action='single_alias',
    required=False,
    default=False,
    help="Enforces the use of the host-assisted migration approach, "
         "which bypasses driver optimizations. Default=False.")
@cliutils.arg(
    '--preserve-metadata',
    '--preserve_metadata',
    action='single_alias',
    metavar='<True|False>',
    choices=['True', 'False'],
    required=True,
    help="Enforces migration to preserve all file metadata when moving its "
         "contents. If set to True, host-assisted migration will not be "
         "attempted.")
@cliutils.arg(
    '--preserve-snapshots',
    '--preserve_snapshots',
    action='single_alias',
    metavar='<True|False>',
    choices=['True', 'False'],
    required=True,
    help="Enforces migration of the share snapshots to the destination. If "
         "set to True, host-assisted migration will not be attempted.")
@cliutils.arg(
    '--writable',
    metavar='<True|False>',
    choices=['True', 'False'],
    required=True,
    help="Enforces migration to keep the share writable while contents are "
         "being moved. If set to True, host-assisted migration will not be "
         "attempted.")
@cliutils.arg(
    '--nondisruptive',
    metavar='<True|False>',
    choices=['True', 'False'],
    required=True,
    help="Enforces migration to be nondisruptive. If set to True, "
         "host-assisted migration will not be attempted.")
@cliutils.arg(
    '--new_share_network',
    '--new-share-network',
    metavar='<new_share_network>',
    action='single_alias',
    required=False,
    help='Specify the new share network for the share. Do not specify this '
         'parameter if the migrating share has to be retained within its '
         'current share network.',
    default=None)
@cliutils.arg(
    '--new_share_type',
    '--new-share-type',
    metavar='<new_share_type>',
    required=False,
    action='single_alias',
    help='Specify the new share type for the share. Do not specify this '
         'parameter if the migrating share has to be retained with its '
         'current share type.',
    default=None)
def do_migration_start(cs, args):
    """Migrates share to a new host (Admin only, Experimental)."""
    share = _find_share(cs, args.share)
    new_share_net_id = None
    if args.new_share_network:
        share_net = _find_share_network(cs, args.new_share_network)
        new_share_net_id = share_net.id if share_net else None
    new_share_type_id = None
    if args.new_share_type:
        share_type = _find_share_type(cs, args.new_share_type)
        new_share_type_id = share_type.id if share_type else None
    share.migration_start(args.host, args.force_host_assisted_migration,
                          args.preserve_metadata, args.writable,
                          args.nondisruptive, args.preserve_snapshots,
                          new_share_net_id, new_share_type_id)


@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of share to complete migration.')
@api_versions.wraps("2.22")
def do_migration_complete(cs, args):
    """Completes migration for a given share (Admin only, Experimental)."""
    share = _find_share(cs, args.share)
    share.migration_complete()


@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of share to cancel migration.')
@api_versions.wraps("2.22")
def do_migration_cancel(cs, args):
    """Cancels migration of a given share when copying

    (Admin only, Experimental).
    """
    share = _find_share(cs, args.share)
    share.migration_cancel()


@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share to modify.')
@cliutils.arg(
    '--task-state',
    '--task_state',
    '--state',
    metavar='<task_state>',
    default='None',
    action='single_alias',
    required=False,
    help=('Indicate which task state to assign the share. Options include '
          'migration_starting, migration_in_progress, migration_completing, '
          'migration_success, migration_error, migration_cancelled, '
          'migration_driver_in_progress, migration_driver_phase1_done, '
          'data_copying_starting, data_copying_in_progress, '
          'data_copying_completing, data_copying_completed, '
          'data_copying_cancelled, data_copying_error. If no value is '
          'provided, None will be used.'))
@api_versions.wraps("2.22")
def do_reset_task_state(cs, args):
    """Explicitly update the task state of a share

    (Admin only, Experimental).
    """
    state = args.task_state
    if args.task_state == 'None':
        state = None
    share = _find_share(cs, args.share)
    share.reset_task_state(state)


@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share to get share migration progress '
         'information.')
@api_versions.wraps("2.22")
def do_migration_get_progress(cs, args):
    """Gets migration progress of a given share when copying

    (Admin only, Experimental).
    """
    share = _find_share(cs, args.share)
    result = share.migration_get_progress()
    # NOTE(ganso): result[0] is response code, result[1] is dict body
    cliutils.print_dict(result[1])


@cliutils.arg(
    'share_server_id',
    metavar='<share_server_id>',
    help='ID of the share server to check if the migration is possible.')
@cliutils.arg(
    'host',
    metavar='<host@backend>',
    help="Destination to migrate the share server to. Use the format "
         "'<node_hostname>@<backend_name>'.")
@cliutils.arg(
    '--preserve-snapshots',
    '--preserve_snapshots',
    action='single_alias',
    metavar='<True|False>',
    choices=['True', 'False'],
    required=True,
    help="Set to True if snapshots must be preserved at the migration "
         "destination.")
@cliutils.arg(
    '--writable',
    metavar='<True|False>',
    choices=['True', 'False'],
    required=True,
    help="Set to True if shares associated with the share server must be "
         "writable through the first phase of the migration.")
@cliutils.arg(
    '--nondisruptive',
    metavar='<True|False>',
    choices=['True', 'False'],
    required=True,
    help="Set to True if migration must be non disruptive to clients that are "
         "using the shares associated with the share server through both "
         "phases of the migration.")
@cliutils.arg(
    '--new_share_network',
    '--new-share-network',
    metavar='<new_share_network>',
    action='single_alias',
    required=False,
    help="New share network to migrate to. Optional, default=None.",
    default=None)
@api_versions.wraps("2.57")
@api_versions.experimental_api
def do_share_server_migration_check(cs, args):
    """Check migration compatibility for a share server with desired properties

    (Admin only, Experimental).
    """
    share_server = _find_share_server(cs, args.share_server_id)

    new_share_net_id = None
    if args.new_share_network:
        share_net = _find_share_network(cs, args.new_share_network)
        new_share_net_id = share_net.id
    result = share_server.migration_check(
        args.host, args.writable, args.nondisruptive, args.preserve_snapshots,
        new_share_net_id)
    cliutils.print_dict(result)


@cliutils.arg(
    'share_server_id',
    metavar='<share_server_id>',
    help='ID of the share server to migrate.')
@cliutils.arg(
    'host',
    metavar='<host@backend>',
    help="Destination to migrate the share server to. Use the format "
         "'<node_hostname>@<backend_name>'.")
@cliutils.arg(
    '--preserve-snapshots',
    '--preserve_snapshots',
    action='single_alias',
    metavar='<True|False>',
    choices=['True', 'False'],
    required=True,
    help="Set to True if snapshots must be preserved at the migration "
         "destination.")
@cliutils.arg(
    '--writable',
    metavar='<True|False>',
    choices=['True', 'False'],
    required=True,
    help="Enforces migration to keep all its shares writable  while contents "
         "are being moved.")
@cliutils.arg(
    '--nondisruptive',
    metavar='<True|False>',
    choices=['True', 'False'],
    required=True,
    help="Enforces migration to be nondisruptive.")
@cliutils.arg(
    '--new_share_network',
    '--new-share-network',
    metavar='<new_share_network>',
    action='single_alias',
    required=False,
    help='Specify a new share network for the share server. Do not '
         'specify this parameter if the migrating share server has '
         'to be retained within its current share network.',
    default=None)
@api_versions.wraps("2.57")
@api_versions.experimental_api
def do_share_server_migration_start(cs, args):
    """Migrates share server to a new host (Admin only, Experimental)."""
    share_server = _find_share_server(cs, args.share_server_id)

    new_share_net_id = None
    if args.new_share_network:
        share_net = _find_share_network(cs, args.new_share_network)
        new_share_net_id = share_net.id
    share_server.migration_start(args.host, args.writable, args.nondisruptive,
                                 args.preserve_snapshots, new_share_net_id)


@cliutils.arg(
    'share_server_id',
    metavar='<share_server_id>',
    help='ID of share server to complete migration.')
@api_versions.wraps("2.57")
@api_versions.experimental_api
def do_share_server_migration_complete(cs, args):
    """Completes migration for a given share server

    (Admin only, Experimental).
    """
    share_server = _find_share_server(cs, args.share_server_id)
    result = share_server.migration_complete()
    cliutils.print_dict(result)


@cliutils.arg(
    'share_server_id',
    metavar='<share_server_id>',
    help='ID of share server to complete migration.')
@api_versions.wraps("2.57")
@api_versions.experimental_api
def do_share_server_migration_cancel(cs, args):
    """Cancels migration of a given share server when copying

    (Admin only, Experimental).
    """
    share_server = _find_share_server(cs, args.share_server_id)
    share_server.migration_cancel()


@cliutils.arg(
    'share_server_id',
    metavar='<share_server_id>',
    help='ID of share server to complete migration.')
@cliutils.arg(
    '--task-state',
    '--task_state',
    '--state',
    metavar='<task_state>',
    default='None',
    action='single_alias',
    required=False,
    help=('Indicate which task state to assign the share server. Options: '
          'migration_starting, migration_in_progress, migration_completing, '
          'migration_success, migration_error, migration_cancel_in_progress, '
          'migration_cancelled, migration_driver_in_progress, '
          'migration_driver_phase1_done. If no value is provided, None will '
          'be used.'))
@api_versions.wraps("2.57")
@api_versions.experimental_api
def do_share_server_reset_task_state(cs, args):
    """Explicitly update the task state of a share

    (Admin only, Experimental).
    """
    state = args.task_state
    if args.task_state == 'None':
        state = None
    share_server = _find_share_server(cs, args.share_server_id)
    share_server.reset_task_state(state)


@cliutils.arg(
    'share_server_id',
    metavar='<share_server_id>',
    help='ID of share server to complete migration.')
@api_versions.wraps("2.57")
@api_versions.experimental_api
def do_share_server_migration_get_progress(cs, args):
    """Gets migration progress of a given share server when copying

    (Admin only, Experimental).
    """
    share_server = _find_share_server(cs, args.share_server_id)
    result = share_server.migration_get_progress()
    cliutils.print_dict(result)


@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share to update metadata on.')
@cliutils.arg(
    'action',
    metavar='<action>',
    choices=['set', 'unset'],
    help="Actions: 'set' or 'unset'.")
@cliutils.arg(
    'metadata',
    metavar='<key=value>',
    nargs='+',
    default=[],
    help='Metadata to set or unset (key is only necessary on unset).')
def do_metadata(cs, args):
    """Set or delete metadata on a share."""
    share = _find_share(cs, args.share)
    metadata = _extract_metadata(args)

    if args.action == 'set':
        cs.shares.set_metadata(share, metadata)
    elif args.action == 'unset':
        cs.shares.delete_metadata(share, sorted(list(metadata), reverse=True))


@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share.')
def do_metadata_show(cs, args):
    """Show metadata of given share."""
    share = _find_share(cs, args.share)
    metadata = cs.shares.get_metadata(share)._info
    cliutils.print_dict(metadata, 'Property')


@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share to update metadata on.')
@cliutils.arg(
    'metadata',
    metavar='<key=value>',
    nargs='+',
    default=[],
    help='Metadata entry or entries to update.')
def do_metadata_update_all(cs, args):
    """Update all metadata of a share."""
    share = _find_share(cs, args.share)
    metadata = _extract_metadata(args)
    metadata = share.update_all_metadata(metadata)._info['metadata']
    cliutils.print_dict(metadata, 'Property')


@api_versions.wraps("2.9")
@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,host,status".')
def do_share_export_location_list(cs, args):
    """List export locations of a given share."""
    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    else:
        list_of_keys = [
            'ID',
            'Path',
            'Preferred',
        ]
    share = _find_share(cs, args.share)
    export_locations = cs.share_export_locations.list(share)
    cliutils.print_list(export_locations, list_of_keys)


@api_versions.wraps("2.9")
@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share.')
@cliutils.arg(
    'export_location',
    metavar='<export_location>',
    help='ID of the share export location.')
def do_share_export_location_show(cs, args):
    """Show export location of the share."""
    share = _find_share(cs, args.share)
    export_location = cs.share_export_locations.get(
        share, args.export_location)
    view_data = export_location._info.copy()
    cliutils.print_dict(view_data)


@cliutils.arg(
    'service_host',
    metavar='<service_host>',
    type=str,
    help='manage-share service host: some.host@driver#pool.')
@cliutils.arg(
    'protocol',
    metavar='<protocol>',
    type=str,
    help='Protocol of the share to manage, such as NFS or CIFS.')
@cliutils.arg(
    'export_path',
    metavar='<export_path>',
    type=str,
    help='Share export path, NFS share such as: 10.0.0.1:/example_path, '
         'CIFS share such as: \\\\10.0.0.1\\example_cifs_share.')
@cliutils.arg(
    '--name',
    metavar='<name>',
    help='Optional share name. (Default=None)',
    default=None)
@cliutils.arg(
    '--description',
    metavar='<description>',
    help='Optional share description. (Default=None)',
    default=None)
@cliutils.arg(
    '--share_type', '--share-type',
    metavar='<share-type>',
    default=None,
    action='single_alias',
    help='Optional share type assigned to share. (Default=None)')
@cliutils.arg(
    '--driver_options', '--driver-options',
    type=str,
    nargs='*',
    metavar='<key=value>',
    action='single_alias',
    help='Driver option key=value pairs (Optional, Default=None).',
    default=None)
@cliutils.arg(
    '--public',
    dest='public',
    action='store_true',
    default=False,
    help="Level of visibility for share. Defines whether other projects are "
         "able to see it or not. Available only for microversion >= 2.8. "
         "(Default=False)")
@cliutils.arg(
    '--share_server_id', '--share-server-id',
    metavar='<share-server-id>',
    default=None,
    action='single_alias',
    help="Share server associated with share when using a share type with "
         "'driver_handles_share_servers' extra_spec set to True. Available "
         "only for microversion >= 2.49. (Default=None)")
@cliutils.arg(
    '--wait',
    action='store_true',
    help='Wait for share management')
def do_manage(cs, args):
    """Manage share not handled by Manila (Admin only)."""
    driver_options = _extract_key_value_options(args, 'driver_options')
    if cs.api_version.matches(api_versions.APIVersion("2.49"),
                              api_versions.APIVersion()):
        share = cs.shares.manage(
            args.service_host, args.protocol, args.export_path,
            driver_options=driver_options, share_type=args.share_type,
            name=args.name, description=args.description,
            is_public=args.public, share_server_id=args.share_server_id)
    else:
        if args.share_server_id:
            raise exceptions.CommandError("Invalid parameter "
                                          "--share_server_id specified. This"
                                          " parameter is only supported on"
                                          " microversion 2.49 or newer.")
        share = cs.shares.manage(
            args.service_host, args.protocol, args.export_path,
            driver_options=driver_options, share_type=args.share_type,
            name=args.name, description=args.description,
            is_public=args.public)

    if args.wait:
        share = _wait_for_resource_status(
            cs, share, resource_type='share',
            expected_status='available'
        )
    _print_share(cs, share)


@api_versions.wraps("2.49")
@cliutils.arg(
    'host',
    metavar='<host>',
    type=str,
    help='Backend name as "<node_hostname>@<backend_name>".')
@cliutils.arg(
    'share_network',
    metavar='<share_network>',
    help="Share network where share server has network allocations in.")
@cliutils.arg(
    'identifier',
    metavar='<identifier>',
    type=str,
    help='A driver-specific share server identifier required by the driver to '
         'manage the share server.')
@cliutils.arg(
    '--driver_options', '--driver-options',
    type=str,
    nargs='*',
    metavar='<key=value>',
    action='single_alias',
    help='One or more driver-specific key=value pairs that may be necessary to'
         ' manage the share server (Optional, Default=None).',
    default=None)
@cliutils.arg(
    '--share-network-subnet', '--share_network_subnet',
    type=str,
    metavar='<share_network_subnet>',
    help="Share network subnet where share server has network allocations in. "
         "The default subnet will be used if it's not specified. Available "
         "for microversion >= 2.51 (Optional, Default=None).",
    default=None)
@cliutils.arg(
    '--wait',
    action='store_true',
    default='False',
    help='Wait for share server to manage')
def do_share_server_manage(cs, args):
    """Manage share server not handled by Manila (Admin only)."""
    driver_options = _extract_key_value_options(args, 'driver_options')

    manage_kwargs = {
        'driver_options': driver_options,
    }
    if cs.api_version < api_versions.APIVersion("2.51"):
        if getattr(args, 'share_network_subnet'):
            raise exceptions.CommandError(
                "Share network subnet option is only available with manila "
                "API version >= 2.51")
    else:
        manage_kwargs['share_network_subnet_id'] = args.share_network_subnet

    share_server = cs.share_servers.manage(
        args.host, args.share_network, args.identifier,
        **manage_kwargs)

    if args.wait:
        try:
            _wait_for_resource_status(
                cs, share_server, resource_type='share_server',
                expected_status='active')
        except exceptions.CommandError as e:
            print(e, file=sys.stderr)

    cliutils.print_dict(share_server._info)


@cliutils.arg(
    'share_server_id',
    metavar='<share_server_id>',
    help='ID of the share server to modify.')
@cliutils.arg(
    '--state',
    metavar='<state>',
    default=constants.STATUS_ACTIVE,
    help=('Indicate which state to assign the share server. Options include '
          'active, error, creating, deleting, managing, unmanaging, '
          'manage_error and unmanage_error. If no state is provided, active '
          'will be used.'))
@api_versions.wraps("2.49")
def do_share_server_reset_state(cs, args):
    """Explicitly update the state of a share server (Admin only)."""
    cs.share_servers.reset_state(args.share_server_id, args.state)


@api_versions.wraps("2.12")
@cliutils.arg(
    'share',
    metavar='<share>',
    type=str,
    help='Name or ID of the share.')
@cliutils.arg(
    'provider_location',
    metavar='<provider_location>',
    type=str,
    help='Provider location of the snapshot on the backend.')
@cliutils.arg(
    '--name',
    metavar='<name>',
    help='Optional snapshot name (Default=None).',
    default=None)
@cliutils.arg(
    '--description',
    metavar='<description>',
    help='Optional snapshot description (Default=None).',
    default=None)
@cliutils.arg(
    '--driver_options', '--driver-options',
    type=str,
    nargs='*',
    metavar='<key=value>',
    action='single_alias',
    help='Optional driver options as key=value pairs (Default=None).',
    default=None)
def do_snapshot_manage(cs, args):
    """Manage share snapshot not handled by Manila (Admin only)."""
    share_ref = _find_share(cs, args.share)

    driver_options = _extract_key_value_options(args, 'driver_options')

    share_snapshot = cs.share_snapshots.manage(
        share_ref, args.provider_location,
        driver_options=driver_options,
        name=args.name, description=args.description
    )

    _print_share_snapshot(cs, share_snapshot)


@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share(s).')
@cliutils.arg(
    '--wait',
    action='store_true',
    help='Wait for share unmanagement')
def do_unmanage(cs, args):
    """Unmanage share (Admin only)."""
    share_ref = _find_share(cs, args.share)
    share_ref.unmanage()
    if args.wait:
        _wait_for_share_status(cs, share_ref, expected_status='unmanaged')


@api_versions.wraps("2.49")
@cliutils.arg(
    'share_server',
    metavar='<share_server>',
    nargs='+',
    help='ID of the share server(s).')
@cliutils.arg(
    '--force',
    dest='force',
    action="store_true",
    required=False,
    default=False,
    help="Enforces the unmanage share server operation, even if the back-end "
         "driver does not support it.")
def do_share_server_unmanage(cs, args):
    """Unmanage share server (Admin only)."""
    failure_count = 0
    for server in args.share_server:
        try:
            cs.share_servers.unmanage(server, args.force)
        except Exception as e:
            failure_count += 1
            print("Unmanage for share server %s failed: %s" % (server, e),
                  file=sys.stderr)

    if failure_count == len(args.share_server):
        raise exceptions.CommandError("Unable to unmanage any of the "
                                      "specified share servers.")


@api_versions.wraps("2.12")
@cliutils.arg(
    'snapshot',
    metavar='<snapshot>',
    nargs='+',
    help='Name or ID of the snapshot(s).')
def do_snapshot_unmanage(cs, args):
    """Unmanage one or more share snapshots (Admin only)."""
    failure_count = 0
    for snapshot in args.snapshot:
        try:
            snapshot_ref = _find_share_snapshot(cs, snapshot)
            snapshot_ref.unmanage_snapshot()
        except Exception as e:
            failure_count += 1
            print("Unmanage for share snapshot %s failed: %s" % (snapshot, e),
                  file=sys.stderr)

    if failure_count == len(args.snapshot):
        raise exceptions.CommandError("Unable to unmanage any of the "
                                      "specified snapshots.")


@api_versions.wraps("2.27")
@cliutils.arg(
    'snapshot',
    metavar='<snapshot>',
    help='Name or ID of the snapshot to restore. The snapshot must be the '
         'most recent one known to manila.')
def do_revert_to_snapshot(cs, args):
    """Revert a share to the specified snapshot."""
    snapshot = _find_share_snapshot(cs, args.snapshot)
    share = _find_share(cs, snapshot.share_id)
    share.revert_to_snapshot(snapshot)


@cliutils.arg(
    'share',
    metavar='<share>',
    nargs='+',
    help='Name or ID of the share(s).')
@cliutils.arg(
    '--share-group', '--share_group', '--group',
    metavar='<share-group>',
    action='single_alias',
    help='Optional share group name or ID which contains the share '
         '(Default=None).',
    default=None)
@cliutils.arg(
    '--wait',
    action='store_true',
    help='Wait for share deletion')
@cliutils.service_type('sharev2')
def do_delete(cs, args):
    """Remove one or more shares."""
    failure_count = 0
    shares_to_delete = []
    for share in args.share:
        try:
            share_ref = _find_share(cs, share)
            shares_to_delete.append(share_ref)
            if args.share_group:
                share_group_id = _find_share_group(cs, args.share_group).id
                cs.shares.delete(share_ref, share_group_id=share_group_id)
            else:
                cs.shares.delete(share_ref)
        except Exception as e:
            failure_count += 1
            print("Delete for share %s failed: %s" % (share, e),
                  file=sys.stderr)

    if failure_count == len(args.share):
        raise exceptions.CommandError("Unable to delete any of the specified "
                                      "shares.")

    if args.wait:
        for share in shares_to_delete:
            try:
                _wait_for_share_status(cs, share, expected_status='deleted')
            except exceptions.CommandError as e:
                print(e, file=sys.stderr)


@cliutils.arg(
    'share',
    metavar='<share>',
    nargs='+',
    help='Name or ID of the share(s) to force delete.')
@cliutils.arg(
    '--wait',
    action='store_true',
    help='Wait for share to delete')
@cliutils.service_type('sharev2')
def do_force_delete(cs, args):
    """Attempt force-delete of share, regardless of state (Admin only)."""
    failure_count = 0
    shares_to_delete = []
    for share in args.share:
        try:
            share_ref = _find_share(cs, share)
            shares_to_delete.append(share_ref)
            share_ref.force_delete()
        except Exception as e:
            failure_count += 1
            print("Delete for share %s failed: %s" % (share, e),
                  file=sys.stderr)
    if failure_count == len(args.share):
        raise exceptions.CommandError("Unable to force delete any of "
                                      "specified shares.")
    if args.wait:
        for share in shares_to_delete:
            try:
                _wait_for_share_status(cs, share, expected_status='deleted')
            except exceptions.CommandError as e:
                print(e, file=sys.stderr)


@cliutils.arg(
    'share',
    metavar='<share>',
    nargs='+',
    help='Name or ID of the share(s).')
@cliutils.service_type('sharev2')
@api_versions.wraps("2.69")
def do_soft_delete(cs, args):
    """Soft delete one or more shares."""
    failure_count = 0

    for share in args.share:
        try:
            share_ref = _find_share(cs, share)
            cs.shares.soft_delete(share_ref)
        except Exception as e:
            failure_count += 1
            print("Soft deletion of share %s failed: %s" % (share, e),
                  file=sys.stderr)

    if failure_count == len(args.share):
        raise exceptions.CommandError("Unable to soft delete any of the "
                                      "specified shares.")


@cliutils.arg(
    'share',
    metavar='<share>',
    nargs='+',
    help='Name or ID of the share(s).')
@cliutils.service_type('sharev2')
@api_versions.wraps("2.69")
def do_restore(cs, args):
    """Restore one or more shares from recycle bin."""
    failure_count = 0

    for share in args.share:
        try:
            share_ref = _find_share(cs, share)
            cs.shares.restore(share_ref)
        except Exception as e:
            failure_count += 1
            print("Restoration of share %s failed: %s" % (share, e),
                  file=sys.stderr)

    if failure_count == len(args.share):
        raise exceptions.CommandError("Unable to restore any of the "
                                      "specified shares.")


@api_versions.wraps("1.0", "2.8")
@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the NAS share.')
def do_show(cs, args):
    """Show details about a NAS share."""
    share = _find_share(cs, args.share)
    _print_share(cs, share)


@api_versions.wraps("2.9")  # noqa
@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the NAS share.')
def do_show(cs, args):  # noqa
    """Show details about a NAS share."""
    share = _find_share(cs, args.share)
    export_locations = cs.share_export_locations.list(share)
    share._info['export_locations'] = export_locations
    _print_share(cs, share)


@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the NAS share to modify.')
@cliutils.arg(
    'access_type',
    metavar='<access_type>',
    help='Access rule type (only "ip", "user"(user or group), "cert" or '
         '"cephx" are supported).')
@cliutils.arg(
    'access_to',
    metavar='<access_to>',
    help='Value that defines access.')
@cliutils.arg(
    '--access-level',
    '--access_level',  # alias
    metavar='<access_level>',
    type=str,
    default=None,
    choices=['rw', 'ro'],
    action='single_alias',
    help='Share access level ("rw" and "ro" access levels are supported). '
         'Defaults to rw.')
@cliutils.arg(
    '--metadata',
    type=str,
    nargs='*',
    metavar='<key=value>',
    help='Space Separated list of key=value pairs of metadata items. '
         'OPTIONAL: Default=None. Available only for microversion >= 2.45.',
    default=None)
@cliutils.arg(
    '--wait',
    action='store_true',
    help='Wait for share access to become active')
def do_access_allow(cs, args):
    """Allow access to a given share."""
    access_metadata = None
    if cs.api_version.matches(api_versions.APIVersion("2.45"),
                              api_versions.APIVersion()):
        access_metadata = _extract_metadata(args)
    elif getattr(args, 'metadata'):
        raise exceptions.CommandError(
            "Adding metadata to access rules is supported only beyond "
            "API version 2.45")

    share = _find_share(cs, args.share)
    access = share.allow(args.access_type, args.access_to, args.access_level,
                         access_metadata)
    if args.wait:
        try:
            if not cs.api_version.matches(api_versions.APIVersion("2.45"),
                                          api_versions.APIVersion()):
                raise exceptions.CommandError(
                    "Waiting on the allowing access operation is only "
                    "available for API versions equal to or greater than 2.45."
                )
            access_id = access.get('id')
            share_access_rule = cs.share_access_rules.get(access_id)
            access = _wait_for_resource_status(
                cs, share_access_rule,
                resource_type='share_access_rule',
                expected_status='active',
                status_attr='state')._info
        except exceptions.CommandError as e:
            print(e, file=sys.stderr)
    cliutils.print_dict(access)


@api_versions.wraps("2.45")
@cliutils.arg(
    'access_id',
    metavar='<access_id>',
    help='ID of the NAS share access rule.')
def do_access_show(cs, args):
    """Show details about a NAS share access rule."""
    access = cs.share_access_rules.get(args.access_id)
    view_data = access._info.copy()
    cliutils.print_dict(view_data)


@api_versions.wraps("2.45")
@cliutils.arg(
    'access_id',
    metavar='<access_id>',
    help='ID of the NAS share access rule.')
@cliutils.arg(
    'action',
    metavar='<action>',
    choices=['set', 'unset'],
    help="Actions: 'set' or 'unset'.")
@cliutils.arg(
    'metadata',
    metavar='<key=value>',
    nargs='+',
    default=[],
    help='Space separated key=value pairs of metadata items to set. '
         'To unset only keys are required. ')
def do_access_metadata(cs, args):
    """Set or delete metadata on a share access rule."""
    share_access = cs.share_access_rules.get(args.access_id)
    metadata = _extract_metadata(args)

    if args.action == 'set':
        cs.share_access_rules.set_metadata(share_access, metadata)
    elif args.action == 'unset':
        cs.share_access_rules.unset_metadata(
            share_access, sorted(list(metadata), reverse=True))


@api_versions.wraps("2.32")
@cliutils.arg(
    'snapshot',
    metavar='<snapshot>',
    help='Name or ID of the share snapshot to allow access to.')
@cliutils.arg(
    'access_type',
    metavar='<access_type>',
    help='Access rule type (only "ip", "user"(user or group), "cert" or '
         '"cephx" are supported).')
@cliutils.arg(
    'access_to',
    metavar='<access_to>',
    help='Value that defines access.')
def do_snapshot_access_allow(cs, args):
    """Allow read only access to a snapshot."""
    share_snapshot = _find_share_snapshot(cs, args.snapshot)
    access = share_snapshot.allow(args.access_type, args.access_to)
    cliutils.print_dict(access)


@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the NAS share to modify.')
@cliutils.arg(
    'id',
    metavar='<id>',
    help='ID of the access rule to be deleted.')
def do_access_deny(cs, args):
    """Deny access to a share."""
    share = _find_share(cs, args.share)
    share.deny(args.id)


@api_versions.wraps("2.32")
@cliutils.arg(
    'snapshot',
    metavar='<snapshot>',
    help='Name or ID of the share snapshot to deny access to.')
@cliutils.arg(
    'id',
    metavar='<id>',
    nargs='+',
    help='ID(s) of the access rule(s) to be deleted.')
def do_snapshot_access_deny(cs, args):
    """Deny access to a snapshot."""
    failure_count = 0
    snapshot = _find_share_snapshot(cs, args.snapshot)
    for access_id in args.id:
        try:
            snapshot.deny(access_id)
        except Exception as e:
            failure_count += 1
            print("Failed to remove rule %(access)s: %(reason)s."
                  % {'access': access_id, 'reason': e},
                  file=sys.stderr)

    if failure_count == len(args.id):
        raise exceptions.CommandError("Unable to delete any of the specified "
                                      "snapshot rules.")


@api_versions.wraps("1.0", "2.20")
@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "access_type,access_to".')
def do_access_list(cs, args):
    """Show access list for share."""
    list_of_keys = [
        'id', 'access_type', 'access_to', 'access_level', 'state',
    ]

    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)

    share = _find_share(cs, args.share)
    access_list = share.access_list()
    cliutils.print_list(access_list, list_of_keys)


@api_versions.wraps("2.21")  # noqa
@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "access_type,access_to".')
def do_access_list(cs, args):  # noqa
    """Show access list for share."""
    list_of_keys = [
        'id', 'access_type', 'access_to', 'access_level', 'state',
        'access_key'
    ]

    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)

    share = _find_share(cs, args.share)
    access_list = share.access_list()
    cliutils.print_list(access_list, list_of_keys)


@api_versions.wraps("2.33")  # noqa
@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "access_type,access_to".')
@cliutils.arg(
    '--metadata',
    type=str,
    nargs='*',
    metavar='<key=value>',
    help='Filters results by a metadata key and value. OPTIONAL: '
         'Default=None. Available only for microversion >= 2.45',
    default=None)
def do_access_list(cs, args):  # noqa
    """Show access list for share."""
    list_of_keys = [
        'id', 'access_type', 'access_to', 'access_level', 'state',
        'access_key', 'created_at', 'updated_at',
    ]

    share = _find_share(cs, args.share)
    if cs.api_version < api_versions.APIVersion("2.45"):
        if getattr(args, 'metadata'):
            raise exceptions.CommandError(
                "Filtering access rules by metadata is supported only beyond "
                "API version 2.45")
        access_list = share.access_list()
    else:
        access_list = cs.share_access_rules.access_list(
            share, {'metadata': _extract_metadata(args)})
    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    cliutils.print_list(access_list, list_of_keys)


@api_versions.wraps("2.32")
@cliutils.arg(
    'snapshot',
    metavar='<snapshot>',
    help='Name or ID of the share snapshot to list access of.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "access_type,access_to".')
def do_snapshot_access_list(cs, args):
    """Show access list for a snapshot."""
    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    else:
        list_of_keys = ['id', 'access_type', 'access_to', 'state']

    snapshot = _find_share_snapshot(cs, args.snapshot)
    access_list = snapshot.access_list()
    cliutils.print_list(access_list, list_of_keys)


@cliutils.arg(
    '--all-tenants', '--all-projects',
    action='single_alias',
    dest='all_projects',
    metavar='<0|1>',
    nargs='?',
    type=int,
    const=1,
    default=0,
    help='Display information from all projects (Admin only).')
@cliutils.arg(
    '--name',
    metavar='<name>',
    type=str,
    default=None,
    help='Filter results by name.')
@cliutils.arg(
    '--description',
    metavar='<description>',
    type=str,
    default=None,
    help='Filter results by description. '
         'Available only for microversion >= 2.36.')
@cliutils.arg(
    '--name~',
    metavar='<name~>',
    type=str,
    default=None,
    help='Filter results matching a share name pattern. '
         'Available only for microversion >= 2.36.')
@cliutils.arg(
    '--description~',
    metavar='<description~>',
    type=str,
    default=None,
    help='Filter results matching a share description pattern. '
         'Available only for microversion >= 2.36.')
@cliutils.arg(
    '--status',
    metavar='<status>',
    type=str,
    default=None,
    help='Filter results by status.')
@cliutils.arg(
    '--share-server-id',
    '--share-server_id', '--share_server-id', '--share_server_id',  # aliases
    metavar='<share_server_id>',
    type=str,
    default=None,
    action='single_alias',
    help='Filter results by share server ID (Admin only).')
@cliutils.arg(
    '--metadata',
    type=str,
    nargs='*',
    metavar='<key=value>',
    help='Filters results by a metadata key and value. OPTIONAL: '
         'Default=None.',
    default=None)
@cliutils.arg(
    '--extra-specs',
    '--extra_specs',  # alias
    type=str,
    nargs='*',
    metavar='<key=value>',
    action='single_alias',
    help='Filters results by a extra specs key and value of share type that '
         'was used for share creation. OPTIONAL: Default=None.',
    default=None)
@cliutils.arg(
    '--share-type', '--volume-type',
    '--share_type', '--share-type-id', '--volume-type-id',  # aliases
    '--share-type_id', '--share_type-id', '--share_type_id',  # aliases
    '--volume_type', '--volume_type_id',
    metavar='<share_type>',
    type=str,
    default=None,
    action='single_alias',
    help='Filter results by a share type id or name that was used for share '
         'creation.')
@cliutils.arg(
    '--limit',
    metavar='<limit>',
    type=int,
    default=None,
    help='Maximum number of shares to return. OPTIONAL: Default=None.')
@cliutils.arg(
    '--offset',
    metavar='<offset>',
    type=int,
    default=None,
    help='Set offset to define start point of share listing. '
         'OPTIONAL: Default=None.')
@cliutils.arg(
    '--sort-key',
    '--sort_key',  # alias
    metavar='<sort_key>',
    type=str,
    default=None,
    action='single_alias',
    help='Key to be sorted, available keys are %(keys)s. '
         'OPTIONAL: Default=None.' % {'keys': constants.SHARE_SORT_KEY_VALUES})
@cliutils.arg(
    '--sort-dir',
    '--sort_dir',  # alias
    metavar='<sort_dir>',
    type=str,
    default=None,
    action='single_alias',
    help='Sort direction, available values are %(values)s. '
         'OPTIONAL: Default=None.' % {'values': constants.SORT_DIR_VALUES})
@cliutils.arg(
    '--snapshot',
    metavar='<snapshot>',
    type=str,
    default=None,
    help='Filer results by snapshot name or id, that was used for share.')
@cliutils.arg(
    '--host',
    metavar='<host>',
    default=None,
    help='Filter results by host.')
@cliutils.arg(
    '--share-network',
    '--share_network',  # alias
    metavar='<share_network>',
    type=str,
    default=None,
    action='single_alias',
    help='Filter results by share-network name or id.')
@cliutils.arg(
    '--project-id',
    '--project_id',  # alias
    metavar='<project_id>',
    type=str,
    default=None,
    action='single_alias',
    help="Filter results by project id. Useful with set key '--all-projects'.")
@cliutils.arg(
    '--public',
    dest='public',
    action='store_true',
    default=False,
    help="Add public shares from all projects to result. (Default=False)")
@cliutils.arg(
    '--share-group', '--share_group', '--group',
    metavar='<share_group>',
    type=str,
    default=None,
    action='single_alias',
    help='Filter results by share group name or ID (Default=None).')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "export_location,is public".')
@cliutils.arg(
    '--export-location', '--export_location',
    metavar='<export_location>',
    type=str,
    default=None,
    action='single_alias',
    help='ID or path of the share export location. '
         'Available only for microversion >= 2.35.')
@cliutils.arg(
    '--count',
    dest='count',
    metavar='<True|False>',
    choices=['True', 'False'],
    default=False,
    help='Display total number of shares to return. '
         'Available only for microversion >= 2.42.')
@cliutils.arg(
    '--soft-deleted', '--soft_deleted',
    action='store_true',
    help='Get shares in recycle bin. If this parameter is set to '
         'True(Default=False), will only show shares in recycle bin. '
         'Available only for microversion >= 2.69.')
@cliutils.service_type('sharev2')
def do_list(cs, args):
    """List NAS shares with filters."""

    columns = args.columns
    all_projects = int(
        os.environ.get("ALL_TENANTS",
                       os.environ.get("ALL_PROJECTS",
                                      args.all_projects))
    )
    if columns is not None:
        list_of_keys = _split_columns(columns=columns)
    else:
        list_of_keys = [
            'ID', 'Name', 'Size', 'Share Proto', 'Status', 'Is Public',
            'Share Type Name', 'Host', 'Availability Zone'
        ]
        if all_projects or args.public:
            list_of_keys.append('Project ID')

    empty_obj = type('Empty', (object,), {'id': None})
    share_type = (_find_share_type(cs, args.share_type)
                  if args.share_type else empty_obj)

    snapshot = (_find_share_snapshot(cs, args.snapshot)
                if args.snapshot else empty_obj)

    share_network = (_find_share_network(cs, args.share_network)
                     if args.share_network else empty_obj)

    share_group = None
    if args.share_group:
        share_group = _find_share_group(cs, args.share_group)

    search_opts = {
        'offset': args.offset,
        'limit': args.limit,
        'all_tenants': all_projects,
        'name': args.name,
        'status': args.status,
        'host': args.host,
        'share_network_id': share_network.id,
        'snapshot_id': snapshot.id,
        'share_type_id': share_type.id,
        'metadata': _extract_metadata(args),
        'extra_specs': _extract_extra_specs(args),
        'share_server_id': args.share_server_id,
        'project_id': args.project_id,
        'is_public': args.public,
    }
    if cs.api_version.matches(api_versions.APIVersion("2.36"),
                              api_versions.APIVersion()):
        search_opts['name~'] = getattr(args, 'name~')
        search_opts['description~'] = getattr(args, 'description~')
        search_opts['description'] = getattr(args, 'description')
    elif (getattr(args, 'name~') or getattr(args, 'description~') or
          getattr(args, 'description')):
        raise exceptions.CommandError(
            "Pattern based filtering (name~, description~ and description)"
            " is only available with manila API version >= 2.36")

    if cs.api_version.matches(api_versions.APIVersion("2.35"),
                              api_versions.APIVersion()):
        search_opts['export_location'] = args.export_location
    elif args.export_location:
        raise exceptions.CommandError(
            "Filtering by export location is only "
            "available with manila API version >= 2.35")

    if (args.count and
            cs.api_version.matches(
                api_versions.APIVersion(), api_versions.APIVersion("2.41"))):
        raise exceptions.CommandError(
            "Display total number of shares is only "
            "available with manila API version >= 2.42")

    if cs.api_version.matches(api_versions.APIVersion("2.69"),
                              api_versions.APIVersion()):
        if args.soft_deleted:
            search_opts['is_soft_deleted'] = args.soft_deleted
    elif args.soft_deleted:
        raise exceptions.CommandError(
            "Filtering by is_soft_deleted is only "
            "available with manila API version >= 2.69")

    if share_group:
        search_opts['share_group_id'] = share_group.id

    total_count = 0
    if strutils.bool_from_string(args.count, strict=True):
        search_opts['with_count'] = args.count
        shares, total_count = cs.shares.list(
            search_opts=search_opts, sort_key=args.sort_key,
            sort_dir=args.sort_dir,
        )
    else:
        shares = cs.shares.list(
            search_opts=search_opts, sort_key=args.sort_key,
            sort_dir=args.sort_dir,
        )
    # NOTE(vponomaryov): usage of 'export_location' and
    # 'export_locations' columns may cause scaling issue using API 2.9+ and
    # when lots of shares are returned.
    if (shares and columns is not None and 'export_location' in columns and
            not hasattr(shares[0], 'export_location')):
        # NOTE(vponomaryov): we will get here only using API 2.9+
        for share in shares:
            els_objs = cs.share_export_locations.list(share)
            els = [el.to_dict()['path'] for el in els_objs]
            setattr(share, 'export_locations', els)
            setattr(share, 'export_location', els[0] if els else None)
    cliutils.print_list(shares, list_of_keys, sortby_index=None)
    if args.count:
        print("Shares in total: %s" % total_count)

    with cs.shares.completion_cache('uuid',
                                    manilaclient.v2.shares.Share,
                                    mode="w"):
        for share in shares:
            cs.shares.write_to_completion_cache('uuid', share.id)

    with cs.shares.completion_cache('name',
                                    manilaclient.v2.shares.Share,
                                    mode="w"):
        for share in shares:
            if share.name is not None:
                cs.shares.write_to_completion_cache('name', share.name)


@cliutils.arg(
    '--share-id',
    '--share_id',  # alias
    metavar='<share_id>',
    default=None,
    action='single_alias',
    help='Filter results by share ID.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,host,status".')
@cliutils.arg(
    '--export-location', '--export_location',
    metavar='<export_location>',
    type=str,
    default=None,
    action='single_alias',
    help='ID or path of the share instance export location. '
         'Available only for microversion >= 2.35.')
@api_versions.wraps("2.3")
def do_share_instance_list(cs, args):
    """List share instances (Admin only)."""
    share = _find_share(cs, args.share_id) if args.share_id else None

    list_of_keys = [
        'ID', 'Share ID', 'Host', 'Status', 'Availability Zone',
        'Share Network ID', 'Share Server ID', 'Share Type ID',
    ]

    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)

    if share:
        instances = cs.shares.list_instances(share)
    else:
        if cs.api_version.matches(
                api_versions.APIVersion("2.35"), api_versions.APIVersion()):
            instances = cs.share_instances.list(args.export_location)
        else:
            if args.export_location:
                raise exceptions.CommandError(
                    "Filtering by export location is only "
                    "available with manila API version >= 2.35")
            instances = cs.share_instances.list()

    cliutils.print_list(instances, list_of_keys)


@api_versions.wraps("2.3", "2.8")
@cliutils.arg(
    'instance',
    metavar='<instance>',
    help='Name or ID of the share instance.')
def do_share_instance_show(cs, args):
    """Show details about a share instance."""
    instance = _find_share_instance(cs, args.instance)
    _print_share_instance(cs, instance)


@api_versions.wraps("2.9")  # noqa
@cliutils.arg(
    'instance',
    metavar='<instance>',
    help='Name or ID of the share instance.')
def do_share_instance_show(cs, args):  # noqa
    """Show details about a share instance (Admin only)."""
    instance = _find_share_instance(cs, args.instance)
    export_locations = cs.share_instance_export_locations.list(instance)
    instance._info['export_locations'] = export_locations
    _print_share_instance(cs, instance)


@cliutils.arg(
    'instance',
    metavar='<instance>',
    nargs='+',
    help='Name or ID of the instance(s) to force delete.')
@api_versions.wraps("2.3")
@cliutils.arg(
    '--wait',
    action='store_true',
    help='Wait for share instance deletion')
@cliutils.service_type('sharev2')
def do_share_instance_force_delete(cs, args):
    """Force-delete the share instance, regardless of state (Admin only)."""
    failure_count = 0
    instances_to_delete = []
    for instance in args.instance:
        try:
            instance_ref = _find_share_instance(cs, instance)
            instances_to_delete.append(instance_ref)
            instance_ref.force_delete()
        except Exception as e:
            failure_count += 1
            print("Delete for share instance %s failed: %s" % (instance, e),
                  file=sys.stderr)
    if failure_count == len(args.instance):
        raise exceptions.CommandError("Unable to force delete any of "
                                      "specified share instances.")
    if args.wait:
        for instance in instances_to_delete:
            try:
                _wait_for_resource_status(
                    cs, instance, resource_type='share_instance',
                    expected_status='deleted')
            except exceptions.CommandError as e:
                print(e, file=sys.stderr)


@cliutils.arg(
    'instance',
    metavar='<instance>',
    help='Name or ID of the share instance to modify.')
@cliutils.arg(
    '--state',
    metavar='<state>',
    default='available',
    help=('Indicate which state to assign the instance. Options include '
          'available, error, creating, deleting, error_deleting, migrating,'
          'migrating_to. If no state is provided, available will be used.'))
@api_versions.wraps("2.3")
def do_share_instance_reset_state(cs, args):
    """Explicitly update the state of a share instance (Admin only)."""
    instance = _find_share_instance(cs, args.instance)
    instance.reset_state(args.state)


@api_versions.wraps("2.9")
@cliutils.arg(
    'instance',
    metavar='<instance>',
    help='Name or ID of the share instance.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,host,status".')
def do_share_instance_export_location_list(cs, args):
    """List export locations of a given share instance."""
    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    else:
        list_of_keys = [
            'ID',
            'Path',
            'Is Admin only',
            'Preferred',
        ]
    instance = _find_share_instance(cs, args.instance)
    export_locations = cs.share_instance_export_locations.list(instance)
    cliutils.print_list(export_locations, list_of_keys)


@api_versions.wraps("2.9")
@cliutils.arg(
    'instance',
    metavar='<instance>',
    help='Name or ID of the share instance.')
@cliutils.arg(
    'export_location',
    metavar='<export_location>',
    help='ID of the share instance export location.')
def do_share_instance_export_location_show(cs, args):
    """Show export location for the share instance."""
    instance = _find_share_instance(cs, args.instance)
    export_location = cs.share_instance_export_locations.get(
        instance, args.export_location)
    view_data = export_location._info.copy()
    cliutils.print_dict(view_data)


@cliutils.arg(
    '--all-tenants', '--all-projects',
    action='single_alias',
    dest='all_projects',
    metavar='<0|1>',
    nargs='?',
    type=int,
    const=1,
    default=0,
    help='Display information from all projects (Admin only).')
@cliutils.arg(
    '--name',
    metavar='<name>',
    type=str,
    default=None,
    help='Filter results by name.')
@cliutils.arg(
    '--description',
    metavar='<description>',
    type=str,
    default=None,
    help='Filter results by description. '
         'Available only for microversion >= 2.36.')
@cliutils.arg(
    '--status',
    metavar='<status>',
    default=None,
    help='Filter results by status.')
@cliutils.arg(
    '--share-id',
    '--share_id',  # alias
    metavar='<share_id>',
    default=None,
    action='single_alias',
    help='Filter results by source share ID.')
@cliutils.arg(
    '--usage',
    dest='usage',
    metavar='any|used|unused',
    nargs='?',
    type=str,
    const='any',
    default=None,
    choices=['any', 'used', 'unused', ],
    help='Either filter or not snapshots by its usage. OPTIONAL: Default=any.')
@cliutils.arg(
    '--limit',
    metavar='<limit>',
    type=int,
    default=None,
    help='Maximum number of share snapshots to return. '
         'OPTIONAL: Default=None.')
@cliutils.arg(
    '--offset',
    metavar='<offset>',
    type=int,
    default=None,
    help='Set offset to define start point of share snapshots listing. '
         'OPTIONAL: Default=None.')
@cliutils.arg(
    '--sort-key',
    '--sort_key',  # alias
    metavar='<sort_key>',
    type=str,
    default=None,
    action='single_alias',
    help='Key to be sorted, available keys are %(keys)s. '
         'Default=None.' % {'keys': constants.SNAPSHOT_SORT_KEY_VALUES})
@cliutils.arg(
    '--sort-dir',
    '--sort_dir',  # alias
    metavar='<sort_dir>',
    type=str,
    default=None,
    action='single_alias',
    help='Sort direction, available values are %(values)s. '
         'OPTIONAL: Default=None.' % {'values': constants.SORT_DIR_VALUES})
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,name".')
@cliutils.arg(
    '--name~',
    metavar='<name~>',
    type=str,
    default=None,
    help='Filter results matching a share snapshot name pattern. '
         'Available only for microversion >= 2.36.')
@cliutils.arg(
    '--description~',
    metavar='<description~>',
    type=str,
    default=None,
    help='Filter results matching a share snapshot description pattern. '
         'Available only for microversion >= 2.36.')
def do_snapshot_list(cs, args):
    """List all the snapshots."""
    all_projects = int(
        os.environ.get("ALL_TENANTS",
                       os.environ.get("ALL_PROJECTS",
                                      args.all_projects))
    )
    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    else:
        list_of_keys = [
            'ID', 'Share ID', 'Status', 'Name', 'Share Size',
        ]
        if all_projects:
            list_of_keys.append('Project ID')

    empty_obj = type('Empty', (object,), {'id': None})
    share = _find_share(cs, args.share_id) if args.share_id else empty_obj
    search_opts = {
        'offset': args.offset,
        'limit': args.limit,
        'all_tenants': all_projects,
        'name': args.name,
        'status': args.status,
        'share_id': share.id,
        'usage': args.usage,
    }
    if cs.api_version.matches(api_versions.APIVersion("2.36"),
                              api_versions.APIVersion()):
        search_opts['name~'] = getattr(args, 'name~')
        search_opts['description~'] = getattr(args, 'description~')
        search_opts['description'] = getattr(args, 'description')
    elif (getattr(args, 'name~') or getattr(args, 'description~') or
          getattr(args, 'description')):
        raise exceptions.CommandError(
            "Pattern based filtering (name~, description~ and description)"
            " is only available with manila API version >= 2.36")

    snapshots = cs.share_snapshots.list(
        search_opts=search_opts,
        sort_key=args.sort_key,
        sort_dir=args.sort_dir,
    )
    cliutils.print_list(snapshots, list_of_keys, sortby_index=None)


@cliutils.arg(
    'snapshot',
    metavar='<snapshot>',
    help='Name or ID of the snapshot.')
def do_snapshot_show(cs, args):
    """Show details about a snapshot."""
    snapshot = _find_share_snapshot(cs, args.snapshot)
    export_locations = cs.share_snapshot_export_locations.list(
        snapshot=snapshot)
    snapshot._info['export_locations'] = export_locations
    _print_share_snapshot(cs, snapshot)


@api_versions.wraps("2.32")
@cliutils.arg(
    'snapshot',
    metavar='<snapshot>',
    help='Name or ID of the snapshot.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,path".')
def do_snapshot_export_location_list(cs, args):
    """List export locations of a given snapshot."""
    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    else:
        list_of_keys = [
            'ID',
            'Path',
        ]
    snapshot = _find_share_snapshot(cs, args.snapshot)
    export_locations = cs.share_snapshot_export_locations.list(
        snapshot)
    cliutils.print_list(export_locations, list_of_keys)


@api_versions.wraps("2.32")
@cliutils.arg(
    'instance',
    metavar='<instance>',
    help='Name or ID of the snapshot instance.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,path,is_admin_only".')
def do_snapshot_instance_export_location_list(cs, args):
    """List export locations of a given snapshot instance."""
    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    else:
        list_of_keys = [
            'ID',
            'Path',
            'Is Admin only',
        ]
    instance = _find_share_snapshot_instance(cs, args.instance)
    export_locations = cs.share_snapshot_instance_export_locations.list(
        instance)
    cliutils.print_list(export_locations, list_of_keys)


@api_versions.wraps("2.32")
@cliutils.arg(
    'snapshot',
    metavar='<snapshot>',
    help='Name or ID of the snapshot.')
@cliutils.arg(
    'export_location',
    metavar='<export_location>',
    help='ID of the share snapshot export location.')
def do_snapshot_export_location_show(cs, args):
    """Show export location of the share snapshot."""
    snapshot = _find_share_snapshot(cs, args.snapshot)
    export_location = cs.share_snapshot_export_locations.get(
        args.export_location, snapshot)
    view_data = export_location._info.copy()
    cliutils.print_dict(view_data)


@api_versions.wraps("2.32")
@cliutils.arg(
    'snapshot_instance',
    metavar='<snapshot_instance>',
    help='ID of the share snapshot instance.')
@cliutils.arg(
    'export_location',
    metavar='<export_location>',
    help='ID of the share snapshot instance export location.')
def do_snapshot_instance_export_location_show(cs, args):
    """Show export location of the share instance snapshot."""
    snapshot_instance = _find_share_snapshot_instance(cs,
                                                      args.snapshot_instance)
    export_location = cs.share_snapshot_instance_export_locations.get(
        args.export_location, snapshot_instance)

    view_data = export_location._info.copy()
    cliutils.print_dict(view_data)


@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share to snapshot.')
@cliutils.arg(
    '--force',
    metavar='<True|False>',
    help='Optional flag to indicate whether '
    'to snapshot a share even if it\'s busy. '
    '(Default=False)',
    default=False)
@cliutils.arg(
    '--name',
    metavar='<name>',
    default=None,
    help='Optional snapshot name. (Default=None)')
@cliutils.arg(
    '--description',
    metavar='<description>',
    default=None,
    help='Optional snapshot description. (Default=None)')
def do_snapshot_create(cs, args):
    """Add a new snapshot."""
    share = _find_share(cs, args.share)
    snapshot = cs.share_snapshots.create(share,
                                         args.force,
                                         args.name,
                                         args.description)
    _print_share_snapshot(cs, snapshot)


@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share to rename.')
@cliutils.arg(
    '--name',
    metavar='<name>',
    default=None,
    help='New name for the share.')
@cliutils.arg(
    '--description',
    metavar='<description>',
    help='Optional share description. (Default=None)',
    default=None)
@cliutils.arg(
    '--is-public',
    '--is_public',  # alias
    metavar='<is_public>',
    default=None,
    type=str,
    action="single_alias",
    help='Public share is visible for all projects.')
def do_update(cs, args):
    """Rename a share."""
    kwargs = {}

    if args.name is not None:
        kwargs['display_name'] = args.name
    if args.description is not None:
        kwargs['display_description'] = args.description
    if args.is_public is not None:
        kwargs['is_public'] = strutils.bool_from_string(args.is_public,
                                                        strict=True)
    if not kwargs:
        msg = "Must supply name, description or is_public value."
        raise exceptions.CommandError(msg)
    _find_share(cs, args.share).update(**kwargs)


@cliutils.arg(
    'snapshot',
    metavar='<snapshot>',
    help='Name or ID of the snapshot to rename.')
@cliutils.arg(
    'name',
    nargs='?',
    metavar='<name>',
    help='New name for the snapshot.')
@cliutils.arg(
    '--description',
    metavar='<description>',
    help='Optional snapshot description. (Default=None)',
    default=None)
def do_snapshot_rename(cs, args):
    """Rename a snapshot."""
    kwargs = {}

    if args.name is not None:
        kwargs['display_name'] = args.name
    if args.description is not None:
        kwargs['display_description'] = args.description
    if not kwargs:
        msg = "Must supply either name or description."
        raise exceptions.CommandError(msg)
    _find_share_snapshot(cs, args.snapshot).update(**kwargs)


@cliutils.arg(
    'snapshot',
    metavar='<snapshot>',
    nargs='+',
    help='Name or ID of the snapshot(s) to delete.')
def do_snapshot_delete(cs, args):
    """Remove one or more snapshots."""
    failure_count = 0

    for snapshot in args.snapshot:
        try:
            snapshot_ref = _find_share_snapshot(
                cs, snapshot)
            cs.share_snapshots.delete(snapshot_ref)
        except Exception as e:
            failure_count += 1
            print("Delete for snapshot %s failed: %s" % (
                snapshot, e), file=sys.stderr)

    if failure_count == len(args.snapshot):
        raise exceptions.CommandError("Unable to delete any of the specified "
                                      "snapshots.")


@cliutils.arg(
    'snapshot',
    metavar='<snapshot>',
    nargs='+',
    help='Name or ID of the snapshot(s) to force delete.')
@cliutils.arg(
    '--wait',
    action='store_true',
    help='Wait for snapshot to delete')
@cliutils.service_type('sharev2')
def do_snapshot_force_delete(cs, args):
    """Attempt force-deletion of one or more snapshots.

    Regardless of the state (Admin only).
    """
    failure_count = 0
    snapshots_to_delete = []

    for snapshot in args.snapshot:
        try:
            snapshot_ref = _find_share_snapshot(
                cs, snapshot)
            snapshots_to_delete.append(snapshot_ref)
            snapshot_ref.force_delete()
        except Exception as e:
            failure_count += 1
            print("Delete for snapshot %s failed: %s" % (
                snapshot, e), file=sys.stderr)

    if failure_count == len(args.snapshot):
        raise exceptions.CommandError("Unable to force delete any of the "
                                      "specified snapshots.")
    if args.wait:
        for snapshot in snapshots_to_delete:
            try:
                _wait_for_resource_status(
                    cs, snapshot, resource_type='snapshot',
                    expected_status='deleted')
            except exceptions.CommandError as e:
                print(e, file=sys.stderr)


@cliutils.arg(
    'snapshot',
    metavar='<snapshot>',
    help='Name or ID of the snapshot to modify.')
@cliutils.arg(
    '--state',
    metavar='<state>',
    default='available',
    help=('Indicate which state to assign the snapshot. '
          'Options include available, error, creating, deleting, '
          'error_deleting. If no state is provided, '
          'available will be used.'))
def do_snapshot_reset_state(cs, args):
    """Explicitly update the state of a snapshot (Admin only)."""
    snapshot = _find_share_snapshot(cs, args.snapshot)
    snapshot.reset_state(args.state)


@api_versions.wraps("2.19")
@cliutils.arg(
    '--snapshot',
    metavar='<snapshot>',
    default=None,
    help='Filter results by share snapshot ID.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id".')
@cliutils.arg(
    '--detailed',
    metavar='<detailed>',
    default=False,
    help='Show detailed information about snapshot instances.'
         ' (Default=False)')
def do_snapshot_instance_list(cs, args):
    """List share snapshot instances."""
    snapshot = (_find_share_snapshot(cs, args.snapshot)
                if args.snapshot else None)
    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    elif args.detailed:
        list_of_keys = ['ID', 'Snapshot ID', 'Status', 'Created_at',
                        'Updated_at', 'Share_id', 'Share_instance_id',
                        'Progress', 'Provider_location']
    else:
        list_of_keys = ['ID', 'Snapshot ID', 'Status']

    instances = cs.share_snapshot_instances.list(
        detailed=args.detailed, snapshot=snapshot)

    cliutils.print_list(instances, list_of_keys)


@api_versions.wraps("2.19")
@cliutils.arg(
    'snapshot_instance',
    metavar='<snapshot_instance>',
    help='ID of the share snapshot instance.')
def do_snapshot_instance_show(cs, args):
    """Show details about a share snapshot instance."""
    snapshot_instance = _find_share_snapshot_instance(
        cs, args.snapshot_instance)
    export_locations = (
        cs.share_snapshot_instance_export_locations.list(snapshot_instance))
    snapshot_instance._info['export_locations'] = export_locations
    _print_share_snapshot(cs, snapshot_instance)


@cliutils.arg(
    'snapshot_instance',
    metavar='<snapshot_instance>',
    help='ID of the snapshot instance to modify.')
@cliutils.arg(
    '--state',
    metavar='<state>',
    default='available',
    help=('Indicate which state to assign the snapshot instance. '
          'Options include available, error, creating, deleting, '
          'error_deleting. If no state is provided, available '
          'will be used.'))
@api_versions.wraps("2.19")
def do_snapshot_instance_reset_state(cs, args):
    """Explicitly update the state of a share snapshot instance."""
    snapshot_instance = _find_share_snapshot_instance(
        cs, args.snapshot_instance)
    snapshot_instance.reset_state(args.state)


@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share to modify.')
@cliutils.arg(
    '--state',
    metavar='<state>',
    default='available',
    help=('Indicate which state to assign the share. Options include '
          'available, error, creating, deleting, error_deleting. If no '
          'state is provided, available will be used.'))
def do_reset_state(cs, args):
    """Explicitly update the state of a share (Admin only)."""
    share = _find_share(cs, args.share)
    share.reset_state(args.state)


@api_versions.wraps("1.0", "2.25")
@cliutils.arg(
    '--nova-net-id',
    '--nova-net_id', '--nova_net_id', '--nova_net-id',  # aliases
    metavar='<nova-net-id>',
    default=None,
    action='single_alias',
    help="Nova net ID. Used to set up network for share servers. This "
         "option is deprecated and will be rejected in newer releases "
         "of OpenStack Manila.")
@cliutils.arg(
    '--neutron-net-id',
    '--neutron-net_id', '--neutron_net_id', '--neutron_net-id',
    metavar='<neutron-net-id>',
    default=None,
    action='single_alias',
    help="Neutron network ID. Used to set up network for share servers.")
@cliutils.arg(
    '--neutron-subnet-id',
    '--neutron-subnet_id', '--neutron_subnet_id', '--neutron_subnet-id',
    metavar='<neutron-subnet-id>',
    default=None,
    action='single_alias',
    help="Neutron subnet ID. Used to set up network for share servers. "
         "This subnet should belong to specified neutron network.")
@cliutils.arg(
    '--name',
    metavar='<name>',
    default=None,
    help="Share network name.")
@cliutils.arg(
    '--description',
    metavar='<description>',
    default=None,
    help="Share network description.")
def do_share_network_create(cs, args):
    """Create a share network to export shares to."""
    values = {
        'neutron_net_id': args.neutron_net_id,
        'neutron_subnet_id': args.neutron_subnet_id,
        'nova_net_id': args.nova_net_id,
        'name': args.name,
        'description': args.description,
    }
    share_network = cs.share_networks.create(**values)
    info = share_network._info.copy()
    cliutils.print_dict(info)


@api_versions.wraps("2.26")  # noqa
@cliutils.arg(
    '--neutron-net-id',
    '--neutron-net_id', '--neutron_net_id', '--neutron_net-id',
    metavar='<neutron-net-id>',
    default=None,
    action='single_alias',
    help="Neutron network ID. Used to set up network for share servers.")
@cliutils.arg(
    '--neutron-subnet-id',
    '--neutron-subnet_id', '--neutron_subnet_id', '--neutron_subnet-id',
    metavar='<neutron-subnet-id>',
    default=None,
    action='single_alias',
    help="Neutron subnet ID. Used to set up network for share servers. "
         "This subnet should belong to specified neutron network.")
@cliutils.arg(
    '--name',
    metavar='<name>',
    default=None,
    help="Share network name.")
@cliutils.arg(
    '--description',
    metavar='<description>',
    default=None,
    help="Share network description.")
@cliutils.arg(
    '--availability-zone', '--availability_zone', '--az',
    metavar='<availability_zone>',
    default=None,
    action='single_alias',
    help="Availability zone in which the subnet should be created. Share "
         "networks can have one or more subnets in different availability "
         "zones when the driver is operating with "
         "'driver_handles_share_servers' extra_spec set to True. Available "
         "only for microversion >= 2.51. (Default=None)")
def do_share_network_create(cs, args):  # noqa
    """Create a share network to export shares to."""
    values = {
        'neutron_net_id': args.neutron_net_id,
        'neutron_subnet_id': args.neutron_subnet_id,
        'name': args.name,
        'description': args.description,
    }
    if cs.api_version >= api_versions.APIVersion("2.51"):
        values['availability_zone'] = args.availability_zone
    elif args.availability_zone:
        raise exceptions.CommandError(
            "Creating share networks with a given az is only "
            "available with manila API version >= 2.51")
    share_network = cs.share_networks.create(**values)
    info = share_network._info.copy()
    cliutils.print_dict(info)


@api_versions.wraps("1.0", "2.25")
@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Name or ID of share network to update.')
@cliutils.arg(
    '--nova-net-id',
    '--nova-net_id', '--nova_net_id', '--nova_net-id',  # aliases
    metavar='<nova-net-id>',
    default=None,
    action='single_alias',
    help="Nova net ID. Used to set up network for share servers. This "
         "option is deprecated and will be rejected in newer releases "
         "of OpenStack Manila.")
@cliutils.arg(
    '--neutron-net-id',
    '--neutron-net_id', '--neutron_net_id', '--neutron_net-id',
    metavar='<neutron-net-id>',
    default=None,
    action='single_alias',
    help="Neutron network ID. Used to set up network for share servers.")
@cliutils.arg(
    '--neutron-subnet-id',
    '--neutron-subnet_id', '--neutron_subnet_id', '--neutron_subnet-id',
    metavar='<neutron-subnet-id>',
    default=None,
    action='single_alias',
    help="Neutron subnet ID. Used to set up network for share servers. "
         "This subnet should belong to specified neutron network.")
@cliutils.arg(
    '--name',
    metavar='<name>',
    default=None,
    help="Share network name.")
@cliutils.arg(
    '--description',
    metavar='<description>',
    default=None,
    help="Share network description.")
def do_share_network_update(cs, args):
    """Update share network data."""
    values = {
        'neutron_net_id': args.neutron_net_id,
        'neutron_subnet_id': args.neutron_subnet_id,
        'nova_net_id': args.nova_net_id,
        'name': args.name,
        'description': args.description,
    }
    share_network = _find_share_network(
        cs, args.share_network).update(**values)
    info = share_network._info.copy()
    cliutils.print_dict(info)


@api_versions.wraps("2.26")  # noqa
@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Name or ID of share network to update.')
@cliutils.arg(
    '--neutron-net-id',
    '--neutron-net_id', '--neutron_net_id', '--neutron_net-id',
    metavar='<neutron-net-id>',
    default=None,
    action='single_alias',
    help="Neutron network ID. Used to set up network for share servers. "
         "This option is deprecated for microversion >= 2.51.")
@cliutils.arg(
    '--neutron-subnet-id',
    '--neutron-subnet_id', '--neutron_subnet_id', '--neutron_subnet-id',
    metavar='<neutron-subnet-id>',
    default=None,
    action='single_alias',
    help="Neutron subnet ID. Used to set up network for share servers. "
         "This subnet should belong to specified neutron network. "
         "This option is deprecated for microversion >= 2.51.")
@cliutils.arg(
    '--name',
    metavar='<name>',
    default=None,
    help="Share network name.")
@cliutils.arg(
    '--description',
    metavar='<description>',
    default=None,
    help="Share network description.")
def do_share_network_update(cs, args):   # noqa
    """Update share network data."""

    values = {
        'neutron_net_id': args.neutron_net_id,
        'neutron_subnet_id': args.neutron_subnet_id,
        'name': args.name,
        'description': args.description,
    }
    share_network = _find_share_network(
        cs, args.share_network).update(**values)
    info = share_network._info.copy()
    cliutils.print_dict(info)


@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Name or ID of the share network to show.')
def do_share_network_show(cs, args):
    """Retrieve details for a share network."""
    share_network = _find_share_network(cs, args.share_network)
    info = share_network._info.copy()
    cliutils.print_dict(info)


@api_versions.wraps("1.0", "2.25")
@cliutils.arg(
    '--all-tenants', '--all-projects',
    action='single_alias',
    dest='all_projects',
    metavar='<0|1>',
    nargs='?',
    type=int,
    const=1,
    default=0,
    help='Display information from all projects (Admin only).')
@cliutils.arg(
    '--project-id',
    '--project_id',  # alias
    metavar='<project_id>',
    action='single_alias',
    default=None,
    help='Filter results by project ID.')
@cliutils.arg(
    '--name',
    metavar='<name>',
    default=None,
    help='Filter results by name.')
@cliutils.arg(
    '--created-since',
    '--created_since',  # alias
    metavar='<created_since>',
    action='single_alias',
    default=None,
    help='''Return only share networks created since given date. '''
         '''The date is in the format 'yyyy-mm-dd'.''')
@cliutils.arg(
    '--created-before',
    '--created_before',  # alias
    metavar='<created_before>',
    action='single_alias',
    default=None,
    help='''Return only share networks created until given date. '''
         '''The date is in the format 'yyyy-mm-dd'.''')
@cliutils.arg(
    '--security-service',
    '--security_service',  # alias
    metavar='<security_service>',
    action='single_alias',
    default=None,
    help='Filter results by attached security service.')
@cliutils.arg(
    '--nova-net-id',
    '--nova_net_id', '--nova_net-id', '--nova-net_id',  # aliases
    metavar='<nova_net_id>',
    action='single_alias',
    default=None,
    help='Filter results by Nova net ID. This option is deprecated and will '
         'be rejected in newer releases of OpenStack Manila.')
@cliutils.arg(
    '--neutron-net-id',
    '--neutron_net_id', '--neutron_net-id', '--neutron-net_id',  # aliases
    metavar='<neutron_net_id>',
    action='single_alias',
    default=None,
    help='Filter results by neutron net ID.')
@cliutils.arg(
    '--neutron-subnet-id',
    '--neutron_subnet_id', '--neutron-subnet_id',  # aliases
    '--neutron_subnet-id',  # alias
    metavar='<neutron_subnet_id>',
    action='single_alias',
    default=None,
    help='Filter results by neutron subnet ID.')
@cliutils.arg(
    '--network-type',
    '--network_type',  # alias
    metavar='<network_type>',
    action='single_alias',
    default=None,
    help='Filter results by network type.')
@cliutils.arg(
    '--segmentation-id',
    '--segmentation_id',  # alias
    metavar='<segmentation_id>',
    type=int,
    action='single_alias',
    default=None,
    help='Filter results by segmentation ID.')
@cliutils.arg(
    '--cidr',
    metavar='<cidr>',
    default=None,
    help='Filter results by CIDR.')
@cliutils.arg(
    '--ip-version',
    '--ip_version',  # alias
    metavar='<ip_version>',
    type=int,
    action='single_alias',
    default=None,
    help='Filter results by IP version.')
@cliutils.arg(
    '--offset',
    metavar='<offset>',
    type=int,
    default=None,
    help='Start position of share networks listing.')
@cliutils.arg(
    '--limit',
    metavar='<limit>',
    type=int,
    default=None,
    help='Number of share networks to return per request.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id".')
def do_share_network_list(cs, args):
    """Get a list of network info."""
    all_projects = int(
        os.environ.get("ALL_TENANTS",
                       os.environ.get("ALL_PROJECTS",
                                      args.all_projects))
    )
    search_opts = {
        'all_tenants': all_projects,
        'project_id': args.project_id,
        'name': args.name,
        'created_since': args.created_since,
        'created_before': args.created_before,
        'nova_net_id': args.nova_net_id,
        'neutron_net_id': args.neutron_net_id,
        'neutron_subnet_id': args.neutron_subnet_id,
        'network_type': args.network_type,
        'segmentation_id': args.segmentation_id,
        'cidr': args.cidr,
        'ip_version': args.ip_version,
        'offset': args.offset,
        'limit': args.limit,
    }
    if args.security_service:
        search_opts['security_service_id'] = _find_security_service(
            cs, args.security_service).id
    share_networks = cs.share_networks.list(search_opts=search_opts)
    fields = ['id', 'name']

    if args.columns is not None:
        fields = _split_columns(columns=args.columns)

    cliutils.print_list(share_networks, fields=fields)


@api_versions.wraps("2.26")  # noqa
@cliutils.arg(
    '--all-tenants', '--all-projects',
    action='single_alias',
    dest='all_projects',
    metavar='<0|1>',
    nargs='?',
    type=int,
    const=1,
    default=0,
    help='Display information from all projects (Admin only).')
@cliutils.arg(
    '--project-id',
    '--project_id',  # alias
    metavar='<project_id>',
    action='single_alias',
    default=None,
    help='Filter results by project ID.')
@cliutils.arg(
    '--name',
    metavar='<name>',
    type=str,
    default=None,
    help='Filter results by name.')
@cliutils.arg(
    '--description',
    metavar='<description>',
    type=str,
    default=None,
    help='Filter results by description. '
         'Available only for microversion >= 2.36.')
@cliutils.arg(
    '--created-since',
    '--created_since',  # alias
    metavar='<created_since>',
    action='single_alias',
    default=None,
    help='''Return only share networks created since given date. '''
         '''The date is in the format 'yyyy-mm-dd'.''')
@cliutils.arg(
    '--created-before',
    '--created_before',  # alias
    metavar='<created_before>',
    action='single_alias',
    default=None,
    help='''Return only share networks created until given date. '''
         '''The date is in the format 'yyyy-mm-dd'.''')
@cliutils.arg(
    '--security-service',
    '--security_service',  # alias
    metavar='<security_service>',
    action='single_alias',
    default=None,
    help='Filter results by attached security service.')
@cliutils.arg(
    '--neutron-net-id',
    '--neutron_net_id', '--neutron_net-id', '--neutron-net_id',  # aliases
    metavar='<neutron_net_id>',
    action='single_alias',
    default=None,
    help='Filter results by neutron net ID.')
@cliutils.arg(
    '--neutron-subnet-id',
    '--neutron_subnet_id', '--neutron-subnet_id',  # aliases
    '--neutron_subnet-id',  # alias
    metavar='<neutron_subnet_id>',
    action='single_alias',
    default=None,
    help='Filter results by neutron subnet ID.')
@cliutils.arg(
    '--network-type',
    '--network_type',  # alias
    metavar='<network_type>',
    action='single_alias',
    default=None,
    help='Filter results by network type.')
@cliutils.arg(
    '--segmentation-id',
    '--segmentation_id',  # alias
    metavar='<segmentation_id>',
    type=int,
    action='single_alias',
    default=None,
    help='Filter results by segmentation ID.')
@cliutils.arg(
    '--cidr',
    metavar='<cidr>',
    default=None,
    help='Filter results by CIDR.')
@cliutils.arg(
    '--ip-version',
    '--ip_version',  # alias
    metavar='<ip_version>',
    type=int,
    action='single_alias',
    default=None,
    help='Filter results by IP version.')
@cliutils.arg(
    '--offset',
    metavar='<offset>',
    type=int,
    default=None,
    help='Start position of share networks listing.')
@cliutils.arg(
    '--limit',
    metavar='<limit>',
    type=int,
    default=None,
    help='Number of share networks to return per request.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id".')
@cliutils.arg(
    '--name~',
    metavar='<name~>',
    type=str,
    default=None,
    help='Filter results matching a share network name pattern. '
         'Available only for microversion >= 2.36.')
@cliutils.arg(
    '--description~',
    metavar='<description~>',
    type=str,
    default=None,
    help='Filter results matching a share network description pattern. '
         'Available only for microversion >= 2.36.')
def do_share_network_list(cs, args):   # noqa
    """Get a list of share networks"""
    all_projects = int(
        os.environ.get("ALL_TENANTS",
                       os.environ.get("ALL_PROJECTS",
                                      args.all_projects))
    )
    search_opts = {
        'all_tenants': all_projects,
        'project_id': args.project_id,
        'name': args.name,
        'created_since': args.created_since,
        'created_before': args.created_before,
        'neutron_net_id': args.neutron_net_id,
        'neutron_subnet_id': args.neutron_subnet_id,
        'network_type': args.network_type,
        'segmentation_id': args.segmentation_id,
        'cidr': args.cidr,
        'ip_version': args.ip_version,
        'offset': args.offset,
        'limit': args.limit,
    }
    if cs.api_version.matches(api_versions.APIVersion("2.36"),
                              api_versions.APIVersion()):
        search_opts['name~'] = getattr(args, 'name~')
        search_opts['description~'] = getattr(args, 'description~')
        search_opts['description'] = getattr(args, 'description')
    elif (getattr(args, 'name~') or getattr(args, 'description~') or
          getattr(args, 'description')):
        raise exceptions.CommandError(
            "Pattern based filtering (name~, description~ and description)"
            " is only available with manila API version >= 2.36")

    if args.security_service:
        search_opts['security_service_id'] = _find_security_service(
            cs, args.security_service).id
    share_networks = cs.share_networks.list(search_opts=search_opts)
    fields = ['id', 'name']

    if args.columns is not None:
        fields = _split_columns(columns=args.columns)

    cliutils.print_list(share_networks, fields=fields)


@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Share network name or ID.')
@cliutils.arg(
    'security_service',
    metavar='<security-service>',
    help='Security service name or ID to associate with.')
def do_share_network_security_service_add(cs, args):
    """Associate security service with share network."""
    share_network = _find_share_network(cs, args.share_network)
    security_service = _find_security_service(cs, args.security_service)
    cs.share_networks.add_security_service(share_network, security_service)


@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Share network name or ID.')
@cliutils.arg(
    'security_service',
    metavar='<security-service>',
    help='Security service name or ID to associate with.')
@cliutils.arg(
    '--reset',
    metavar='<True|False>',
    choices=['True', 'False'],
    required=False,
    default=False,
    help='Reset and restart the check operation.'
         '(Optional, Default=False)')
@api_versions.wraps("2.63")
def do_share_network_security_service_add_check(cs, args):
    """Associate security service with share network."""
    share_network = _find_share_network(cs, args.share_network)
    security_service = _find_security_service(cs, args.security_service)
    add_sec_service_result = cs.share_networks.add_security_service_check(
        share_network, security_service, reset_operation=args.reset)
    # result[0] is response code, result[1] is dict body
    cliutils.print_dict(add_sec_service_result[1])


@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Share network name or ID.')
@cliutils.arg(
    'security_service',
    metavar='<security-service>',
    help='Security service name or ID to dissociate.')
def do_share_network_security_service_remove(cs, args):
    """Dissociate security service from share network."""
    share_network = _find_share_network(cs, args.share_network)
    security_service = _find_security_service(cs, args.security_service)
    cs.share_networks.remove_security_service(share_network, security_service)


@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Share network name or ID.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,name".')
def do_share_network_security_service_list(cs, args):
    """Get list of security services associated with a given share network."""
    share_network = _find_share_network(cs, args.share_network)
    search_opts = {
        'share_network_id': share_network.id,
    }
    security_services = cs.security_services.list(search_opts=search_opts)
    fields = ['id', 'name', 'status', 'type', ]

    if args.columns is not None:
        fields = _split_columns(columns=args.columns)

    cliutils.print_list(security_services, fields=fields)


@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Share network name or ID.')
@cliutils.arg(
    'current_security_service',
    metavar='<current-security-service>',
    help='Current security service name or ID.')
@cliutils.arg(
    'new_security_service',
    metavar='<new-security-service>',
    help='New security service name or ID.')
@api_versions.wraps("2.63")
def do_share_network_security_service_update(cs, args):
    """Update a current security service to a new security service."""
    share_network = _find_share_network(cs, args.share_network)
    current_security_service = _find_security_service(
        cs, args.current_security_service)
    new_security_service = _find_security_service(
        cs, args.new_security_service)
    cs.share_networks.update_share_network_security_service(
        share_network, current_security_service, new_security_service)


@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Share network name or ID.')
@cliutils.arg(
    'current_security_service',
    metavar='<current-security-service>',
    help='Current security service name or ID.')
@cliutils.arg(
    'new_security_service',
    metavar='<new-security-service>',
    help='New security service name or ID.')
@cliutils.arg(
    '--reset',
    metavar='<True|False>',
    choices=['True', 'False'],
    required=False,
    default=False,
    help='Reset and start again the check operation.'
         '(Optional, Default=False)')
@api_versions.wraps("2.63")
def do_share_network_security_service_update_check(cs, args):
    """Check if a security service update on the share network is supported.

    This call can be repeated until a successful result is obtained.
    """
    share_network = _find_share_network(cs, args.share_network)
    current_security_service = _find_security_service(
        cs, args.current_security_service)
    new_security_service = _find_security_service(
        cs, args.new_security_service)
    share_network_update_check = (
        cs.share_networks.update_share_network_security_service_check(
            share_network, current_security_service, new_security_service,
            reset_operation=args.reset))
    # result[0] is response code, result[1] is dict body
    cliutils.print_dict(share_network_update_check[1])


@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Share network name or ID.')
@cliutils.arg(
    '--state',
    metavar='<state>',
    default=constants.STATUS_ACTIVE,
    help=('Indicate which state to assign the share network. Options include '
          'active, error, network change. If no state is provided, active '
          'will be used.'))
@api_versions.wraps("2.63")
def do_share_network_reset_state(cs, args):
    """Explicitly update the state of a share network (Admin only)."""
    share_network = _find_share_network(cs, args.share_network)
    cs.share_networks.reset_state(share_network, args.state)


@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Share network name or ID.')
@cliutils.arg(
    '--neutron-net-id',
    '--neutron-net_id', '--neutron_net_id', '--neutron_net-id',
    metavar='<neutron-net-id>',
    default=None,
    action='single_alias',
    help="Neutron network ID. Used to set up network for share servers. "
         "Optional, Default = None.")
@cliutils.arg(
    '--neutron-subnet-id',
    '--neutron-subnet_id', '--neutron_subnet_id', '--neutron_subnet-id',
    metavar='<neutron-subnet-id>',
    default=None,
    action='single_alias',
    help="Neutron subnet ID. Used to set up network for share servers. "
         "This subnet should belong to specified neutron network. "
         "Optional, Default = None.")
@cliutils.arg(
    '--availability-zone',
    '--availability_zone',
    '--az',
    default=None,
    action='single_alias',
    metavar='<availability-zone>',
    help='Optional availability zone that the subnet is available within '
         '(Default=None). If None, the subnet will be considered as being '
         'available across all availability zones.')
def do_share_network_subnet_create(cs, args):
    """Add a new subnet into a share network."""
    if xor(bool(args.neutron_net_id), bool(args.neutron_subnet_id)):
        raise exceptions.CommandError(
            "Both neutron_net_id and neutron_subnet_id should be specified. "
            "Alternatively, neither of them should be specified.")

    share_network = _find_share_network(cs, args.share_network)
    values = {
        'share_network_id': share_network.id,
        'neutron_net_id': args.neutron_net_id,
        'neutron_subnet_id': args.neutron_subnet_id,
        'availability_zone': args.availability_zone,
    }
    share_network_subnet = cs.share_network_subnets.create(**values)
    info = share_network_subnet._info.copy()
    cliutils.print_dict(info)


@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Share network name or ID.')
@cliutils.arg(
    '--neutron-net-id',
    '--neutron-net_id', '--neutron_net_id', '--neutron_net-id',
    metavar='<neutron-net-id>',
    default=None,
    action='single_alias',
    help="Neutron network ID. Used to set up network for share servers. "
         "Optional, Default = None.")
@cliutils.arg(
    '--neutron-subnet-id',
    '--neutron-subnet_id', '--neutron_subnet_id', '--neutron_subnet-id',
    metavar='<neutron-subnet-id>',
    default=None,
    action='single_alias',
    help="Neutron subnet ID. Used to set up network for share servers. "
         "This subnet should belong to specified neutron network. "
         "Optional, Default = None.")
@cliutils.arg(
    '--availability-zone',
    '--availability_zone',
    '--az',
    default=None,
    action='single_alias',
    metavar='<availability-zone>',
    help='Optional availability zone that the subnet is available within '
         '(Default=None). If None, the subnet will be considered as being '
         'available across all availability zones.')
@cliutils.arg(
    '--reset',
    metavar='<True|False>',
    choices=['True', 'False'],
    required=False,
    default=False,
    help='Reset and start again the check operation.'
         '(Optional, Default=False)')
@api_versions.wraps("2.70")
def do_share_network_subnet_create_check(cs, args):
    """Check if a new subnet can be added to a share network."""
    if xor(bool(args.neutron_net_id), bool(args.neutron_subnet_id)):
        raise exceptions.CommandError(
            "Both neutron_net_id and neutron_subnet_id should be specified. "
            "Alternatively, neither of them should be specified.")

    share_network = _find_share_network(cs, args.share_network)
    values = {
        'neutron_net_id': args.neutron_net_id,
        'neutron_subnet_id': args.neutron_subnet_id,
        'availability_zone': args.availability_zone,
        'reset_operation': args.reset,
    }
    subnet_create_check = (
        cs.share_networks.share_network_subnet_create_check(
            share_network.id, **values))
    # result[0] is response code, result[1] is dict body
    cliutils.print_dict(subnet_create_check[1])


@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Share network name or ID.')
@cliutils.arg(
    'share_network_subnet',
    metavar='<share-network-subnet>',
    nargs='+',
    help='Name or ID of share network subnet(s) to be deleted.')
def do_share_network_subnet_delete(cs, args):
    """Delete one or more share network subnets."""
    failure_count = 0
    share_network_ref = _find_share_network(cs, args.share_network)

    for subnet in args.share_network_subnet:
        try:
            cs.share_network_subnets.delete(share_network_ref, subnet)
        except Exception as e:
            failure_count += 1
            print("Deletion of share network subnet %s failed: %s" % (
                subnet, e), file=sys.stderr)

    if failure_count == len(args.share_network_subnet):
        raise exceptions.CommandError("Unable to delete any of the specified "
                                      "share network subnets.")


@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    help='Name or ID of share network(s) to which the subnet belongs.')
@cliutils.arg(
    'share_network_subnet',
    metavar='<share-network-subnet>',
    help='Share network subnet ID to show.')
def do_share_network_subnet_show(cs, args):
    """Show share network subnet."""
    share_network = _find_share_network(cs, args.share_network)
    share_network_subnet = cs.share_network_subnets.get(
        share_network.id, args.share_network_subnet)
    view_data = share_network_subnet._info.copy()
    cliutils.print_dict(view_data)


@cliutils.arg(
    'share_network',
    metavar='<share-network>',
    nargs='+',
    help='Name or ID of share network(s) to be deleted.')
def do_share_network_delete(cs, args):
    """Delete one or more share networks."""
    failure_count = 0

    for share_network in args.share_network:
        try:
            share_ref = _find_share_network(
                cs, share_network)
            cs.share_networks.delete(share_ref)
        except Exception as e:
            failure_count += 1
            print("Delete for share network %s failed: %s" % (
                share_network, e), file=sys.stderr)

    if failure_count == len(args.share_network):
        raise exceptions.CommandError("Unable to delete any of the specified "
                                      "share networks.")


@cliutils.arg(
    'type',
    metavar='<type>',
    help="Security service type: 'ldap', 'kerberos' or 'active_directory'.")
@cliutils.arg(
    '--dns-ip',
    metavar='<dns_ip>',
    default=None,
    help="DNS IP address used inside project's network.")
@cliutils.arg(
    '--ou',
    metavar='<ou>',
    default=None,
    help="Security service OU (Organizational Unit). Available only for "
         "microversion >= 2.44.")
@cliutils.arg(
    '--server',
    metavar='<server>',
    default=None,
    help="Security service IP address or hostname.")
@cliutils.arg(
    '--domain',
    metavar='<domain>',
    default=None,
    help="Security service domain.")
@cliutils.arg(
    '--user',
    metavar='<user>',
    default=None,
    help="Security service user or group used by project.")
@cliutils.arg(
    '--password',
    metavar='<password>',
    default=None,
    help="Password used by user.")
@cliutils.arg(
    '--name',
    metavar='<name>',
    default=None,
    help="Security service name.")
@cliutils.arg(
    '--description',
    metavar='<description>',
    default=None,
    help="Security service description.")
def do_security_service_create(cs, args):
    """Create security service used by project."""
    values = {
        'dns_ip': args.dns_ip,
        'server': args.server,
        'domain': args.domain,
        'user': args.user,
        'password': args.password,
        'name': args.name,
        'description': args.description,
    }

    if cs.api_version.matches(api_versions.APIVersion("2.44"),
                              api_versions.APIVersion()):
        values['ou'] = args.ou
    elif args.ou:
        raise exceptions.CommandError(
            "Security service Organizational Unit (ou) option "
            "is only available with manila API version >= 2.44")

    security_service = cs.security_services.create(args.type, **values)
    info = security_service._info.copy()
    cliutils.print_dict(info)


@cliutils.arg(
    'security_service',
    metavar='<security-service>',
    help='Security service name or ID to update.')
@cliutils.arg(
    '--dns-ip',
    metavar='<dns-ip>',
    default=None,
    help="DNS IP address used inside project's network.")
@cliutils.arg(
    '--ou',
    metavar='<ou>',
    default=None,
    help="Security service OU (Organizational Unit). Available only for "
         "microversion >= 2.44.")
@cliutils.arg(
    '--server',
    metavar='<server>',
    default=None,
    help="Security service IP address or hostname.")
@cliutils.arg(
    '--domain',
    metavar='<domain>',
    default=None,
    help="Security service domain.")
@cliutils.arg(
    '--user',
    metavar='<user>',
    default=None,
    help="Security service user or group used by project.")
@cliutils.arg(
    '--password',
    metavar='<password>',
    default=None,
    help="Password used by user.")
@cliutils.arg(
    '--name',
    metavar='<name>',
    default=None,
    help="Security service name.")
@cliutils.arg(
    '--description',
    metavar='<description>',
    default=None,
    help="Security service description.")
def do_security_service_update(cs, args):
    """Update security service."""
    values = {
        'dns_ip': args.dns_ip,
        'server': args.server,
        'domain': args.domain,
        'user': args.user,
        'password': args.password,
        'name': args.name,
        'description': args.description,
    }

    if cs.api_version.matches(api_versions.APIVersion("2.44"),
                              api_versions.APIVersion()):
        values['ou'] = args.ou
    elif args.ou:
        raise exceptions.CommandError(
            "Security service Organizational Unit (ou) option "
            "is only available with manila API version >= 2.44")

    security_service = _find_security_service(
        cs, args.security_service).update(**values)
    cliutils.print_dict(security_service._info)


@cliutils.arg(
    'security_service',
    metavar='<security-service>',
    help='Security service name or ID to show.')
def do_security_service_show(cs, args):
    """Show security service."""
    security_service = _find_security_service(cs, args.security_service)
    info = security_service._info.copy()
    cliutils.print_dict(info)


@cliutils.arg(
    '--all-tenants', '--all-projects',
    action='single_alias',
    dest='all_projects',
    metavar='<0|1>',
    nargs='?',
    type=int,
    const=1,
    default=0,
    help='Display information from all projects (Admin only).')
@cliutils.arg(
    '--share-network',
    '--share_network',  # alias
    metavar='<share_network>',
    action='single_alias',
    default=None,
    help='Filter results by share network id or name.')
@cliutils.arg(
    '--status',
    metavar='<status>',
    default=None,
    help='Filter results by status.')
@cliutils.arg(
    '--name',
    metavar='<name>',
    default=None,
    help='Filter results by name.')
@cliutils.arg(
    '--type',
    metavar='<type>',
    default=None,
    help='Filter results by type.')
@cliutils.arg(
    '--user',
    metavar='<user>',
    default=None,
    help='Filter results by user or group used by projects.')
@cliutils.arg(
    '--dns-ip',
    '--dns_ip',  # alias
    metavar='<dns_ip>',
    action='single_alias',
    default=None,
    help="Filter results by DNS IP address used inside project's network.")
@cliutils.arg(
    '--ou',
    metavar='<ou>',
    default=None,
    help="Filter results by security service OU (Organizational Unit)."
         " Available only for microversion >= 2.44.")
@cliutils.arg(
    '--server',
    metavar='<server>',
    default=None,
    help="Filter results by security service IP address or hostname.")
@cliutils.arg(
    '--domain',
    metavar='<domain>',
    default=None,
    help="Filter results by domain.")
@cliutils.arg(
    '--detailed',
    dest='detailed',
    metavar='<0|1>',
    nargs='?',
    type=int,
    const=1,
    default=0,
    help="Show detailed information about filtered security services.")
@cliutils.arg(
    '--offset',
    metavar="<offset>",
    default=None,
    help='Start position of security services listing.')
@cliutils.arg(
    '--limit',
    metavar="<limit>",
    default=None,
    help='Number of security services to return per request.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "name,type".')
def do_security_service_list(cs, args):
    """Get a list of security services."""
    all_projects = int(
        os.environ.get("ALL_TENANTS",
                       os.environ.get("ALL_PROJECTS",
                                      args.all_projects))
    )
    search_opts = {
        'all_tenants': all_projects,
        'status': args.status,
        'name': args.name,
        'type': args.type,
        'user': args.user,
        'dns_ip': args.dns_ip,
        'server': args.server,
        'domain': args.domain,
        'offset': args.offset,
        'limit': args.limit,
    }

    if cs.api_version.matches(api_versions.APIVersion("2.44"),
                              api_versions.APIVersion()):
        search_opts['ou'] = args.ou
    elif args.ou:
        raise exceptions.CommandError(
            "Security service Organizational Unit (ou) option "
            "is only available with manila API version >= 2.44")

    if args.share_network:
        search_opts['share_network_id'] = _find_share_network(
            cs, args.share_network).id
    security_services = cs.security_services.list(search_opts=search_opts,
                                                  detailed=args.detailed)
    fields = ['id', 'name', 'status', 'type', ]
    if args.columns is not None:
        fields = _split_columns(columns=args.columns)

    if args.detailed:
        fields.append('share_networks')
    cliutils.print_list(security_services, fields=fields)


@cliutils.arg(
    'security_service',
    metavar='<security-service>',
    nargs='+',
    help='Name or ID of the security service(s) to delete.')
def do_security_service_delete(cs, args):
    """Delete one or more security services."""
    failure_count = 0

    for security_service in args.security_service:
        try:
            security_ref = _find_security_service(
                cs, security_service)
            cs.security_services.delete(security_ref)
        except Exception as e:
            failure_count += 1
            print("Delete for security service %s failed: %s" % (
                security_service, e), file=sys.stderr)

    if failure_count == len(args.security_service):
        raise exceptions.CommandError("Unable to delete any of the specified "
                                      "security services.")


@cliutils.arg(
    '--host',
    metavar='<hostname>',
    default=None,
    help='Filter results by name of host.')
@cliutils.arg(
    '--status',
    metavar='<status>',
    default=None,
    help='Filter results by status.')
@cliutils.arg(
    '--share-network',
    metavar='<share_network>',
    default=None,
    help='Filter results by share network.')
@cliutils.arg(
    '--project-id',
    metavar='<project_id>',
    default=None,
    help='Filter results by project ID.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,host,status".')
@cliutils.arg(
    '--share-network-subnet', '--share_network_subnet',
    type=str,
    metavar='<share_network_subnet>',
    help="Filter results by share network subnet that the share server's "
         "network allocation exists whithin. Available for micro version "
         ">= 2.51 (Optional, Default=None).",
    default=None)
def do_share_server_list(cs, args):
    """List all share servers (Admin only)."""
    search_opts = {
        "host": args.host,
        "share_network": args.share_network,
        "status": args.status,
        "project_id": args.project_id,
    }
    fields = [
        "Id",
        "Host",
        "Status",
        "Share Network",
        "Project Id",
        "Updated_at",
    ]

    if cs.api_version < api_versions.APIVersion("2.51"):
        if getattr(args, 'share_network_subnet'):
            raise exceptions.CommandError(
                "Share network subnet option is only available with manila "
                "API version >= 2.51")
    elif cs.api_version < api_versions.APIVersion("2.70"):
        search_opts.update({
            'share_network_subnet_id': args.share_network_subnet})
        fields.append("Share Network Subnet Id")
    else:
        search_opts.update({
            'share_network_subnet_id': args.share_network_subnet})
        fields.append("Share Network Subnet IDs")

    if args.columns is not None:
        fields = _split_columns(columns=args.columns)

    share_servers = cs.share_servers.list(search_opts=search_opts)
    cliutils.print_list(share_servers, fields=fields)


@cliutils.arg(
    'id',
    metavar='<id>',
    type=str,
    help='ID of share server.')
def do_share_server_show(cs, args):
    """Show share server info (Admin only)."""
    share_server = cs.share_servers.get(args.id)
    # All 'backend_details' data already present as separated strings,
    # so remove big dict from view.
    if "backend_details" in share_server._info:
        del share_server._info["backend_details"]
    cliutils.print_dict(share_server._info)


@cliutils.arg(
    'id',
    metavar='<id>',
    type=str,
    help='ID of share server.')
def do_share_server_details(cs, args):
    """Show share server details (Admin only)."""
    details = cs.share_servers.details(args.id)
    cliutils.print_dict(details._info)


@cliutils.arg(
    'id',
    metavar='<id>',
    nargs='+',
    type=str,
    help='ID of the share server(s) to delete.')
@cliutils.arg(
    '--wait',
    action='store_true',
    help='Wait for share server to delete')
@cliutils.service_type('sharev2')
def do_share_server_delete(cs, args):
    """Delete one or more share servers (Admin only)."""

    failure_count = 0
    share_servers_to_delete = []

    for server_id in args.id:
        try:
            id_ref = _find_share_server(cs, server_id)
            share_servers_to_delete.append(id_ref)
            id_ref.delete()
        except Exception as e:
            failure_count += 1
            print("Delete for share server %s failed: %s" % (
                server_id, e), file=sys.stderr)

    if failure_count == len(args.id):
        raise exceptions.CommandError("Unable to delete any of the specified "
                                      "share servers.")

    if args.wait:
        for share_server in share_servers_to_delete:
            try:
                _wait_for_resource_status(
                    cs, share_server, resource_type='share_server',
                    expected_status='deleted')
            except exceptions.CommandError as e:
                print(e, file=sys.stderr)


@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,name".')
def do_availability_zone_list(cs, args):
    """List all availability zones."""

    if args.columns is not None:
        fields = _split_columns(columns=args.columns)
    else:
        fields = ("Id", "Name", "Created_At", "Updated_At")

    availability_zones = cs.availability_zones.list()
    cliutils.print_list(availability_zones, fields=fields)


@cliutils.arg(
    '--host',
    metavar='<hostname>',
    default=None,
    help='Name of host.')
@cliutils.arg(
    '--binary',
    metavar='<binary>',
    default=None,
    help='Service binary.')
@cliutils.arg(
    '--status',
    metavar='<status>',
    default=None,
    help='Filter results by status.')
@cliutils.arg(
    '--state',
    metavar='<state>',
    default=None,
    help='Filter results by state.')
@cliutils.arg(
    '--zone',
    metavar='<zone>',
    default=None,
    help='Availability zone.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,host".')
def do_service_list(cs, args):
    """List all services (Admin only)."""
    search_opts = {
        'status': args.status,
        'host': args.host,
        'binary': args.binary,
        'zone': args.zone,
        'state': args.state,
    }
    fields = ["Id", "Binary", "Host", "Zone", "Status", "State", "Updated_at"]

    if args.columns is not None:
        fields = _split_columns(columns=args.columns)

    services = cs.services.list(search_opts=search_opts)
    cliutils.print_list(services, fields=fields)


@cliutils.arg(
    'host',
    metavar='<hostname>',
    help="Host name as 'example_host@example_backend'.")
@cliutils.arg(
    'binary',
    metavar='<binary>',
    help="Service binary, could be 'manila-share' or 'manila-scheduler'.")
def do_service_enable(cs, args):
    """Enables 'manila-share' or 'manila-scheduler' services (Admin only)."""
    columns = ("Host", "Binary", "Enabled")
    result = cs.services.enable(args.host, args.binary)
    result.enabled = not result.disabled
    cliutils.print_list([result], columns)


@cliutils.arg(
    'host',
    metavar='<hostname>',
    help="Host name as 'example_host@example_backend'.")
@cliutils.arg(
    'binary',
    metavar='<binary>',
    help="Service binary, could be 'manila-share' or 'manila-scheduler'.")
def do_service_disable(cs, args):
    """Disables 'manila-share' or 'manila-scheduler' services (Admin only)."""
    columns = ("Host", "Binary", "Enabled")
    result = cs.services.disable(args.host, args.binary)
    result.enabled = not result.disabled
    cliutils.print_list([result], columns)


def _print_dict(data_dict):
    formatted_data = []

    for date in data_dict:
        formatted_data.append("%s : %s" % (date, data_dict[date]))

    return "\n".join(formatted_data)


@cliutils.arg(
    '--host',
    metavar='<host>',
    type=str,
    default='.*',
    help='Filter results by host name.  Regular expressions are supported.')
@cliutils.arg(
    '--backend',
    metavar='<backend>',
    type=str,
    default='.*',
    help='Filter results by backend name.  Regular expressions are supported.')
@cliutils.arg(
    '--pool',
    metavar='<pool>',
    type=str,
    default='.*',
    help='Filter results by pool name.  Regular expressions are supported.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "name,host".')
@cliutils.arg(
    '--detail', '--detailed',
    action='store_true',
    help='Show detailed information about pools. If this parameter is set '
         'to True, --columns parameter will be ignored if present. '
         '(Default=False)')
@cliutils.arg(
    '--share-type', '--share_type',
    '--share-type-id', '--share_type_id',
    metavar='<share_type>',
    type=str,
    default=None,
    action='single_alias',
    help='Filter results by share type name or ID. (Default=None)'
         'Available only for microversion >= 2.23.')
def do_pool_list(cs, args):
    """List all backend storage pools known to the scheduler (Admin only)."""

    search_opts = {
        'host': args.host,
        'backend': args.backend,
        'pool': args.pool,
        'share_type': args.share_type,
    }

    if args.detail:
        fields = ["Name", "Host", "Backend", "Pool", "Capabilities"]
    else:
        fields = ["Name", "Host", "Backend", "Pool"]

    pools = cs.pools.list(detailed=args.detail, search_opts=search_opts)
    if args.columns is not None:
        fields = _split_columns(columns=args.columns)
        pools = cs.pools.list(detailed=True, search_opts=search_opts)

    if args.detail:
        for info in pools:
            backend = dict()
            backend['name'] = info.name
            backend.update(info.capabilities)
            cliutils.print_dict(backend)
    else:
        cliutils.print_list(pools, fields=fields)


@cliutils.arg('share', metavar='<share>',
              help='Name or ID of share to extend.')
@cliutils.arg('new_size',
              metavar='<new_size>',
              type=int,
              help='New size of share, in GiBs.')
@cliutils.arg(
    '--wait',
    action='store_true',
    help='Wait for share extension')
@cliutils.arg(
    '--force',
    action='store_true',
    help='Force attempt the extension of the share, only available with '
         'microversion 2.64 and higher. (admin only)')
@cliutils.service_type('sharev2')
def do_extend(cs, args):
    """Increases the size of an existing share."""
    share = _find_share(cs, args.share)
    force = False
    if args.force:
        if cs.api_version < api_versions.APIVersion("2.64"):
            raise exceptions.CommandError(
                "args 'force' is available only starting with "
                "'2.64' API microversion.")
        force = True
    if force:
        cs.shares.extend(share, args.new_size, force=force)
    else:
        cs.shares.extend(share, args.new_size)

    if args.wait:
        share = _wait_for_share_status(cs, share)
    else:
        share = _find_share(cs, args.share)
    _print_share(cs, share)

@cliutils.arg('share', metavar='<share>',
              help='Name or ID of share to shrink.')
@cliutils.arg('new_size',
              metavar='<new_size>',
              type=int,
              help='New size of share, in GiBs.')
@cliutils.arg(
    '--wait',
    action='store_true',
    help='Wait for share shrinkage')
@cliutils.service_type('sharev2')
def do_shrink(cs, args):
    """Decreases the size of an existing share."""
    share = _find_share(cs, args.share)
    cs.shares.shrink(share, args.new_size)

    if args.wait:
        share = _wait_for_share_status(cs, share)
    else:
        share = _find_share(cs, args.share)
    _print_share(cs, share)

##############################################################################
#
# Share types
#
##############################################################################


def _print_type_extra_specs(share_type):
    """Prints share type extra specs or share group type specs."""
    try:
        return _print_dict(share_type.get_keys())
    except exceptions.NotFound:
        return None


def _print_type_required_extra_specs(share_type):
    try:
        return _print_dict(share_type.get_required_keys())
    except exceptions.NotFound:
        return "N/A"


def _print_type_optional_extra_specs(share_type):
    try:
        return _print_dict(share_type.get_optional_keys())
    except exceptions.NotFound:
        return "N/A"


def _is_share_type_public(share_type):
    return 'public' if share_type.is_public else 'private'


def _print_share_type_list(stypes, default_share_type=None, columns=None,
                           description=False):

    def _is_default(share_type):
        if hasattr(share_type, 'is_default'):
            return 'YES' if share_type.is_default else '-'
        elif default_share_type:
            default = default_share_type.id
            return 'YES' if share_type.id == default else '-'
        else:
            return '-'

    formatters = {
        'visibility': _is_share_type_public,
        'is_default': _is_default,
        'required_extra_specs': _print_type_required_extra_specs,
        'optional_extra_specs': _print_type_optional_extra_specs,
    }

    for stype in stypes:
        stype = stype.to_dict()
        stype['visibility'] = stype.pop('is_public', 'unknown')

    fields = [
        'ID',
        'Name',
        'visibility',
        'is_default',
        'required_extra_specs',
        'optional_extra_specs',
    ]
    if description:
        fields.append('Description')
    if columns is not None:
        fields = _split_columns(columns=columns, title=False)

    cliutils.print_list(stypes, fields, formatters)


def _print_share_type(stype, default_share_type=None, show_des=False):

    def _is_default(share_type):
        if hasattr(share_type, 'is_default'):
            return 'YES' if share_type.is_default else '-'
        return '-'

    stype_dict = {
        'ID': stype.id,
        'Name': stype.name,
        'Visibility': _is_share_type_public(stype),
        'is_default': _is_default(stype),
        'required_extra_specs': _print_type_required_extra_specs(stype),
        'optional_extra_specs': _print_type_optional_extra_specs(stype),
    }
    if show_des:
        stype_dict['Description'] = stype.description
    cliutils.print_dict(stype_dict)


def _print_type_and_extra_specs_list(stypes, columns=None):
    """Prints extra specs for a list of share types or share group types."""
    formatters = {
        'all_extra_specs': _print_type_extra_specs,
    }
    fields = ['ID', 'Name', 'all_extra_specs']

    if columns is not None:
        fields = _split_columns(columns=columns, title=False)

    cliutils.print_list(stypes, fields, formatters)


def _find_share_type(cs, stype):
    """Get a share type by name or ID."""
    return apiclient_utils.find_resource(cs.share_types, stype)


@cliutils.arg(
    '--all',
    dest='all',
    action='store_true',
    default=False,
    help='Display all share types whatever public or private '
         'OPTIONAL: Default=False. (Admin only).')
@cliutils.arg(
    '--extra-specs',
    '--extra_specs',
    type=str,
    nargs='*',
    metavar='<key=value>',
    action='single_alias',
    default=None,
    help='Filters results by a extra specs key and value of share type that '
         'was used for share creation. Available only for microversion >= '
         '2.43. OPTIONAL: Default=None.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,name".')
def do_type_list(cs, args):
    """Print a list of available 'share types'."""
    search_opts = None
    show_all = args.all
    extra_specs = _extract_extra_specs(args)
    if extra_specs:
        if cs.api_version < api_versions.APIVersion("2.43"):
            raise exceptions.CommandError(
                "Filter by 'extra_specs' is available only starting with "
                "'2.43' API microversion.")
        search_opts = {
            'extra_specs': extra_specs
        }

    share_types = cs.share_types.list(show_all=show_all,
                                      search_opts=search_opts)
    default = None
    if share_types and not hasattr(share_types[0], 'is_default'):
        if ((args.columns and 'is_default' in args.columns) or
                args.columns is None):
            default = cs.share_types.get()

    show_des = cs.api_version.matches(
        api_versions.APIVersion("2.41"), api_versions.APIVersion())
    _print_share_type_list(share_types, default_share_type=default,
                           columns=args.columns, description=show_des)


@cliutils.arg(
    'share_type',
    metavar='<share_type>',
    help='Name or ID of the share type.')
def do_type_show(cs, args):
    """Show share type details."""
    share_type = _find_share_type(cs, args.share_type)
    default = None
    if (share_type and not hasattr(share_type, 'is_default')):
        default = cs.share_types.get()
    _print_type_show(share_type, default_share_type=default)


@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,name".')
def do_extra_specs_list(cs, args):
    """Print a list of current 'share types and extra specs' (Admin Only)."""
    stypes = cs.share_types.list()
    _print_type_and_extra_specs_list(stypes, columns=args.columns)


@cliutils.arg(
    'name',
    metavar='<name>',
    help="Name of the new share type.")
@cliutils.arg(
    'spec_driver_handles_share_servers',
    metavar='<spec_driver_handles_share_servers>',
    type=str,
    help="Required extra specification. "
         "Valid values are 'true'/'1' and 'false'/'0'.")
@cliutils.arg(
    '--description',
    metavar='<description>',
    type=str,
    default=None,
    help='Filter results by description. '
         'Available only for microversion >= 2.41.')
@cliutils.arg(
    '--snapshot_support',
    '--snapshot-support',
    metavar='<snapshot_support>',
    action='single_alias',
    help="Boolean extra spec used for filtering of back ends by their "
         "capability to create share snapshots.")
@cliutils.arg(
    '--create_share_from_snapshot_support',
    '--create-share-from-snapshot-support',
    metavar='<create_share_from_snapshot_support>',
    action='single_alias',
    help="Boolean extra spec used for filtering of back ends by their "
         "capability to create shares from snapshots.")
@cliutils.arg(
    '--revert_to_snapshot_support',
    '--revert-to-snapshot-support',
    metavar='<revert_to_snapshot_support>',
    action='single_alias',
    help="Boolean extra spec used for filtering of back ends by their "
         "capability to revert shares to snapshots. (Default is False).")
@cliutils.arg(
    '--mount_snapshot_support',
    '--mount-snapshot-support',
    metavar='<mount_snapshot_support>',
    action='single_alias',
    help="Boolean extra spec used for filtering of back ends by their "
         "capability to mount share snapshots. (Default is False).")
@cliutils.arg(
    '--extra-specs',
    '--extra_specs',  # alias
    type=str,
    nargs='*',
    metavar='<key=value>',
    action='single_alias',
    help="Extra specs key and value of share type that will be"
         " used for share type creation. OPTIONAL: Default=None."
         " example --extra-specs  thin_provisioning='<is> True', "
         "replication_type=readable.",
    default=None)
@cliutils.arg(
    '--is_public',
    '--is-public',
    metavar='<is_public>',
    action='single_alias',
    help="Make type accessible to the public (default true).")
def do_type_create(cs, args):
    """Create a new share type (Admin only)."""
    kwargs = {
        "name": args.name,
        "is_public": strutils.bool_from_string(args.is_public, default=True),
    }
    try:
        kwargs['spec_driver_handles_share_servers'] = (
            strutils.bool_from_string(
                args.spec_driver_handles_share_servers, strict=True))
    except ValueError as e:
        msg = ("Argument spec_driver_handles_share_servers "
               "argument is not valid: %s" % str(e))
        raise exceptions.CommandError(msg)

    kwargs['extra_specs'] = _extract_extra_specs(args)

    if 'driver_handles_share_servers' in kwargs['extra_specs']:
        msg = ("Argument 'driver_handles_share_servers' is already "
               "set via positional argument.")
        raise exceptions.CommandError(msg)

    show_des = False
    if cs.api_version.matches(api_versions.APIVersion("2.41"),
                              api_versions.APIVersion()):
        show_des = True
        kwargs['description'] = getattr(args, 'description')
    elif getattr(args, 'description'):
        raise exceptions.CommandError(
            "Pattern based option (description)"
            " is only available with manila API version >= 2.41")

    boolean_keys = (
        'snapshot_support',
        'create_share_from_snapshot_support',
        'revert_to_snapshot_support',
        'mount_snapshot_support'
    )
    for key in boolean_keys:
        value = getattr(args, key)

        if value is not None and key in kwargs['extra_specs']:
            msg = ("Argument '%s' value specified twice." % key)
            raise exceptions.CommandError(msg)

        try:
            if value:
                kwargs['extra_specs'][key] = (
                    strutils.bool_from_string(value, strict=True))
            elif key in kwargs['extra_specs']:
                kwargs['extra_specs'][key] = (
                    strutils.bool_from_string(
                        kwargs['extra_specs'][key], strict=True))
        except ValueError as e:
            msg = ("Argument '%s' is of boolean "
                   "type and has invalid value: %s" % (key, str(e)))
            raise exceptions.CommandError(msg)

    stype = cs.share_types.create(**kwargs)
    _print_share_type(stype, show_des=show_des)


@cliutils.arg(
    'id',
    metavar='<id>',
    help="Name or ID of the share type to update.")
@cliutils.arg(
    '--name',
    metavar='<name>',
    type=str,
    help="New name of share type.")
@cliutils.arg(
    '--description',
    metavar='<description>',
    type=str,
    default=None,
    help="New description of share type.")
@cliutils.arg(
    '--is-public',
    '--is_public',
    metavar='<is_public>',
    action='single_alias',
    help="New visibility of the share type. If set to True, share type will "
         "be available to all projects in the cloud.")
@api_versions.wraps("2.50")
def do_type_update(cs, args):
    """Update share type name, description, and/or visibility. (Admin only)."""
    name = getattr(args, 'name')
    description = getattr(args, 'description')
    is_public = getattr(args, 'is_public')
    if not name and description is None and is_public is None:
        msg = ("A description and/or non-empty name and/or boolean is_public "
               "must be supplied to update the respective attributes of the "
               "share type.")
        raise exceptions.CommandError(msg)
    kwargs = {}
    kwargs['name'] = name
    if is_public:
        try:
            kwargs['is_public'] = strutils.bool_from_string(is_public,
                                                            strict=True)
        except ValueError as e:
            raise exceptions.CommandError("The value of 'is_public' is"
                                          " invalid: %s", str(e))

    kwargs['description'] = description
    stype = _find_share_type(cs, args.id)
    stype = stype.update(**kwargs)
    _print_share_type(stype, show_des=True)


@cliutils.arg(
    'id',
    metavar='<id>',
    nargs='+',
    help="Name or ID of the share type(s) to delete.")
def do_type_delete(cs, args):
    """Delete one or more specific share types (Admin only)."""

    failure_count = 0

    for name_or_id in args.id:
        try:
            id_ref = _find_share_type(cs, name_or_id)
            cs.share_types.delete(id_ref)
        except Exception as e:
            failure_count += 1
            print("Delete for share type %s failed: %s" % (
                name_or_id, e), file=sys.stderr)

    if failure_count == len(args.id):
        raise exceptions.CommandError("Unable to delete any of the specified "
                                      "share types.")


@cliutils.arg(
    'stype',
    metavar='<stype>',
    help="Name or ID of the share type.")
@cliutils.arg(
    'action',
    metavar='<action>',
    choices=['set', 'unset'],
    help="Actions: 'set' or 'unset'.")
@cliutils.arg(
    'metadata',
    metavar='<key=value>',
    nargs='*',
    default=None,
    help='Extra_specs to set or unset (key is only necessary on unset).')
def do_type_key(cs, args):
    """Set or unset extra_spec for a share type (Admin only)."""
    stype = _find_share_type(cs, args.stype)

    if args.metadata is not None:
        keypair = _extract_metadata(args)

        if args.action == 'set':
            stype.set_keys(keypair)
        elif args.action == 'unset':
            stype.unset_keys(list(keypair))


@cliutils.arg(
    'share_type',
    metavar='<share_type>',
    help="Filter results by share type name or ID.")
def do_type_access_list(cs, args):
    """Print access information about the given share type (Admin only)."""
    share_type = _find_share_type(cs, args.share_type)
    if share_type.is_public:
        raise exceptions.CommandError("Forbidden to get access list "
                                      "for public share type.")
    access_list = cs.share_type_access.list(share_type)

    columns = ['Project_ID']
    cliutils.print_list(access_list, columns)


@cliutils.arg(
    'share_type',
    metavar='<share_type>',
    help="Share type name or ID to add access"
         " for the given project.")
@cliutils.arg(
    'project_id',
    metavar='<project_id>',
    help='Project ID to add share type access for.')
def do_type_access_add(cs, args):
    """Adds share type access for the given project (Admin only)."""
    vtype = _find_share_type(cs, args.share_type)
    cs.share_type_access.add_project_access(vtype, args.project_id)


@cliutils.arg(
    'share_type',
    metavar='<share_type>',
    help=('Share type name or ID to remove access '
          'for the given project.'))
@cliutils.arg(
    'project_id',
    metavar='<project_id>',
    help='Project ID to remove share type access for.')
def do_type_access_remove(cs, args):
    """Removes share type access for the given project (Admin only)."""
    vtype = _find_share_type(cs, args.share_type)
    cs.share_type_access.remove_project_access(
        vtype, args.project_id)


##############################################################################
#
# Share group types
#
##############################################################################


def _print_share_group_type_list(share_group_types,
                                 default_share_group_type=None, columns=None):

    def _is_default(share_group_type):
        if hasattr(share_group_type, 'is_default'):
            return 'YES' if share_group_type.is_default else '-'
        elif default_share_group_type:
            default = default_share_group_type.id
            return 'YES' if share_group_type.id == default else '-'
        else:
            return '-'

    formatters = {
        'visibility': _is_share_type_public,
        'is_default': _is_default,
    }

    for sg_type in share_group_types:
        sg_type = sg_type.to_dict()
        sg_type['visibility'] = sg_type.pop('is_public', 'unknown')

    fields = [
        'ID',
        'Name',
        'visibility',
        'is_default',
    ]
    if columns is not None:
        fields = _split_columns(columns=columns, title=False)

    cliutils.print_list(share_group_types, fields, formatters,
                        sortby_index=None)


def _print_share_group_type(share_group_type, default_share_type=None):

    def _is_default(share_group_type):
        if hasattr(share_group_type, 'is_default'):
            return 'YES' if share_group_type.is_default else '-'
        return '-'

    share_group_type_dict = {
        'ID': share_group_type.id,
        'Name': share_group_type.name,
        'Visibility': _is_share_type_public(share_group_type),
        'is_default': _is_default(share_group_type)
    }
    cliutils.print_dict(share_group_type_dict)


def _find_share_group_type(cs, sg_type):
    """Get a share group type by name or ID."""
    return apiclient_utils.find_resource(cs.share_group_types, sg_type)


@cliutils.arg(
    '--all',
    dest='all',
    action='store_true',
    default=False,
    help='Display all share group types (Admin only).')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,name".')
@cliutils.service_type('sharev2')
def do_share_group_type_list(cs, args):
    """Print a list of available 'share group types'."""

    sg_types = cs.share_group_types.list(show_all=args.all)

    default = None
    if sg_types and not hasattr(sg_types[0], 'is_default'):
        if ((args.columns and 'is_default' in args.columns) or
                args.columns is None):
            default = cs.share_group_types.get()

    _print_share_group_type_list(
        sg_types, default_share_group_type=default, columns=args.columns)


@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,name".')
@cliutils.service_type('sharev2')
def do_share_group_type_specs_list(cs, args):
    """Print a list of 'share group types specs' (Admin Only)."""

    sg_types = cs.share_group_types.list()
    _print_type_and_extra_specs_list(sg_types, columns=args.columns)


@cliutils.arg(
    'name',
    metavar='<name>',
    help='Name of the new share group type.')
@cliutils.arg(
    'share_types',
    metavar='<share_types>',
    type=str,
    help='Comma-separated list of share type names or IDs.')
@cliutils.arg(
    '--is_public',
    '--is-public',
    metavar='<is_public>',
    action='single_alias',
    help='Make type accessible to the public (default true).')
@cliutils.arg(
    '--group-specs',
    '--group_specs',
    metavar='<key=value>',
    type=str,
    nargs='*',
    action='single_alias',
    default=None,
    help='Share Group type extra specs by key and value. '
         'OPTIONAL: Default=None. '
         'Example: "--group-specs consistent_snapshot_support=host".',)
@cliutils.service_type('sharev2')
def do_share_group_type_create(cs, args):
    """Create a new share group type (Admin only)."""

    share_types = [_find_share_type(cs, share_type)
                   for share_type in args.share_types.split(',')]

    kwargs = {
        'share_types': share_types,
        'name': args.name,
        'is_public': strutils.bool_from_string(args.is_public, default=True),
    }
    if args.group_specs is not None:
        kwargs['group_specs'] = _extract_group_specs(args)

    sg_type = cs.share_group_types.create(**kwargs)
    _print_share_group_type(sg_type)


@cliutils.arg(
    'id',
    metavar='<id>',
    help="Name or ID of the share group type to delete.")
@cliutils.service_type('sharev2')
def do_share_group_type_delete(cs, args):
    """Delete a specific share group type (Admin only)."""
    share_group_type = _find_share_group_type(cs, args.id)
    cs.share_group_types.delete(share_group_type)


@cliutils.arg(
    'share_group_type',
    metavar='<share_group_type>',
    help="Name or ID of the share group type.")
@cliutils.arg(
    'action',
    metavar='<action>',
    choices=['set', 'unset'],
    help="Actions: 'set' or 'unset'.")
@cliutils.arg(
    'group_specs',
    metavar='<key=value>',
    nargs='*',
    default=None,
    help='Group specs to set or unset (key is only necessary on unset).')
@cliutils.service_type('sharev2')
def do_share_group_type_key(cs, args):
    """Set or unset group_spec for a share group type (Admin only)."""
    sg_type = _find_share_group_type(cs, args.share_group_type)
    if args.group_specs is not None:
        keypair = _extract_group_specs(args)
        if args.action == 'set':
            sg_type.set_keys(keypair)
        elif args.action == 'unset':
            sg_type.unset_keys(list(keypair))


@cliutils.arg(
    'share_group_type',
    metavar='<share_group_type>',
    help="Filter results by share group type name or ID.")
def do_share_group_type_access_list(cs, args):
    """Print access information about a share group type (Admin only)."""
    share_group_type = _find_share_group_type(cs, args.share_group_type)
    if share_group_type.is_public:
        raise exceptions.CommandError(
            "Forbidden to get access list for public share group type.")
    access_list = cs.share_group_type_access.list(share_group_type)
    columns = ['Project_ID']
    cliutils.print_list(access_list, columns)


@cliutils.arg(
    'share_group_type',
    metavar='<share_group_type>',
    help='Share group type name or ID to add access for the given project.')
@cliutils.arg(
    'project_id',
    metavar='<project_id>',
    help='Project ID to add share group type access for.')
def do_share_group_type_access_add(cs, args):
    """Adds share group type access for the given project (Admin only)."""
    share_group_type = _find_share_group_type(cs, args.share_group_type)
    cs.share_group_type_access.add_project_access(
        share_group_type, args.project_id)


@cliutils.arg(
    'share_group_type',
    metavar='<share_group_type>',
    help='Share group type name or ID to remove access for the given project.')
@cliutils.arg(
    'project_id',
    metavar='<project_id>',
    help='Project ID to remove share group type access for.')
def do_share_group_type_access_remove(cs, args):
    """Removes share group type access for the given project (Admin only)."""
    share_group_type = _find_share_group_type(cs, args.share_group_type)
    cs.share_group_type_access.remove_project_access(
        share_group_type, args.project_id)


##############################################################################
#
# Share groups
#
##############################################################################


@cliutils.arg(
    '--name',
    metavar='<name>',
    help='Optional share group name. (Default=None)',
    default=None)
@cliutils.arg(
    '--description',
    metavar='<description>',
    help='Optional share group description. (Default=None)',
    default=None)
@cliutils.arg(
    '--share-types', '--share_types',
    metavar='<share_types>',
    type=str,
    default=None,
    action='single_alias',
    help='Comma-separated list of share types. (Default=None)')
@cliutils.arg(
    '--share-group-type', '--share_group_type', '--type',
    metavar='<share_group_type>',
    type=str,
    default=None,
    action='single_alias',
    help="Share group type name or ID of the share group to be created. "
         "(Default=None)")
@cliutils.arg(
    '--share-network',
    '--share_network',
    metavar='<share_network>',
    type=str,
    default=None,
    action='single_alias',
    help='Specify share network name or id.')
@cliutils.arg(
    '--source-share-group-snapshot',
    '--source_share_group_snapshot',
    metavar='<source_share_group_snapshot>',
    type=str,
    action='single_alias',
    help='Optional share group snapshot name or ID to create the share group '
         'from. (Default=None)',
    default=None)
@cliutils.arg(
    '--availability-zone',
    '--availability_zone',
    '--az',
    default=None,
    action='single_alias',
    metavar='<availability-zone>',
    help='Optional availability zone in which group should be created. '
         '(Default=None)')
@cliutils.arg(
    '--wait',
    action='store_true',
    default=False,
    help='Wait for share group to create')
@cliutils.service_type('sharev2')
def do_share_group_create(cs, args):
    """Creates a new share group."""
    share_types = []
    if args.share_types:
        s_types = args.share_types.split(',')
        for s_type in s_types:
            share_type = _find_share_type(cs, s_type)
            share_types.append(share_type)

    share_group_type = None
    if args.share_group_type:
        share_group_type = _find_share_group_type(cs, args.share_group_type)

    share_network = None
    if args.share_network:
        share_network = _find_share_network(cs, args.share_network)

    share_group_snapshot = None
    if args.source_share_group_snapshot:
        share_group_snapshot = _find_share_group_snapshot(
            cs, args.source_share_group_snapshot)

    kwargs = {
        'share_group_type': share_group_type,
        'share_types': share_types or None,
        'name': args.name,
        'description': args.description,
        'availability_zone': args.availability_zone,
        'source_share_group_snapshot': share_group_snapshot,
        'share_network': share_network,
    }

    share_group = cs.share_groups.create(**kwargs)

    if args.wait:
        try:
            share_group = _wait_for_resource_status(
                cs, share_group, resource_type='share_group',
                expected_status='available')
        except exceptions.CommandError as e:
            print(e, file=sys.stderr)

    _print_share_group(cs, share_group)


@cliutils.arg(
    '--all-tenants', '--all-projects',
    action='single_alias',
    dest='all_projects',
    metavar='<0|1>',
    nargs='?',
    type=int,
    const=1,
    default=0,
    help='Display information from all projects (Admin only).')
@cliutils.arg(
    '--name',
    metavar='<name>',
    type=str,
    default=None,
    help='Filter results by name.')
@cliutils.arg(
    '--description',
    metavar='<description>',
    type=str,
    default=None,
    help='Filter results by description. '
         'Available only for microversion >= 2.36.')
@cliutils.arg(
    '--status',
    metavar='<status>',
    type=str,
    default=None,
    help='Filter results by status.')
@cliutils.arg(
    '--share-server-id', '--share-server_id',
    '--share_server-id', '--share_server_id',
    metavar='<share_server_id>',
    type=str,
    default=None,
    action='single_alias',
    help='Filter results by share server ID (Admin only).')
@cliutils.arg(
    '--share-group-type', '--share-group-type-id',
    '--share_group_type', '--share_group_type_id',
    metavar='<share_group_type>',
    type=str,
    default=None,
    action='single_alias',
    help='Filter results by a share group type ID or name that was used for '
         'share group creation.')
@cliutils.arg(
    '--snapshot',
    metavar='<snapshot>',
    type=str,
    default=None,
    help='Filter results by share group snapshot name or ID that was used to '
         'create the share group.')
@cliutils.arg(
    '--host',
    metavar='<host>',
    default=None,
    help='Filter results by host.')
@cliutils.arg(
    '--share-network', '--share_network',
    metavar='<share_network>',
    type=str,
    default=None,
    action='single_alias',
    help='Filter results by share-network name or ID.')
@cliutils.arg(
    '--project-id', '--project_id',
    metavar='<project_id>',
    type=str,
    default=None,
    action='single_alias',
    help="Filter results by project ID. Useful with set key '--all-projects'.")
@cliutils.arg(
    '--limit',
    metavar='<limit>',
    type=int,
    default=None,
    help='Maximum number of share groups to return. (Default=None)')
@cliutils.arg(
    '--offset',
    metavar="<offset>",
    default=None,
    help='Start position of share group listing.')
@cliutils.arg(
    '--sort-key', '--sort_key',
    metavar='<sort_key>',
    type=str,
    default=None,
    action='single_alias',
    help='Key to be sorted, available keys are %(keys)s. Default=None.' % {
        'keys': constants.SHARE_GROUP_SORT_KEY_VALUES})
@cliutils.arg(
    '--sort-dir', '--sort_dir',
    metavar='<sort_dir>',
    type=str,
    default=None,
    action='single_alias',
    help='Sort direction, available values are %(values)s. '
         'OPTIONAL: Default=None.' % {'values': constants.SORT_DIR_VALUES})
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,name".')
@cliutils.arg(
    '--name~',
    metavar='<name~>',
    type=str,
    default=None,
    help='Filter results matching a share group name pattern. '
         'Available only for microversion >= 2.36.')
@cliutils.arg(
    '--description~',
    metavar='<description~>',
    type=str,
    default=None,
    help='Filter results matching a share group description pattern. '
         'Available only for microversion >= 2.36.')
@cliutils.service_type('sharev2')
def do_share_group_list(cs, args):
    """List share groups with filters."""
    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    else:
        list_of_keys = ('ID', 'Name', 'Status', 'Description')

    all_projects = int(
        os.environ.get("ALL_TENANTS",
                       os.environ.get("ALL_PROJECTS",
                                      args.all_projects))
    )
    empty_obj = type('Empty', (object,), {'id': None})
    sg_type = (_find_share_group_type(cs, args.share_group_type)
               if args.share_group_type else empty_obj)
    snapshot = (_find_share_snapshot(cs, args.snapshot)
                if args.snapshot else empty_obj)
    share_network = (_find_share_network(cs, args.share_network)
                     if args.share_network else empty_obj)

    search_opts = {
        'offset': args.offset,
        'limit': args.limit,
        'all_tenants': all_projects,
        'name': args.name,
        'status': args.status,
        'share_server_id': args.share_server_id,
        'share_group_type_id': sg_type.id,
        'source_share_group_snapshot_id': snapshot.id,
        'host': args.host,
        'share_network_id': share_network.id,
        'project_id': args.project_id,
    }
    if cs.api_version.matches(api_versions.APIVersion("2.36"),
                              api_versions.APIVersion()):
        search_opts['name~'] = getattr(args, 'name~')
        search_opts['description~'] = getattr(args, 'description~')
        search_opts['description'] = getattr(args, 'description')
    elif (getattr(args, 'name~') or getattr(args, 'description~') or
          getattr(args, 'description')):
        raise exceptions.CommandError(
            "Pattern based filtering (name~, description~ and description)"
            " is only available with manila API version >= 2.36")

    share_groups = cs.share_groups.list(
        search_opts=search_opts, sort_key=args.sort_key,
        sort_dir=args.sort_dir)
    cliutils.print_list(share_groups, fields=list_of_keys,
                        sortby_index=None)


@cliutils.arg(
    'share_group',
    metavar='<share_group>',
    help='Name or ID of the share group.')
@cliutils.service_type('sharev2')
def do_share_group_show(cs, args):
    """Show details about a share group."""
    share_group = _find_share_group(cs, args.share_group)
    _print_share_group(cs, share_group)


@cliutils.arg(
    'share_group',
    metavar='<share_group>',
    help='Name or ID of the share group to update.')
@cliutils.arg(
    '--name',
    metavar='<name>',
    default=None,
    help='Optional new name for the share group. (Default=None)')
@cliutils.arg(
    '--description',
    metavar='<description>',
    help='Optional share group description. (Default=None)',
    default=None)
@cliutils.service_type('sharev2')
def do_share_group_update(cs, args):
    """Update a share group."""
    kwargs = {}

    if args.name is not None:
        kwargs['name'] = args.name
    if args.description is not None:
        kwargs['description'] = args.description

    if not kwargs:
        msg = "Must supply name and/or description"
        raise exceptions.CommandError(msg)
    share_group = _find_share_group(cs, args.share_group)
    share_group = cs.share_groups.update(share_group, **kwargs)
    _print_share_group(cs, share_group)


@cliutils.arg(
    'share_group',
    metavar='<share_group>',
    nargs='+',
    help='Name or ID of the share group(s).')
@cliutils.arg(
    '--force',
    action='store_true',
    default=False,
    help='Attempt to force delete the share group (Default=False)'
         ' (Admin only).')
@cliutils.arg(
    '--wait',
    action='store_true',
    default=False,
    help='Wait for share group to delete')
@cliutils.service_type('sharev2')
def do_share_group_delete(cs, args):
    """Delete one or more share groups."""
    failure_count = 0
    share_group_to_delete = []

    for share_group in args.share_group:
        try:
            share_group_ref = _find_share_group(cs, share_group)
            share_group_to_delete.append(share_group_ref)
            share_group_ref.delete(args.force)
        except Exception as e:
            failure_count += 1
            print("Delete for share group %s failed: %s" % (
                share_group, e), file=sys.stderr)

    if failure_count == len(args.share_group):
        raise exceptions.CommandError("Unable to delete any of the specified "
                                      "share groups.")

    if args.wait:
        for share_group in share_group_to_delete:
            try:
                _wait_for_resource_status(
                    cs, share_group, resource_type='share_group',
                    expected_status='deleted')
            except exceptions.CommandError as e:
                print(e, file=sys.stderr)


@cliutils.arg(
    'share_group',
    metavar='<share_group>',
    help='Name or ID of the share group to modify.')
@cliutils.arg(
    '--state',
    metavar='<state>',
    default='available',
    help=('Indicate which state to assign the share group. '
          'Options include available, error, creating, deleting, '
          'error_deleting. If no state is provided, '
          'available will be used.'))
@cliutils.service_type('sharev2')
def do_share_group_reset_state(cs, args):
    """Explicitly update the state of a share group

    (Admin only).
    """
    share_group = _find_share_group(cs, args.share_group)
    cs.share_groups.reset_state(share_group, args.state)


##############################################################################
#
# Share group snapshots
#
##############################################################################


@cliutils.arg(
    'share_group',
    metavar='<share_group>',
    help='Name or ID of the share group.')
@cliutils.arg(
    '--name',
    metavar='<name>',
    help='Optional share group snapshot name. (Default=None)',
    default=None)
@cliutils.arg(
    '--description',
    metavar='<description>',
    help='Optional share group snapshot description. (Default=None)',
    default=None)
@cliutils.service_type('sharev2')
def do_share_group_snapshot_create(cs, args):
    """Creates a new share group snapshot."""
    kwargs = {'name': args.name, 'description': args.description}
    share_group = _find_share_group(cs, args.share_group)
    sg_snapshot = cs.share_group_snapshots.create(share_group.id, **kwargs)
    _print_share_group_snapshot(cs, sg_snapshot)


@cliutils.arg(
    '--all-tenants', '--all-projects',
    action='single_alias',
    dest='all_projects',
    metavar='<0|1>',
    nargs='?',
    type=int,
    const=1,
    default=0,
    help='Display information from all projects (Admin only).')
@cliutils.arg(
    '--name',
    metavar='<name>',
    default=None,
    help='Filter results by name.')
@cliutils.arg(
    '--status',
    metavar='<status>',
    default=None,
    help='Filter results by status.')
@cliutils.arg(
    '--share-group-id', '--share_group_id',
    metavar='<share_group_id>',
    default=None,
    action='single_alias',
    help='Filter results by share group ID.')
@cliutils.arg(
    '--limit',
    metavar='<limit>',
    type=int,
    default=None,
    help='Maximum number of share group snapshots to return. '
         '(Default=None)')
@cliutils.arg(
    '--offset',
    metavar="<offset>",
    default=None,
    help='Start position of share group snapshot listing.')
@cliutils.arg(
    '--sort-key', '--sort_key',
    metavar='<sort_key>',
    type=str,
    default=None,
    action='single_alias',
    help='Key to be sorted, available keys are %(keys)s. Default=None.' % {
        'keys': constants.SHARE_GROUP_SNAPSHOT_SORT_KEY_VALUES})
@cliutils.arg(
    '--sort-dir', '--sort_dir',
    metavar='<sort_dir>',
    type=str,
    default=None,
    action='single_alias',
    help='Sort direction, available values are %(values)s. '
         'OPTIONAL: Default=None.' % {'values': constants.SORT_DIR_VALUES})
@cliutils.arg(
    '--detailed',
    dest='detailed',
    default=True,
    help='Show detailed information about share group snapshots.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,name".')
@cliutils.service_type('sharev2')
def do_share_group_snapshot_list(cs, args):
    """List share group snapshots with filters."""
    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    else:
        list_of_keys = ('id', 'name', 'status', 'description')

    all_projects = int(
        os.environ.get("ALL_TENANTS",
                       os.environ.get("ALL_PROJECTS",
                                      args.all_projects))
    )

    search_opts = {
        'offset': args.offset,
        'limit': args.limit,
        'all_tenants': all_projects,
        'name': args.name,
        'status': args.status,
        'share_group_id': args.share_group_id,
    }
    share_group_snapshots = cs.share_group_snapshots.list(
        detailed=args.detailed, search_opts=search_opts,
        sort_key=args.sort_key, sort_dir=args.sort_dir)
    cliutils.print_list(share_group_snapshots, fields=list_of_keys,
                        sortby_index=None)


@cliutils.arg(
    'share_group_snapshot',
    metavar='<share_group_snapshot>',
    help='Name or ID of the share group snapshot.')
@cliutils.service_type('sharev2')
def do_share_group_snapshot_show(cs, args):
    """Show details about a share group snapshot."""
    sg_snapshot = _find_share_group_snapshot(cs, args.share_group_snapshot)
    _print_share_group_snapshot(cs, sg_snapshot)


@cliutils.arg(
    'share_group_snapshot',
    metavar='<share_group_snapshot>',
    help='Name or ID of the share group snapshot.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,name".')
@cliutils.service_type('sharev2')
def do_share_group_snapshot_list_members(cs, args):
    """List members of a share group snapshot."""
    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    else:
        list_of_keys = ('Share ID', 'Size')

    sg_snapshot = _find_share_group_snapshot(cs, args.share_group_snapshot)
    members = [type('ShareGroupSnapshotMember', (object,), member)
               for member in sg_snapshot._info.get('members', [])]
    cliutils.print_list(members, fields=list_of_keys)


@cliutils.arg(
    '--state',
    metavar='<state>',
    default='available',
    help=('Indicate which state to assign the share group snapshot. '
          'Options include available, error, creating, deleting, '
          'error_deleting. If no state is provided, '
          'available will be used.'))
@cliutils.arg(
    'share_group_snapshot',
    metavar='<share_group_snapshot>',
    help='Name or ID of the share group snapshot.')
@cliutils.service_type('sharev2')
def do_share_group_snapshot_reset_state(cs, args):
    """Explicitly update the state of a share group snapshot

    (Admin only).
    """
    sg_snapshot = _find_share_group_snapshot(cs, args.share_group_snapshot)
    cs.share_group_snapshots.reset_state(sg_snapshot, args.state)


@cliutils.arg(
    'share_group_snapshot',
    metavar='<share_group_snapshot>',
    help='Name or ID of the share group snapshot to update.')
@cliutils.arg(
    '--name',
    metavar='<name>',
    default=None,
    help='Optional new name for the share group snapshot. (Default=None)')
@cliutils.arg(
    '--description',
    metavar='<description>',
    help='Optional share group snapshot description. (Default=None)',
    default=None)
@cliutils.service_type('sharev2')
def do_share_group_snapshot_update(cs, args):
    """Update a share group snapshot."""
    kwargs = {}

    if args.name is not None:
        kwargs['name'] = args.name

    if args.description is not None:
        kwargs['description'] = args.description

    if not kwargs:
        msg = "Must supply name and/or description"
        raise exceptions.CommandError(msg)

    sg_snapshot = _find_share_group_snapshot(cs, args.share_group_snapshot)
    cs.share_group_snapshots.update(sg_snapshot, **kwargs)


@cliutils.arg(
    'share_group_snapshot',
    metavar='<share_group_snapshot>',
    nargs='+',
    help='Name or ID of the share group snapshot(s) to delete.')
@cliutils.arg(
    '--force',
    action='store_true',
    default=False,
    help='Attempt to force delete the share group snapshot(s) (Default=False)'
         ' (Admin only).')
@cliutils.service_type('sharev2')
def do_share_group_snapshot_delete(cs, args):
    """Remove one or more share group snapshots."""
    failure_count = 0
    kwargs = {}

    kwargs['force'] = args.force

    for sg_snapshot in args.share_group_snapshot:
        try:
            sg_snapshot_ref = _find_share_group_snapshot(cs, sg_snapshot)
            cs.share_group_snapshots.delete(sg_snapshot_ref, **kwargs)
        except Exception as e:
            failure_count += 1
            print("Delete for share group snapshot %s failed: %s" % (
                sg_snapshot, e), file=sys.stderr)

    if failure_count == len(args.share_group_snapshot):
        raise exceptions.CommandError("Unable to delete any of the specified "
                                      "share group snapshots.")


##############################################################################
#
# Share replicas
#
##############################################################################


@cliutils.arg(
    '--share-id',
    '--share_id',
    '--si',  # alias
    metavar='<share_id>',
    default=None,
    action='single_alias',
    help='List replicas belonging to share.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "replica_state,id".')
@api_versions.wraps("2.11")
def do_share_replica_list(cs, args):
    """List share replicas."""
    share = _find_share(cs, args.share_id) if args.share_id else None

    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    else:
        list_of_keys = [
            'ID',
            'Status',
            'Replica State',
            'Share ID',
            'Host',
            'Availability Zone',
            'Updated At',
        ]

    if share:
        replicas = cs.share_replicas.list(share)
    else:
        replicas = cs.share_replicas.list()

    cliutils.print_list(replicas, list_of_keys)


@api_versions.wraps("2.11", "2.66")
@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share to replicate.')
@cliutils.arg(
    '--availability-zone',
    '--availability_zone',
    '--az',
    default=None,
    action='single_alias',
    metavar='<availability-zone>',
    help='Optional Availability zone in which replica should be created.')
def do_share_replica_create(cs, args):
    """Create a share replica."""
    share = _find_share(cs, args.share)

    body = {
        'share': share,
        'availability_zone': args.availability_zone,
    }
    replica = cs.share_replicas.create(**body)
    _print_share_replica(cs, replica)


@api_versions.wraps("2.67", "2.71")
@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share to replicate.')
@cliutils.arg(
    '--availability-zone',
    '--availability_zone',
    '--az',
    default=None,
    action='single_alias',
    metavar='<availability-zone>',
    help='Optional Availability zone in which replica should be created.')
@cliutils.arg(
    '--scheduler-hints',
    '--scheduler_hints',
    '--sh',
    metavar='<key=value>',
    nargs='*',
    help='Scheduler hints for the share replica as key=value pairs, '
         'Supported key is only_host. Available for microversion >= 2.67. ',
    default=None)
def do_share_replica_create(cs, args):  # noqa
    """Create a share replica."""
    share = _find_share(cs, args.share)

    scheduler_hints = {}
    if args.scheduler_hints:
        hints = _extract_key_value_options(args, 'scheduler_hints')
        if 'only_host' not in hints.keys() or len(hints) > 1:
            raise exceptions.CommandError(
                "The only valid key supported with the --scheduler-hints "
                "argument is 'only_host'.")
        scheduler_hints['only_host'] = hints.get('only_host')

    body = {
        'share': share,
        'availability_zone': args.availability_zone,
    }
    if scheduler_hints:
        body['scheduler_hints'] = scheduler_hints

    replica = cs.share_replicas.create(**body)
    _print_share_replica(cs, replica)


@api_versions.wraps("2.72")
@cliutils.arg(
    'share',
    metavar='<share>',
    help='Name or ID of the share to replicate.')
@cliutils.arg(
    '--availability-zone',
    '--availability_zone',
    '--az',
    default=None,
    action='single_alias',
    metavar='<availability-zone>',
    help='Optional Availability zone in which replica should be created.')
@cliutils.arg(
    '--scheduler-hints',
    '--scheduler_hints',
    '--sh',
    metavar='<key=value>',
    nargs='*',
    help='Scheduler hints for the share replica as key=value pairs, '
         'Supported key is only_host. Available for microversion >= 2.67. ',
    default=None)
@cliutils.arg(
    '--share-network',
    '--share_network',
    metavar='<network-info>',
    default=None,
    action='single_alias',
    help='Optional network info ID or name. '
         'Available only for microversion >= 2.72')
def do_share_replica_create(cs, args):  # noqa
    """Create a share replica."""
    share = _find_share(cs, args.share)

    scheduler_hints = {}
    if args.scheduler_hints:
        hints = _extract_key_value_options(args, 'scheduler_hints')
        if 'only_host' not in hints.keys() or len(hints) > 1:
            raise exceptions.CommandError(
                "The only valid key supported with the --scheduler-hints "
                "argument is 'only_host'.")
        scheduler_hints['only_host'] = hints.get('only_host')

    share_network = None
    if args.share_network:
        share_network = _find_share_network(cs, args.share_network)

    body = {
        'share': share,
        'availability_zone': args.availability_zone,
    }
    if scheduler_hints:
        body['scheduler_hints'] = scheduler_hints

    if share_network:
        body['share_network'] = share_network

    replica = cs.share_replicas.create(**body)
    _print_share_replica(cs, replica)


@cliutils.arg(
    'replica',
    metavar='<replica>',
    help='ID of the share replica.')
@api_versions.wraps("2.11", "2.46")
def do_share_replica_show(cs, args):
    """Show details about a replica."""

    replica = cs.share_replicas.get(args.replica)
    _print_share_replica(cs, replica)


@api_versions.wraps("2.47")  # noqa
@cliutils.arg(
    'replica',
    metavar='<replica>',
    help='ID of the share replica.')
def do_share_replica_show(cs, args):  # noqa
    """Show details about a replica."""

    replica = cs.share_replicas.get(args.replica)
    export_locations = cs.share_replica_export_locations.list(replica)
    replica._info['export_locations'] = export_locations
    _print_share_replica(cs, replica)


@cliutils.arg(
    'replica',
    metavar='<replica>',
    nargs='+',
    help='ID of the share replica.')
@cliutils.arg(
    '--force',
    action='store_true',
    default=False,
    help='Attempt to force deletion of a replica on its backend. Using '
         'this option will purge the replica from Manila even if it '
         'is not cleaned up on the backend. Defaults to False.')
@api_versions.wraps("2.11")
def do_share_replica_delete(cs, args):
    """Remove one or more share replicas."""
    failure_count = 0
    kwargs = {
        "force": args.force
    }

    for replica in args.replica:
        try:
            replica_ref = _find_share_replica(cs, replica)
            cs.share_replicas.delete(replica_ref, **kwargs)
        except Exception as e:
            failure_count += 1
            print("Delete for share replica %s failed: %s" % (replica, e),
                  file=sys.stderr)

    if failure_count == len(args.replica):
        raise exceptions.CommandError("Unable to delete any of the specified "
                                      "replicas.")


@cliutils.arg(
    'replica',
    metavar='<replica>',
    help='ID of the share replica.')
@api_versions.wraps("2.11")
def do_share_replica_promote(cs, args):
    """Promote specified replica to 'active' replica_state."""
    replica = _find_share_replica(cs, args.replica)
    cs.share_replicas.promote(replica)


@api_versions.wraps("2.47")
@cliutils.arg(
    'replica',
    metavar='<replica>',
    help='ID of the share replica.')
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "id,path,replica_state".')
def do_share_replica_export_location_list(cs, args):
    """List export locations of a share replica."""
    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    else:
        list_of_keys = [
            'ID',
            'Availability Zone',
            'Replica State',
            'Preferred',
            'Path',
        ]
    replica = _find_share_replica(cs, args.replica)
    export_locations = cs.share_replica_export_locations.list(replica)
    cliutils.print_list(export_locations, list_of_keys)


@api_versions.wraps("2.47")
@cliutils.arg(
    'replica',
    metavar='<replica>',
    help='Name or ID of the share instance.')
@cliutils.arg(
    'export_location',
    metavar='<export_location>',
    help='ID of the share instance export location.')
def do_share_replica_export_location_show(cs, args):
    """Show details of a share replica's export location."""
    replica = _find_share_replica(cs, args.replica)
    export_location = cs.share_replica_export_locations.get(
        replica, args.export_location)
    view_data = export_location._info.copy()
    cliutils.print_dict(view_data)


@cliutils.arg(
    'replica',
    metavar='<replica>',
    help='ID of the share replica to modify.')
@cliutils.arg(
    '--state',
    metavar='<state>',
    default='available',
    help=('Indicate which state to assign the replica. Options include '
          'available, error, creating, deleting, error_deleting. If no '
          'state is provided, available will be used.'))
@api_versions.wraps("2.11")
def do_share_replica_reset_state(cs, args):
    """Explicitly update the 'status' of a share replica."""
    replica = _find_share_replica(cs, args.replica)
    cs.share_replicas.reset_state(replica, args.state)


@cliutils.arg(
    'replica',
    metavar='<replica>',
    help='ID of the share replica to modify.')
@cliutils.arg(
    '--replica-state',
    '--replica_state',
    '--state',  # alias for user sanity
    metavar='<replica_state>',
    default='out_of_sync',
    action='single_alias',
    help=('Indicate which replica_state to assign the replica. Options '
          'include in_sync, out_of_sync, active, error. If no '
          'state is provided, out_of_sync will be used.'))
@api_versions.wraps("2.11")
def do_share_replica_reset_replica_state(cs, args):
    """Explicitly update the 'replica_state' of a share replica."""
    replica = _find_share_replica(cs, args.replica)
    cs.share_replicas.reset_replica_state(replica, args.replica_state)


@cliutils.arg(
    'replica',
    metavar='<replica>',
    help='ID of the share replica to resync.')
@api_versions.wraps("2.11")
def do_share_replica_resync(cs, args):
    """Attempt to update the share replica with its 'active' mirror."""
    replica = _find_share_replica(cs, args.replica)
    cs.share_replicas.resync(replica)


##############################################################################
#
# User Messages
#
##############################################################################


@api_versions.wraps("2.37")
@cliutils.arg(
    '--resource_id',
    '--resource-id',
    '--resource',
    metavar='<resource_id>',
    default=None,
    action='single_alias',
    help='Filters results by a resource uuid. Default=None.')
@cliutils.arg(
    '--resource_type',
    '--resource-type',
    metavar='<type>',
    default=None,
    action='single_alias',
    help='Filters results by a resource type. Default=None. '
         'Example: "manila message-list --resource_type share"')
@cliutils.arg(
    '--action_id',
    '--action-id',
    '--action',
    metavar='<id>',
    default=None,
    action='single_alias',
    help='Filters results by action id. Default=None.')
@cliutils.arg(
    '--detail_id',
    '--detail-id',
    '--detail',
    metavar='<id>',
    default=None,
    action='single_alias',
    help='Filters results by detail id. Default=None.')
@cliutils.arg(
    '--request_id',
    '--request-id',
    '--request',
    metavar='<request_id>',
    default=None,
    action='single_alias',
    help='Filters results by request id. Default=None.')
@cliutils.arg(
    '--level',
    '--message_level',
    '--message-level',
    metavar='<level>',
    default=None,
    action='single_alias',
    help='Filters results by the message level. Default=None. '
         'Example: "manila message-list --level ERROR".')
@cliutils.arg(
    '--limit',
    metavar='<limit>',
    type=int,
    default=None,
    help='Maximum number of messages to return. (Default=None)')
@cliutils.arg(
    '--offset',
    metavar="<offset>",
    default=None,
    help='Start position of message listing.')
@cliutils.arg(
    '--sort-key', '--sort_key',
    metavar='<sort_key>',
    type=str,
    default=None,
    action='single_alias',
    help='Key to be sorted, available keys are %(keys)s. Default=desc.' % {
        'keys': constants.MESSAGE_SORT_KEY_VALUES})
@cliutils.arg(
    '--sort-dir', '--sort_dir',
    metavar='<sort_dir>',
    type=str,
    default=None,
    action='single_alias',
    help='Sort direction, available values are %(values)s. '
         'OPTIONAL: Default=None.' % {'values': constants.SORT_DIR_VALUES})
@cliutils.arg(
    '--columns',
    metavar='<columns>',
    type=str,
    default=None,
    help='Comma separated list of columns to be displayed '
         'example --columns "resource_id,user_message".')
@cliutils.arg(
    '--since',
    metavar='<since>',
    default=None,
    help='Return only user messages created since given date. '
         'The date format must be conforming to ISO8601. '
         'Available only for microversion >= 2.52.')
@cliutils.arg(
    '--before',
    metavar='<before>',
    default=None,
    help='Return only user messages created before given date. '
         'The date format must be conforming to ISO8601. '
         'Available only for microversion >= 2.52.')
def do_message_list(cs, args):
    """Lists all messages."""
    if args.columns is not None:
        list_of_keys = _split_columns(columns=args.columns)
    else:
        list_of_keys = ['ID', 'Resource Type', 'Resource ID', 'Action ID',
                        'User Message', 'Detail ID', 'Created At']

    search_opts = {
        'offset': args.offset,
        'limit': args.limit,
        'request_id': args.request_id,
        'resource_type': args.resource_type,
        'resource_id': args.resource_id,
        'action_id': args.action_id,
        'detail_id': args.detail_id,
        'message_level': args.level
    }
    if cs.api_version < api_versions.APIVersion("2.52"):
        msg = ("Filtering messages by 'since' and 'before' is possible only "
               "with Manila API version >=2.52")
        if getattr(args, 'since') or getattr(args, 'before'):
            raise exceptions.CommandError(msg)
    else:
        search_opts['created_since'] = args.since
        search_opts['created_before'] = args.before

    messages = cs.messages.list(
        search_opts=search_opts, sort_key=args.sort_key,
        sort_dir=args.sort_dir)
    cliutils.print_list(messages, fields=list_of_keys, sortby_index=None)


@cliutils.arg(
    'message',
    metavar='<message>',
    help='ID of the message.')
@api_versions.wraps("2.37")
def do_message_show(cs, args):
    """Show details about a message."""

    message = cs.messages.get(args.message)
    _print_message(message)


@api_versions.wraps("2.37")
@cliutils.arg(
    'message',
    metavar='<message>',
    nargs='+',
    help='ID of the message(s).')
def do_message_delete(cs, args):
    """Remove one or more messages."""
    failure_count = 0

    for message in args.message:
        try:
            message_ref = _find_message(cs, message)
            cs.messages.delete(message_ref)
        except Exception as e:
            failure_count += 1
            print("Delete for message %s failed: %s" % (message, e),
                  file=sys.stderr)

    if failure_count == len(args.message):
        raise exceptions.CommandError("Unable to delete any of the specified "
                                      "messages.")


def _print_message(message):
    message_dict = {
        'id': message.id,
        'resource_type': message.resource_type,
        'resource_id': message.resource_id,
        'action_id': message.action_id,
        'user_message': message.user_message,
        'message_level': message.message_level,
        'detail_id': message.detail_id,
        'created_at': message.created_at,
        'expires_at': message.expires_at,
        'request_id': message.request_id,
    }
    cliutils.print_dict(message_dict)
