# Copyright (C) 2014, 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.

import functools
import os
import re
import shlex
import threading
import time

from oslo_concurrency import processutils as putils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import units
import six

from cinder import exception
from cinder.i18n import _LE, _LW
from cinder.openstack.common import loopingcall
from cinder import utils
from cinder.volume.drivers.hitachi import hbsd_basiclib as basic_lib

GETSTORAGEARRAY_ONCE = 100
MAX_SNAPSHOT_COUNT = 1021
SNAP_LAST_PATH_SSB = '0xB958,0x020A'
HOST_IO_SSB = '0xB958,0x0233'
INVALID_LUN_SSB = '0x2E20,0x0000'
INTERCEPT_LDEV_SSB = '0x2E22,0x0001'
HOSTGROUP_INSTALLED = '0xB956,0x3173'

LDEV_STATUS_WAITTIME = 120
LUN_DELETE_WAITTIME = basic_lib.DEFAULT_PROCESS_WAITTIME
LUN_DELETE_INTERVAL = 3
EXEC_MAX_WAITTIME = 30
EXEC_RETRY_INTERVAL = 5
HORCM_WAITTIME = 1
PAIR_TYPE = ('HORC', 'MRCF', 'QS')
PERMITTED_TYPE = ('CVS', 'HDP', 'HDT')

RAIDCOM_LOCK_FILE = basic_lib.LOCK_DIR + 'raidcom_'
HORCMGR_LOCK_FILE = basic_lib.LOCK_DIR + 'horcmgr_'
RESOURCE_LOCK_FILE = basic_lib.LOCK_DIR + 'raidcom_resource_'

STATUS_TABLE = {
    'SMPL': basic_lib.SMPL,
    'COPY': basic_lib.COPY,
    'RCPY': basic_lib.COPY,
    'PAIR': basic_lib.PAIR,
    'PFUL': basic_lib.PAIR,
    'PSUS': basic_lib.PSUS,
    'PFUS': basic_lib.PSUS,
    'SSUS': basic_lib.PSUS,
    'PSUE': basic_lib.PSUE,
}
NOT_SET = '-'
HORCM_RUNNING = 1
COPY_GROUP = basic_lib.NAME_PREFIX + '%s%s%03X%d'
SNAP_NAME = basic_lib.NAME_PREFIX + 'snap'
LDEV_NAME = basic_lib.NAME_PREFIX + 'ldev-%d-%d'
MAX_MUNS = 3

EX_ENAUTH = 202
EX_ENOOBJ = 205
EX_CMDRJE = 221
EX_CMDIOE = 237
EX_INVCMD = 240
EX_INVMOD = 241
EX_ENODEV = 246
EX_ENOENT = 247
EX_OPTINV = 248
EX_ATTDBG = 250
EX_ATTHOR = 251
EX_COMERR = 255
EX_UNKOWN = -1

NO_SUCH_DEVICE = (EX_ENODEV, EX_ENOENT)

COMMAND_IO_TO_RAID = (EX_CMDRJE, EX_CMDIOE, EX_INVCMD, EX_INVMOD, EX_OPTINV)

HORCM_ERROR = (EX_ATTDBG, EX_ATTHOR, EX_COMERR)

MAX_HOSTGROUPS = 254
MAX_HLUN = 2047

DEFAULT_PORT_BASE = 31000

LOG = logging.getLogger(__name__)

volume_opts = [
    cfg.StrOpt('hitachi_horcm_numbers',
               default='200,201',
               help='Instance numbers for HORCM'),
    cfg.StrOpt('hitachi_horcm_user',
               default=None,
               help='Username of storage system for HORCM'),
    cfg.StrOpt('hitachi_horcm_password',
               default=None,
               help='Password of storage system for HORCM',
               secret=True),
    cfg.BoolOpt('hitachi_horcm_add_conf',
                default=True,
                help='Add to HORCM configuration'),
]

CONF = cfg.CONF
CONF.register_opts(volume_opts)


