class DS8KArrayMediator(ArrayMediatorAbstract):
    SUPPORTED_FROM_VERSION = '7.5.1'

    @classproperty
    def array_type(self):
        return settings.ARRAY_TYPE_DS8K

    @classproperty
    def port(self):
        return 8452

    @classproperty
    def max_object_name_length(self):
        return 16

    @classproperty
    def max_object_prefix_length(self):
        return 5

    @classproperty
    def max_connections(self):
        # max for rest api is 128.
        return 50

    @classproperty
    def minimal_volume_size_in_bytes(self):
        return 512  # 1 block, 512 bytes

    @classproperty
    def maximal_volume_size_in_bytes(self):
        return 16 * 1024 * 1024 * 1024 * 1024

    @classproperty
    def max_lun_retries(self):
        return 10

    @classproperty
    def default_object_prefix(self):
        return None

    def __init__(self, user, password, endpoint):
        self.user = user
        self.service_address = \
            endpoint[0] if isinstance(endpoint, list) else endpoint
        self.password = password

        self._connect()

    def _connect(self):
        try:
            self.client = RESTClient(
                service_address=self.service_address,
                user=self.user,
                password=self.password,
            )

            self.system_info = self.get_system_info()

            if parse(self.version) < parse(self.SUPPORTED_FROM_VERSION):
                raise array_errors.UnsupportedStorageVersionError(
                    self.version, self.SUPPORTED_FROM_VERSION)
        except (exceptions.ClientError, exceptions.Unauthorized) as e:
            # BE7A002D=Authentication has failed because the user name and
            # password combination that you have entered is not valid.
            if ERROR_CODE_INVALID_CREDENTIALS or KNOWN_ERROR_CODE_INVALID_CREDENTIALS in str(
                    e.message).upper():
                raise array_errors.CredentialsError(self.service_address)
            raise ConnectionError()
        except exceptions.ClientException as e:
            logger.error(
                'Failed to connect to DS8K array {}, reason is {}'.format(
                    self.service_address, e.details))
            raise ConnectionError()

    def disconnect(self):
        pass

    def is_active(self):
        return self.client.is_valid()

    def get_system_info(self):
        return self.client.get_system()

    @property
    def identifier(self):
        return self.system_info.id

    @property
    def name(self):
        return self.system_info.name or self.identifier

    @property
    def version(self):
        return parse_version(self.system_info.bundle)

    @property
    def wwnn(self):
        return self.system_info.wwnn

    def _generate_volume_scsi_identifier(self, volume_id):
        return '6{}000000000000{}'.format(self.wwnn[1:], volume_id)

    def _get_copy_source_id(self, api_volume):
        copy_source_id = None
        flashcopy_as_target = get_flashcopy_as_target_if_exists(
            api_volume=api_volume)
        if flashcopy_as_target:
            source_volume_id = flashcopy_as_target.sourcevolume
            copy_source_id = self._generate_volume_scsi_identifier(
                volume_id=source_volume_id)
        return copy_source_id

    def _generate_volume_response(self, api_volume):

        return Volume(
            vol_size_bytes=int(api_volume.cap),
            vol_id=self._generate_volume_scsi_identifier(
                volume_id=api_volume.id),
            vol_name=api_volume.name,
            array_address=self.service_address,
            copy_source_id=self._get_copy_source_id(api_volume=api_volume),
            pool=api_volume.pool,
            array_type=self.array_type)

    def _create_api_volume(self, name, size_in_bytes, space_efficiency,
                           pool_id):
        logger.info(
            "Creating volume with name: {}, size: {}, in pool: {}, with parameters: {}"
            .format(name, size_in_bytes, pool_id, space_efficiency))
        try:
            cli_kwargs = {}
            cli_kwargs.update({
                'name':
                name,
                'capacity_in_bytes':
                size_in_bytes,
                'pool_id':
                pool_id,
                'tp':
                get_array_space_efficiency(space_efficiency),
            })
            logger.debug("Start to create volume with parameters: {}".format(
                cli_kwargs))

            # get the volume before creating again, to make sure it is not existing,
            # because volume name is not unique in ds8k.
            api_volume = self._get_api_volume_by_name(name, pool_id=pool_id)
            logger.info("Found volume {}".format(name))
            if api_volume is not None:
                raise array_errors.VolumeAlreadyExists(name, self.identifier)
            api_volume = self.client.create_volume(**cli_kwargs)

            logger.info("finished creating volume {}".format(name))
            return self.client.get_volume(api_volume.id)
        except (exceptions.NotFound, exceptions.InternalServerError) as ex:
            if ERROR_CODE_RESOURCE_NOT_EXISTS or INCORRECT_ID in str(
                    ex.message).upper():
                raise array_errors.PoolDoesNotExist(pool_id, self.identifier)
            logger.error(
                "Failed to create volume {} on array {}, reason is: {}".format(
                    name, self.identifier, ex.details))
            raise array_errors.VolumeCreationError(name)
        except (exceptions.ClientError, exceptions.ClientException) as ex:
            if ERROR_CODE_CREATE_VOLUME_NOT_ENOUGH_EXTENTS in str(
                    ex.message).upper():
                raise array_errors.NotEnoughSpaceInPool(id_or_name=pool_id)
            logger.error(
                "Failed to create volume {} on array {}, reason is: {}".format(
                    name, self.identifier, ex.details))
            raise array_errors.VolumeCreationError(name)

    def create_volume(self, volume_name, size_in_bytes, space_efficiency,
                      pool):
        api_volume = self._create_api_volume(volume_name, size_in_bytes,
                                             space_efficiency, pool)
        return self._generate_volume_response(api_volume)

    def _extend_volume(self, api_volume, new_size_in_bytes):
        try:
            self.client.extend_volume(volume_id=api_volume.id,
                                      new_size_in_bytes=new_size_in_bytes)
        except exceptions.NotFound:
            raise array_errors.ObjectNotFoundError(api_volume.id)
        except (exceptions.ClientError, exceptions.ClientException) as ex:
            if ERROR_CODE_EXPAND_VOLUME_NOT_ENOUGH_EXTENTS in str(
                    ex.message).upper():
                raise array_errors.NotEnoughSpaceInPool(api_volume.pool)

    def copy_to_existing_volume_from_source(self,
                                            name,
                                            source_name,
                                            source_capacity_in_bytes,
                                            minimum_volume_size_in_bytes,
                                            pool=None):
        logger.debug(
            "Copy source {0} data to volume {1}. source capacity {2}. Minimal requested volume capacity {3}"
            .format(name, source_name, source_capacity_in_bytes,
                    minimum_volume_size_in_bytes))
        api_new_volume = self._get_api_volume_by_name(name, pool_id=pool)
        api_source_object = self._get_api_volume_by_name(source_name,
                                                         pool_id=pool)
        if minimum_volume_size_in_bytes < source_capacity_in_bytes:
            self._extend_volume(api_volume=api_new_volume,
                                new_size_in_bytes=source_capacity_in_bytes)
        options = [FLASHCOPY_PERSISTENT_OPTION]
        self._create_flashcopy(source_volume_id=api_source_object.id,
                               target_volume_id=api_new_volume.id,
                               options=options)

    def _delete_volume(self, volume_id, not_exist_err=True):
        logger.info("Deleting volume {}".format(volume_id))
        try:
            self.client.delete_volume(volume_id=volume_id)
            logger.info("Finished deleting volume {}".format(volume_id))
        except exceptions.NotFound:
            if not_exist_err:
                raise array_errors.ObjectNotFoundError(volume_id)
        except exceptions.ClientException as ex:
            logger.error(
                "Failed to delete volume {} on array {}, reason is: {}".format(
                    volume_id, self.identifier, ex.details))
            raise array_errors.VolumeDeletionError(volume_id)

    def _safe_delete_flashcopies(self, flashcopies, volume_name):
        for flashcopy in flashcopies:
            self._ensure_flashcopy_safe_to_delete(flashcopy, volume_name)
        for flashcopy in flashcopies:
            self._delete_flashcopy(flashcopy.id)

    def _ensure_flashcopy_safe_to_delete(self, flashcopy, volume_name):
        flashcopy_process = self._get_flashcopy_process(flashcopy.id)
        if flashcopy.backgroundcopy == "disabled":
            raise array_errors.ObjectIsStillInUseError(
                id_or_name=volume_name, used_by=[flashcopy.representation])
        if flashcopy_process.out_of_sync_tracks != '0':
            raise array_errors.ObjectIsStillInUseError(
                id_or_name=volume_name,
                used_by=[flashcopy_process.representation])

    def _delete_object(self, object_id, object_is_snapshot=False):
        api_volume = self._get_api_volume_by_id(object_id)
        if object_is_snapshot and not is_snapshot(api_volume):
            raise array_errors.ObjectNotFoundError(name=object_id)
        flashcopies = api_volume.flashcopy
        flashcopies_as_source = [
            flashcopy for flashcopy in flashcopies
            if flashcopy.sourcevolume == api_volume.id
        ]
        self._safe_delete_flashcopies(flashcopies=flashcopies_as_source,
                                      volume_name=api_volume.name)
        flashcopy_as_target = get_flashcopy_as_target_if_exists(
            api_volume=api_volume)
        if flashcopy_as_target:
            self._delete_flashcopy(flashcopy_id=flashcopy_as_target.id)
        self._delete_volume(object_id)

    @convert_scsi_id_to_array_id
    def delete_volume(self, volume_id):
        logger.info("Deleting volume with id : {0}".format(volume_id))
        self._delete_object(volume_id)
        logger.info("Finished deleting volume {}".format(volume_id))

    def get_volume(self, name, pool=None):
        logger.debug("Getting volume {} in pool {}".format(name, pool))
        api_volume = self._get_api_volume_by_name(volume_name=name,
                                                  pool_id=pool)
        if api_volume:
            return self._generate_volume_response(api_volume)
        raise array_errors.ObjectNotFoundError(name)

    @convert_scsi_id_to_array_id
    def expand_volume(self, volume_id, required_bytes):
        logger.info("Expanding volume with id : {0} to {1} bytes".format(
            volume_id, required_bytes))
        api_volume = self._get_api_volume_by_id(volume_id)
        flashcopies = api_volume.flashcopy
        self._safe_delete_flashcopies(flashcopies=flashcopies,
                                      volume_name=api_volume.name)

        self._extend_volume(api_volume=api_volume,
                            new_size_in_bytes=required_bytes)
        logger.info("Finished Expanding volume {0}.".format(volume_id))

    @convert_scsi_id_to_array_id
    def get_volume_mappings(self, volume_id):
        logger.debug("Getting volume mappings for volume {}".format(volume_id))
        try:
            host_name_to_lun_id = {}
            for host in self.client.get_hosts():
                host_mappings = host.mappings_briefs
                for mapping in host_mappings:
                    if volume_id == mapping["volume_id"]:
                        host_name_to_lun_id[host.name] = scsilun_to_int(
                            mapping["lunid"])
                        break
            logger.debug(
                "Found volume mappings: {}".format(host_name_to_lun_id))
            return host_name_to_lun_id
        except exceptions.ClientException as ex:
            logger.error("Failed to get volume mappings. Reason is: {}".format(
                ex.details))
            raise ex

    @convert_scsi_id_to_array_id
    def map_volume(self, volume_id, host_name):
        logger.debug("Mapping volume {} to host {}".format(
            volume_id, host_name))
        try:
            mapping = self.client.map_volume_to_host(host_name, volume_id)
            lun = scsilun_to_int(mapping.lunid)
            logger.debug(
                "Successfully mapped volume to host with lun {}".format(lun))
            return lun
        except exceptions.NotFound:
            raise array_errors.HostNotFoundError(host_name)
        except exceptions.ClientException as ex:
            # [BE586015] addLunMappings Volume group operation failure: volume does not exist.
            if ERROR_CODE_VOLUME_NOT_FOUND_FOR_MAPPING in str(
                    ex.message).upper():
                raise array_errors.ObjectNotFoundError(volume_id)
            raise array_errors.MappingError(volume_id, host_name, ex.details)

    @convert_scsi_id_to_array_id
    def unmap_volume(self, volume_id, host_name):
        logger.debug("Unmapping volume {} from host {}".format(
            volume_id, host_name))
        try:
            mappings = self.client.get_host_mappings(host_name)
            lunid = None
            for mapping in mappings:
                if mapping.volume == volume_id:
                    lunid = mapping.id
                    break
            if lunid is not None:
                self.client.unmap_volume_from_host(host_name=host_name,
                                                   lunid=lunid)
                logger.debug(
                    "Successfully unmapped volume from host with lun {}.".
                    format(lunid))
            else:
                raise array_errors.ObjectNotFoundError(volume_id)
        except exceptions.NotFound as ex:
            if HOST_DOES_NOT_EXIST in str(ex.message).upper():
                raise array_errors.HostNotFoundError(host_name)
            if MAPPING_DOES_NOT_EXIST in str(ex.message).upper():
                raise array_errors.VolumeAlreadyUnmappedError(volume_id)
        except exceptions.ClientException as ex:
            raise array_errors.UnmappingError(volume_id, host_name, ex.details)

    def _get_api_volume_from_volumes(self, volume_candidates, volume_name):
        for volume in volume_candidates:
            logger.info("Checking volume: {}".format(volume.name))
            if volume.name == volume_name:
                logger.debug("Found volume: {}".format(volume))
                volume.flashcopy = self.client.get_flashcopies_by_volume(
                    volume.id)
                return volume
        return None

    def _get_api_volume_by_name(self, volume_name, pool_id):
        logger.info("Getting volume {} in pool {}".format(
            volume_name, pool_id))
        if pool_id is None:
            logger.error(
                "pool_id is not specified, can not get volumes from storage.")
            raise array_errors.PoolParameterIsMissing(self.array_type)

        try:
            volume_candidates = []
            volume_candidates.extend(self.client.get_volumes_by_pool(pool_id))
        except (exceptions.NotFound, exceptions.InternalServerError) as ex:
            if ERROR_CODE_RESOURCE_NOT_EXISTS or INCORRECT_ID in str(
                    ex.message).upper():
                raise array_errors.PoolDoesNotExist(pool_id, self.identifier)
            raise ex

        return self._get_api_volume_from_volumes(volume_candidates,
                                                 volume_name)

    def _get_api_volume_by_id(self, volume_id, not_exist_err=True):
        try:
            volume = self.client.get_volume(volume_id)
            volume.flashcopy = self.client.get_flashcopies_by_volume(volume.id)
            return volume
        except exceptions.NotFound:
            if not_exist_err:
                raise array_errors.ObjectNotFoundError(volume_id)
        except (exceptions.ClientError, exceptions.InternalServerError) as ex:
            if INCORRECT_ID in str(ex.message).upper():
                raise array_errors.IllegalObjectID(volume_id)

    def _get_flashcopy_process(self, flashcopy_id, not_exist_err=True):
        logger.info("Getting flashcopy {}".format(flashcopy_id))
        try:
            return self.client.get_flashcopies(flashcopy_id)
        except exceptions.NotFound as ex:
            if ERROR_CODE_RESOURCE_NOT_EXISTS in str(ex.message).upper():
                logger.info("{} not found".format(flashcopy_id))
                if not_exist_err:
                    raise ex
        except Exception as ex:
            logger.exception(ex)
            raise ex

    def _get_api_snapshot(self, snapshot_name, pool_id=None):
        logger.debug("Get snapshot : {} in pool: {}".format(
            snapshot_name, pool_id))
        api_snapshot = self._get_api_volume_by_name(volume_name=snapshot_name,
                                                    pool_id=pool_id)
        if not api_snapshot:
            return None
        if not is_snapshot(api_snapshot):
            logger.error(
                "FlashCopy relationship not found for target volume: {}".
                format(snapshot_name))
            raise array_errors.ExpectedSnapshotButFoundVolumeError(
                api_snapshot.name, self.service_address)
        return api_snapshot

    @convert_scsi_id_to_array_id
    def get_snapshot(self, volume_id, snapshot_name, pool=None):
        if not pool:
            source_api_volume = self._get_api_volume_by_id(volume_id)
            pool = source_api_volume.pool
        api_snapshot = self._get_api_snapshot(snapshot_name, pool)
        if api_snapshot is None:
            return None
        return self._generate_snapshot_response_with_verification(api_snapshot)

    def _create_similar_volume(self, target_volume_name, source_api_volume,
                               pool_id):
        logger.info(
            "creating target api volume '{0}' from source volume '{1}'".format(
                target_volume_name, source_api_volume.name))
        space_efficiency = source_api_volume.tp
        size_in_bytes = int(source_api_volume.cap)
        if not pool_id:
            pool_id = source_api_volume.pool
        return self._create_api_volume(target_volume_name, size_in_bytes,
                                       space_efficiency, pool_id)

    def _create_flashcopy(self, source_volume_id, target_volume_id, options):
        logger.info(
            "creating FlashCopy relationship from '{0}' to '{1}'".format(
                source_volume_id, target_volume_id))
        options.append(FLASHCOPY_PERMIT_SPACE_EFFICIENT_TARGET_OPTION)
        try:
            api_flashcopy = self.client.create_flashcopy(
                source_volume_id=source_volume_id,
                target_volume_id=target_volume_id,
                options=options)
        except (exceptions.ClientError, exceptions.ClientException) as ex:
            if ERROR_CODE_ALREADY_FLASHCOPY in str(ex.message).upper():
                raise array_errors.SnapshotAlreadyExists(
                    target_volume_id, self.service_address)
            if ERROR_CODE_VOLUME_NOT_FOUND_OR_ALREADY_PART_OF_CS_RELATIONSHIP in str(
                    ex.message).upper():
                raise array_errors.ObjectNotFoundError('{} or {}'.format(
                    source_volume_id, target_volume_id))
            raise ex
        flashcopy_state = self.get_flashcopy_state(api_flashcopy.id)
        if not flashcopy_state == FLASHCOPY_STATE_VALID:
            self._delete_flashcopy(api_flashcopy.id)
            raise ValueError(
                "Flashcopy state is not correct. expected: '{}' , got: '{}'.".
                format(FLASHCOPY_STATE_VALID, flashcopy_state))
        return self._get_api_volume_by_id(target_volume_id)

    @retry(Exception, tries=11, delay=1)
    def _delete_target_volume_if_exist(self, target_volume_id):
        self._delete_volume(target_volume_id, not_exist_err=False)

    def _create_snapshot(self, target_volume_name, source_api_volume, pool_id):
        target_api_volume = self._create_similar_volume(
            target_volume_name, source_api_volume, pool_id)
        options = [
            FLASHCOPY_NO_BACKGROUND_COPY_OPTION, FLASHCOPY_PERSISTENT_OPTION
        ]
        try:
            return self._create_flashcopy(source_api_volume.id,
                                          target_api_volume.id, options)
        except (array_errors.ObjectNotFoundError,
                array_errors.SnapshotAlreadyExists) as ex:
            logger.error("Failed to create snapshot '{0}': {1}".format(
                target_volume_name, ex))
            self._delete_target_volume_if_exist(target_api_volume.id)
            raise ex

    def _generate_snapshot_response_with_verification(self, api_object):
        flashcopy_as_target = get_flashcopy_as_target_if_exists(api_object)
        if flashcopy_as_target is None or flashcopy_as_target.backgroundcopy != "disabled":
            raise array_errors.ExpectedSnapshotButFoundVolumeError(
                api_object.name, self.service_address)
        return self._generate_snapshot_response(
            api_object, flashcopy_as_target.sourcevolume)

    @convert_scsi_id_to_array_id
    def get_object_by_id(self, object_id, object_type):
        api_object = self._get_api_volume_by_id(object_id, not_exist_err=False)
        if not api_object:
            return None
        if object_type is controller_config.SNAPSHOT_TYPE_NAME:
            return self._generate_snapshot_response_with_verification(
                api_object)
        return self._generate_volume_response(api_object)

    @convert_scsi_id_to_array_id
    def create_snapshot(self, volume_id, snapshot_name, pool=None):
        logger.info("creating snapshot '{0}' from volume '{1}'".format(
            snapshot_name, volume_id))
        source_api_volume = self._get_api_volume_by_id(volume_id)
        if source_api_volume is None:
            raise array_errors.ObjectNotFoundError(volume_id)
        target_api_volume = self._create_snapshot(snapshot_name,
                                                  source_api_volume, pool)
        logger.info(
            "finished creating snapshot '{0}' from volume '{1}'".format(
                snapshot_name, volume_id))
        return self._generate_snapshot_response(target_api_volume, volume_id)

    def _delete_flashcopy(self, flashcopy_id):
        try:
            self.client.delete_flashcopy(flashcopy_id)
        except exceptions.ClientException as ex:
            logger.error(
                "Failed to delete flashcopy {} on array {}, reason is: {}".
                format(flashcopy_id, self.identifier, ex.details))
            raise ex

    @convert_scsi_id_to_array_id
    def delete_snapshot(self, snapshot_id):
        logger.info("Deleting snapshot with id : {0}".format(snapshot_id))
        self._delete_object(snapshot_id, object_is_snapshot=True)
        logger.info("Finished snapshot deletion. id : {0}".format(snapshot_id))

    def get_iscsi_targets_by_iqn(self):
        return {}

    def get_array_fc_wwns(self, host_name=None):
        logger.debug(
            "Getting the connected fc port wwpns for host {} from array".
            format(host_name))

        try:
            host = self.client.get_host(host_name)
            wwpns = [
                port[LOGIN_PORT_WWPN] for port in host.login_ports
                if port[LOGIN_PORT_STATE] == LOGIN_PORT_STATE_ONLINE
            ]
            logger.debug("Found wwpns: {}".format(wwpns))
            return wwpns
        except exceptions.NotFound:
            raise array_errors.HostNotFoundError(host_name)
        except exceptions.ClientException as ex:
            logger.error("Failed to get array fc wwpn. Reason is: {}".format(
                ex.details))
            raise ex

    def get_host_by_host_identifiers(self, initiators):
        logger.debug("Getting host by initiators: {}".format(initiators))
        found = ""
        for host in self.client.get_hosts():
            host_ports = host.host_ports_briefs
            wwpns = [p["wwpn"] for p in host_ports]
            if initiators.is_array_wwns_match(wwpns):
                found = host.name
                break
        if found:
            logger.debug("found host {0} with fc wwpns: {1}".format(
                found, initiators.fc_wwns))
            return found, [config.FC_CONNECTIVITY_TYPE]
        logger.debug(
            "can not found host by initiators: {0} ".format(initiators))
        raise array_errors.HostNotFoundError(initiators)

    def validate_supported_space_efficiency(self, space_efficiency):
        logger.debug(
            "validate_supported_space_efficiency for space efficiency : {0}".
            format(space_efficiency))

        if (space_efficiency and space_efficiency.lower() not in [
                config.SPACE_EFFICIENCY_THIN, config.SPACE_EFFICIENCY_NONE
        ]):
            logger.error("space efficiency is not supported.")
            raise array_errors.SpaceEfficiencyNotSupported(space_efficiency)

        logger.debug("Finished validate_supported_space_efficiency.")

    def _generate_snapshot_response(self, api_snapshot, source_volume_id):
        return Snapshot(
            capacity_bytes=int(api_snapshot.cap),
            snapshot_id=self._generate_volume_scsi_identifier(api_snapshot.id),
            snapshot_name=api_snapshot.name,
            array_address=self.service_address,
            volume_id=self._generate_volume_scsi_identifier(source_volume_id),
            is_ready=True,
            array_type=self.array_type)

    def get_flashcopy_state(self, flashcopy_id):
        flashcopy_process = self._get_flashcopy_process(flashcopy_id)
        return flashcopy_process.state
