# Copyright 2014 Mirantis Inc.
# 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.

import ast
import re
import time

from oslo_utils import strutils
from tempest.lib.cli import base
from tempest.lib.cli import output_parser
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions as tempest_lib_exc

from manilaclient.common import constants
from manilaclient import config
from manilaclient.tests.functional import exceptions
from manilaclient.tests.functional import utils

CONF = config.CONF
MESSAGE = 'message'
SHARE = 'share'
SHARE_TYPE = 'share_type'
SHARE_NETWORK = 'share_network'
SHARE_NETWORK_SUBNET = 'share_network_subnet'
SHARE_SERVER = 'share_server'
SNAPSHOT = 'snapshot'
SHARE_REPLICA = 'share_replica'
TRANSFER = 'transfer'


def not_found_wrapper(f):

    def wrapped_func(self, *args, **kwargs):
        try:
            return f(self, *args, **kwargs)
        except tempest_lib_exc.CommandFailed as e:
            for regexp in (r'No (\w+) with a name or ID',
                           r'not(.*){0,5}found'):
                if re.search(regexp, str(e.stderr)):
                    # Raise appropriate 'NotFound' error
                    raise tempest_lib_exc.NotFound()
            raise

    return wrapped_func


def forbidden_wrapper(f):

    def wrapped_func(self, *args, **kwargs):
        try:
            return f(self, *args, **kwargs)
        except tempest_lib_exc.CommandFailed as e:
            if re.search('HTTP 403', str(e.stderr)):
                # Raise appropriate 'Forbidden' error.
                raise tempest_lib_exc.Forbidden()
            raise

    return wrapped_func


