# Copyright (C) 2020, Hitachi, Ltd.
#
#    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.
#
"""REST interface module for Hitachi HBSD Driver."""

from collections import defaultdict

from oslo_config import cfg
from oslo_log import log as logging
from oslo_service import loopingcall
from oslo_utils import excutils
from oslo_utils import timeutils
from oslo_utils import units

from cinder import exception
from cinder.volume import configuration
from cinder.volume.drivers.hitachi import hbsd_common as common
from cinder.volume.drivers.hitachi import hbsd_rest_api as rest_api
from cinder.volume.drivers.hitachi import hbsd_utils as utils
from cinder.volume.drivers.san import san

_LU_PATH_DEFINED = ('B958', '015A')
NORMAL_STS = 'NML'
_LUN_MAX_WAITTIME = 50
_LUN_RETRY_INTERVAL = 1
PAIR_ATTR = 'HTI'
_SNAP_MODE = 'A'
_CLONE_MODE = 'C'
_NORMAL_MODE = '-'

_PERMITTED_TYPES = set(['CVS', 'HDP', 'HDT'])

_CHECK_LDEV_MANAGEABILITY_KEYS = (
    'emulationType', 'numOfPorts', 'attributes', 'status')
_CHECK_LDEV_SIZE_KEYS = ('blockCapacity',)

SMPL = 1
PVOL = 2
SVOL = 3

COPY = 2
PAIR = 3
PSUS = 4
PSUE = 5
SMPP = 6
UNKN = 0xff

_STATUS_TABLE = {
    'SMPL': SMPL,
    'COPY': COPY,
    'RCPY': COPY,
    'PAIR': PAIR,
    'PFUL': PAIR,
    'PSUS': PSUS,
    'PFUS': PSUS,
    'SSUS': PSUS,
    'PSUE': PSUE,
    'PSUP': PSUS,
    'SSUP': PSUS,
    'SMPP': SMPP,
}

SNAP_NAME = 'HBSD-snap'
CLONE_NAME = 'HBSD-clone'

_SNAP_HASH_SIZE = 8

EX_ENOOBJ = 'EX_ENOOBJ'

_REST_DEFAULT_PORT = 443

_GET_LDEV_COUNT = 16384
_MAX_LDEV_ID = 65535
EX_ENLDEV = 'EX_ENLDEV'
EX_INVARG = 'EX_INVARG'
_INVALID_RANGE = [EX_ENLDEV, EX_INVARG]


REST_VOLUME_OPTS = [
    cfg.BoolOpt(
        'hitachi_rest_tcp_keepalive',
        default=True,
        help='Enables or disables use of REST API tcp keepalive'),
    cfg.BoolOpt(
        'hitachi_discard_zero_page',
        default=True,
        help='Enable or disable zero page reclamation in a DP-VOL.'),
]

_REQUIRED_REST_OPTS = [
    'san_login',
    'san_password',
    'san_ip',
]

CONF = cfg.CONF
CONF.register_opts(REST_VOLUME_OPTS, group=configuration.SHARED_CONF_GROUP)

LOG = logging.getLogger(__name__)
MSG = utils.HBSDMsg


def _is_valid_target(target, target_name, target_ports):
    """Check if the specified target is valid."""
    return (target[:utils.PORT_ID_LENGTH] in target_ports and
            target_name.startswith(utils.TARGET_PREFIX))