class DS8KArrayMediator(ArrayMediatorAbstract):
    SUPPORTED_FROM_VERSION = '7.5.1'

    @classproperty
    def array_type(self):
        return settings.ARRAY_TYPE_DS8K

    @classproperty
    def port(self):
        return 8452

    @classproperty
    def max_volume_name_length(self):
        return 16

    @classproperty
    def max_volume_prefix_length(self):
        return 5

    @classproperty
    def max_connections(self):
        # max for rest api is 128.
        return 50

    @classproperty
    def max_snapshot_name_length(self):
        return self.max_volume_name_length

    @classproperty
    def max_snapshot_prefix_length(self):
        return self.max_volume_prefix_length

    @classproperty
    def minimal_volume_size_in_bytes(self):
        return 512  # 1 block, 512 bytes

    @classproperty
    def max_lun_retries(self):
        return 10

    def __init__(self, user, password, endpoint):
        self.user = user
        self.service_address = \
            endpoint[0] if isinstance(endpoint, list) else endpoint
        self.password = password

        self._connect()

    def _connect(self):
        try:
            self.client = RESTClient(
                service_address=self.service_address,
                user=self.user,
                password=self.password,
            )

            self.system_info = self.get_system_info()

            if parse(self.version) < parse(self.SUPPORTED_FROM_VERSION):
                raise array_errors.UnsupportedStorageVersionError(
                    self.version, self.SUPPORTED_FROM_VERSION)
        except (exceptions.ClientError, exceptions.Unauthorized) as e:
            # BE7A002D=Authentication has failed because the user name and
            # password combination that you have entered is not valid.
            if ERROR_CODE_INVALID_CREDENTIALS or KNOWN_ERROR_CODE_INVALID_CREDENTIALS in str(
                    e.message).upper():
                raise array_errors.CredentialsError(self.service_address)
            else:
                raise ConnectionError()
        except exceptions.ClientException as e:
            logger.error(
                'Failed to connect to DS8K array {}, reason is {}'.format(
                    self.service_address, e.details))
            raise ConnectionError()

    def disconnect(self):
        pass

    def is_active(self):
        return self.client.is_valid()

    def get_system_info(self):
        return self.client.get_system()

    @property
    def identifier(self):
        return self.system_info.id

    @property
    def name(self):
        return self.system_info.name or self.identifier

    @property
    def version(self):
        return parse_version(self.system_info.bundle)

    @property
    def wwnn(self):
        return self.system_info.wwnn

    def _generate_volume_scsi_identifier(self, volume_id):
        return '6{}000000000000{}'.format(self.wwnn[1:], volume_id)

    def _generate_volume_response(self, api_volume):
        source_volume_id = get_source_volume_id_if_exists(api_volume)
        return Volume(vol_size_bytes=int(api_volume.cap),
                      vol_id=self._generate_volume_scsi_identifier(
                          api_volume.id),
                      vol_name=api_volume.name,
                      array_address=self.service_address,
                      copy_src_object_id=source_volume_id,
                      pool_name=api_volume.pool,
                      array_type=self.array_type)

    @staticmethod
    def get_se_capability_value(capabilities):
        capability = capabilities.get(config.CAPABILITIES_SPACEEFFICIENCY)
        if capability:
            capability = capability.lower()
            if capability == config.CAPABILITY_THIN:
                return "ese"
        return "none"

    def create_volume(self,
                      name,
                      size_in_bytes,
                      capabilities,
                      pool_id,
                      volume_prefix=""):
        logger.info("Creating volume with name: {}, size: {}, in pool: {}, "
                    "with capabilities: {}".format(name, size_in_bytes,
                                                   pool_id, capabilities))
        try:
            cli_kwargs = {}
            cli_kwargs.update({
                'name': name,
                'capacity_in_bytes': size_in_bytes,
                'pool_id': pool_id,
                'tp': self.get_se_capability_value(capabilities),
            })
            logger.debug("Start to create volume with parameters: {}".format(
                cli_kwargs))

            try:
                # get the volume before creating again, to make sure it is not existing,
                # because volume name is not unique in ds8k.
                volume = self.get_volume(
                    name,
                    volume_context={config.CONTEXT_POOL: pool_id},
                    volume_prefix=volume_prefix)
                logger.info("Found volume {}".format(name))
                return volume
            except array_errors.VolumeNotFoundError:
                volume = self.client.create_volume(**cli_kwargs)

                logger.info("finished creating volume {}".format(name))
                return self._generate_volume_response(
                    self.client.get_volume(volume.id))
        except (exceptions.NotFound, exceptions.InternalServerError) as ex:
            if ERROR_CODE_RESOURCE_NOT_EXISTS or INCORRECT_ID in str(
                    ex.message).upper():
                raise array_errors.PoolDoesNotExist(pool_id, self.identifier)
            else:
                logger.error(
                    "Failed to create volume {} on array {}, reason is: {}".
                    format(name, self.identifier, ex.details))
                raise array_errors.VolumeCreationError(name)
        except exceptions.ClientException as ex:
            logger.error(
                "Failed to create volume {} on array {}, reason is: {}".format(
                    name, self.identifier, ex.details))
            raise array_errors.VolumeCreationError(name)

    def _extend_volume(self, volume_id, new_size_in_bytes):
        try:
            self.client.extend_volume(volume_id=volume_id,
                                      new_size_in_bytes=new_size_in_bytes)
        except exceptions.NotFound:
            raise array_errors.VolumeNotFoundError(volume_id)

    def copy_to_existing_volume_from_snapshot(self,
                                              name,
                                              src_snap_name,
                                              src_snap_capacity_in_bytes,
                                              min_vol_size_in_bytes,
                                              pool=None):
        logger.debug(
            "Copy snapshot {0} data to volume {1}. Snapshot capacity {2}. Minimal requested volume capacity {3}"
            .format(name, src_snap_name, src_snap_capacity_in_bytes,
                    min_vol_size_in_bytes))
        api_new_volume = self._get_api_volume_by_name(name, pool_id=pool)
        api_snapshot = self.get_snapshot(
            src_snap_name, volume_context={config.CONTEXT_POOL: pool})
        if min_vol_size_in_bytes < src_snap_capacity_in_bytes:
            self._extend_volume(volume_id=api_new_volume.id,
                                new_size_in_bytes=src_snap_capacity_in_bytes)
        options = [FLASHCOPY_PERSISTENT_OPTION]
        self._create_flashcopy(source_volume_id=api_snapshot.id,
                               target_volume_id=api_new_volume.id,
                               options=options)

    def _delete_volume(self, volume_id, not_exist_err=True):
        logger.info("Deleting volume {}".format(volume_id))
        try:
            self.client.delete_volume(
                volume_id=get_volume_id_from_scsi_identifier(volume_id))
            logger.info("Finished deleting volume {}".format(volume_id))
        except exceptions.NotFound:
            if not_exist_err:
                raise array_errors.VolumeNotFoundError(volume_id)
        except exceptions.ClientException as ex:
            logger.error(
                "Failed to delete volume {} on array {}, reason is: {}".format(
                    volume_id, self.identifier, ex.details))
            raise array_errors.VolumeDeletionError(volume_id)

    def delete_volume(self, volume_id):
        logger.info("Deleting volume with id : {0}".format(volume_id))
        volume_id = get_volume_id_from_scsi_identifier(volume_id)
        api_volume = self._get_api_volume_by_id(volume_id)
        for flashcopy in api_volume.flashcopy:
            self._delete_flashcopy(flashcopy.id)
        self._delete_volume(volume_id)
        logger.info("Finished deleting volume {}".format(volume_id))

    def get_volume(self, name, volume_context=None, volume_prefix=""):
        logger.debug("Getting volume {} under context {}".format(
            name, volume_context))
        if not volume_context:
            logger.error(
                "volume_context is not specified, can not get volumes from storage."
            )
            raise array_errors.VolumeNotFoundError(name)

        api_volume = self._get_api_volume_by_name(
            volume_name=name, pool_id=volume_context[config.CONTEXT_POOL])

        if api_volume:
            return self._generate_volume_response(api_volume)

        raise array_errors.VolumeNotFoundError(name)

    def get_volume_name(self, volume_id):
        logger.debug("Searching for volume with id: {0}".format(volume_id))
        volume_id = get_volume_id_from_scsi_identifier(volume_id)
        try:
            api_volume = self.client.get_volume(volume_id)
        except exceptions.NotFound:
            raise array_errors.VolumeNotFoundError(volume_id)

        vol_name = api_volume.name
        logger.debug("found volume name : {0}".format(vol_name))
        return vol_name

    def is_volume_has_snapshots(self, volume_id):
        array_volume_id = get_volume_id_from_scsi_identifier(volume_id)
        array_volume = self._get_api_volume_by_id(array_volume_id)
        flash_copies = array_volume.flashcopy
        for flashcopy in flash_copies:
            if flashcopy.sourcevolume == array_volume_id:
                return True
        return False

    def get_volume_mappings(self, volume_id):
        logger.debug("Getting volume mappings for volume {}".format(volume_id))
        volume_id = get_volume_id_from_scsi_identifier(volume_id)
        try:
            host_name_to_lun_id = {}
            for host in self.client.get_hosts():
                host_mappings = host.mappings_briefs
                for mapping in host_mappings:
                    if volume_id == mapping["volume_id"]:
                        host_name_to_lun_id[host.name] = scsilun_to_int(
                            mapping["lunid"])
                        break
            logger.debug(
                "Found volume mappings: {}".format(host_name_to_lun_id))
            return host_name_to_lun_id
        except exceptions.ClientException as ex:
            logger.error("Failed to get volume mappings. Reason is: {}".format(
                ex.details))
            raise ex

    def map_volume(self, volume_id, host_name):
        logger.debug("Mapping volume {} to host {}".format(
            volume_id, host_name))
        array_volume_id = get_volume_id_from_scsi_identifier(volume_id)
        try:
            mapping = self.client.map_volume_to_host(host_name,
                                                     array_volume_id)
            lun = scsilun_to_int(mapping.lunid)
            logger.debug(
                "Successfully mapped volume to host with lun {}".format(lun))
            return lun
        except exceptions.NotFound:
            raise array_errors.HostNotFoundError(host_name)
        except exceptions.ClientException as ex:
            # [BE586015] addLunMappings Volume group operation failure: volume does not exist.
            if ERROR_CODE_VOLUME_NOT_FOUND_FOR_MAPPING in str(
                    ex.message).upper():
                raise array_errors.VolumeNotFoundError(volume_id)
            else:
                raise array_errors.MappingError(volume_id, host_name,
                                                ex.details)

    def unmap_volume(self, volume_id, host_name):
        logger.debug("Unmapping volume {} from host {}".format(
            volume_id, host_name))
        array_volume_id = get_volume_id_from_scsi_identifier(volume_id)
        try:
            mappings = self.client.get_host_mappings(host_name)
            lunid = None
            for mapping in mappings:
                if mapping.volume == array_volume_id:
                    lunid = mapping.id
                    break
            if lunid is not None:
                self.client.unmap_volume_from_host(host_name=host_name,
                                                   lunid=lunid)
                logger.debug(
                    "Successfully unmapped volume from host with lun {}.".
                    format(lunid))
            else:
                raise array_errors.VolumeNotFoundError(volume_id)
        except exceptions.NotFound:
            raise array_errors.HostNotFoundError(host_name)
        except exceptions.ClientException as ex:
            raise array_errors.UnMappingError(volume_id, host_name, ex.details)

    def _get_pools(self):
        logger.info("Getting pools")
        try:
            pools = self.client.get_pools()
        except exceptions.ClientException as ex:
            logger.error("Failed to get pools, reason is: {}".format(
                ex.details))
            raise ex
        logger.info("Pools found: {}".format(pools))
        return pools

    def get_flashcopies_by_volume(self, volume_id):
        try:
            return self.client.get_flashcopies_by_volume(volume_id)
        except exceptions.NotFound:
            raise array_errors.VolumeNotFoundError(volume_id)

    def _get_api_volume_by_name(self, volume_name, pool_id):
        logger.info("Getting volume {} in pool {}".format(
            volume_name, pool_id))
        if not pool_id:
            pools = self._get_pools()
        else:
            pools = [Munch({"id": pool_id})]

        for pool in pools:
            volume_candidates = []
            try:
                volume_candidates.extend(
                    self.client.get_volumes_by_pool(pool.id))
            except (exceptions.NotFound, exceptions.InternalServerError) as ex:
                if ERROR_CODE_RESOURCE_NOT_EXISTS or INCORRECT_ID in str(
                        ex.message).upper():
                    raise array_errors.PoolDoesNotExist(
                        pool.id, self.identifier)
                else:
                    raise ex
            for volume in volume_candidates:
                logger.info("Checking volume: {}".format(volume.name))
                if volume.name == volume_name:
                    logger.debug("Found volume: {}".format(volume))
                    volume.flashcopy = self.get_flashcopies_by_volume(
                        volume.id)
                    return volume
        return None

    def _get_api_volume_by_id(self, volume_id, not_exist_err=True):
        try:
            volume = self.client.get_volume(volume_id)
            volume.flashcopy = self.get_flashcopies_by_volume(volume.id)
            return volume
        except exceptions.NotFound:
            if not_exist_err:
                raise array_errors.VolumeNotFoundError(volume_id)

    def _get_flashcopy(self, flashcopy_id, not_exist_err=True):
        logger.info("Getting flashcopy {}".format(flashcopy_id))
        try:
            return self.client.get_flashcopies(flashcopy_id)
        except exceptions.NotFound as ex:
            if ERROR_CODE_RESOURCE_NOT_EXISTS in str(ex.message).upper():
                logger.info("{} not found".format(flashcopy_id))
                if not_exist_err:
                    raise ex
        except Exception as ex:
            logger.exception(ex)
            raise ex

    def get_snapshot(self, snapshot_name, volume_context=None):
        logger.debug("Get snapshot : {} with context: {}".format(
            snapshot_name, volume_context))
        if not volume_context:
            logger.error(
                "volume_context is not specified, can not get volumes from storage."
            )
            raise array_errors.VolumeNotFoundError(snapshot_name)
        target_api_volume = self._get_api_volume_by_name(
            volume_name=snapshot_name,
            pool_id=volume_context[config.CONTEXT_POOL])
        if not target_api_volume:
            return None
        if not target_api_volume.flashcopy:
            logger.error(
                "FlashCopy relationship not found for target volume: {}".
                format(snapshot_name))
            raise array_errors.SnapshotNameBelongsToVolumeError(
                target_api_volume.name, self.service_address)
        flashcopy_rel = self._get_flashcopy(target_api_volume.flashcopy[0].id)
        source_volume_name = self.get_volume_name(
            flashcopy_rel.source_volume['id'])
        return self._generate_snapshot_response(target_api_volume,
                                                source_volume_name)

    def _create_similar_volume(self, target_volume_name, source_volume_name,
                               pool_id):
        logger.info(
            "creating target api volume '{0}' from source volume '{1}'".format(
                target_volume_name, source_volume_name))
        source_api_volume = self._get_api_volume_by_name(source_volume_name,
                                                         pool_id=pool_id)
        if not source_api_volume:
            raise array_errors.VolumeNotFoundError(source_volume_name)
        capabilities = {
            config.CAPABILITIES_SPACEEFFICIENCY: source_api_volume.tp
        }
        size_in_bytes = int(source_api_volume.cap)
        pool = source_api_volume.pool
        return self.create_volume(target_volume_name, size_in_bytes,
                                  capabilities, pool)

    def _create_flashcopy(self,
                          source_volume_id,
                          target_volume_id,
                          options=None):
        logger.info(
            "creating FlashCopy relationship from '{0}' to '{1}'".format(
                source_volume_id, target_volume_id))
        source_volume_id = get_volume_id_from_scsi_identifier(source_volume_id)
        target_volume_id = get_volume_id_from_scsi_identifier(target_volume_id)
        if not options:
            options = []
        options.append(FLASHCOPY_PERMIT_SPACE_EFFICIENT_TARGET)
        try:
            api_flashcopy = self.client.create_flashcopy(
                source_volume_id=source_volume_id,
                target_volume_id=target_volume_id,
                options=options)
        except (exceptions.ClientError, exceptions.ClientException) as ex:
            if ERROR_CODE_ALREADY_FLASHCOPY in str(ex.message).upper():
                raise array_errors.SnapshotAlreadyExists(
                    target_volume_id, self.service_address)
            elif ERROR_CODE_VOLUME_NOT_FOUND_OR_ALREADY_PART_OF_CS_RELATIONSHIP in str(
                    ex.message).upper():
                raise array_errors.VolumeNotFoundError('{} or {}'.format(
                    source_volume_id, target_volume_id))
            else:
                raise ex
        if not self.validate_flashcopy(api_flashcopy.id):
            self._delete_flashcopy(api_flashcopy.id)
            logger.info("Flashcopy is not in a valid state")
            raise ValueError
        return self._get_api_volume_by_id(target_volume_id)

    @retry(Exception, tries=11, delay=1)
    def _delete_target_volume_if_exist(self, target_volume_id):
        self._delete_volume(target_volume_id, not_exist_err=False)

    def _create_snapshot(self, target_volume_name, pool_id,
                         source_volume_name):
        target_volume = self._create_similar_volume(target_volume_name,
                                                    source_volume_name,
                                                    pool_id)
        source_volume = self.get_volume(
            source_volume_name, volume_context={config.CONTEXT_POOL: pool_id})
        options = [
            FLASHCOPY_NO_BACKGROUND_COPY_OPTION, FLASHCOPY_PERSISTENT_OPTION
        ]
        try:
            return self._create_flashcopy(source_volume.id, target_volume.id,
                                          options)
        except (array_errors.VolumeNotFoundError,
                array_errors.SnapshotAlreadyExists) as ex:
            logger.error("Failed to create snapshot '{0}': {1}".format(
                target_volume_name, ex))
            self._delete_target_volume_if_exist(target_volume.id)
            raise ex

    def get_snapshot_by_id(self, src_snapshot_id):
        src_snapshot_id = get_volume_id_from_scsi_identifier(src_snapshot_id)
        api_snapshot = self._get_api_volume_by_id(src_snapshot_id)
        src_volume_id = get_source_volume_id_if_exists(api_snapshot)
        api_source_volume = self._get_api_volume_by_id(src_volume_id)
        return self._generate_snapshot_response(api_snapshot,
                                                api_source_volume.name)

    def create_snapshot(self, name, volume_name, volume_context=None):
        logger.info("creating snapshot '{0}' from volume '{1}'".format(
            name, volume_name))
        if not volume_context:
            logger.error(
                "volume_context is not specified, can not get volumes from storage."
            )
        pool = volume_context[config.CONTEXT_POOL]
        target_api_volume = self._create_snapshot(
            name, pool, source_volume_name=volume_name)
        logger.info(
            "finished creating snapshot '{0}' from volume '{1}'".format(
                name, volume_name))
        return self._generate_snapshot_response(target_api_volume, volume_name)

    def _delete_flashcopy(self, flascopy_id):
        try:
            self.client.delete_flashcopy(flascopy_id)
        except exceptions.NotFound:
            raise array_errors.VolumeNotFoundError(flascopy_id)
        except exceptions.ClientException as ex:
            logger.error(
                "Failed to delete flascopy {} on array {}, reason is: {}".
                format(flascopy_id, self.identifier, ex.details))
            raise ex

    def delete_snapshot(self, snapshot_id):
        logger.info("Deleting snapshot with id : {0}".format(snapshot_id))
        volume_id = get_volume_id_from_scsi_identifier(snapshot_id)
        api_volume = self._get_api_volume_by_id(volume_id, not_exist_err=False)
        if not api_volume:
            raise array_errors.SnapshotNotFoundError(snapshot_id)
        if not api_volume.flashcopy:
            logger.error(
                "FlashCopy relationship not found for target volume: {}".
                format(api_volume.name))
            raise array_errors.SnapshotNameBelongsToVolumeError(
                api_volume.name, self.service_address)
        self._check_snapshot_use_status(volume_id, api_volume.flashcopy)
        self.delete_volume(volume_id)
        logger.info("Finished snapshot deletion. id : {0}".format(snapshot_id))

    def get_iscsi_targets_by_iqn(self):
        return {}

    def get_array_fc_wwns(self, host_name=None):
        logger.debug(
            "Getting the connected fc port wwpns for host {} from array".
            format(host_name))

        try:
            host = self.client.get_host(host_name)
            wwpns = [
                port[LOGIN_PORT_WWPN] for port in host.login_ports
                if port[LOGIN_PORT_STATE] == LOGIN_PORT_STATE_ONLINE
            ]
            logger.debug("Found wwpns: {}".format(wwpns))
            return wwpns
        except exceptions.NotFound:
            raise array_errors.HostNotFoundError(host_name)
        except exceptions.ClientException as ex:
            logger.error("Failed to get array fc wwpn. Reason is: {}".format(
                ex.details))
            raise ex

    def get_host_by_host_identifiers(self, initiators):
        logger.debug("Getting host by initiators: {}".format(initiators))
        found = ""
        for host in self.client.get_hosts():
            host_ports = host.host_ports_briefs
            wwpns = [p["wwpn"] for p in host_ports]
            if initiators.is_array_wwns_match(wwpns):
                found = host.name
                break
        if found:
            logger.debug("found host {0} with fc wwpns: {1}".format(
                found, initiators.fc_wwns))
            return found, [config.FC_CONNECTIVITY_TYPE]
        else:
            logger.debug(
                "can not found host by initiators: {0} ".format(initiators))
            raise array_errors.HostNotFoundError(initiators)

    def validate_supported_capabilities(self, capabilities):
        logger.debug("Validating capabilities: {0}".format(capabilities))

        # Currently, we only support one capability "SpaceEfficiency"
        # The value should be: "thin"
        if (capabilities and capabilities.get(
                config.CAPABILITIES_SPACEEFFICIENCY).lower() not in [
                    config.CAPABILITY_THIN,
                ]):
            logger.error("capabilities is not supported.")
            raise array_errors.StorageClassCapabilityNotSupported(capabilities)

        logger.debug("Finished validating capabilities.")

    def _generate_snapshot_response(self, api_snapshot, source_volume_name):
        return Snapshot(capacity_bytes=int(api_snapshot.cap),
                        snapshot_id=self._generate_volume_scsi_identifier(
                            api_snapshot.id),
                        snapshot_name=api_snapshot.name,
                        array_address=self.service_address,
                        volume_name=source_volume_name,
                        is_ready=True,
                        array_type=self.array_type)

    def validate_flashcopy(self, flashcopy_id):
        api_flashcopy = self._get_flashcopy(flashcopy_id)
        return api_flashcopy.state == 'valid'

    def _check_snapshot_use_status(self, snapshot_id, flashcopy_list):
        for flashcopy in flashcopy_list:
            logger.info("Deleting flashcopy: {}".format(flashcopy))
            if flashcopy.sourcevolume == snapshot_id:
                flashcopy_rel = self._get_flashcopy(flashcopy.id)
                if flashcopy_rel.out_of_sync_tracks != '0':
                    raise array_errors.SnapshotIsStillInUseError(
                        snapshot_id, flashcopy_rel.targetvolume)