class ManilaCLIClient(base.CLIClient):

    def __init__(self, *args, **kwargs):
        super(ManilaCLIClient, self).__init__(*args, **kwargs)
        if CONF.enable_protocols:
            self.share_protocol = CONF.enable_protocols[0]
        else:
            msg = "Configuration option 'enable_protocols' is not defined."
            raise exceptions.InvalidConfiguration(reason=msg)
        self.build_interval = CONF.build_interval
        self.build_timeout = CONF.build_timeout

    def manila(self, action, flags='', params='', fail_ok=False,
               endpoint_type='publicURL', merge_stderr=False,
               microversion=None):
        """Executes manila command for the given action.

        :param action: the cli command to run using manila
        :type action: string
        :param flags: any optional cli flags to use. For specifying
                      microversion, please, use 'microversion' param
        :type flags: string
        :param params: any optional positional args to use
        :type params: string
        :param fail_ok: if True an exception is not raised when the
                        cli return code is non-zero
        :type fail_ok: boolean
        :param endpoint_type: the type of endpoint for the service
        :type endpoint_type: string
        :param merge_stderr: if True the stderr buffer is merged into stdout
        :type merge_stderr: boolean
        :param microversion: API microversion to be used for request
        :type microversion: str
        """
        flags += ' --endpoint-type %s' % endpoint_type
        if not microversion:
            # NOTE(vponomaryov): use max API version from config
            microversion = CONF.max_api_microversion

        # NOTE(vponomaryov): it is possible that param 'flags' already
        # can contain '--os-share-api-version' key. If it is so and we
        # reached this part then value of 'microversion' param will be
        # used and existing one in 'flags' param will be ignored.
        flags += ' --os-share-api-version %s' % microversion
        return self.cmd_with_auth(
            'manila', action, flags, params, fail_ok, merge_stderr)

    def wait_for_resource_deletion(self, res_type, res_id, interval=3,
                                   timeout=180, microversion=None, **kwargs):
        """Resource deletion waiter.

        :param res_type: text -- type of resource
        :param res_id: text -- ID of resource to use for deletion check
        :param interval: int -- interval between requests in seconds
        :param timeout: int -- total time in seconds to wait for deletion
        :param args: dict -- additional keyword arguments for deletion func
        """
        if res_type == SHARE_TYPE:
            func = self.is_share_type_deleted
        elif res_type == SHARE_NETWORK:
            func = self.is_share_network_deleted
        elif res_type == SHARE_NETWORK_SUBNET:
            func = self.is_share_network_subnet_deleted
        elif res_type == SHARE_SERVER:
            func = self.is_share_server_deleted
        elif res_type == SHARE:
            func = self.is_share_deleted
        elif res_type == SNAPSHOT:
            func = self.is_snapshot_deleted
        elif res_type == MESSAGE:
            func = self.is_message_deleted
        elif res_type == SHARE_REPLICA:
            func = self.is_share_replica_deleted
        elif res_type == TRANSFER:
            func = self.is_share_transfer_deleted
        else:
            raise exceptions.InvalidResource(message=res_type)

        end_loop_time = time.time() + timeout
        deleted = func(res_id, microversion=microversion, **kwargs)

        while not (deleted or time.time() > end_loop_time):
            time.sleep(interval)
            deleted = func(res_id, microversion=microversion, **kwargs)

        if not deleted:
            raise exceptions.ResourceReleaseFailed(
                res_type=res_type, res_id=res_id)

    def list_availability_zones(self, columns=None, microversion=None):
        """List availability zones.

        :param columns: comma separated string of columns.
            Example, "--columns id,name"
        :param microversion: API microversion that should be used.
        """
        cmd = 'availability-zone-list'
        if columns is not None:
            cmd += ' --columns ' + columns
        azs_raw = self.manila(cmd, microversion=microversion)
        azs = output_parser.listing(azs_raw)
        return azs

    # Share types

    def create_share_type(self, name=None, driver_handles_share_servers=True,
                          snapshot_support=None,
                          create_share_from_snapshot=None,
                          revert_to_snapshot=None, mount_snapshot=None,
                          is_public=True, microversion=None, extra_specs=None,
                          description=None):
        """Creates share type.

        :param name: text -- name of share type to use, if not set then
            autogenerated will be used
        :param description: text -- description of share type to use.
            Default is None.
        :param driver_handles_share_servers: bool/str -- boolean or its
            string alias. Default is True.
        :param snapshot_support: bool/str -- boolean or its
            string alias. Default is None.
        :param is_public: bool/str -- boolean or its string alias. Default is
            True.
        :param extra_specs: -- dictionary of extra specs Default is None.
        :param create_share_from_snapshot: -- boolean or its string
            alias. Default is None.
        :param revert_to_snapshot: -- boolean or its string alias. Default is
            None.
        :param mount_snapshot: -- boolean or its string alias. Default is None.
        """
        if name is None:
            name = data_utils.rand_name('manilaclient_functional_test')
        dhss = driver_handles_share_servers
        if not isinstance(dhss, str):
            dhss = str(dhss)
        if not isinstance(is_public, str):
            is_public = str(is_public)

        cmd = ('type-create %(name)s %(dhss)s --is-public %(is_public)s ') % {
            'name': name, 'dhss': dhss, 'is_public': is_public}

        if description is not None:
            cmd += " --description " + description

        if snapshot_support is not None:
            if not isinstance(snapshot_support, str):
                snapshot_support = str(snapshot_support)
            cmd += " --snapshot-support " + snapshot_support

        if create_share_from_snapshot is not None:
            if not isinstance(create_share_from_snapshot, str):
                create_share_from_snapshot = str(create_share_from_snapshot)
            cmd += (" --create-share-from-snapshot-support " +
                    create_share_from_snapshot)

        if revert_to_snapshot is not None:
            if not isinstance(revert_to_snapshot, str):
                revert_to_snapshot = str(revert_to_snapshot)
            cmd += (" --revert-to-snapshot-support " + revert_to_snapshot)

        if mount_snapshot is not None:
            if not isinstance(mount_snapshot, str):
                mount_snapshot = str(mount_snapshot)
            cmd += (" --mount-snapshot-support " + mount_snapshot)

        if extra_specs is not None:
            extra_spec_str = ''
            for k, v in extra_specs.items():
                if not isinstance(v, str):
                    extra_specs[k] = str(v)
                extra_spec_str += "{}='{}' ".format(k, v)
            cmd += " --extra_specs " + extra_spec_str

        share_type_raw = self.manila(cmd, microversion=microversion)
        share_type = utils.details(share_type_raw)
        return share_type

    def update_share_type(self, share_type_id, name=None,
                          is_public=None, microversion=None,
                          description=None):
        """Update share type.

        :param share_type_id: text -- id of share type.
        :param name: text -- new name of share type, if not set then
            it will not be updated.
        :param description: text -- new description of share type.
            if not set then it will not be updated.
        :param is_public: bool/str -- boolean or its string alias.
            new visibility of the share type.If set to True, share
            type will be available to all tenants in the cloud.
        """

        cmd = ('type-update %(share_type_id)s ') % {
            'share_type_id': share_type_id}

        if is_public is not None:
            if not isinstance(is_public, str):
                is_public = str(is_public)
            cmd += " --is_public " + is_public

        if description:
            cmd += " --description " + description
        elif description == "":
            cmd += ' --description "" '

        if name:
            cmd += " --name " + name

        share_type_raw = self.manila(cmd, microversion=microversion)
        share_type = utils.details(share_type_raw)
        return share_type

    @not_found_wrapper
    def delete_share_type(self, share_type, microversion=None):
        """Deletes share type by its Name or ID."""
        return self.manila(
            'type-delete %s' % share_type, microversion=microversion)

    def list_share_types(self, list_all=True, columns=None, search_opts=None,
                         microversion=None):
        """List share types.

        :param list_all: bool -- whether to list all share types or only public
        :param search_opts: dict search_opts for filter search.
        :param columns: comma separated string of columns.
            Example, "--columns id,name"
        """
        cmd = 'type-list'
        if list_all:
            cmd += ' --all'
        if search_opts is not None:
            extra_specs = search_opts.get('extra_specs')
            if extra_specs:
                cmd += ' --extra_specs'
                for spec_key in extra_specs.keys():
                    cmd += ' ' + spec_key + '=' + extra_specs[spec_key]
        if columns is not None:
            cmd += ' --columns ' + columns
        share_types_raw = self.manila(cmd, microversion=microversion)
        share_types = output_parser.listing(share_types_raw)
        return share_types

    def get_share_type(self, share_type, microversion=None):
        """Get share type.

        :param share_type: str -- Name or ID of share type, or None to
                    retrieve default share type
        """
        share_types = self.list_share_types(True, microversion=microversion)
        for stype in share_types:
            if share_type is None and stype["is_default"] == 'YES':
                return stype
            elif share_type in (stype['ID'], stype['Name']):
                return stype
        raise tempest_lib_exc.NotFound()

    def is_share_type_deleted(self, share_type, microversion=None):
        """Says whether share type is deleted or not.

        :param share_type: text -- Name or ID of share type
        """
        # NOTE(vponomaryov): we use 'list' operation because there is no
        # 'get/show' operation for share-types available for CLI
        share_types = self.list_share_types(
            list_all=True, microversion=microversion)
        for list_element in share_types:
            if share_type in (list_element['ID'], list_element['Name']):
                return False
        return True

    def wait_for_share_type_deletion(self, share_type, microversion=None):
        """Wait for share type deletion by its Name or ID.

        :param share_type: text -- Name or ID of share type
        """
        self.wait_for_resource_deletion(
            SHARE_TYPE, res_id=share_type, interval=2, timeout=6,
            microversion=microversion)

    def get_project_id(self, name_or_id):
        identity_api_version = '3'
        flags = (
            "--os-username %(username)s "
            "--os-project-name %(project_name)s "
            "--os-password %(password)s "
            "--os-identity-api-version %(identity_api_version)s "
        ) % {
            "username": CONF.admin_username,
            "project_name": CONF.admin_tenant_name,
            "password": CONF.admin_password,
            "identity_api_version": identity_api_version,
        }

        if identity_api_version == "3":
            if CONF.admin_project_domain_name:
                flags += (
                    "--os-project-domain-name %s " %
                    CONF.admin_project_domain_name)
            elif CONF.admin_project_domain_id:
                flags += (
                    "--os-project-domain-id %s " %
                    CONF.admin_project_domain_id)

            if CONF.admin_user_domain_name:
                flags += (
                    "--os-user-domain-name %s " %
                    CONF.admin_user_domain_name)
            elif CONF.admin_user_domain_id:
                flags += (
                    "--os-user-domain-id %s " %
                    CONF.admin_user_domain_id)

        project_id = self.openstack(
            'project show -f value -c id %s' % name_or_id, flags=flags)
        return project_id.strip()

    @not_found_wrapper
    def add_share_type_access(self, share_type_name_or_id, project_id,
                              microversion=None):
        data = dict(st=share_type_name_or_id, project=project_id)
        self.manila('type-access-add %(st)s %(project)s' % data,
                    microversion=microversion)

    @not_found_wrapper
    def remove_share_type_access(self, share_type_name_or_id, project_id,
                                 microversion=None):
        data = dict(st=share_type_name_or_id, project=project_id)
        self.manila('type-access-remove %(st)s %(project)s' % data,
                    microversion=microversion)

    @not_found_wrapper
    def list_share_type_access(self, share_type_id, microversion=None):
        projects_raw = self.manila(
            'type-access-list %s' % share_type_id, microversion=microversion)
        projects = output_parser.listing(projects_raw)
        project_ids = [pr['Project_ID'] for pr in projects]
        return project_ids

    @not_found_wrapper
    def set_share_type_extra_specs(self, share_type_name_or_id, extra_specs,
                                   microversion=None):
        """Set key-value pair for share type."""
        if not (isinstance(extra_specs, dict) and extra_specs):
            raise exceptions.InvalidData(
                message='Provided invalid extra specs - %s' % extra_specs)
        cmd = 'type-key %s set ' % share_type_name_or_id
        for key, value in extra_specs.items():
            cmd += '%(key)s=%(value)s ' % {'key': key, 'value': value}
        return self.manila(cmd, microversion=microversion)

    @not_found_wrapper
    def unset_share_type_extra_specs(self, share_type_name_or_id,
                                     extra_specs_keys, microversion=None):
        """Unset key-value pair for share type."""
        if not (isinstance(extra_specs_keys, (list, tuple, set)) and
                extra_specs_keys):
            raise exceptions.InvalidData(
                message='Provided invalid extra specs - %s' % extra_specs_keys)
        cmd = 'type-key %s unset ' % share_type_name_or_id
        for key in extra_specs_keys:
            cmd += '%s ' % key
        return self.manila(cmd, microversion=microversion)

    def list_all_share_type_extra_specs(self, microversion=None):
        """List extra specs for all share types."""
        extra_specs_raw = self.manila(
            'extra-specs-list', microversion=microversion)
        extra_specs = utils.listing(extra_specs_raw)
        return extra_specs

    def list_share_type_extra_specs(self, share_type_name_or_id,
                                    microversion=None):
        """List extra specs for specific share type by its Name or ID."""
        all_share_types = self.list_all_share_type_extra_specs(
            microversion=microversion)
        for share_type in all_share_types:
            if share_type_name_or_id in (share_type['ID'], share_type['Name']):
                return share_type['all_extra_specs']
        raise exceptions.ShareTypeNotFound(share_type=share_type_name_or_id)

    # Share networks

    def create_share_network(self, name=None, description=None,
                             neutron_net_id=None, neutron_subnet_id=None,
                             availability_zone=None,
                             microversion=None):
        """Creates share network.

        :param name: text -- desired name of new share network
        :param description: text -- desired description of new share network
        :param neutron_net_id: text -- ID of Neutron network
        :param neutron_subnet_id: text -- ID of Neutron subnet
        """
        params = self._combine_share_network_data(
            name=name,
            description=description,
            neutron_net_id=neutron_net_id,
            neutron_subnet_id=neutron_subnet_id,
            availability_zone=availability_zone
        )
        share_network_raw = self.manila(
            'share-network-create %s' % params, microversion=microversion)
        share_network = output_parser.details(share_network_raw)
        return share_network

    def _combine_share_network_data(self, name=None, description=None,
                                    neutron_net_id=None,
                                    neutron_subnet_id=None,
                                    availability_zone=None):
        """Combines params for share network operations 'create' and 'update'.

        :returns: text -- set of CLI parameters
        """
        data = dict()
        if name is not None:
            data['--name'] = name
        if description is not None:
            data['--description'] = description
        if neutron_net_id is not None:
            data['--neutron_net_id'] = neutron_net_id
        if neutron_subnet_id is not None:
            data['--neutron_subnet_id'] = neutron_subnet_id
        if availability_zone is not None:
            data['--availability_zone'] = availability_zone
        cmd = ''
        for key, value in data.items():
            cmd += "%(k)s=%(v)s " % {'k': key, 'v': value}
        return cmd

    @not_found_wrapper
    def get_share_network(self, share_network, microversion=None):
        """Returns share network by its Name or ID."""
        share_network_raw = self.manila(
            'share-network-show %s' % share_network, microversion=microversion)
        share_network = output_parser.details(share_network_raw)
        return share_network

    @not_found_wrapper
    def update_share_network(self, share_network, name=None, description=None,
                             neutron_net_id=None,
                             neutron_subnet_id=None, microversion=None):
        """Updates share-network by its name or ID.

        :param name: text -- new name for share network
        :param description: text -- new description for share network
        :param neutron_net_id: text -- ID of some Neutron network
        :param neutron_subnet_id: text -- ID of some Neutron subnet
        """
        sn_params = self._combine_share_network_data(
            name=name,
            description=description,
            neutron_net_id=neutron_net_id,
            neutron_subnet_id=neutron_subnet_id)
        share_network_raw = self.manila(
            'share-network-update %(sn)s %(params)s' % dict(
                sn=share_network, params=sn_params),
            microversion=microversion)
        share_network = output_parser.details(share_network_raw)
        return share_network

    @not_found_wrapper
    def delete_share_network(self, share_network, microversion=None):
        """Deletes share network by its Name or ID."""
        return self.manila('share-network-delete %s' % share_network,
                           microversion=microversion)

    @staticmethod
    def _stranslate_to_cli_optional_param(param):
        if len(param) < 1 or not isinstance(param, str):
            raise exceptions.InvalidData(
                'Provided wrong parameter for translation.')
        while not param[0:2] == '--':
            param = '-' + param
        return param.replace('_', '-')

    def list_share_networks(self, all_tenants=False, filters=None,
                            columns=None, microversion=None):
        """List share networks.

        :param all_tenants: bool -- whether to list share-networks that belong
            only to current project or for all tenants.
        :param filters: dict -- filters for listing of share networks.
            Example, input:
                {'project_id': 'foo'}
                {'-project_id': 'foo'}
                {'--project_id': 'foo'}
                {'project-id': 'foo'}
            will be transformed to filter parameter "--project-id=foo"
         :param columns: comma separated string of columns.
            Example, "--columns id"
        """
        cmd = 'share-network-list '
        if columns is not None:
            cmd += ' --columns ' + columns
        if all_tenants:
            cmd += ' --all-tenants '
        if filters and isinstance(filters, dict):
            for k, v in filters.items():
                cmd += '%(k)s=%(v)s ' % {
                    'k': self._stranslate_to_cli_optional_param(k), 'v': v}
        share_networks_raw = self.manila(cmd, microversion=microversion)
        share_networks = utils.listing(share_networks_raw)
        return share_networks

    def share_network_reset_state(self, id=None, state=None,
                                  microversion=None):
        cmd = 'share-network-reset-state %s ' % id
        if state:
            cmd += '--state %s' % state
        share_network_raw = self.manila(cmd, microversion=microversion)
        share_network = utils.listing(share_network_raw)
        return share_network

    def share_network_security_service_add(
            self, share_network_id, security_service_id, microversion=None):
        cmd = ('share-network-security-service-add %(network_id)s '
               '%(service_id)s' % {'network_id': share_network_id,
                                   'service_id': security_service_id})
        self.manila(cmd, microversion=microversion)

    def share_network_security_service_update(
            self, share_network_id, current_security_service_id,
            new_security_service_id, microversion=None):
        cmd = (
            'share-network-security-service-update %(network_id)s '
            '%(current_service_id)s %(new_security_service_id)s' % {
                'network_id': share_network_id,
                'current_service_id': current_security_service_id,
                'new_security_service_id': new_security_service_id})
        self.manila(cmd, microversion=microversion)

    def share_network_security_service_add_check(
            self, share_network_id, security_service_id,
            reset=False, microversion=None):
        cmd = (
            'share-network-security-service-add-check %(network_id)s '
            '%(security_service_id)s' % {
                'network_id': share_network_id,
                'security_service_id': security_service_id})

        if reset:
            cmd += '--reset %s' % reset

        return output_parser.details(
            self.manila(cmd, microversion=microversion))

    def share_network_security_service_update_check(
            self, share_network_id, current_security_service_id,
            new_security_service_id, reset=False, microversion=None):
        cmd = (
            'share-network-security-service-update-check %(network_id)s '
            '%(current_security_service_id)s %(new_security_service_id)s ' % {
                'network_id': share_network_id,
                'current_security_service_id': current_security_service_id,
                'new_security_service_id': new_security_service_id})

        if reset:
            cmd += '--reset %s' % reset

        return output_parser.details(
            self.manila(cmd, microversion=microversion))

    def share_network_security_service_list(
            self, share_network_id, microversion=None):
        cmd = ('share-network-security-service-list %s' % share_network_id)
        share_networks_raw = self.manila(cmd, microversion=microversion)
        network_services = utils.listing(share_networks_raw)
        return network_services

    def is_share_network_deleted(self, share_network, microversion=None):
        """Says whether share network is deleted or not.

        :param share_network: text -- Name or ID of share network
        """
        share_types = self.list_share_networks(True, microversion=microversion)
        for list_element in share_types:
            if share_network in (list_element['id'], list_element['name']):
                return False
        return True

    def wait_for_share_network_deletion(self, share_network,
                                        microversion=None):
        """Wait for share network deletion by its Name or ID.

        :param share_network: text -- Name or ID of share network
        """
        self.wait_for_resource_deletion(
            SHARE_NETWORK, res_id=share_network, interval=2, timeout=6,
            microversion=microversion)

    def share_network_subnet_create_check(
            self, share_network_id, neutron_net_id=None,
            neutron_subnet_id=None, availability_zone=None, reset=False,
            microversion=None):
        params = self._combine_share_network_subnet_data(
            neutron_net_id=neutron_net_id,
            neutron_subnet_id=neutron_subnet_id,
            availability_zone=availability_zone)
        cmd = (
            'share-network-subnet-create-check %(network_id)s '
            '%(params)s' % {
                'network_id': share_network_id,
                'params': params})

        if reset:
            cmd += '--reset %s' % reset

        return output_parser.details(
            self.manila(cmd, microversion=microversion))

    # Share Network Subnets

    def _combine_share_network_subnet_data(self, neutron_net_id=None,
                                           neutron_subnet_id=None,
                                           availability_zone=None):
        """Combines params for share network subnet 'create' operation.

        :returns: text -- set of CLI parameters
        """
        data = dict()
        if neutron_net_id is not None:
            data['--neutron_net_id'] = neutron_net_id
        if neutron_subnet_id is not None:
            data['--neutron_subnet_id'] = neutron_subnet_id
        if availability_zone is not None:
            data['--availability_zone'] = availability_zone
        cmd = ''
        for key, value in data.items():
            cmd += "%(k)s=%(v)s " % dict(k=key, v=value)
        return cmd

    def add_share_network_subnet(self, share_network,
                                 neutron_net_id=None, neutron_subnet_id=None,
                                 availability_zone=None, microversion=None):
        """Create new share network subnet for the given share network."""
        params = self._combine_share_network_subnet_data(
            neutron_net_id=neutron_net_id,
            neutron_subnet_id=neutron_subnet_id,
            availability_zone=availability_zone)
        share_network_subnet_raw = self.manila(
            'share-network-subnet-create %(sn)s %(params)s' %
            {'sn': share_network, 'params': params}, microversion=microversion)
        share_network_subnet = output_parser.details(share_network_subnet_raw)
        return share_network_subnet

    def get_share_network_subnet(self, share_network, share_network_subnet,
                                 microversion=None):
        """Returns share network subnet by share network ID and subnet ID."""

        share_network_subnet_raw = self.manila(
            'share-network-subnet-show %(share_net)s %(share_subnet)s' % {
                'share_net': share_network,
                'share_subnet': share_network_subnet,
            })
        share_network_subnet = output_parser.details(share_network_subnet_raw)
        return share_network_subnet

    def get_share_network_subnets(self, share_network, microversion=None):
        share_network = self.get_share_network(share_network,
                                               microversion=microversion)
        raw_subnets = share_network.get('share_network_subnets')
        subnets = ast.literal_eval(raw_subnets)
        # NOTE(lseki): convert literal None to string 'None'
        for subnet in subnets:
            for k, v in subnet.items():
                subnet[k] = str(v) if v is None else v
        return subnets

    @not_found_wrapper
    def delete_share_network_subnet(self, share_network, share_network_subnet,
                                    microversion=None):
        """Delete a share_network."""
        self.manila(
            'share-network-subnet-delete %(share_net)s %(share_subnet)s' % {
                'share_net': share_network,
                'share_subnet': share_network_subnet,
            }, microversion=microversion)

    def is_share_network_subnet_deleted(self, share_network_subnet,
                                        share_network, microversion=None):
        # NOTE(lseki): the parameter share_network_subnet comes before
        # share_network, because the wrapper method wait_for_resource_deletion
        # expects the resource id in first place (subnet ID in this case).
        """Says whether share network subnet is deleted or not.

        :param share_network_subnet: text -- Name or ID of share network subnet
        :param share_network: text -- Name or ID of share network the subnet
            belongs to
        """
        subnets = self.get_share_network_subnets(share_network)
        return not any(subnet['id'] == share_network_subnet
                       for subnet in subnets)

    def wait_for_share_network_subnet_deletion(self, share_network_subnet,
                                               share_network,
                                               microversion=None):
        # NOTE(lseki): the parameter share_network_subnet comes before
        # share_network, because the wrapper method wait_for_resource_deletion
        # expects the resource id in first place (subnet ID in this case).
        """Wait for share network subnet deletion by its Name or ID.

        :param share_network_subnet: text -- Name or ID of share network subnet
        :param share_network: text -- Name or ID of share network the subnet
            belongs to
        """
        args = {'share_network': share_network}
        self.wait_for_resource_deletion(
            SHARE_NETWORK_SUBNET, res_id=share_network_subnet,
            interval=2, timeout=6, microversion=microversion, **args)

    # Shares

    def create_share(self, share_protocol, size, share_network=None,
                     share_type=None, name=None, description=None,
                     public=False, snapshot=None, metadata=None, wait=False,
                     microversion=None):
        """Creates a share.

        :param share_protocol: str -- share protocol of a share.
        :param size: int/str -- desired size of a share.
        :param share_network: str -- Name or ID of share network to use.
        :param share_type: str -- Name or ID of share type to use.
        :param name: str -- desired name of new share.
        :param description: str -- desired description of new share.
        :param public: bool -- should a share be public or not.
            Default is False.
        :param snapshot: str -- Name or ID of a snapshot to use as source.
        :param metadata: dict -- key-value data to provide with share creation.
        :param wait: bool - the client must wait for "available" state
        :param microversion: str -- API microversion that should be used.
        """
        cmd = 'create %(share_protocol)s %(size)s ' % {
            'share_protocol': share_protocol, 'size': size}
        if share_network is not None:
            cmd += '--share-network %s ' % share_network
        if share_type is not None:
            cmd += '--share-type %s ' % share_type
        if name is None:
            name = data_utils.rand_name('autotest_share_name')
        cmd += '--name %s ' % name
        if description is None:
            description = data_utils.rand_name('autotest_share_description')
        cmd += '--description %s ' % description
        if public:
            cmd += '--public '
        if snapshot is not None:
            cmd += '--snapshot %s ' % snapshot
        if metadata:
            metadata_cli = ''
            for k, v in metadata.items():
                metadata_cli += '%(k)s=%(v)s ' % {'k': k, 'v': v}
            if metadata_cli:
                cmd += '--metadata %s ' % metadata_cli
        if wait:
            cmd += '--wait '
        share_raw = self.manila(cmd, microversion=microversion)
        share = output_parser.details(share_raw)
        return share

    @not_found_wrapper
    def get_share(self, share, microversion=None):
        """Returns a share by its Name or ID."""
        share_raw = self.manila('show %s' % share, microversion=microversion)
        share = output_parser.details(share_raw)
        return share

    @not_found_wrapper
    def update_share(self, share, name=None, description=None,
                     is_public=False, microversion=None):
        """Updates a share.

        :param share: str -- name or ID of a share that should be updated.
        :param name: str -- desired name of new share.
        :param description: str -- desired description of new share.
        :param is_public: bool -- should a share be public or not.
            Default is False.
        """
        cmd = 'update %s ' % share
        if name:
            cmd += '--name %s ' % name
        if description:
            cmd += '--description %s ' % description
        is_public = strutils.bool_from_string(is_public, strict=True)
        cmd += '--is-public %s ' % is_public

        return self.manila(cmd, microversion=microversion)

    @not_found_wrapper
    @forbidden_wrapper
    def delete_share(self, shares, share_group_id=None, wait=False,
                     microversion=None):
        """Deletes share[s] by Names or IDs.

        :param shares: either str or list of str that can be either Name
            or ID of a share(s) that should be deleted.
        :param share_group_id: a common share group ID for the shares being
          deleted
        :param wait: bool -- whether to wait for the shares to be deleted
        """
        if not isinstance(shares, list):
            shares = [shares]
        cmd = 'delete '
        for share in shares:
            cmd += '%s ' % share
        if share_group_id:
            cmd += '--share-group-id %s ' % share_group_id
        if wait:
            cmd += '--wait '
        return self.manila(cmd, microversion=microversion)

    @not_found_wrapper
    @forbidden_wrapper
    def soft_delete_share(self, shares, microversion=None):
        """Soft Delete share[s] by Names or IDs.

        :param shares: either str or list of str that can be either Name
            or ID of a share(s) that should be soft deleted.
        """
        if not isinstance(shares, list):
            shares = [shares]
        cmd = 'soft-delete '
        for share in shares:
            cmd += '%s ' % share
        return self.manila(cmd, microversion=microversion)

    @not_found_wrapper
    @forbidden_wrapper
    def restore_share(self, shares, microversion=None):
        """Restore share[s] by Names or IDs.

        :param shares: either str or list of str that can be either Name
            or ID of a share(s) that should be soft deleted.
        """
        if not isinstance(shares, list):
            shares = [shares]
        cmd = 'restore '
        for share in shares:
            cmd += '%s ' % share
        return self.manila(cmd, microversion=microversion)

    def create_share_transfer(self, share_id, name=None,
                              microversion=None):
        """Create a share transfer.

        :param share_id: ID of share.
        ":param name: name of transfer.
        """
        cmd = 'share-transfer-create %s ' % share_id
        if name:
            cmd += '--name %s' % name
        transfer_raw = self.manila(cmd, microversion=microversion)
        transfer = output_parser.details(transfer_raw)
        return transfer

    def delete_share_transfer(self, transfer, microversion=None):
        """Delete a share transfer.

        :param transfer: ID or name of share transfer.
        """
        cmd = 'share-transfer-delete %s ' % transfer
        self.manila(cmd, microversion=microversion)

    def get_share_transfer(self, transfer, microversion=None):
        """Get a share transfer.

        :param transfer: ID or name of share transfer.
        """
        cmd = 'share-transfer-show %s ' % transfer
        transfer_raw = self.manila(cmd, microversion=microversion)
        transfer = output_parser.details(transfer_raw)
        return transfer

    def list_share_transfer(self, microversion=None):
        """Get a share transfer."""

        cmd = 'share-transfer-list '
        transfer_raw = self.manila(cmd, microversion=microversion)
        transfers = utils.listing(transfer_raw)
        return transfers

    def accept_share_transfer(self, transfer, auth_key,
                              microversion=None):
        """Accept a share transfer.

        :param transfer: ID or name of share transfer.
        """
        cmd = 'share-transfer-accept %s %s' % (transfer, auth_key)
        self.manila(cmd, microversion=microversion)

    def list_shares(self, all_tenants=False, is_soft_deleted=False,
                    filters=None, columns=None, is_public=False,
                    microversion=None):
        """List shares.

        :param all_tenants: bool -- whether to list shares that belong
            only to current project or for all tenants.
        :param is_soft_deleted: bool -- whether to list shares that has
            been soft deleted to recycle bin.
        :param filters: dict -- filters for listing of shares.
            Example, input:
                {'project_id': 'foo'}
                {-'project_id': 'foo'}
                {--'project_id': 'foo'}
                {'project-id': 'foo'}
            will be transformed to filter parameter "--project-id=foo"
        :param columns: comma separated string of columns.
            Example, "--columns Name,Size"
        :param is_public: bool -- should list public shares or not.
            Default is False.
        :param microversion: str -- the request api version.
        """
        cmd = 'list '
        if all_tenants:
            cmd += '--all-tenants '
        if is_public:
            cmd += '--public '
        if is_soft_deleted:
            cmd += '--soft-deleted '
        if filters and isinstance(filters, dict):
            for k, v in filters.items():
                cmd += '%(k)s=%(v)s ' % {
                    'k': self._stranslate_to_cli_optional_param(k), 'v': v}
        if columns is not None:
            cmd += '--columns ' + columns
        shares_raw = self.manila(cmd, microversion=microversion)
        shares = utils.listing(shares_raw)
        return shares

    def list_share_instances(self, share_id=None, filters=None,
                             microversion=None):
        """List share instances.

        :param share_id: ID of a share to filter by.
        :param filters: dict -- filters for listing of shares.
            Example, input:
                {'project_id': 'foo'}
                {-'project_id': 'foo'}
                {--'project_id': 'foo'}
                {'project-id': 'foo'}
            will be transformed to filter parameter "--export-location=foo"
        :param microversion: API microversion to be used for request.
        """
        cmd = 'share-instance-list '
        if share_id:
            cmd += '--share-id %s' % share_id
        if filters and isinstance(filters, dict):
            for k, v in filters.items():
                cmd += '%(k)s=%(v)s ' % {
                    'k': self._stranslate_to_cli_optional_param(k), 'v': v}
        share_instances_raw = self.manila(cmd, microversion=microversion)
        share_instances = utils.listing(share_instances_raw)
        return share_instances

    def is_share_deleted(self, share, microversion=None):
        """Says whether share is deleted or not.

        :param share: str -- Name or ID of share
        """
        try:
            self.get_share(share, microversion=microversion)
            return False
        except tempest_lib_exc.NotFound:
            return True

    def wait_for_share_deletion(self, share, microversion=None):
        """Wait for share deletion by its Name or ID.

        :param share: str -- Name or ID of share
        """
        self.wait_for_resource_deletion(
            SHARE, res_id=share, interval=5, timeout=300,
            microversion=microversion)

    def is_share_transfer_deleted(self, transfer, microversion=None):
        """Says whether transfer is deleted or not.

        :param transfer: str -- Name or ID of transfer
        """
        try:
            self.get_transfer(transfer, microversion=microversion)
            return False
        except tempest_lib_exc.NotFound:
            return True

    def wait_for_transfer_deletion(self, transfer, microversion=None):
        """Wait for transfer deletion by its Name or ID.

        :param transfer: str -- Name or ID of transfer.
        """
        self.wait_for_resource_deletion(
            SHARE, res_id=transfer, interval=5, timeout=300,
            microversion=microversion)

    def wait_for_share_soft_deletion(self, share_id, microversion=None):
        body = self.get_share(share_id, microversion=microversion)
        is_soft_deleted = body['is_soft_deleted']
        start = int(time.time())

        while is_soft_deleted == "False":
            time.sleep(self.build_interval)
            body = self.get_share(share_id, microversion=microversion)
            is_soft_deleted = body['is_soft_deleted']

            if is_soft_deleted == "True":
                return

            if int(time.time()) - start >= self.build_timeout:
                message = ("Share %(share_id)s failed to be soft deleted "
                           "within the required time %(build_timeout)s." %
                           {"share_id": share_id,
                            "build_timeout": self.build_timeout})
                raise tempest_lib_exc.TimeoutException(message)

    def wait_for_share_restore(self, share_id, microversion=None):
        body = self.get_share(share_id, microversion=microversion)
        is_soft_deleted = body['is_soft_deleted']
        start = int(time.time())

        while is_soft_deleted == "True":
            time.sleep(self.build_interval)
            body = self.get_share(share_id, microversion=microversion)
            is_soft_deleted = body['is_soft_deleted']

            if is_soft_deleted == "False":
                return

            if int(time.time()) - start >= self.build_timeout:
                message = ("Share %(share_id)s failed to be restored "
                           "within the required time %(build_timeout)s." %
                           {"share_id": share_id,
                            "build_timeout": self.build_timeout})
                raise tempest_lib_exc.TimeoutException(message)

    def wait_for_resource_status(self, resource_id, status, microversion=None,
                                 resource_type="share"):
        """Waits for a share to reach a given status."""
        get_func = getattr(self, 'get_' + resource_type)
        body = get_func(resource_id, microversion=microversion)
        share_status = body['status']
        start = int(time.time())

        while share_status != status:
            time.sleep(self.build_interval)
            body = get_func(resource_id, microversion=microversion)
            share_status = body['status']

            if share_status == status:
                return
            elif 'error' in share_status.lower():
                raise exceptions.ShareBuildErrorException(share=resource_id)

            if int(time.time()) - start >= self.build_timeout:
                message = ("Resource %(resource_id)s failed to reach "
                           "%(status)s  status within the required time "
                           "(%(build_timeout)s)." %
                           {"resource_id": resource_id, "status": status,
                            "build_timeout": self.build_timeout})
                raise tempest_lib_exc.TimeoutException(message)

    def wait_for_migration_task_state(self, share_id, dest_host,
                                      task_state_to_wait, microversion=None):
        """Waits for a share to migrate to a certain host."""
        statuses = ((task_state_to_wait,)
                    if not isinstance(task_state_to_wait, (tuple, list, set))
                    else task_state_to_wait)
        share = self.get_share(share_id, microversion=microversion)
        start = int(time.time())
        while share['task_state'] not in statuses:
            time.sleep(self.build_interval)
            share = self.get_share(share_id, microversion=microversion)
            if share['task_state'] in statuses:
                break
            elif share['task_state'] == constants.TASK_STATE_MIGRATION_ERROR:
                raise exceptions.ShareMigrationException(
                    share_id=share['id'], src=share['host'], dest=dest_host)
            elif int(time.time()) - start >= self.build_timeout:
                message = ('Share %(share_id)s failed to reach a status in'
                           '%(status)s when migrating from host %(src)s to '
                           'host %(dest)s within the required time '
                           '%(timeout)s.' % {
                               'src': share['host'],
                               'dest': dest_host,
                               'share_id': share['id'],
                               'timeout': self.build_timeout,
                               'status': str(statuses),
                           })
                raise tempest_lib_exc.TimeoutException(message)
        return share

    @not_found_wrapper
    def _set_share_metadata(self, share, data, update_all=False,
                            microversion=None):
        """Sets a share metadata.

        :param share: str -- Name or ID of a share.
        :param data: dict -- key-value pairs to set as metadata.
        :param update_all: bool -- if set True then all keys except provided
            will be deleted.
        """
        if not (isinstance(data, dict) and data):
            msg = ('Provided invalid data for setting of share metadata - '
                   '%s' % data)
            raise exceptions.InvalidData(message=msg)
        if update_all:
            cmd = 'metadata-update-all %s ' % share
        else:
            cmd = 'metadata %s set ' % share
        for k, v in data.items():
            cmd += '%(k)s=%(v)s ' % {'k': k, 'v': v}
        return self.manila(cmd, microversion=microversion)

    def update_all_share_metadata(self, share, data, microversion=None):
        metadata_raw = self._set_share_metadata(
            share, data, True, microversion=microversion)
        metadata = output_parser.details(metadata_raw)
        return metadata

    def set_share_metadata(self, share, data, microversion=None):
        return self._set_share_metadata(
            share, data, False, microversion=microversion)

    @not_found_wrapper
    def unset_share_metadata(self, share, keys, microversion=None):
        """Unsets some share metadata by keys.

        :param share: str -- Name or ID of a share
        :param keys: str/list -- key or list of keys to unset.
        """
        if not (isinstance(keys, list) and keys):
            msg = ('Provided invalid data for unsetting of share metadata - '
                   '%s' % keys)
            raise exceptions.InvalidData(message=msg)
        cmd = 'metadata %s unset ' % share
        for key in keys:
            cmd += '%s ' % key
        return self.manila(cmd, microversion=microversion)

    @not_found_wrapper
    def get_share_metadata(self, share, microversion=None):
        """Returns list of all share metadata.

        :param share: str -- Name or ID of a share.
        """
        metadata_raw = self.manila(
            'metadata-show %s' % share, microversion=microversion)
        metadata = output_parser.details(metadata_raw)
        return metadata

    def create_snapshot(self, share, name=None, description=None,
                        force=False, microversion=None):
        """Creates a snapshot."""
        cmd = 'snapshot-create %(share)s ' % {'share': share}
        if name is None:
            name = data_utils.rand_name('autotest_snapshot_name')
        cmd += '--name %s ' % name
        if description is None:
            description = data_utils.rand_name('autotest_snapshot_description')
        cmd += '--description %s ' % description
        if force:
            cmd += '--force %s' % force
        snapshot_raw = self.manila(cmd, microversion=microversion)
        snapshot = output_parser.details(snapshot_raw)
        return snapshot

    @not_found_wrapper
    def get_snapshot(self, snapshot, microversion=None):
        """Retrieves a snapshot by its Name or ID."""
        snapshot_raw = self.manila('snapshot-show %s' % snapshot,
                                   microversion=microversion)
        snapshot = output_parser.details(snapshot_raw)
        return snapshot

    @not_found_wrapper
    def list_snapshot_export_locations(self, snapshot, columns=None,
                                       microversion=None):
        """List snapshot export locations.

        :param snapshot: str -- Name or ID of a snapshot.
        :param columns: str -- comma separated string of columns.
            Example, "--columns uuid,path".
        :param microversion: API microversion to be used for request.
        """
        cmd = "snapshot-export-location-list %s" % snapshot
        if columns is not None:
            cmd += " --columns " + columns
        export_locations_raw = self.manila(cmd, microversion=microversion)
        export_locations = utils.listing(export_locations_raw)
        return export_locations

    @not_found_wrapper
    @forbidden_wrapper
    def list_snapshot_instance_export_locations(self, snapshot_instance,
                                                columns=None,
                                                microversion=None):
        """List snapshot instance export locations.

        :param snapshot_instance: str -- Name or ID of a snapshot instance.
        :param columns: str -- comma separated string of columns.
            Example, "--columns uuid,path".
        :param microversion: API microversion to be used for request.
        """
        cmd = "snapshot-instance-export-location-list %s" % snapshot_instance
        if columns is not None:
            cmd += " --columns " + columns
        export_locations_raw = self.manila(cmd, microversion=microversion)
        export_locations = utils.listing(export_locations_raw)
        return export_locations

    @not_found_wrapper
    @forbidden_wrapper
    def delete_snapshot(self, snapshot, microversion=None):
        """Deletes snapshot by Names or IDs."""
        return self.manila(
            "snapshot-delete %s" % snapshot, microversion=microversion)

    def list_snapshot_instances(self, snapshot_id=None, columns=None,
                                detailed=None, microversion=None):
        """List snapshot instances."""
        cmd = 'snapshot-instance-list '
        if snapshot_id:
            cmd += '--snapshot %s' % snapshot_id
        if columns is not None:
            cmd += ' --columns ' + columns
        if detailed:
            cmd += ' --detailed True '
        snapshot_instances_raw = self.manila(cmd, microversion=microversion)
        snapshot_instances = utils.listing(snapshot_instances_raw)
        return snapshot_instances

    def get_snapshot_instance(self, id=None, microversion=None):
        """Get snapshot instance."""
        cmd = 'snapshot-instance-show %s ' % id
        snapshot_instance_raw = self.manila(cmd, microversion=microversion)
        snapshot_instance = output_parser.details(snapshot_instance_raw)
        return snapshot_instance

    def reset_snapshot_instance(self, id=None, state=None, microversion=None):
        """Reset snapshot instance status."""
        cmd = 'snapshot-instance-reset-state %s ' % id
        if state:
            cmd += '--state %s' % state
        snapshot_instance_raw = self.manila(cmd, microversion=microversion)
        snapshot_instance = utils.listing(snapshot_instance_raw)
        return snapshot_instance

    def is_snapshot_deleted(self, snapshot, microversion=None):
        """Indicates whether snapshot is deleted or not.

        :param snapshot: str -- Name or ID of snapshot
        """
        try:
            self.get_snapshot(snapshot, microversion=microversion)
            return False
        except tempest_lib_exc.NotFound:
            return True

    def wait_for_snapshot_deletion(self, snapshot, microversion=None):
        """Wait for snapshot deletion by its Name or ID.

        :param snapshot: str -- Name or ID of snapshot
        """
        self.wait_for_resource_deletion(
            SNAPSHOT, res_id=snapshot, interval=5, timeout=300,
            microversion=microversion)

    def wait_for_snapshot_status(self, snapshot, status, microversion=None):
        """Waits for a snapshot to reach a given status."""
        body = self.get_snapshot(snapshot, microversion=microversion)
        snapshot_name = body['name']
        snapshot_status = body['status']
        start = int(time.time())

        while snapshot_status != status:
            time.sleep(self.build_interval)
            body = self.get_snapshot(snapshot, microversion=microversion)
            snapshot_status = body['status']

            if snapshot_status == status:
                return
            elif 'error' in snapshot_status.lower():
                raise exceptions.SnapshotBuildErrorException(snapshot=snapshot)

            if int(time.time()) - start >= self.build_timeout:
                message = (
                    "Snapshot %(snapshot_name)s failed to reach %(status)s "
                    "status within the required time (%(timeout)s s)." % {
                        "snapshot_name": snapshot_name, "status": status,
                        "timeout": self.build_timeout})
                raise tempest_lib_exc.TimeoutException(message)

    @not_found_wrapper
    def list_access(self, entity_id, columns=None, microversion=None,
                    is_snapshot=False, metadata=None):
        """Returns list of access rules for a share.

        :param entity_id: str -- Name or ID of a share or snapshot.
        :param columns: comma separated string of columns.
            Example, "--columns access_type,access_to"
        :param is_snapshot: Boolean value to determine if should list
            access of a share or snapshot.
        """
        if is_snapshot:
            cmd = 'snapshot-access-list %s ' % entity_id
        else:
            cmd = 'access-list %s ' % entity_id
        if columns is not None:
            cmd += ' --columns ' + columns
        if metadata:
            metadata_cli = ''
            for k, v in metadata.items():
                metadata_cli += '%(k)s=%(v)s ' % {'k': k, 'v': v}
            if metadata_cli:
                cmd += ' --metadata %s ' % metadata_cli
        access_list_raw = self.manila(cmd, microversion=microversion)
        return output_parser.listing(access_list_raw)

    @not_found_wrapper
    def get_access(self, share_id, access_id, microversion=None,
                   is_snapshot=False):
        for access in self.list_access(share_id, microversion=microversion,
                                       is_snapshot=is_snapshot):
            if access['id'] == access_id:
                return access
        raise tempest_lib_exc.NotFound()

    @not_found_wrapper
    def access_show(self, access_id, microversion=None):
        raw_access = self.manila("access-show %s" % access_id,
                                 microversion=microversion)
        return output_parser.details(raw_access)

    @not_found_wrapper
    def access_set_metadata(self, access_id, metadata, microversion=None):
        if not (isinstance(metadata, dict) and metadata):
            msg = ('Provided invalid metadata for setting of access rule'
                   ' metadata - %s' % metadata)
            raise exceptions.InvalidData(message=msg)
        cmd = "access-metadata %s set " % access_id
        for k, v in metadata.items():
            cmd += '%(k)s=%(v)s ' % {'k': k, 'v': v}
        return self.manila(cmd, microversion=microversion)

    @not_found_wrapper
    def access_unset_metadata(self, access_id, keys, microversion=None):
        if not (isinstance(keys, (list, tuple, set)) and keys):
            raise exceptions.InvalidData(
                message='Provided invalid keys - %s' % keys)
        cmd = 'access-metadata %s unset ' % access_id
        for key in keys:
            cmd += '%s ' % key
        return self.manila(cmd, microversion=microversion)

    @not_found_wrapper
    def snapshot_access_allow(self, snapshot_id, access_type, access_to,
                              microversion=None):
        raw_access = self.manila(
            'snapshot-access-allow %(id)s %(type)s %(access_to)s' % {
                'id': snapshot_id,
                'type': access_type,
                'access_to': access_to,
            },
            microversion=microversion)
        return output_parser.details(raw_access)

    @not_found_wrapper
    def snapshot_access_deny(self, snapshot_id, access_id, microversion=None):
        return self.manila(
            'snapshot-access-deny %(share_id)s %(access_id)s' % {
                'share_id': snapshot_id,
                'access_id': access_id,
            },
            microversion=microversion)

    @not_found_wrapper
    def access_allow(self, share_id, access_type, access_to, access_level,
                     metadata=None, microversion=None):
        cmd = ('access-allow  --access-level %(level)s %(id)s %(type)s '
               '%(access_to)s' % {
                   'level': access_level,
                   'id': share_id,
                   'type': access_type,
                   'access_to': access_to})
        if metadata:
            metadata_cli = ''
            for k, v in metadata.items():
                metadata_cli += '%(k)s=%(v)s ' % {'k': k, 'v': v}
            if metadata_cli:
                cmd += ' --metadata %s ' % metadata_cli
        raw_access = self.manila(cmd, microversion=microversion)
        return output_parser.details(raw_access)

    @not_found_wrapper
    def access_deny(self, share_id, access_id, microversion=None):
        return self.manila(
            'access-deny %(share_id)s %(access_id)s' % {
                'share_id': share_id,
                'access_id': access_id,
            },
            microversion=microversion)

    def wait_for_access_rule_status(self, share_id, access_id, state='active',
                                    microversion=None, is_snapshot=False):
        access = self.get_access(
            share_id, access_id, microversion=microversion,
            is_snapshot=is_snapshot)

        start = int(time.time())
        while access['state'] != state:
            time.sleep(self.build_interval)
            access = self.get_access(
                share_id, access_id, microversion=microversion,
                is_snapshot=is_snapshot)

            if access['state'] == state:
                return
            elif access['state'] == 'error':
                raise exceptions.AccessRuleCreateErrorException(
                    access=access_id)

            if int(time.time()) - start >= self.build_timeout:
                message = (
                    "Access rule %(access)s failed to reach %(state)s state "
                    "within the required time (%(build_timeout)s s)." % {
                        "access": access_id, "state": state,
                        "build_timeout": self.build_timeout})
                raise tempest_lib_exc.TimeoutException(message)

    def wait_for_access_rule_deletion(self, share_id, access_id,
                                      microversion=None, is_snapshot=False):
        try:
            access = self.get_access(
                share_id, access_id, microversion=microversion,
                is_snapshot=is_snapshot)
        except tempest_lib_exc.NotFound:
            return

        start = int(time.time())
        while True:
            time.sleep(self.build_interval)
            try:
                access = self.get_access(
                    share_id, access_id, microversion=microversion,
                    is_snapshot=is_snapshot)
            except tempest_lib_exc.NotFound:
                return

            if access['state'] == 'error':
                raise exceptions.AccessRuleDeleteErrorException(
                    access=access_id)

            if int(time.time()) - start >= self.build_timeout:
                message = (
                    "Access rule %(access)s failed to reach deleted state "
                    "within the required time (%(timeout)s s)." %
                    {"access": access_id, "timeout": self.build_timeout})
                raise tempest_lib_exc.TimeoutException(message)

    def reset_task_state(self, share_id, state, version=None):
        state = '--task_state %s' % state if state else ''
        return self.manila('reset-task-state %(state)s %(share)s' % {
            'state': state,
            'share': share_id,
        }, microversion=version)

    def migration_start(self, share_id, dest_host, writable, nondisruptive,
                        preserve_metadata, preserve_snapshots,
                        force_host_assisted_migration, new_share_network=None,
                        new_share_type=None):
        cmd = ('migration-start %(share)s %(host)s '
               '--writable %(writable)s --nondisruptive %(nondisruptive)s '
               '--preserve-metadata %(preserve_metadata)s '
               '--preserve-snapshots %(preserve_snapshots)s') % {
            'share': share_id,
            'host': dest_host,
            'writable': writable,
            'nondisruptive': nondisruptive,
            'preserve_metadata': preserve_metadata,
            'preserve_snapshots': preserve_snapshots,
        }
        if force_host_assisted_migration:
            cmd += (' --force-host-assisted-migration %s'
                    % force_host_assisted_migration)
        if new_share_network:
            cmd += ' --new-share-network %s' % new_share_network
        if new_share_type:
            cmd += ' --new-share-type %s' % new_share_type
        return self.manila(cmd)

    def migration_complete(self, share_id):
        return self.manila('migration-complete %s' % share_id)

    def migration_cancel(self, share_id):
        return self.manila('migration-cancel %s' % share_id)

    def migration_get_progress(self, share_id):
        result = self.manila('migration-get-progress %s' % share_id)
        return output_parser.details(result)

    def pool_list(self, detail=False):
        cmd = 'pool-list'
        if detail:
            cmd += ' --column name,host,backend,pool,capabilities'
        response = self.manila(cmd)
        return output_parser.listing(response)

    def create_security_service(self, type='ldap', name=None, description=None,
                                dns_ip=None, ou=None, server=None, domain=None,
                                user=None, password=None, default_ad_site=None,
                                microversion=None):
        """Creates security service.

        :param type: security service type (ldap, kerberos or active_directory)
        :param name: desired name of new security service.
        :param description: desired description of new security service.
        :param dns_ip: DNS IP address inside tenant's network.
        :param ou: security service organizational unit
        :param server: security service IP address or hostname.
        :param domain: security service domain.
        :param user: user of the new security service.
        :param password: password used by user.
        :param default_ad_site: default AD site
        """

        cmd = 'security-service-create %s ' % type
        cmd += self. _combine_security_service_data(
            name=name,
            description=description,
            dns_ip=dns_ip,
            ou=ou,
            server=server,
            domain=domain,
            user=user,
            password=password,
            default_ad_site=default_ad_site)

        ss_raw = self.manila(cmd, microversion=microversion)
        security_service = output_parser.details(ss_raw)
        return security_service

    @not_found_wrapper
    def update_security_service(self, security_service, name=None,
                                description=None, dns_ip=None, ou=None,
                                server=None, domain=None, user=None,
                                password=None, default_ad_site=None,
                                microversion=None):
        cmd = 'security-service-update %s ' % security_service
        cmd += self. _combine_security_service_data(
            name=name,
            description=description,
            dns_ip=dns_ip,
            ou=ou,
            server=server,
            domain=domain,
            user=user,
            password=password,
            default_ad_site=default_ad_site)
        return output_parser.details(
            self.manila(cmd, microversion=microversion))

    def _combine_security_service_data(self, name=None, description=None,
                                       dns_ip=None, ou=None, server=None,
                                       domain=None, user=None, password=None,
                                       default_ad_site=None):
        data = ''
        if name is not None:
            data += '--name %s ' % name
        if description is not None:
            data += '--description %s ' % description
        if dns_ip is not None:
            data += '--dns-ip %s ' % dns_ip
        if ou is not None:
            data += '--ou %s ' % ou
        if server is not None:
            data += '--server %s ' % server
        if domain is not None:
            data += '--domain %s ' % domain
        if user is not None:
            data += '--user %s ' % user
        if password is not None:
            data += '--password %s ' % password
        if default_ad_site is not None:
            data += '--default-ad-site %s ' % default_ad_site
        return data

    @not_found_wrapper
    def list_share_export_locations(self, share, columns=None,
                                    microversion=None):
        """List share export locations.

        :param share: str -- Name or ID of a share.
        :param columns: str -- comma separated string of columns.
            Example, "--columns uuid,path".
        :param microversion: API microversion to be used for request.
        """
        cmd = "share-export-location-list %s" % share
        if columns is not None:
            cmd += " --columns " + columns
        export_locations_raw = self.manila(cmd, microversion=microversion)
        export_locations = utils.listing(export_locations_raw)
        return export_locations

    @not_found_wrapper
    def get_snapshot_export_location(self, snapshot, export_location_uuid,
                                     microversion=None):
        """Returns an export location by snapshot and its UUID.

        :param snapshot: str -- Name or ID of a snapshot.
        :param export_location_uuid: str -- UUID of an export location.
        :param microversion: API microversion to be used for request.
        """
        snapshot_raw = self.manila(
            'snapshot-export-location-show %(snapshot)s %(el_uuid)s' % {
                'snapshot': snapshot,
                'el_uuid': export_location_uuid,
            },
            microversion=microversion)
        snapshot = output_parser.details(snapshot_raw)
        return snapshot

    @not_found_wrapper
    def get_snapshot_instance_export_location(
            self, snapshot, export_location_uuid, microversion=None):
        """Returns an export location by snapshot instance and its UUID.

        :param snapshot: str -- Name or ID of a snapshot instance.
        :param export_location_uuid: str -- UUID of an export location.
        :param microversion: API microversion to be used for request.
        """
        snapshot_raw = self.manila(
            'snapshot-instance-export-location-show %(snapshot)s %(el_uuid)s'
            % {'snapshot': snapshot, 'el_uuid': export_location_uuid},
            microversion=microversion)
        snapshot = output_parser.details(snapshot_raw)
        return snapshot

    @not_found_wrapper
    def get_share_export_location(self, share, export_location_uuid,
                                  microversion=None):
        """Returns an export location by share and its UUID.

        :param share: str -- Name or ID of a share.
        :param export_location_uuid: str -- UUID of an export location.
        :param microversion: API microversion to be used for request.
        """
        share_raw = self.manila(
            'share-export-location-show %(share)s %(el_uuid)s' % {
                'share': share,
                'el_uuid': export_location_uuid,
            },
            microversion=microversion)
        share = output_parser.details(share_raw)
        return share

    @not_found_wrapper
    @forbidden_wrapper
    def list_share_instance_export_locations(self, share_instance,
                                             columns=None, microversion=None):
        """List share instance export locations.

        :param share_instance: str -- Name or ID of a share instance.
        :param columns: str -- comma separated string of columns.
            Example, "--columns uuid,path".
        :param microversion: API microversion to be used for request.
        """
        cmd = "share-instance-export-location-list %s" % share_instance
        if columns is not None:
            cmd += " --columns " + columns
        export_locations_raw = self.manila(cmd, microversion=microversion)
        export_locations = utils.listing(export_locations_raw)
        return export_locations

    @not_found_wrapper
    @forbidden_wrapper
    def get_share_instance_export_location(self, share_instance,
                                           export_location_uuid,
                                           microversion=None):
        """Returns an export location by share instance and its UUID.

        :param share_instance: str -- Name or ID of a share instance.
        :param export_location_uuid: str -- UUID of an export location.
        :param microversion: API microversion to be used for request.
        """
        share_raw = self.manila(
            'share-instance-export-location-show '
            '%(share_instance)s %(el_uuid)s' % {
                'share_instance': share_instance,
                'el_uuid': export_location_uuid,
            },
            microversion=microversion)
        share = output_parser.details(share_raw)
        return share

    # Share servers

    @not_found_wrapper
    def get_share_server(self, share_server, microversion=None):
        """Returns share server by its Name or ID."""
        share_server_raw = self.manila(
            'share-server-show %s' % share_server, microversion=microversion)
        share_server = output_parser.details(share_server_raw)
        return share_server

    def list_share_servers(self, filters=None, columns=None,
                           microversion=None):
        """List share servers.

        :param filters: dict -- filters for listing of share servers.
            Example, input:
                {'project_id': 'foo'}
                {'-project_id': 'foo'}
                {'--project_id': 'foo'}
                {'project-id': 'foo'}
            will be transformed to filter parameter "--project-id=foo"
         :param columns: comma separated string of columns.
            Example, "--columns id"
        """
        cmd = 'share-server-list '
        if columns is not None:
            cmd += ' --columns ' + columns
        if filters and isinstance(filters, dict):
            for k, v in filters.items():
                cmd += '%(k)s=%(v)s ' % {
                    'k': self._stranslate_to_cli_optional_param(k), 'v': v}
        share_servers_raw = self.manila(cmd, microversion=microversion)
        share_servers = utils.listing(share_servers_raw)
        return share_servers

    @not_found_wrapper
    def delete_share_server(self, share_server, microversion=None):
        """Deletes share server by its Name or ID."""
        return self.manila('share-server-delete %s' % share_server,
                           microversion=microversion)

    def is_share_server_deleted(self, share_server_id, microversion=None):
        """Says whether share server is deleted or not.

        :param share_server: text -- ID of the share server
        """
        servers = self.list_share_servers(microversion=microversion)
        for list_element in servers:
            if share_server_id == list_element['Id']:
                return False
        return True

    def wait_for_share_server_deletion(self, share_server, microversion=None):
        """Wait for share server deletion by its Name or ID.

        :param share_server: text -- Name or ID of share server
        """
        self.wait_for_resource_deletion(
            SHARE_SERVER, res_id=share_server, interval=3, timeout=60,
            microversion=microversion)

    def unmanage_share(self, server_id):
        return self.manila('unmanage %s ' % server_id)

    def unmanage_server(self, share_server_id):
        return self.manila('share-server-unmanage %s ' % share_server_id)

    def share_server_manage(self, host, share_network, identifier,
                            driver_options=None):
        if driver_options:
            command = ('share-server-manage %s %s %s %s' %
                       (host, share_network, identifier, driver_options))
        else:
            command = ('share-server-manage %s %s %s' % (host, share_network,
                       identifier))
        managed_share_server_raw = self.manila(command)
        managed_share_server = output_parser.details(managed_share_server_raw)
        return managed_share_server['id']

    def manage_share(self, host, protocol, export_location, share_server):
        managed_share_raw = self.manila(
            'manage %s %s %s --share-server-id %s' % (host, protocol,
                                                      export_location,
                                                      share_server))
        managed_share = output_parser.details(managed_share_raw)
        return managed_share['id']

    def share_server_migration_check(self, server_id, dest_host, writable,
                                     nondisruptive, preserve_snapshots,
                                     new_share_network=None):
        cmd = ('share-server-migration-check %(server_id)s %(host)s '
               '--writable %(writable)s --nondisruptive %(nondisruptive)s '
               '--preserve-snapshots %(preserve_snapshots)s') % {
            'server_id': server_id,
            'host': dest_host,
            'writable': writable,
            'nondisruptive': nondisruptive,
            'preserve_snapshots': preserve_snapshots,
        }
        if new_share_network:
            cmd += ' --new-share-network %s' % new_share_network
        result = self.manila(cmd)
        return output_parser.details(result)

    def share_server_migration_start(self, server_id, dest_host,
                                     writable=False, nondisruptive=False,
                                     preserve_snapshots=False,
                                     new_share_network=None):
        cmd = ('share-server-migration-start %(server_id)s %(host)s '
               '--writable %(writable)s --nondisruptive %(nondisruptive)s '
               '--preserve-snapshots %(preserve_snapshots)s') % {
            'server_id': server_id,
            'host': dest_host,
            'writable': writable,
            'nondisruptive': nondisruptive,
            'preserve_snapshots': preserve_snapshots,
        }
        if new_share_network:
            cmd += ' --new-share-network %s' % new_share_network
        return self.manila(cmd)

    def share_server_migration_complete(self, server_id):
        return self.manila('share-server-migration-complete %s' % server_id)

    def share_server_migration_cancel(self, server_id):
        return self.manila('share-server-migration-cancel %s' % server_id)

    def share_server_migration_get_progress(self, server_id):
        result = self.manila('share-server-migration-get-progress %s'
                             % server_id)
        return output_parser.details(result)

    def wait_for_server_migration_task_state(self, share_server_id, dest_host,
                                             task_state_to_wait,
                                             microversion=None):
        """Waits for a certain server task state. """
        statuses = ((task_state_to_wait,)
                    if not isinstance(task_state_to_wait, (tuple, list, set))
                    else task_state_to_wait)
        server = self.get_share_server(share_server=share_server_id,
                                       microversion=microversion)
        start = int(time.time())
        while server['task_state'] not in statuses:
            time.sleep(self.build_interval)
            server = self.get_share_server(share_server=share_server_id,
                                           microversion=microversion)
            if server['task_state'] in statuses:
                return server
            elif server['task_state'] == constants.TASK_STATE_MIGRATION_ERROR:
                raise exceptions.ShareServerMigrationException(
                    server_id=server['id'])
            elif int(time.time()) - start >= self.build_timeout:
                message = ('Server %(share_server_id)s failed to reach the '
                           'status in %(status)s while migrating from host '
                           '%(src)s to host %(dest)s within the required time '
                           '%(timeout)s.' % {
                               'src': server['host'],
                               'dest': dest_host,
                               'share_server_id': server['id'],
                               'timeout': self.build_timeout,
                               'status': str(statuses),
                           })
                raise tempest_lib_exc.TimeoutException(message)

    # user messages

    def wait_for_message(self, resource_id):
        """Waits until a message for a resource with given id exists"""
        start = int(time.time())
        message = None

        while not message:
            time.sleep(self.build_interval)
            for msg in self.list_messages():
                if msg['Resource ID'] == resource_id:
                    return msg

            if int(time.time()) - start >= self.build_timeout:
                message = ('No message for resource with id %s was created in'
                           ' the required time (%s s).' %
                           (resource_id, self.build_timeout))
                raise tempest_lib_exc.TimeoutException(message)

    def list_messages(self, columns=None, microversion=None):
        """List messages.

        :param columns: str -- comma separated string of columns.
            Example, "--columns id,resource_id".
        :param microversion: API microversion to be used for request.
        """
        cmd = "message-list"
        if columns is not None:
            cmd += " --columns " + columns
        messages_raw = self.manila(cmd, microversion=microversion)
        messages = utils.listing(messages_raw)
        return messages

    @not_found_wrapper
    def get_message(self, message, microversion=None):
        """Returns share server by its Name or ID."""
        message_raw = self.manila(
            'message-show %s' % message, microversion=microversion)
        message = output_parser.details(message_raw)
        return message

    @not_found_wrapper
    def delete_message(self, message, microversion=None):
        """Deletes message by its ID."""
        return self.manila('message-delete %s' % message,
                           microversion=microversion)

    def is_message_deleted(self, message, microversion=None):
        """Indicates whether message is deleted or not.

        :param message: str -- ID of message
        """
        try:
            self.get_message(message, microversion=microversion)
            return False
        except tempest_lib_exc.NotFound:
            return True

    def wait_for_message_deletion(self, message, microversion=None):
        """Wait for message deletion by its ID.

        :param message: text -- ID of message
        """
        self.wait_for_resource_deletion(
            MESSAGE, res_id=message, interval=3, timeout=60,
            microversion=microversion)

    # Share replicas

    def create_share_replica(self, share, availability_zone=None,
                             share_network=None, microversion=None):
        """Create a share replica.

        :param share: str -- Name or ID of a share to create a replica of
        """
        cmd = "share-replica-create %s" % share
        if availability_zone is not None:
            cmd += " --availability_zone " + availability_zone
        if share_network is not None:
            cmd += " --share_network " + share_network

        replica = self.manila(cmd, microversion=microversion)
        return output_parser.details(replica)

    @not_found_wrapper
    def get_share_replica(self, replica, microversion=None):
        cmd = "share-replica-show %s" % replica
        replica = self.manila(cmd, microversion=microversion)
        return output_parser.details(replica)

    @not_found_wrapper
    @forbidden_wrapper
    def delete_share_replica(self, share_replica, microversion=None):
        """Deletes share replica by ID."""
        return self.manila(
            "share-replica-delete %s" % share_replica,
            microversion=microversion)

    def is_share_replica_deleted(self, replica, microversion=None):
        """Indicates whether a share replica is deleted or not.

        :param replica: str -- ID of share replica
        """
        try:
            self.get_share_replica(replica, microversion=microversion)
            return False
        except tempest_lib_exc.NotFound:
            return True

    def wait_for_share_replica_deletion(self, replica, microversion=None):
        """Wait for share replica deletion by its ID.

        :param replica: text -- ID of share replica
        """
        self.wait_for_resource_deletion(
            SHARE_REPLICA, res_id=replica, interval=3, timeout=60,
            microversion=microversion)

    def wait_for_share_replica_status(self, share_replica,
                                      status="available",
                                      microversion=None):
        """Waits for a share replica to reach a given status."""
        replica = self.get_share_replica(share_replica,
                                         microversion=microversion)
        share_replica_status = replica['status']
        start = int(time.time())

        while share_replica_status != status:
            time.sleep(self.build_interval)
            replica = self.get_share_replica(share_replica,
                                             microversion=microversion)
            share_replica_status = replica['status']

            if share_replica_status == status:
                return replica
            elif 'error' in share_replica_status.lower():
                raise exceptions.ShareReplicaBuildErrorException(
                    replica=share_replica)

            if int(time.time()) - start >= self.build_timeout:
                message = (
                    "Share replica %(id)s failed to reach %(status)s "
                    "status within the required time "
                    "(%(build_timeout)s s)." % {
                        "id": share_replica, "status": status,
                        "build_timeout": self.build_timeout})
                raise tempest_lib_exc.TimeoutException(message)
        return replica

    @not_found_wrapper
    @forbidden_wrapper
    def list_share_replica_export_locations(self, share_replica,
                                            columns=None, microversion=None):
        """List share replica export locations.

        :param share_replica: str -- ID of share replica.
        :param columns: str -- comma separated string of columns.
            Example, "--columns id,path".
        :param microversion: API microversion to be used for request.
        """
        cmd = "share-replica-export-location-list %s" % share_replica
        if columns is not None:
            cmd += " --columns " + columns
        export_locations_raw = self.manila(cmd, microversion=microversion)
        export_locations = utils.listing(export_locations_raw)
        return export_locations

    @not_found_wrapper
    @forbidden_wrapper
    def get_share_replica_export_location(self, share_replica,
                                          export_location_uuid,
                                          microversion=None):
        """Returns an export location by share replica and export location ID.

        :param share_replica: str -- ID of share replica.
        :param export_location_uuid: str -- UUID of an export location.
        :param microversion: API microversion to be used for request.
        """
        export_raw = self.manila(
            'share-replica-export-location-show '
            '%(share_replica)s %(el_uuid)s' % {
                'share_replica': share_replica,
                'el_uuid': export_location_uuid,
            },
            microversion=microversion)
        export = output_parser.details(export_raw)
        return export