def _check_ldev_manageability(ldev_info, ldev, existing_ref):
    """Check if the LDEV meets the criteria for being managed."""
    if ldev_info['status'] != NORMAL_STS:
        msg = utils.output_log(MSG.INVALID_LDEV_FOR_MANAGE)
        raise exception.ManageExistingInvalidReference(
            existing_ref=existing_ref, reason=msg)
    attributes = set(ldev_info['attributes'])
    if (not ldev_info['emulationType'].startswith('OPEN-V') or
            len(attributes) < 2 or not attributes.issubset(_PERMITTED_TYPES)):
        msg = utils.output_log(MSG.INVALID_LDEV_ATTR_FOR_MANAGE, ldev=ldev,
                               ldevtype=utils.NVOL_LDEV_TYPE)
        raise exception.ManageExistingInvalidReference(
            existing_ref=existing_ref, reason=msg)
    if ldev_info['numOfPorts']:
        msg = utils.output_log(MSG.INVALID_LDEV_PORT_FOR_MANAGE, ldev=ldev)
        raise exception.ManageExistingInvalidReference(
            existing_ref=existing_ref, reason=msg)


def _check_ldev_size(ldev_info, ldev, existing_ref):
    """Hitachi storage calculates volume sizes in a block unit, 512 bytes."""
    if ldev_info['blockCapacity'] % utils.GIGABYTE_PER_BLOCK_SIZE:
        msg = utils.output_log(MSG.INVALID_LDEV_SIZE_FOR_MANAGE, ldev=ldev)
        raise exception.ManageExistingInvalidReference(
            existing_ref=existing_ref, reason=msg)