def horcm_synchronized(function):
    @functools.wraps(function)
    def wrapper(*args, **kargs):
        if len(args) == 1:
            inst = args[0].conf.hitachi_horcm_numbers[0]
            raidcom_obj_lock = args[0].raidcom_lock
        else:
            inst = args[1]
            raidcom_obj_lock = args[0].raidcom_pair_lock
        raidcom_lock_file = '%s%d' % (RAIDCOM_LOCK_FILE, inst)
        lock = basic_lib.get_process_lock(raidcom_lock_file)
        with raidcom_obj_lock, lock:
            return function(*args, **kargs)
    return wrapper


def storage_synchronized(function):
    @functools.wraps(function)
    def wrapper(*args, **kargs):
        serial = args[0].conf.hitachi_serial_number
        resource_lock = args[0].resource_lock
        resource_lock_file = '%s%s' % (RESOURCE_LOCK_FILE, serial)
        lock = basic_lib.get_process_lock(resource_lock_file)
        with resource_lock, lock:
            return function(*args, **kargs)
    return wrapper


class HBSDHORCM(basic_lib.HBSDBasicLib):

    def __init__(self, conf):
        super(HBSDHORCM, self).__init__(conf=conf)

        self.copy_groups = [None] * MAX_MUNS
        self.raidcom_lock = threading.Lock()
        self.raidcom_pair_lock = threading.Lock()
        self.horcmgr_lock = threading.Lock()
        self.horcmgr_flock = None
        self.resource_lock = threading.Lock()

    def check_param(self):
        numbers = self.conf.hitachi_horcm_numbers.split(',')
        if len(numbers) != 2:
            msg = basic_lib.output_err(601, param='hitachi_horcm_numbers')
            raise exception.HBSDError(message=msg)
        for i in numbers:
            if not i.isdigit():
                msg = basic_lib.output_err(601, param='hitachi_horcm_numbers')
                raise exception.HBSDError(message=msg)
        self.conf.hitachi_horcm_numbers = map(int, numbers)
        inst = self.conf.hitachi_horcm_numbers[0]
        pair_inst = self.conf.hitachi_horcm_numbers[1]
        if inst == pair_inst:
            msg = basic_lib.output_err(601, param='hitachi_horcm_numbers')
            raise exception.HBSDError(message=msg)
        for param in ('hitachi_horcm_user', 'hitachi_horcm_password'):
            if not getattr(self.conf, param):
                msg = basic_lib.output_err(601, param=param)
                raise exception.HBSDError(message=msg)
        if self.conf.hitachi_thin_pool_id == self.conf.hitachi_pool_id:
            msg = basic_lib.output_err(601, param='hitachi_thin_pool_id')
            raise exception.HBSDError(message=msg)
        for opt in volume_opts:
            getattr(self.conf, opt.name)

    def set_copy_groups(self, host_ip):
        serial = self.conf.hitachi_serial_number
        inst = self.conf.hitachi_horcm_numbers[1]

        for mun in range(MAX_MUNS):
            copy_group = COPY_GROUP % (host_ip, serial, inst, mun)
            self.copy_groups[mun] = copy_group

    def set_pair_flock(self):
        inst = self.conf.hitachi_horcm_numbers[1]
        name = '%s%d' % (HORCMGR_LOCK_FILE, inst)
        self.horcmgr_flock = basic_lib.FileLock(name, self.horcmgr_lock)
        return self.horcmgr_flock

    def check_horcm(self, inst):
        args = 'HORCMINST=%d horcmgr -check' % inst
        ret, _stdout, _stderr = self.exec_command('env', args=args,
                                                  printflag=False)
        return ret

    def shutdown_horcm(self, inst):
        ret, stdout, stderr = self.exec_command(
            'horcmshutdown.sh', args=six.text_type(inst), printflag=False)
        return ret

    def start_horcm(self, inst):
        return self.exec_command('horcmstart.sh', args=six.text_type(inst),
                                 printflag=False)

    def _wait_for_horcm_shutdown(self, inst):
        if self.check_horcm(inst) != HORCM_RUNNING:
            raise loopingcall.LoopingCallDone()

        if self.shutdown_horcm(inst):
            LOG.error(_LE("Failed to shutdown horcm."))
            raise loopingcall.LoopingCallDone()

    @horcm_synchronized
    def restart_horcm(self, inst=None):
        if inst is None:
            inst = self.conf.hitachi_horcm_numbers[0]

        loop = loopingcall.FixedIntervalLoopingCall(
            self._wait_for_horcm_shutdown, inst)

        loop.start(interval=HORCM_WAITTIME).wait()

        ret, stdout, stderr = self.start_horcm(inst)
        if ret:
            msg = basic_lib.output_err(
                600, cmd='horcmstart.sh', ret=ret, out=stdout, err=stderr)
            raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)

    def restart_pair_horcm(self):
        inst = self.conf.hitachi_horcm_numbers[1]
        self.restart_horcm(inst=inst)

    def setup_horcmgr(self, host_ip):
        pair_inst = self.conf.hitachi_horcm_numbers[1]
        self.set_copy_groups(host_ip)
        if self.conf.hitachi_horcm_add_conf:
            self.create_horcmconf()
            self.create_horcmconf(inst=pair_inst)
        self.restart_horcm()
        with self.horcmgr_flock:
            self.restart_pair_horcm()
        ret, stdout, stderr = self.comm_login()
        if ret:
            msg = basic_lib.output_err(
                600, cmd='raidcom -login', ret=ret, out=stdout, err=stderr)
            raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)

    def _wait_for_exec_horcm(self, cmd, args, printflag, start):
        if cmd == 'raidcom':
            serial = self.conf.hitachi_serial_number
            inst = self.conf.hitachi_horcm_numbers[0]
            raidcom_obj_lock = self.raidcom_lock
            args = '%s -s %s -I%d' % (args, serial, inst)
        else:
            inst = self.conf.hitachi_horcm_numbers[1]
            raidcom_obj_lock = self.raidcom_pair_lock
            args = '%s -ISI%d' % (args, inst)
        user = self.conf.hitachi_horcm_user
        passwd = self.conf.hitachi_horcm_password
        raidcom_lock_file = '%s%d' % (RAIDCOM_LOCK_FILE, inst)
        lock = basic_lib.get_process_lock(raidcom_lock_file)

        with raidcom_obj_lock, lock:
            ret, stdout, stderr = self.exec_command(cmd, args=args,
                                                    printflag=printflag)

        if not ret or ret <= 127:
            raise loopingcall.LoopingCallDone((ret, stdout, stderr))

        if time.time() - start >= EXEC_MAX_WAITTIME:
            LOG.error(_LE("horcm command timeout."))
            raise loopingcall.LoopingCallDone((ret, stdout, stderr))

        if (ret == EX_ENAUTH and
                not re.search("-login %s %s" % (user, passwd), args)):
            _ret, _stdout, _stderr = self.comm_login()
            if _ret:
                LOG.error(_LE("Failed to authenticate user."))
                raise loopingcall.LoopingCallDone((ret, stdout, stderr))

        elif ret in HORCM_ERROR:
            _ret = 0
            with raidcom_obj_lock, lock:
                if self.check_horcm(inst) != HORCM_RUNNING:
                    _ret, _stdout, _stderr = self.start_horcm(inst)
            if _ret and _ret != HORCM_RUNNING:
                LOG.error(_LE("Failed to start horcm."))
                raise loopingcall.LoopingCallDone((ret, stdout, stderr))

        elif ret not in COMMAND_IO_TO_RAID:
            LOG.error(_LE("Unexpected error occurs in horcm."))
            raise loopingcall.LoopingCallDone((ret, stdout, stderr))

    def exec_raidcom(self, cmd, args, printflag=True):
        loop = loopingcall.FixedIntervalLoopingCall(
            self._wait_for_exec_horcm, cmd, args, printflag, time.time())

        return loop.start(interval=EXEC_RETRY_INTERVAL).wait()

    def comm_login(self):
        rmi_user = self.conf.hitachi_horcm_user
        rmi_pass = self.conf.hitachi_horcm_password
        args = '-login %s %s' % (rmi_user, rmi_pass)
        return self.exec_raidcom('raidcom', args, printflag=False)

    def comm_lock(self):
        ret, _stdout, stderr = self.exec_raidcom('raidcom', 'lock resource')
        if ret:
            msg = basic_lib.output_err(
                603, serial=self.conf.hitachi_serial_number,
                inst=self.conf.hitachi_horcm_numbers[0], ret=ret, err=stderr)
            raise exception.HBSDError(message=msg)

    def comm_unlock(self):
        self.exec_raidcom('raidcom', 'unlock resource')

    def comm_reset_status(self):
        self.exec_raidcom('raidcom', 'reset command_status')

    def comm_get_status(self):
        return self.exec_raidcom('raidcom', 'get command_status')

    def get_command_error(self, stdout):
        lines = stdout.splitlines()
        line = shlex.split(lines[1])
        return int(line[3])

    def comm_get_ldev(self, ldev):
        opt = 'get ldev -ldev_id %s' % ldev
        ret, stdout, stderr = self.exec_raidcom('raidcom', opt,
                                                printflag=False)
        if ret:
            opt = 'raidcom %s' % opt
            msg = basic_lib.output_err(
                600, cmd=opt, ret=ret, out=stdout, err=stderr)
            raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)
        return stdout

    def add_used_hlun(self, port, gid, used_list):
        opt = 'get lun -port %s-%d' % (port, gid)
        ret, stdout, stderr = self.exec_raidcom('raidcom', opt,
                                                printflag=False)
        if ret:
            opt = 'raidcom %s' % opt
            msg = basic_lib.output_err(
                600, cmd=opt, ret=ret, out=stdout, err=stderr)
            raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)
        lines = stdout.splitlines()
        for line in lines[1:]:
            lun = int(shlex.split(line)[3])
            if lun not in used_list:
                used_list.append(lun)

    def get_unused_ldev(self, ldev_range):
        start = ldev_range[0]
        end = ldev_range[1]

        while start < end:
            if end - start + 1 > GETSTORAGEARRAY_ONCE:
                cnt = GETSTORAGEARRAY_ONCE
            else:
                cnt = end - start + 1
            opt = 'get ldev -ldev_id %d -cnt %d' % (start, cnt)
            ret, stdout, stderr = self.exec_raidcom('raidcom', opt,
                                                    printflag=False)
            if ret:
                opt = 'raidcom %s' % opt
                msg = basic_lib.output_err(
                    600, cmd=opt, ret=ret, out=stdout, err=stderr)
                raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)

            lines = stdout.splitlines()
            ldev_num = None
            for line in lines:
                if re.match("LDEV :", line):
                    ldev_num = int(shlex.split(line)[2])
                    continue
                if re.match("VOL_TYPE : NOT DEFINED", line):
                    return ldev_num

            start += GETSTORAGEARRAY_ONCE
        else:
            msg = basic_lib.output_err(648, resource='LDEV')
            raise exception.HBSDError(message=msg)

    def get_hgname_gid(self, port, host_grp_name):
        opt = 'get host_grp -port %s -key host_grp' % port
        ret, stdout, stderr = self.exec_raidcom('raidcom', opt,
                                                printflag=False)
        if ret:
            opt = 'raidcom %s' % opt
            msg = basic_lib.output_err(
                600, cmd=opt, ret=ret, out=stdout, err=stderr)
            raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)
        lines = stdout.splitlines()
        for line in lines[1:]:
            line = shlex.split(line)
            if line[2] == host_grp_name:
                return int(line[1])
        return None

    def get_unused_gid(self, range, port):
        _min = range[0]
        _max = range[1]
        opt = 'get host_grp -port %s -key host_grp' % port
        ret, stdout, stderr = self.exec_raidcom('raidcom', opt,
                                                printflag=False)
        if ret:
            opt = 'raidcom %s' % opt
            msg = basic_lib.output_err(
                600, cmd=opt, ret=ret, out=stdout, err=stderr)
            raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)

        lines = stdout.splitlines()
        free_gid = None
        for line in lines[_min + 1:]:
            line = shlex.split(line)
            if int(line[1]) > _max:
                break
            if line[2] == '-':
                free_gid = int(line[1])
                break
        if free_gid is None:
            msg = basic_lib.output_err(648, resource='GID')
            raise exception.HBSDError(message=msg)
        return free_gid

    def comm_set_target_wwns(self, target_ports):
        opt = 'get port'
        ret, stdout, stderr = self.exec_raidcom('raidcom', opt,
                                                printflag=False)
        if ret:
            opt = 'raidcom %s' % opt
            msg = basic_lib.output_err(
                600, cmd=opt, ret=ret, out=stdout, err=stderr)
            raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)

        target_wwns = {}
        lines = stdout.splitlines()
        for line in lines[1:]:
            line = shlex.split(line)
            port = line[0][:5]
            if target_ports and port not in target_ports:
                continue

            target_wwns[port] = line[10]
        LOG.debug('target wwns: %s' % target_wwns)
        return target_wwns

    def comm_get_hbawwn(self, hostgroups, wwns, port, is_detected):
        opt = 'get host_grp -port %s' % port
        ret, stdout, stderr = self.exec_raidcom('raidcom', opt,
                                                printflag=False)
        if ret:
            opt = 'raidcom %s' % opt
            msg = basic_lib.output_err(
                600, cmd=opt, ret=ret, out=stdout, err=stderr)
            raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)

        lines = stdout.splitlines()
        found_wwns = 0
        for line in lines[1:]:
            line = shlex.split(line)
            if not re.match(basic_lib.NAME_PREFIX, line[2]):
                continue
            gid = line[1]
            opt = 'get hba_wwn -port %s-%s' % (port, gid)
            ret, stdout, stderr = self.exec_raidcom(
                'raidcom', opt, printflag=False)
            if ret:
                opt = 'raidcom %s' % opt
                msg = basic_lib.output_err(
                    600, cmd=opt, ret=ret, out=stdout, err=stderr)
                raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)

            lines = stdout.splitlines()
            for line in lines[1:]:
                hba_info = shlex.split(line)

                if hba_info[3] in wwns:
                    hostgroups.append({'port': six.text_type(port),
                                       'gid': int(hba_info[1]),
                                       'initiator_wwn': hba_info[3],
                                       'detected': is_detected})
                    found_wwns += 1
                if len(wwns) == found_wwns:
                    break

            if len(wwns) == found_wwns:
                break

    def comm_chk_login_wwn(self, wwns, port):
        opt = 'get port -port %s' % port
        ret, stdout, stderr = self.exec_raidcom('raidcom', opt,
                                                printflag=False)

        if ret:
            opt = 'raidcom %s' % opt
            msg = basic_lib.output_err(
                600, cmd=opt, ret=ret, out=stdout, err=stderr)
            raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)

        lines = stdout.splitlines()
        for line in lines[1:]:
            login_info = shlex.split(line)
            if login_info[1] in wwns:
                return True
        else:
            return False

    def comm_get_hostgroup_info(self, hgs, wwns, target_ports, login=True):
        security_ports = []
        hostgroups = []

        opt = 'get port'
        ret, stdout, stderr = self.exec_raidcom('raidcom', opt,
                                                printflag=False)
        if ret:
            opt = 'raidcom %s' % opt
            msg = basic_lib.output_err(
                600, cmd=opt, ret=ret, out=stdout, err=stderr)
            raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)

        lines = stdout.splitlines()

        for line in lines[1:]:
            line = shlex.split(line)
            port = line[0][:5]
            if target_ports and port not in target_ports:
                continue
            security = True if line[7] == 'Y' else False

            is_detected = None
            if login:
                is_detected = self.comm_chk_login_wwn(wwns, port)

            if security:
                self.comm_get_hbawwn(hostgroups, wwns, port, is_detected)
                security_ports.append(port)

        for hostgroup in hostgroups:
            hgs.append(hostgroup)

        return security_ports

    def _get_lun(self, port, gid, ldev):
        lun = None

        opt = 'get lun -port %s-%d' % (port, gid)
        ret, stdout, stderr = self.exec_raidcom('raidcom', opt,
                                                printflag=False)
        if ret:
            opt = 'raidcom %s' % opt
            msg = basic_lib.output_err(
                600, cmd=opt, ret=ret, out=stdout, err=stderr)
            raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)

        lines = stdout.splitlines()
        for line in lines[1:]:
            line = shlex.split(line)
            if line[5] == six.text_type(ldev):
                lun = int(line[3])
                break

        return lun

    def _wait_for_delete_lun(self, hostgroup, ldev, start):
        opt = 'delete lun -port %s-%d -ldev_id %d' % (hostgroup['port'],
                                                      hostgroup['gid'], ldev)
        ret, stdout, stderr = self.exec_raidcom('raidcom', opt)
        if not ret:
            raise loopingcall.LoopingCallDone()

        if (re.search('SSB=%s' % SNAP_LAST_PATH_SSB, stderr) and
                not self.comm_get_snapshot(ldev) or
                re.search('SSB=%s' % HOST_IO_SSB, stderr)):
            msg = basic_lib.set_msg(310, ldev=ldev, reason=stderr)
            LOG.warning(msg)

            if time.time() - start >= LUN_DELETE_WAITTIME:
                msg = basic_lib.output_err(
                    637, method='_wait_for_delete_lun',
                    timeout=LUN_DELETE_WAITTIME)
                raise exception.HBSDError(message=msg)
        else:
            opt = 'raidcom %s' % opt
            msg = basic_lib.output_err(
                600, cmd=opt, ret=ret, out=stdout, err=stderr)
            raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)

    def comm_delete_lun_core(self, hostgroup, ldev):
        loop = loopingcall.FixedIntervalLoopingCall(
            self._wait_for_delete_lun, hostgroup, ldev, time.time())

        loop.start(interval=LUN_DELETE_INTERVAL).wait()

    @storage_synchronized
    def comm_delete_lun(self, hostgroups, ldev):
        try:
            deleted_hostgroups = []
            self.comm_lock()
            no_ldev_cnt = 0
            for hostgroup in hostgroups:
                port = hostgroup['port']
                gid = hostgroup['gid']
                is_deleted = False
                for deleted in deleted_hostgroups:
                    if port == deleted['port'] and gid == deleted['gid']:
                        is_deleted = True
                if is_deleted:
                    continue
                try:
                    self.comm_delete_lun_core(hostgroup, ldev)
                except exception.HBSDCmdError as ex:
                    no_ldev_cnt += 1
                    if ex.ret == EX_ENOOBJ:
                        if no_ldev_cnt != len(hostgroups):
                            continue
                        raise exception.HBSDNotFound
                    else:
                        raise
                deleted_hostgroups.append({'port': port, 'gid': gid})
        finally:
            self.comm_unlock()

    def _check_ldev_status(self, ldev, status):
        opt = ('get ldev -ldev_id %s -check_status %s -time %s' %
               (ldev, status, LDEV_STATUS_WAITTIME))
        ret, _stdout, _stderr = self.exec_raidcom('raidcom', opt)
        return ret

    @storage_synchronized
    def comm_add_ldev(self, pool_id, ldev, capacity, is_vvol):
        emulation = 'OPEN-V'
        if is_vvol:
            opt = ('add ldev -pool snap -ldev_id %d '
                   '-capacity %dG -emulation %s'
                   % (ldev, capacity, emulation))
        else:
            opt = ('add ldev -pool %d -ldev_id %d '
                   '-capacity %dG -emulation %s'
                   % (pool_id, ldev, capacity, emulation))

        try:
            self.comm_lock()
            self.comm_reset_status()
            ret, stdout, stderr = self.exec_raidcom('raidcom', opt)
            if ret:
                if re.search('SSB=%s' % INTERCEPT_LDEV_SSB, stderr):
                    raise exception.HBSDNotFound
                opt = 'raidcom %s' % opt
                msg = basic_lib.output_err(
                    600, cmd=opt, ret=ret, out=stdout, err=stderr)
                raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)

            if self._check_ldev_status(ldev, "NML"):
                msg = basic_lib.output_err(653, ldev=ldev)
                raise exception.HBSDError(message=msg)
        finally:
            self.comm_unlock()

    @storage_synchronized
    def comm_add_hostgrp(self, port, gid, host_grp_name):
        opt = 'add host_grp -port %s-%d -host_grp_name %s' % (port, gid,
                                                              host_grp_name)
        try:
            self.comm_lock()
            ret, stdout, stderr = self.exec_raidcom('raidcom', opt)
            if ret:
                if re.search('SSB=%s' % HOSTGROUP_INSTALLED, stderr):
                    raise exception.HBSDNotFound
                else:
                    opt = 'raidcom %s' % opt
                    msg = basic_lib.output_err(
                        600, cmd=opt, ret=ret, out=stdout, err=stderr)
                    raise exception.HBSDCmdError(
                        message=msg, ret=ret, err=stderr)
        finally:
            self.comm_unlock()

    @storage_synchronized
    def comm_del_hostgrp(self, port, gid, host_grp_name):
        opt = 'delete host_grp -port %s-%d %s' % (port, gid, host_grp_name)
        try:
            self.comm_lock()
            ret, stdout, stderr = self.exec_raidcom('raidcom', opt)
            if ret:
                opt = 'raidcom %s' % opt
                msg = basic_lib.output_err(
                    600, cmd=opt, ret=ret, out=stdout, err=stderr)
                raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)
        finally:
            self.comm_unlock()

    @storage_synchronized
    def comm_add_hbawwn(self, port, gid, wwn):
        opt = 'add hba_wwn -port %s-%s -hba_wwn %s' % (port, gid, wwn)
        try:
            self.comm_lock()
            ret, stdout, stderr = self.exec_raidcom('raidcom', opt)
            if ret:
                opt = 'raidcom %s' % opt
                msg = basic_lib.output_err(
                    600, cmd=opt, ret=ret, out=stdout, err=stderr)
                raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)
        finally:
            self.comm_unlock()

    @storage_synchronized
    def comm_add_lun(self, unused_command, hostgroups, ldev, is_once=False):
        tmp_hostgroups = hostgroups[:]
        is_ok = False
        used_list = []
        lun = None
        old_lun = None

        for hostgroup in hostgroups:
            port = hostgroup['port']
            gid = hostgroup['gid']
            self.add_used_hlun(port, gid, used_list)
            lun = self._get_lun(port, gid, ldev)

            # When 'lun' or 'old_lun' is 0, it should be true.
            # So, it cannot remove 'is not None'.
            if lun is not None:
                if old_lun is not None and old_lun != lun:
                    msg = basic_lib.output_err(648, resource='LUN (HLUN)')
                    raise exception.HBSDError(message=msg)
                is_ok = True
                hostgroup['lun'] = lun
                tmp_hostgroups.remove(hostgroup)
                old_lun = lun

            if is_once:
                # When 'lun' is 0, it should be true.
                # So, it cannot remove 'is not None'.
                if lun is not None:
                    return
                elif len(used_list) < MAX_HLUN + 1:
                    break
                else:
                    tmp_hostgroups.remove(hostgroup)
                    if tmp_hostgroups:
                        used_list = []

        if not used_list:
            lun = 0
        elif lun is None:
            for i in range(MAX_HLUN + 1):
                if i not in used_list:
                    lun = i
                    break
            else:
                raise exception.HBSDNotFound

        opt = None
        ret = 0
        stdout = None
        stderr = None
        invalid_hgs_str = None

        try:
            self.comm_lock()
            for hostgroup in tmp_hostgroups:
                port = hostgroup['port']
                gid = hostgroup['gid']
                if not hostgroup['detected']:
                    if invalid_hgs_str:
                        invalid_hgs_str = '%s, %s:%d' % (invalid_hgs_str,
                                                         port, gid)
                    else:
                        invalid_hgs_str = '%s:%d' % (port, gid)
                    continue
                opt = 'add lun -port %s-%d -ldev_id %d -lun_id %d' % (
                    port, gid, ldev, lun)
                ret, stdout, stderr = self.exec_raidcom('raidcom', opt)
                if not ret:
                    is_ok = True
                    hostgroup['lun'] = lun
                    if is_once:
                        break
                else:
                    msg = basic_lib.set_msg(
                        314, ldev=ldev, lun=lun, port=port, id=gid)
                    LOG.warning(msg)

        finally:
            self.comm_unlock()

        if not is_ok:
            if stderr:
                opt = 'raidcom %s' % opt
                msg = basic_lib.output_err(
                    600, cmd=opt, ret=ret, out=stdout, err=stderr)
                raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)
            else:
                msg = basic_lib.output_err(659, gid=invalid_hgs_str)
                raise exception.HBSDError(message=msg)

    @storage_synchronized
    def comm_delete_ldev(self, ldev, is_vvol):
        ret = -1
        stdout = ""
        stderr = ""
        try:
            self.comm_lock()
            self.comm_reset_status()
            opt = 'delete ldev -ldev_id %d' % ldev
            ret, stdout, stderr = self.exec_raidcom('raidcom', opt)
            if ret:
                if re.search('SSB=%s' % INVALID_LUN_SSB, stderr):
                    raise exception.HBSDNotFound
                opt = 'raidcom %s' % opt
                msg = basic_lib.output_err(
                    600, cmd=opt, ret=ret, out=stdout, err=stderr)
                raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)
            ret, stdout, stderr = self.comm_get_status()
            if ret or self.get_command_error(stdout):
                opt = 'raidcom %s' % opt
                msg = basic_lib.output_err(
                    600, cmd=opt, ret=ret, out=stdout, err=stderr)
                raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)
        finally:
            self.comm_unlock()

    @storage_synchronized
    def comm_extend_ldev(self, ldev, old_size, new_size):
        ret = -1
        stdout = ""
        stderr = ""
        extend_size = new_size - old_size
        try:
            self.comm_lock()
            self.comm_reset_status()
            opt = 'extend ldev -ldev_id %d -capacity %dG' % (ldev, extend_size)
            ret, stdout, stderr = self.exec_raidcom('raidcom', opt)
            if ret:
                opt = 'raidcom %s' % opt
                msg = basic_lib.output_err(
                    600, cmd=opt, ret=ret, out=stdout, err=stderr)
                raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)
            ret, stdout, stderr = self.comm_get_status()
            if ret or self.get_command_error(stdout):
                opt = 'raidcom %s' % opt
                msg = basic_lib.output_err(
                    600, cmd=opt, ret=ret, out=stdout, err=stderr)
                raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)
        finally:
            self.comm_unlock()

    def comm_get_dp_pool(self, pool_id):
        opt = 'get dp_pool'
        ret, stdout, stderr = self.exec_raidcom('raidcom', opt,
                                                printflag=False)
        if ret:
            opt = 'raidcom %s' % opt
            msg = basic_lib.output_err(
                600, cmd=opt, ret=ret, out=stdout, err=stderr)
            raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)

        lines = stdout.splitlines()
        for line in lines[1:]:
            if int(shlex.split(line)[0]) == pool_id:
                free_gb = int(shlex.split(line)[3]) / 1024
                total_gb = int(shlex.split(line)[4]) / 1024
                return total_gb, free_gb

        msg = basic_lib.output_err(640, pool_id=pool_id)
        raise exception.HBSDError(message=msg)

    @storage_synchronized
    def comm_modify_ldev(self, ldev):
        args = 'modify ldev -ldev_id %d -status discard_zero_page' % ldev
        try:
            self.comm_lock()
            ret, stdout, stderr = self.exec_raidcom('raidcom', args)
            if ret:
                msg = basic_lib.set_msg(315, ldev=ldev, reason=stderr)
                LOG.warning(msg)
        finally:
            self.comm_unlock()

    def is_detected(self, port, wwn):
        return self.comm_chk_login_wwn([wwn], port)

    def discard_zero_page(self, ldev):
        try:
            self.comm_modify_ldev(ldev)
        except Exception as e:
            LOG.warning(_LW('Failed to discard zero page: %s') %
                        six.text_type(e))

    @storage_synchronized
    def comm_add_snapshot(self, pvol, svol):
        pool = self.conf.hitachi_thin_pool_id
        copy_size = self.conf.hitachi_copy_speed
        args = ('add snapshot -ldev_id %d %d -pool %d '
                '-snapshot_name %s -copy_size %d'
                % (pvol, svol, pool, SNAP_NAME, copy_size))
        try:
            self.comm_lock()
            ret, stdout, stderr = self.exec_raidcom('raidcom', args)
            if ret:
                opt = 'raidcom %s' % args
                msg = basic_lib.output_err(
                    600, cmd=opt, ret=ret, out=stdout, err=stderr)
                raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)
        finally:
            self.comm_unlock()

    @storage_synchronized
    def comm_delete_snapshot(self, ldev):
        args = 'delete snapshot -ldev_id %d' % ldev
        try:
            self.comm_lock()
            ret, stdout, stderr = self.exec_raidcom('raidcom', args)
            if ret:
                opt = 'raidcom %s' % args
                msg = basic_lib.output_err(
                    600, cmd=opt, ret=ret, out=stdout, err=stderr)
                raise exception.HBSDCmdError(message=msg, ret=ret, err=stderr)
        finally:
            self.comm_unlock()

    @storage_synchronized
    def comm_modify_snapshot(self, ldev, op):
        args = ('modify snapshot -ldev_id %d -snapshot_data %s' % (ldev, op))