class HBSDREST(common.HBSDCommon):
    """REST interface class for Hitachi HBSD Driver."""

    def __init__(self, conf, storage_protocol, db):
        """Initialize instance variables."""
        super(HBSDREST, self).__init__(conf, storage_protocol, db)
        self.conf.append_config_values(REST_VOLUME_OPTS)
        self.conf.append_config_values(san.san_opts)

        self.client = None

    def setup_client(self):
        """Initialize RestApiClient."""
        verify = self.conf.driver_ssl_cert_verify
        if verify:
            verify_path = self.conf.safe_get('driver_ssl_cert_path')
            if verify_path:
                verify = verify_path
        self.verify = verify
        self.client = rest_api.RestApiClient(
            self.conf.san_ip,
            self.conf.san_api_port,
            self.conf.hitachi_storage_id,
            self.conf.san_login,
            self.conf.san_password,
            tcp_keepalive=self.conf.hitachi_rest_tcp_keepalive,
            verify=verify)
        self.client.login()

    def need_client_setup(self):
        """Check if the making of the communication client is necessary."""
        return not self.client or not self.client.get_my_session()

    def enter_keep_session(self):
        """Begin the keeping of the session."""
        if self.client is not None:
            self.client.enter_keep_session()

    def _create_ldev_on_storage(self, size):
        """Create an LDEV on the storage system."""
        body = {
            'byteFormatCapacity': '%sG' % size,
            'poolId': self.storage_info['pool_id'],
            'isParallelExecutionEnabled': True,
        }
        if self.storage_info['ldev_range']:
            min_ldev, max_ldev = self.storage_info['ldev_range'][:2]
            body['startLdevId'] = min_ldev
            body['endLdevId'] = max_ldev
        return self.client.add_ldev(body, no_log=True)

    def create_ldev(self, size):
        """Create an LDEV of the specified size and the specified type."""
        ldev = self._create_ldev_on_storage(size)
        LOG.debug('Created logical device. (LDEV: %s)', ldev)
        return ldev

    def modify_ldev_name(self, ldev, name):
        """Modify LDEV name."""
        body = {'label': name}
        self.client.modify_ldev(ldev, body)

    def delete_ldev_from_storage(self, ldev):
        """Delete the specified LDEV from the storage."""
        result = self.client.get_ldev(ldev)
        if result['emulationType'] == 'NOT DEFINED':
            utils.output_log(MSG.LDEV_NOT_EXIST, ldev=ldev)
            return
        self.client.delete_ldev(
            ldev,
            timeout_message=(MSG.LDEV_DELETION_WAIT_TIMEOUT, {'ldev': ldev}))

    def _get_copy_pair_status(self, ldev):
        """Return the status of the volume in a copy pair."""
        params_s = {"svolLdevId": ldev}
        result_s = self.client.get_snapshots(params_s)
        if not result_s:
            params_p = {"pvolLdevId": ldev}
            result_p = self.client.get_snapshots(params_p)
            if not result_p:
                return SMPL
            return _STATUS_TABLE.get(result_p[0]['status'], UNKN)
        return _STATUS_TABLE.get(result_s[0]['status'], UNKN)

    def _wait_copy_pair_status(self, ldev, status, interval=3,
                               timeout=utils.DEFAULT_PROCESS_WAITTIME):
        """Wait until the S-VOL status changes to the specified status."""

        def _wait_for_copy_pair_status(
                start_time, ldev, status, timeout):
            """Raise True if the S-VOL is in the specified status."""
            if not isinstance(status, set):
                status = set([status])
            if self._get_copy_pair_status(ldev) in status:
                raise loopingcall.LoopingCallDone()
            if utils.timed_out(start_time, timeout):
                raise loopingcall.LoopingCallDone(False)

        loop = loopingcall.FixedIntervalLoopingCall(
            _wait_for_copy_pair_status, timeutils.utcnow(),
            ldev, status, timeout)
        if not loop.start(interval=interval).wait():
            msg = utils.output_log(
                MSG.PAIR_STATUS_WAIT_TIMEOUT, svol=ldev)
            raise utils.HBSDError(msg)

    def _create_snap_pair(self, pvol, svol):
        """Create a snapshot copy pair on the storage."""
        snapshot_name = '%(prefix)s%(svol)s' % {
            'prefix': SNAP_NAME,
            'svol': svol % _SNAP_HASH_SIZE,
        }
        try:
            body = {"snapshotGroupName": snapshot_name,
                    "snapshotPoolId": self.storage_info['snap_pool_id'],
                    "pvolLdevId": pvol,
                    "svolLdevId": svol,
                    "autoSplit": True,
                    "canCascade": True,
                    "isDataReductionForceCopy": True}
            self.client.add_snapshot(body)
        except utils.HBSDError as ex:
            if (utils.safe_get_err_code(ex.kwargs.get('errobj')) ==
                    rest_api.INVALID_SNAPSHOT_POOL and
                    not self.conf.hitachi_snap_pool):
                msg = utils.output_log(
                    MSG.INVALID_PARAMETER, param='hitachi_snap_pool')
                raise utils.HBSDError(msg)
            else:
                raise
        try:
            self._wait_copy_pair_status(svol, PSUS)
        except Exception:
            with excutils.save_and_reraise_exception():
                try:
                    self._delete_pair_from_storage(pvol, svol)
                except utils.HBSDError:
                    utils.output_log(
                        MSG.DELETE_PAIR_FAILED, pvol=pvol, svol=svol)

    def _create_clone_pair(self, pvol, svol):
        """Create a clone copy pair on the storage."""
        snapshot_name = '%(prefix)s%(svol)s' % {
            'prefix': CLONE_NAME,
            'svol': svol % _SNAP_HASH_SIZE,
        }
        try:
            body = {"snapshotGroupName": snapshot_name,
                    "snapshotPoolId": self.storage_info['snap_pool_id'],
                    "pvolLdevId": pvol,
                    "svolLdevId": svol,
                    "isClone": True,
                    "clonesAutomation": True,
                    "copySpeed": 'medium',
                    "isDataReductionForceCopy": True}
            self.client.add_snapshot(body)
        except utils.HBSDError as ex:
            if (utils.safe_get_err_code(ex.kwargs.get('errobj')) ==
                    rest_api.INVALID_SNAPSHOT_POOL and
                    not self.conf.hitachi_snap_pool):
                msg = utils.output_log(
                    MSG.INVALID_PARAMETER, param='hitachi_snap_pool')
                raise utils.HBSDError(msg)
            else:
                raise
        try:
            self._wait_copy_pair_status(svol, set([PSUS, SMPP, SMPL]))
        except Exception:
            with excutils.save_and_reraise_exception():
                try:
                    self._delete_pair_from_storage(pvol, svol)
                except utils.HBSDError:
                    utils.output_log(
                        MSG.DELETE_PAIR_FAILED, pvol=pvol, svol=svol)

    def create_pair_on_storage(self, pvol, svol, is_snapshot=False):
        """Create a copy pair on the storage."""
        if is_snapshot:
            self._create_snap_pair(pvol, svol)
        else:
            self._create_clone_pair(pvol, svol)

    def get_ldev_info(self, keys, ldev, **kwargs):
        """Return a dictionary of LDEV-related items."""
        d = {}
        result = self.client.get_ldev(ldev, **kwargs)
        for key in keys:
            d[key] = result.get(key)
        return d

    def _wait_copy_pair_deleting(self, ldev):
        """Wait until the LDEV is no longer in a copy pair."""

        def _wait_for_copy_pair_smpl(start_time, ldev):
            """Raise True if the LDEV is no longer in a copy pair."""
            ldev_info = self.get_ldev_info(['status', 'attributes'], ldev)
            if (ldev_info['status'] != NORMAL_STS or
                    PAIR_ATTR not in ldev_info['attributes']):
                raise loopingcall.LoopingCallDone()
            if utils.timed_out(
                    start_time, utils.DEFAULT_PROCESS_WAITTIME):
                raise loopingcall.LoopingCallDone(False)

        loop = loopingcall.FixedIntervalLoopingCall(
            _wait_for_copy_pair_smpl, timeutils.utcnow(), ldev)
        if not loop.start(interval=10).wait():
            msg = utils.output_log(
                MSG.PAIR_STATUS_WAIT_TIMEOUT, svol=ldev)
            raise utils.HBSDError(msg)

    def _delete_pair_from_storage(self, pvol, svol):
        """Disconnect the volume pair that consists of the specified LDEVs."""
        params_s = {"svolLdevId": svol}
        result = self.client.get_snapshots(params_s)
        if not result:
            return
        mun = result[0]['muNumber']
        # If the snapshot is in deleting status,
        # not need to call a delete operation.
        if _STATUS_TABLE.get(result[0]['status']) != SMPP:
            self.client.unassign_snapshot_volume(pvol, mun,
                                                 ignore_all_errors=True)
            ignore_return_code = [EX_ENOOBJ]
            self.client.delete_snapshot(
                pvol, mun, ignore_return_code=ignore_return_code)
        self._wait_copy_pair_deleting(svol)

    def delete_pair_based_on_svol(self, pvol, svol_info):
        """Disconnect all volume pairs to which the specified S-VOL belongs."""
        # If the pair status does not satisfy the execution condition,
        if not (svol_info['is_psus'] or
                _STATUS_TABLE.get(svol_info['status']) == SMPP):
            msg = utils.output_log(
                MSG.UNABLE_TO_DELETE_PAIR, pvol=pvol, svol=svol_info['ldev'])
            raise utils.HBSDBusy(msg)

        self._delete_pair_from_storage(pvol, svol_info['ldev'])

    def check_param(self):
        """Check parameter values and consistency among them."""
        super(HBSDREST, self).check_param()
        utils.check_opts(self.conf, REST_VOLUME_OPTS)
        utils.check_opts(self.conf, san.san_opts)
        LOG.debug(
            'Setting ldev_range: %s', self.storage_info['ldev_range'])
        for opt in _REQUIRED_REST_OPTS:
            if not self.conf.safe_get(opt):
                msg = utils.output_log(MSG.INVALID_PARAMETER, param=opt)
                raise utils.HBSDError(msg)
        if not self.conf.safe_get('san_api_port'):
            self.conf.san_api_port = _REST_DEFAULT_PORT

    def _find_lun(self, ldev, port, gid):
        """Get LUN using."""
        luns_info = self.client.get_luns(port, gid)
        for lun_info in luns_info:
            if lun_info['ldevId'] == ldev:
                return lun_info['lun']
        return None

    def _run_add_lun(self, ldev, port, gid, lun=None):
        """Create a LUN between the specified LDEV and port-gid."""
        ignore_error = [_LU_PATH_DEFINED]
        if lun is not None:
            ignore_error = [rest_api.ANOTHER_LDEV_MAPPED]
        assigned_lun, errobj = self.client.add_lun(
            port, gid, ldev, lun=lun,
            ignore_error=ignore_error,
            interval=_LUN_RETRY_INTERVAL,
            timeout=_LUN_MAX_WAITTIME)
        err_code = utils.safe_get_err_code(errobj)
        if lun is None:
            if err_code == _LU_PATH_DEFINED:
                lun = self._find_lun(ldev, port, gid)
                LOG.debug(
                    'An logical unit path has already defined in the '
                    'specified logical device. (LDEV: %(ldev)s, '
                    'port: %(port)s, gid: %(gid)s, lun: %(lun)s)',
                    {'ldev': ldev, 'port': port, 'gid': gid, 'lun': lun})
            else:
                lun = assigned_lun
        elif err_code == rest_api.ANOTHER_LDEV_MAPPED:
            utils.output_log(MSG.MAP_LDEV_FAILED,
                             ldev=ldev, port=port, id=gid, lun=lun)
            return None
        LOG.debug(
            'Created logical unit path to the specified logical device. '
            '(LDEV: %(ldev)s, port: %(port)s, '
            'gid: %(gid)s, lun: %(lun)s)',
            {'ldev': ldev, 'port': port, 'gid': gid, 'lun': lun})
        return lun

    def map_ldev(self, targets, ldev):
        """Create the path between the server and the LDEV and return LUN."""
        port, gid = targets['list'][0]
        lun = self._run_add_lun(ldev, port, gid)
        targets['lun'][port] = True
        for port, gid in targets['list'][1:]:
            # When multipath is configured, Nova compute expects that
            # target_lun define the same value in all storage target.
            # Therefore, it should use same value of lun in other target.
            try:
                lun2 = self._run_add_lun(ldev, port, gid, lun=lun)
                if lun2 is not None:
                    targets['lun'][port] = True
            except utils.HBSDError:
                utils.output_log(MSG.MAP_LDEV_FAILED, ldev=ldev,
                                 port=port, id=gid, lun=lun)
        return lun

    def attach_ldev(self, volume, ldev, connector, targets):
        """Initialize connection between the server and the volume."""
        target_ports = self.get_target_ports(connector)
        if (self.find_targets_from_storage(
                targets, connector, target_ports) and
                self.conf.hitachi_group_create):
            self.create_mapping_targets(targets, connector)

        utils.require_target_existed(targets)

        targets['list'].sort()
        for port in target_ports:
            targets['lun'][port] = False
        return int(self.map_ldev(targets, ldev))

    def _find_mapped_targets_from_storage(self, targets, ldev, target_ports):
        """Update port-gid list for the specified LDEV."""
        ldev_info = self.get_ldev_info(['ports'], ldev)
        if not ldev_info['ports']:
            return
        for port_info in ldev_info['ports']:
            if _is_valid_target(port_info['portId'],
                                port_info['hostGroupName'],
                                target_ports):
                targets['list'].append(port_info)

    def _get_unmap_targets_list(self, target_list, mapped_list):
        """Return a list of IDs of ports that need to be disconnected."""
        unmap_list = []
        for mapping_info in mapped_list:
            if ((mapping_info['portId'][:utils.PORT_ID_LENGTH],
                 mapping_info['hostGroupNumber'])
                    in target_list):
                unmap_list.append(mapping_info)
        return unmap_list

    def unmap_ldev(self, targets, ldev):
        """Delete the LUN between the specified LDEV and port-gid."""
        interval = _LUN_RETRY_INTERVAL
        ignore_return_code = [EX_ENOOBJ]
        ignore_message_id = [rest_api.MSGID_SPECIFIED_OBJECT_DOES_NOT_EXIST]
        timeout = utils.DEFAULT_PROCESS_WAITTIME
        for target in targets['list']:
            port = target['portId']
            gid = target['hostGroupNumber']
            lun = target['lun']
            self.client.delete_lun(port, gid, lun,
                                   interval=interval,
                                   ignore_return_code=ignore_return_code,
                                   ignore_message_id=ignore_message_id,
                                   timeout=timeout)
            LOG.debug(
                'Deleted logical unit path of the specified logical '
                'device. (LDEV: %(ldev)s, host group: %(target)s)',
                {'ldev': ldev, 'target': target})

    def _get_target_luns(self, target):
        """Get the LUN mapping information of the host group."""
        port = target['portId']
        gid = target['hostGroupNumber']
        mapping_list = []
        luns_info = self.client.get_luns(port, gid)
        if luns_info:
            for lun_info in luns_info:
                mapping_list.append((port, gid, lun_info['lun'],
                                     lun_info['ldevId']))
        return mapping_list

    def delete_target_from_storage(self, port, gid):
        """Delete the host group or the iSCSI target from the port."""
        result = 1
        try:
            self.client.delete_host_grp(port, gid)
            result = 0
        except utils.HBSDError:
            utils.output_log(MSG.DELETE_TARGET_FAILED, port=port, id=gid)
        else:
            LOG.debug(
                'Deleted target. (port: %(port)s, gid: %(gid)s)',
                {'port': port, 'gid': gid})
        return result

    def _clean_mapping_targets(self, targets):
        """Delete the empty host group without LU."""
        deleted_targets = []
        for target in targets['list']:
            if not len(self._get_target_luns(target)):
                port = target['portId']
                gid = target['hostGroupNumber']
                ret = self.delete_target_from_storage(port, gid)
                if not ret:
                    deleted_targets.append(port)
        return deleted_targets

    def detach_ldev(self, volume, ldev, connector):
        """Terminate connection between the server and the volume."""
        targets = {
            'info': {},
            'list': [],
            'iqns': {},
        }
        mapped_targets = {
            'list': [],
        }
        unmap_targets = {}
        deleted_targets = []

        target_ports = self.get_target_ports(connector)
        self.find_targets_from_storage(targets, connector, target_ports)
        self._find_mapped_targets_from_storage(
            mapped_targets, ldev, target_ports)
        unmap_targets['list'] = self._get_unmap_targets_list(
            targets['list'], mapped_targets['list'])
        unmap_targets['list'].sort(
            reverse=True,
            key=lambda port: (port.get('portId'), port.get('hostGroupNumber')))
        self.unmap_ldev(unmap_targets, ldev)

        if self.conf.hitachi_group_delete:
            deleted_targets = self._clean_mapping_targets(unmap_targets)
        return deleted_targets

    def find_all_mapped_targets_from_storage(self, targets, ldev):
        """Add all port-gids connected with the LDEV to the list."""
        ldev_info = self.get_ldev_info(['ports'], ldev)
        if ldev_info['ports']:
            for port in ldev_info['ports']:
                targets['list'].append(port)

    def extend_ldev(self, ldev, old_size, new_size):
        """Extend the specified LDEV to the specified new size."""
        body = {"parameters": {"additionalByteFormatCapacity":
                               '%sG' % (new_size - old_size)}}
        self.client.extend_ldev(ldev, body)

    def get_pool_info(self):
        """Return the total and free capacity of the storage pool."""
        result = self.client.get_pool(
            self.storage_info['pool_id'],
            ignore_message_id=[rest_api.MSGID_SPECIFIED_OBJECT_DOES_NOT_EXIST])

        if 'errorSource' in result:
            msg = utils.output_log(MSG.POOL_NOT_FOUND,
                                   pool=self.storage_info['pool_id'])
            raise utils.HBSDError(msg)

        tp_cap = result['totalPoolCapacity'] / units.Ki
        ta_cap = result['availableVolumeCapacity'] / units.Ki
        tl_cap = result['totalLocatedCapacity'] / units.Ki

        return tp_cap, ta_cap, tl_cap

    def discard_zero_page(self, volume):
        """Return the volume's no-data pages to the storage pool."""
        if self.conf.hitachi_discard_zero_page:
            ldev = utils.get_ldev(volume)
            try:
                self.client.discard_zero_page(ldev)
            except utils.HBSDError:
                utils.output_log(MSG.DISCARD_ZERO_PAGE_FAILED, ldev=ldev)

    def _get_copy_pair_info(self, ldev):
        """Return info of the copy pair."""
        params_p = {"pvolLdevId": ldev}
        result_p = self.client.get_snapshots(params_p)
        if result_p:
            is_psus = _STATUS_TABLE.get(result_p[0]['status']) == PSUS
            pvol, svol = ldev, int(result_p[0]['svolLdevId'])
            status = result_p[0]['status']
        else:
            params_s = {"svolLdevId": ldev}
            result_s = self.client.get_snapshots(params_s)
            if result_s:
                is_psus = _STATUS_TABLE.get(result_s[0]['status']) == PSUS
                pvol, svol = int(result_s[0]['pvolLdevId']), ldev
                status = result_s[0]['status']
            else:
                return None, None
        LOG.debug(
            'Copy pair status. (P-VOL: %(pvol)s, S-VOL: %(svol)s, '
            'status: %(status)s)',
            {'pvol': pvol, 'svol': svol, 'status': status})

        return pvol, [{'ldev': svol, 'is_psus': is_psus, 'status': status}]

    def get_pair_info(self, ldev):
        """Return info of the volume pair."""
        pair_info = {}
        ldev_info = self.get_ldev_info(['status', 'attributes'], ldev)
        if (ldev_info['status'] != NORMAL_STS or
                PAIR_ATTR not in ldev_info['attributes']):
            return None

        pvol, svol_info = self._get_copy_pair_info(ldev)
        if pvol is not None:
            pair_info['pvol'] = pvol
            pair_info.setdefault('svol_info', [])
            pair_info['svol_info'].extend(svol_info)

        return pair_info

    def get_ldev_by_name(self, name):
        """Get the LDEV number from the given name."""
        ignore_message_id = ['KART40044-E']
        ignore_return_code = _INVALID_RANGE
        if self.storage_info['ldev_range']:
            start, end = self.storage_info['ldev_range'][:2]
            if end - start + 1 > _GET_LDEV_COUNT:
                cnt = _GET_LDEV_COUNT
            else:
                cnt = end - start + 1
        else:
            start = 0
            end = _MAX_LDEV_ID
            cnt = _GET_LDEV_COUNT

        for current in range(start, end, cnt):
            params = {'headLdevId': current, 'ldevOption': 'dpVolume',
                      'count': cnt}
            ldev_list = self.client.get_ldevs(
                params, ignore_message_id=ignore_message_id,
                ignore_return_code=ignore_return_code)
            for ldev_data in ldev_list:
                if 'label' in ldev_data and name == ldev_data['label']:
                    return ldev_data['ldevId']
        return None

    def check_ldev_manageability(self, ldev, existing_ref):
        """Check if the LDEV meets the criteria for being managed."""
        ldev_info = self.get_ldev_info(
            _CHECK_LDEV_MANAGEABILITY_KEYS, ldev)
        _check_ldev_manageability(ldev_info, ldev, existing_ref)

    def get_ldev_size_in_gigabyte(self, ldev, existing_ref):
        """Return the size[GB] of the specified LDEV."""
        ldev_info = self.get_ldev_info(
            _CHECK_LDEV_SIZE_KEYS, ldev)
        _check_ldev_size(ldev_info, ldev, existing_ref)
        return ldev_info['blockCapacity'] / utils.GIGABYTE_PER_BLOCK_SIZE

    def _get_pool_id(self, name):
        """Get the pool id from specified name."""
        pool_list = self.client.get_pools()
        for pool_data in pool_list:
            if pool_data['poolName'] == name:
                return pool_data['poolId']
        return None

    def check_pool_id(self):
        """Check the pool id of hitachi_pool and hitachi_snap_pool."""
        pool = self.conf.hitachi_pool
        if pool is not None:
            if pool.isdigit():
                self.storage_info['pool_id'] = int(pool)
            else:
                self.storage_info['pool_id'] = self._get_pool_id(pool)
        if self.storage_info['pool_id'] is None:
            msg = utils.output_log(
                MSG.POOL_NOT_FOUND, pool=self.conf.hitachi_pool)
            raise utils.HBSDError(msg)

        snap_pool = self.conf.hitachi_snap_pool
        if snap_pool is not None:
            if snap_pool.isdigit():
                self.storage_info['snap_pool_id'] = int(snap_pool)
            else:
                self.storage_info['snap_pool_id'] = (
                    self._get_pool_id(snap_pool))
                if self.storage_info['snap_pool_id'] is None:
                    msg = utils.output_log(MSG.POOL_NOT_FOUND,
                                           pool=self.conf.hitachi_snap_pool)
                    raise utils.HBSDError(msg)
        else:
            self.storage_info['snap_pool_id'] = self.storage_info['pool_id']

    def _to_hostgroup(self, port, gid):
        """Get a host group name from host group ID."""
        return self.client.get_host_grp(port, gid)['hostGroupName']

    def get_port_hostgroup_map(self, ldev_id):
        """Get the mapping of a port and host group."""
        hostgroups = defaultdict(list)
        ldev_info = self.get_ldev_info(['ports'], ldev_id)
        if not ldev_info['ports']:
            return hostgroups
        for port in ldev_info['ports']:
            portId = port["portId"]
            hostgroup = self._to_hostgroup(
                portId, port["hostGroupNumber"])
            hostgroups[portId].append(hostgroup)
        return hostgroups

    def check_pair_svol(self, ldev):
        """Check if the specified LDEV is S-VOL in a copy pair."""
        ldev_info = self.get_ldev_info(['status',
                                        'snapshotPoolId'], ldev)
        if ldev_info['status'] != NORMAL_STS:
            return False
        if ldev_info['snapshotPoolId'] is not None:
            _, svol_info = self._get_copy_pair_info(ldev)
            if svol_info and svol_info[0]['status'] == 'PSUP':
                self._wait_copy_pair_deleting(ldev)
                return False
            else:
                return True
        return False

    def restore_ldev(self, pvol, svol):
        """Restore a pair of the specified LDEV."""
        timeout = utils.MAX_PROCESS_WAITTIME

        params_s = {"svolLdevId": svol}
        result = self.client.get_snapshots(params_s)
        mun = result[0]['muNumber']
        body = {"parameters": {"autoSplit": True}}
        self.client.restore_snapshot(pvol, mun, body)

        self._wait_copy_pair_status(
            svol, PSUS, timeout=timeout, interval=10)

    def has_snap_pair(self, pvol, svol):
        """Check if the volume have the pair of the snapshot."""
        ldev_info = self.get_ldev_info(['status', 'attributes'], svol)
        if (ldev_info['status'] != NORMAL_STS or
                PAIR_ATTR not in ldev_info['attributes']):
            return False
        params_s = {"svolLdevId": svol}
        result = self.client.get_snapshots(params_s)
        if not result:
            return False
        return (result[0]['primaryOrSecondary'] == "S-VOL" and
                int(result[0]['pvolLdevId']) == pvol)
