from __future__ import absolute_import

import os
import logging
import glob
from fnmatch import fnmatch
from collections import defaultdict
from functools import partial
import errno
import time
import signal
import math
import numbers
import stat

import six
from six.moves import map

from vdsm import constants
from vdsm import jobs
from vdsm import utils
from vdsm.common import concurrent
from vdsm.common import function
from vdsm.common.threadlocal import vars
from vdsm.common import api
from vdsm.common import exception
from vdsm.common import supervdsm
from vdsm.common.marks import deprecated
from vdsm.common.time import monotonic_time
from vdsm.config import config
from vdsm.storage import blockSD
from vdsm.storage import clusterlock
from vdsm.storage import constants as sc
from vdsm.storage import devicemapper
from vdsm.storage import dispatcher
from vdsm.storage import exception as se
from vdsm.storage import fileUtils
from vdsm.storage import glusterSD
from vdsm.storage import image
from vdsm.storage import imagetickets
from vdsm.storage import iscsi
from vdsm.storage import localFsSD
from vdsm.storage import lvm
from vdsm.storage import merge
from vdsm.storage import mpathhealth
from vdsm.storage import misc
from vdsm.storage import monitor
from vdsm.storage import mount
from vdsm.storage import multipath
from vdsm.storage import nbd
from vdsm.storage import nfsSD
from vdsm.storage import outOfProcess as oop
from vdsm.storage import qemuimg
from vdsm.storage import resourceManager as rm
from vdsm.storage import sd
from vdsm.storage import sp
from vdsm.storage import storageServer
from vdsm.storage import taskManager
from vdsm.storage import types
from vdsm.storage import udev
from vdsm.storage.constants import STORAGE
from vdsm.storage.sdc import sdCache
from vdsm.storage.spbackends import MAX_POOL_DESCRIPTION_SIZE, MAX_DOMAINS
from vdsm.storage.spbackends import StoragePoolDiskBackend
from vdsm.storage.spbackends import StoragePoolMemoryBackend

from vdsm.storage.sdm.api import (
    amend_volume,
    copy_data,
    merge as api_merge,
    move_device,
    reduce_domain,
    sparsify_volume,
    update_volume
)

GUID = "guid"
NAME = "name"
UUID = "uuid"
TYPE = "type"
INITIALIZED = "initialized"
CAPACITY = "capacity"

QEMU_READABLE_TIMEOUT = 30

HSM_DOM_MON_LOCK = "HsmDomainMonitorLock"

DISCONNECTED_HOST_ID = 1


def public(f):
    logged = api.logged("vdsm.api")
    return dispatcher.exported(logged(f))



CON_TYPE_ID_2_CON_TYPE = {
    sd.LOCALFS_DOMAIN: 'localfs',
    sd.NFS_DOMAIN: 'nfs',
    sd.ISCSI_DOMAIN: 'iscsi',
    sd.FCP_DOMAIN: 'fcp',
    sd.POSIXFS_DOMAIN: 'posixfs',
    sd.GLUSTERFS_DOMAIN: 'glusterfs'}


def _updateIfaceNameIfNeeded(iface, netIfaceName):
    if iface.netIfaceName is None:
        iface.netIfaceName = netIfaceName
        iface.update()
        return True

    return False


def _resolveIscsiIface(ifaceName, initiatorName, netIfaceName):
    if not ifaceName:
        return iscsi.IscsiInterface('default')

    for iface in iscsi.iterateIscsiInterfaces():

        if iface.name != ifaceName:
            continue

        if netIfaceName is not None:
            if (not _updateIfaceNameIfNeeded(iface, netIfaceName) and
                    netIfaceName != iface.netIfaceName):
                logging.error('iSCSI netIfaceName coming from engine [%s] '
                              'is different from iface.net_ifacename '
                              'present on the system [%s]. Aborting iscsi '
                              'iface [%s] configuration.' %
                              (netIfaceName, iface.netIfaceName, iface.name))

                raise se.iSCSIifaceError()

        return iface

    iface = iscsi.IscsiInterface(ifaceName, initiatorName=initiatorName,
                                 netIfaceName=netIfaceName)
    iface.create()
    return iface


def _connectionDict2ConnectionInfo(conTypeId, conDict):
    def getIntParam(optDict, key, default):
        res = optDict.get(key, default)
        if res is None:
            return res

        try:
            return int(res)
        except ValueError:
            raise se.InvalidParameterException(key, res)

    def tryDeprecatedNfsParams(conDict):
        if (conDict.get('protocol_version', None),
                conDict.get('retrans', None),
                conDict.get('timeout', None)) == (None, None, None):
            conf_options = config.get(
                'irs', 'nfs_mount_options').replace(' ', '')
            if (frozenset(conf_options.split(',')) !=
                    frozenset(storageServer.NFSConnection.DEFAULT_OPTIONS)):
                logging.warning("Using deprecated nfs_mount_options from"
                                " vdsm.conf to mount %s: %s",
                                conDict.get('connection', '(unknown)'),
                                conf_options)
                return storageServer.PosixFsConnectionParameters(
                    conDict["id"],
                    conDict.get('connection', None),
                    'nfs',
                    conf_options)
        return None

    typeName = CON_TYPE_ID_2_CON_TYPE[conTypeId]
    if typeName == 'localfs':
        params = storageServer.LocaFsConnectionParameters(
            conDict["id"],
            conDict.get('connection', None))
    elif typeName == 'nfs':
        params = tryDeprecatedNfsParams(conDict)
        if params is not None:
            typeName = 'posixfs'
        else:
            version = conDict.get('protocol_version', "3")
            version = str(version)
            if version == "auto":
                version = None

            params = storageServer.NfsConnectionParameters(
                conDict["id"],
                conDict.get('connection', None),
                getIntParam(conDict, 'retrans', None),
                getIntParam(conDict, 'timeout', None),
                version,
                conDict.get('mnt_options', None))
    elif typeName == 'posixfs':
        params = storageServer.PosixFsConnectionParameters(
            conDict["id"],
            conDict.get('connection', None),
            conDict.get('vfs_type', None),
            conDict.get('mnt_options', None))
    elif typeName == 'glusterfs':
        params = storageServer.GlusterFsConnectionParameters(
            conDict["id"],
            conDict.get('connection', None),
            conDict.get('vfs_type', None),
            conDict.get('mnt_options', None))
    elif typeName == 'iscsi':
        portal = iscsi.IscsiPortal(
            conDict.get('connection', None),
            int(conDict.get('port', None)))
        tpgt = int(conDict.get('tpgt', iscsi.DEFAULT_TPGT))

        target = iscsi.IscsiTarget(portal, tpgt, conDict.get('iqn', None))

        iface = _resolveIscsiIface(conDict.get('ifaceName', None),
                                   conDict.get('initiatorName', None),
                                   conDict.get('netIfaceName', None))


        username = conDict.get('user')
        if not username:
            username = None
        password = conDict.get('password')
        if not getattr(password, "value", None):
            password = None
        cred = None
        if username or password:
            cred = iscsi.ChapCredentials(username, password)

        params = storageServer.IscsiConnectionParameters(
            conDict["id"],
            target,
            iface,
            cred)
    elif typeName == 'fcp':
        params = storageServer.FcpConnectionParameters(conDict["id"])
    else:
        raise se.StorageServerActionError()

    return storageServer.ConnectionInfo(typeName, params)


class HSM(object):
    _pool = sp.DisconnectedPool()
    log = logging.getLogger('storage.HSM')

    @classmethod
    def validateSdUUID(cls, sdUUID):
        sdDom = sdCache.produce(sdUUID=sdUUID)
        sdDom.validate()
        return sdDom

    @classmethod
    def validateBackupDom(cls, sdUUID):
        if not sdCache.produce(sdUUID=sdUUID).isBackup():
            raise se.StorageDomainTypeNotBackup(sdUUID)

    @classmethod
    def validateNonDomain(cls, sdUUID):
        try:
            sdCache.produce(sdUUID=sdUUID)
            raise se.StorageDomainAlreadyExists(sdUUID)
        except se.StorageDomainMetadataNotFound:
            raise se.StorageDomainNotEmpty()
        except se.StorageDomainDoesNotExist:
            pass

    @classmethod
    def getPool(cls, spUUID):
        if cls._pool.is_connected() and cls._pool.spUUID == spUUID:
            return cls._pool
        raise se.StoragePoolUnknown(spUUID)

    @classmethod
    def setPool(cls, pool):
        cls._pool = pool

    def __init__(self):
        self._start_time = monotonic_time()
        self._ready = False
        self.log.info("START HSM init")
        rm.registerNamespace(STORAGE, rm.SimpleResourceFactory())
        self.taskMng = taskManager.TaskManager()

        self.log.info("Creating data-center mount directory %r",
                      sc.REPO_MOUNT_DIR)
        fileUtils.createdir(sc.REPO_MOUNT_DIR)
        storageServer.MountConnection.setLocalPathBase(sc.REPO_MOUNT_DIR)
        storageServer.LocalDirectoryConnection.setLocalPathBase(
            sc.REPO_MOUNT_DIR)

        sp.StoragePool.cleanupMasterMount()
        self.__releaseLocks()

        self._preparedVolumes = defaultdict(list)

        self.__validateLvmLockingType()

        self.tasksDir = config.get('irs', 'hsm_tasks')

        try:
            self.__cleanStorageRepository()
        except Exception:
            self.log.warn("Failed to clean Storage Repository.", exc_info=True)

        self.multipathListener = udev.MultipathListener()
        self.mpathhealth_monitor = mpathhealth.Monitor()
        self.multipathListener.register(self.mpathhealth_monitor)
        self.multipathListener.start()

        def storageRefresh():
            sdCache.refreshStorage()
            lvm.bootstrap(skiplvs=blockSD.SPECIAL_LVS_V4)
            self._ready = True
            self.log.info("FINISH HSM init succeeded in %.2f seconds",
                          monotonic_time() - self._start_time)

        storageRefreshThread = concurrent.thread(storageRefresh,
                                                 name="hsm/init",
                                                 log=self.log)
        storageRefreshThread.start()

        monitorInterval = config.getint('irs', 'sd_health_check_delay')
        self.domainMonitor = monitor.DomainMonitor(monitorInterval)

    @property
    def ready(self):
        return self._ready

    @public
    def registerDomainStateChangeCallback(self, callbackFunc):
        self.domainMonitor.onDomainStateChange.register(callbackFunc)

    def _hsmSchedule(self, name, func, *args):
        self.taskMng.scheduleJob("hsm", None, vars.task, name, func, *args)

    def __validateLvmLockingType(self):
        rc, out, err = misc.execCmd([constants.EXT_LVM, "dumpconfig",
                                     "global/locking_type"],
                                    sudo=True)
        if rc != 0:
            self.log.error("Can't validate lvm locking_type. %d %s %s",
                           rc, out, err)
            return False

        try:
            lvmLockingType = int(out[0].split('=')[1])
        except (ValueError, IndexError):
            self.log.error("Can't parse lvm locking_type. %s", out)
            return False

        if lvmLockingType != 1:
            self.log.error("Invalid lvm locking_type. %d", lvmLockingType)
            return False

        return True

    def __cleanStorageRepository(self):

        self.log.debug("Started cleaning storage "
                       "repository at '%s'", sc.REPO_DATA_CENTER)

        mountList = []
        whiteList = [
            self.tasksDir,
            os.path.join(self.tasksDir, "*"), sc.REPO_MOUNT_DIR,
        ]

        def isInWhiteList(path):
            fullpath = os.path.abspath(path)

            if os.path.islink(fullpath):
                symlpath = os.readlink(fullpath)

                basepath = os.path.dirname(fullpath)
                fullpath = os.path.abspath(os.path.join(basepath, symlpath))

            return any(fnmatch(fullpath, x) for x in whiteList)

        for mnt in mount.iterMounts():
            mountPoint = os.path.abspath(mnt.fs_file)
            if mountPoint.startswith(sc.REPO_DATA_CENTER):
                mountList.append(mountPoint)

        self.log.debug("White list: %s", whiteList)
        self.log.debug("Mount list: %s", mountList)

        self.log.debug("Cleaning leftovers")
        rmDirList = []

        for base, dirs, files in misc.walk(sc.REPO_DATA_CENTER,
                                           blacklist=mountList):
            for directory in dirs:
                fullPath = os.path.join(base, directory)

                if isInWhiteList(fullPath):
                    dirs.remove(directory)
                else:
                    rmDirList.insert(0, os.path.join(base, fullPath))

            for fname in files:
                fullPath = os.path.join(base, fname)

                if isInWhiteList(fullPath):
                    continue
                self.log.info("Unlinking file %r", fullPath)
                try:
                    os.unlink(os.path.join(base, fullPath))
                except Exception:
                    self.log.warn("Cold not delete file "
                                  "'%s'", fullPath, exc_info=True)

        for directory in rmDirList:
            try:
                if os.path.islink(directory):
                    self.log.info("Unlinking symlink %r", directory)
                    os.unlink(directory)
                else:
                    self.log.info("Removing directory %r", directory)
                    os.rmdir(directory)
            except Exception:
                self.log.warn("Cold not delete directory "
                              "'%s'", directory, exc_info=True)

        self.log.debug("Finished cleaning storage "
                       "repository at '%s'", sc.REPO_DATA_CENTER)

    @public
    def getConnectedStoragePoolsList(self, options=None):
        vars.task.setDefaultException(se.StoragePoolActionError())
        pools = [self._pool.spUUID] if self._pool.is_connected() else []
        return dict(poollist=pools)

    @public
    def spmStart(self, spUUID, prevID, prevLVER,
                 maxHostID=clusterlock.MAX_HOST_ID, domVersion=None,
                 options=None):
        vars.task.setDefaultException(se.SpmStartError(
            "spUUID=%s, prevID=%s, prevLVER=%s, maxHostID=%s, domVersion=%s"
            % (spUUID, prevID, prevLVER, maxHostID, domVersion)))

        if domVersion is not None:
            domVersion = int(domVersion)
            sd.validateDomainVersion(domVersion)

        self.getPool(spUUID).validateNotSPM()

        vars.task.getExclusiveLock(STORAGE, spUUID)
        pool = self.getPool(spUUID)
        pool.validateNotSPM()
        self._hsmSchedule("spmStart", pool.startSpm, prevID, prevLVER,
                          maxHostID, domVersion)

    @public
    def spmStop(self, spUUID, options=None):
        vars.task.setDefaultException(se.SpmStopError(spUUID))
        vars.task.getExclusiveLock(STORAGE, spUUID)

        pool = self.getPool(spUUID)
        pool.stopSpm()

    @staticmethod
    def _getSpmStatusInfo(pool):
        return dict(
            zip(('spmStatus', 'spmLver', 'spmId'),
                (pool.spmRole,) + pool.getSpmStatus()))

    @public
    def getSpmStatus(self, spUUID, options=None):
        pool = self.getPool(spUUID)
        try:
            status = self._getSpmStatusInfo(pool)
        except (se.LogicalVolumeRefreshError, IOError):
            self.log.error("Can't read LV based metadata", exc_info=True)
            raise se.StorageDomainMasterError("Can't read LV based metadata")
        except se.InquireNotSupportedError:
            self.log.error("Inquire spm status isn't supported by "
                           "the current cluster lock")
            raise
        except se.StorageException as e:
            self.log.error("MD read error: %s", str(e), exc_info=True)
            raise se.StorageDomainMasterError("MD read error")
        except (KeyError, ValueError):
            self.log.error("Non existent or invalid MD key", exc_info=True)
            raise se.StorageDomainMasterError("Version or spm id invalid")

        return dict(spm_st=status)

    @public
    def extendVolume(self, sdUUID, spUUID, imgUUID, volumeUUID, size,
                     isShuttingDown=None, options=None):
        vars.task.setDefaultException(
            se.VolumeExtendingError(
                "spUUID=%s, sdUUID=%s, volumeUUID=%s, size=%s" %
                (spUUID, sdUUID, volumeUUID, size)))
        size = misc.validateN(size, "size")
        size = math.ceil(size / 2 ** 20)

        pool = self.getPool(spUUID)
        pool.extendVolume(sdUUID, volumeUUID, size, isShuttingDown)

    @public
    def reduceVolume(self, spUUID, sdUUID, imgUUID, volUUID,
                     allowActive=False):
        msg = "spUUID=%s, sdUUID=%s, imgUUID=%s, volUUID=%s, " \
              "allowActive=%s" % \
              (spUUID, sdUUID, imgUUID, volUUID, allowActive)
        vars.task.setDefaultException(se.StorageException(msg))
        pool = self.getPool(spUUID)
        sdCache.produce(sdUUID)
        vars.task.getSharedLock(STORAGE, sdUUID)
        self._spmSchedule(spUUID, "reduceVolume", pool.reduceVolume,
                          sdUUID, imgUUID, volUUID, allowActive)

    @public
    def extendVolumeSize(self, spUUID, sdUUID, imgUUID, volUUID, newSize):
        pool = self.getPool(spUUID)
        new_capacity = misc.validateN(newSize, "newSize")
        new_capacity = utils.round(new_capacity, sc.BLOCK_SIZE_4K)
        vars.task.getSharedLock(STORAGE, sdUUID)
        self._spmSchedule(
            spUUID, "extendVolumeSize", pool.extendVolumeSize, sdUUID,
            imgUUID, volUUID, new_capacity)

    @public
    def updateVolumeSize(self, spUUID, sdUUID, imgUUID, volUUID, newSize):
        newSizeBytes = int(newSize)
        domain = sdCache.produce(sdUUID=sdUUID)
        volToExtend = domain.produceVolume(imgUUID, volUUID)
        volPath = volToExtend.getVolumePath()
        volFormat = volToExtend.getFormat()

        if not volToExtend.isLeaf():
            raise se.VolumeNonWritable(volUUID)

        if volFormat != sc.COW_FORMAT:
            return dict(size=str(volToExtend.getVolumeSize()))

        qemuImgFormat = sc.fmt2str(sc.COW_FORMAT)

        volToExtend.prepare()
        try:
            imgInfo = qemuimg.info(volPath, qemuImgFormat)
            if imgInfo['virtualsize'] > newSizeBytes:
                self.log.error(
                    "volume %s size %s is larger than the size requested "
                    "for the extension %s", volUUID, imgInfo['virtualsize'],
                    newSizeBytes)
                raise se.VolumeResizeValueError(str(newSizeBytes))
            volToExtend.setCapacity(0)
            qemuimg.resize(volPath, newSizeBytes, qemuImgFormat)
            virtual_size = qemuimg.info(volPath,
                                        qemuImgFormat)['virtualsize']
        finally:
            volToExtend.teardown(sdUUID, volUUID)

        volToExtend.setCapacity(virtual_size)

        return dict(size=str(virtual_size))

    @public
    def extendStorageDomain(self, sdUUID, spUUID, guids,
                            force=False, options=None):
        vars.task.setDefaultException(
            se.StorageDomainActionError(
                "sdUUID=%s, devlist=%s" % (sdUUID, guids)))

        vars.task.getSharedLock(STORAGE, sdUUID)
        pool = self.getPool(spUUID)
        dmDevs = tuple(os.path.join(devicemapper.DMPATH_PREFIX, guid) for guid
                       in guids)
        pool.extendSD(sdUUID, dmDevs, force)

    @public
    def resizePV(self, sdUUID, spUUID, guid, options=None):
        vars.task.setDefaultException(
            se.StorageDomainActionError(
                "sdUUID=%s, PV=%s" % (sdUUID, guid)))

        vars.task.getSharedLock(STORAGE, sdUUID)
        pool = self.getPool(spUUID)
        pool.resizePV(sdUUID, guid)

        pv = lvm.getPV(guid)
        return dict(size=str(pv.size))

    def _detachStorageDomainFromOldPools(self, sdUUID):
        dom = sdCache.produce(sdUUID=sdUUID)
        try:
            host_id = self._pool.id
        except se.StoragePoolNotConnected:
            host_id = dom.getReservedId()
            self.log.warn(
                "Host not connected to pool, using reserved host id: %d",
                host_id)
        dom.acquireHostId(host_id)
        try:
            dom.acquireClusterLock(host_id)
            try:
                for domPoolUUID in dom.getPools():
                    dom.detach(domPoolUUID)
            finally:
                dom.releaseClusterLock()
        finally:
            dom.releaseHostId(host_id)

    @public
    def forcedDetachStorageDomain(self, sdUUID, spUUID, options=None):
        vars.task.setDefaultException(
            se.StorageDomainActionError(
                "sdUUID=%s, spUUID=%s" % (sdUUID, spUUID)))

        if spUUID == sd.BLANK_UUID:
            self._detachStorageDomainFromOldPools(sdUUID)
        else:
            vars.task.getExclusiveLock(STORAGE, spUUID)
            pool = self.getPool(spUUID)
            if sdUUID == pool.masterDomain.sdUUID:
                raise se.CannotDetachMasterStorageDomain(sdUUID)
            pool.forcedDetachSD(sdUUID)

    @public
    def detachStorageDomain(self, sdUUID, spUUID, msdUUID=None,
                            masterVersion=None, options=None):
        vars.task.setDefaultException(
            se.StorageDomainActionError(
                "sdUUID=%s, spUUID=%s, msdUUID=%s, masterVersion=%s" %
                (sdUUID, spUUID, msdUUID, masterVersion)))

        vars.task.getExclusiveLock(STORAGE, spUUID)
        vars.task.getExclusiveLock(STORAGE, sdUUID)
        pool = self.getPool(spUUID)
        pool.detachSD(sdUUID)

    @public
    def sendExtendMsg(self, spUUID, volDict, newSize, callbackFunc):
        newSize = misc.validateN(newSize, "newSize") / 2 ** 20
        try:
            pool = self.getPool(spUUID)
        except se.StoragePoolUnknown:
            pass
        else:
            if pool.hsmMailer:
                pool.hsmMailer.sendExtendMsg(volDict, newSize, callbackFunc)

    def _spmSchedule(self, spUUID, name, func, *args):
        pool = self.getPool(spUUID)
        pool.validateSPM()
        self.taskMng.scheduleJob("spm", pool.tasksDir, vars.task,
                                 name, func, *args)

    @public
    def createStoragePool(self, poolType, spUUID, poolName, masterDom,
                          domList, masterVersion, lockPolicy=None,
                          lockRenewalIntervalSec=None, leaseTimeSec=None,
                          ioOpTimeoutSec=None, leaseRetries=None,
                          options=None):
        leaseParams = sd.packLeaseParams(
            lockRenewalIntervalSec=lockRenewalIntervalSec,
            leaseTimeSec=leaseTimeSec,
            ioOpTimeoutSec=ioOpTimeoutSec,
            leaseRetries=leaseRetries)
        vars.task.setDefaultException(
            se.StoragePoolCreationError(
                "spUUID=%s, poolName=%s, masterDom=%s, domList=%s, "
                "masterVersion=%s, clusterlock params: (%s)" %
                (spUUID, poolName, masterDom, domList, masterVersion,
                 leaseParams)))
        misc.validateUUID(spUUID, 'spUUID')
        if masterDom not in domList:
            raise se.InvalidParameterException("masterDom", str(masterDom))

        if len(poolName) > MAX_POOL_DESCRIPTION_SIZE:
            raise se.StoragePoolDescriptionTooLongError()

        msd = sdCache.produce(sdUUID=masterDom)
        msdType = msd.getStorageType()
        msdVersion = msd.getVersion()
        if (msdType in sd.BLOCK_DOMAIN_TYPES and
                msdVersion in blockSD.VERS_METADATA_LV and
                len(domList) > MAX_DOMAINS):
            raise se.TooManyDomainsInStoragePoolError()

        vars.task.getExclusiveLock(STORAGE, spUUID)
        for dom in sorted(domList):
            vars.task.getExclusiveLock(STORAGE, dom)

        pool = sp.StoragePool(spUUID, self.domainMonitor, self.taskMng)
        pool.setBackend(StoragePoolDiskBackend(pool))

        return pool.create(poolName, masterDom, domList, masterVersion,
                           leaseParams)

    @public
    def connectStoragePool(self, spUUID, hostID, msdUUID, masterVersion,
                           domainsMap=None, options=None):
        vars.task.setDefaultException(
            se.StoragePoolConnectionError(
                "spUUID=%s, msdUUID=%s, masterVersion=%s, hostID=%s, "
                "domainsMap=%s" %
                (spUUID, msdUUID, masterVersion, hostID, domainsMap)))
        with rm.acquireResource(STORAGE, HSM_DOM_MON_LOCK, rm.EXCLUSIVE):
            return self._connectStoragePool(
                spUUID, hostID, msdUUID, masterVersion, domainsMap)

    @staticmethod
    def _updateStoragePool(pool, hostId, msdUUID, masterVersion, domainsMap):
        if hostId != pool.id:
            raise se.StoragePoolConnected(
                "hostId=%s, newHostId=%s" % (pool.id, hostId))

        if domainsMap is None:
            if not isinstance(pool.getBackend(), StoragePoolDiskBackend):
                raise se.StoragePoolConnected('Cannot downgrade pool backend')
        else:
            if isinstance(pool.getBackend(), StoragePoolMemoryBackend):
                pool.getBackend().updateVersionAndDomains(
                    masterVersion, domainsMap)
            else:
                pool.setBackend(
                    StoragePoolMemoryBackend(pool, masterVersion, domainsMap))

        pool.refresh(msdUUID, masterVersion)

    def _connectStoragePool(self, spUUID, hostID, msdUUID,
                            masterVersion, domainsMap=None, options=None):
        misc.validateUUID(spUUID, 'spUUID')
        if self._pool.is_connected() and self._pool.spUUID != spUUID:
            raise se.CannotConnectMultiplePools(self._pool.spUUID)

        try:
            self.getPool(spUUID)
        except se.StoragePoolUnknown:
        else:
            with rm.acquireResource(STORAGE, spUUID, rm.SHARED):
                self._updateStoragePool(self.getPool(spUUID), hostID, msdUUID,
                                        masterVersion, domainsMap)
                return True

        with rm.acquireResource(STORAGE, spUUID, rm.EXCLUSIVE):
            try:
                pool = self.getPool(spUUID)
            except se.StoragePoolUnknown:
            else:
                self._updateStoragePool(pool, hostID, msdUUID, masterVersion,
                                        domainsMap)
                return True

            pool = sp.StoragePool(spUUID, self.domainMonitor, self.taskMng)
            pool.backend = StoragePoolDiskBackend(pool)

            if domainsMap is None:
                pool.setBackend(StoragePoolDiskBackend(pool))
            else:
                pool.setBackend(
                    StoragePoolMemoryBackend(pool, masterVersion, domainsMap))

            res = pool.connect(hostID, msdUUID, masterVersion)
            if res:
                self.setPool(pool)
            return res

    @public
    def disconnectStoragePool(self, spUUID, hostID, remove=False,
                              options=None):
        vars.task.setDefaultException(
            se.StoragePoolDisconnectionError(
                "spUUID=%s, hostID=%s" % (spUUID, hostID)))
        misc.validateN(hostID, 'hostID')
        try:
            pool = self.getPool(spUUID)
        except se.StoragePoolUnknown:
            self.log.warning("Already disconnected from %r", spUUID)
            return

        self.getPool(spUUID).validateNotSPM()

        vars.task.getExclusiveLock(STORAGE, spUUID)
        pool = self.getPool(spUUID)

        return self._disconnectPool(pool, hostID, remove)

    def _disconnectPool(self, pool, hostID, remove):
        pool.validateNotSPM()
        with rm.acquireResource(STORAGE, HSM_DOM_MON_LOCK, rm.EXCLUSIVE):
            res = pool.disconnect()
            self.setPool(sp.DisconnectedPool())
        return res

    @public
    def destroyStoragePool(self, spUUID, hostID, options=None):
        vars.task.setDefaultException(
            se.StoragePoolDestroyingError(
                "spUUID=%s, hostID=%s" % (spUUID, hostID)))
        self.log.info("spUUID=%s", spUUID)

        pool = self.getPool(spUUID)
        if not pool.id == hostID:
            raise se.HostIdMismatch(spUUID)

        vars.task.getExclusiveLock(STORAGE, pool.spUUID)
        domList = sorted(pool.getDomains().keys())
        for sdUUID in domList:
            vars.task.getExclusiveLock(STORAGE, sdUUID)

        pool.detachAllDomains()
        return self._disconnectPool(pool, hostID, remove=True)

    @public
    def attachStorageDomain(self, sdUUID, spUUID, options=None):
        vars.task.setDefaultException(
            se.StorageDomainActionError(
                "sdUUID=%s, spUUID=%s" % (sdUUID, spUUID)))

        vars.task.getExclusiveLock(STORAGE, spUUID)
        vars.task.getExclusiveLock(STORAGE, sdUUID)
        pool = self.getPool(spUUID)
        pool.attachSD(sdUUID)

    @public
    def deactivateStorageDomain(self, sdUUID, spUUID, msdUUID,
                                masterVersion, options=None):
        vars.task.setDefaultException(
            se.StorageDomainActionError(
                "sdUUID=%s, spUUID=%s, msdUUID=%s, masterVersion=%s" %
                (sdUUID, spUUID, msdUUID, masterVersion)
            )
        )

        vars.task.getExclusiveLock(STORAGE, spUUID)
        vars.task.getExclusiveLock(STORAGE, sdUUID)
        pool = self.getPool(spUUID)
        pool.deactivateSD(sdUUID, msdUUID, masterVersion)

    @public
    def activateStorageDomain(self, sdUUID, spUUID, options=None):
        vars.task.setDefaultException(
            se.StorageDomainActionError(
                "sdUUID=%s, spUUID=%s" % (sdUUID, spUUID)))

        vars.task.getExclusiveLock(STORAGE, spUUID)
        vars.task.getExclusiveLock(STORAGE, sdUUID)
        pool = self.getPool(spUUID)
        pool.activateSD(sdUUID)

    @deprecated
    @public
    def setStoragePoolDescription(self, spUUID, description, options=None):
        """
        Deprecated, as the storage pool's metadata is no longer persisted.
        TODO: Remove when the support for 3.5 clusters is dropped.
        """

    @deprecated
    @public
    def setVolumeDescription(self, sdUUID, spUUID, imgUUID, volUUID,
                             description, options=None):
        vars.task.getSharedLock(STORAGE, sdUUID)
        pool = self.getPool(spUUID)
        pool.setVolumeDescription(sdUUID, imgUUID, volUUID, description)

    @deprecated
    @public
    def setVolumeLegality(self, sdUUID, spUUID, imgUUID, volUUID, legality,
                          options=None):
        vars.task.getSharedLock(STORAGE, sdUUID)

        pool = self.getPool(spUUID)
        pool.setVolumeLegality(sdUUID, imgUUID, volUUID, legality)

    @public
    def updateVM(self, spUUID, vmList, sdUUID=None, options=None):
        vars.task.getSharedLock(STORAGE, spUUID)
        pool = self.getPool(spUUID)
        if not sdUUID or sdUUID == sd.BLANK_UUID:
            sdUUID = pool.masterDomain.sdUUID

        vars.task.getExclusiveLock(STORAGE, "vms_" + sdUUID)
        pool.updateVM(vmList=vmList, sdUUID=sdUUID)

    @public
    def removeVM(self, spUUID, vmUUID, sdUUID=None, options=None):
        vars.task.getSharedLock(STORAGE, spUUID)
        pool = self.getPool(spUUID)
        if not sdUUID or sdUUID == sd.BLANK_UUID:
            sdUUID = pool.masterDomain.sdUUID

        vars.task.getSharedLock(STORAGE, "vms_" + sdUUID)
        vars.task.getExclusiveLock(STORAGE, "vms_%s_%s" % (vmUUID, sdUUID))
        pool.removeVM(vmUUID=vmUUID, sdUUID=sdUUID)

    @public
    def getVmsList(self, spUUID, sdUUID=None, options=None):
        pool = self.getPool(spUUID)
        if not sdUUID or sdUUID == sd.BLANK_UUID:
            sdUUID = pool.masterDomain.sdUUID
        vars.task.getSharedLock(STORAGE, sdUUID)
        vms = pool.getVmsList(sdUUID)
        return dict(vmlist=vms)

    @public
    def getVmsInfo(self, spUUID, sdUUID, vmList=None, options=None):
        pool = self.getPool(spUUID)
        if sdUUID and sdUUID != sd.BLANK_UUID:
            self.validateBackupDom(sdUUID)
        else:
            sdUUID = pool.masterDomain.sdUUID
        vars.task.getSharedLock(STORAGE, sdUUID)
        vms = pool.getVmsInfo(sdUUID, vmList)
        return dict(vmlist=vms)

    @public
    def createVolume(self, sdUUID, spUUID, imgUUID, size, volFormat,
                     preallocate, diskType, volUUID, desc,
                     srcImgUUID=sc.BLANK_UUID,
                     srcVolUUID=sc.BLANK_UUID,
                     initialSize=None):
        argsStr = ("sdUUID=%s, spUUID=%s, imgUUID=%s, size=%s, volFormat=%s, "
                   "preallocate=%s, diskType=%s, volUUID=%s, desc=%s, "
                   "srcImgUUID=%s, srcVolUUID=%s, initialSize=%s" %
                   (sdUUID, spUUID, imgUUID, size, volFormat, preallocate,
                    diskType, volUUID, desc, srcImgUUID, srcVolUUID,
                    initialSize))
        vars.task.setDefaultException(se.VolumeCreationError(argsStr))

        diskType = str(diskType)

        pool = self.getPool(spUUID)
        dom = sdCache.produce(sdUUID=sdUUID)
        misc.validateUUID(imgUUID, 'imgUUID')
        misc.validateUUID(volUUID, 'volUUID')
        capacity = misc.validateSize(size, "size")
        initial_size = None
        if initialSize is not None:
            initial_size = misc.validateSize(initialSize, "initialSize")

        if srcImgUUID:
            misc.validateUUID(srcImgUUID, 'srcImgUUID')
        if srcVolUUID:
            misc.validateUUID(srcVolUUID, 'srcVolUUID')
        dom.validateCreateVolumeParams(
            volFormat, srcVolUUID, diskType=diskType, preallocate=preallocate)

        vars.task.getSharedLock(STORAGE, sdUUID)
        self._spmSchedule(spUUID, "createVolume", pool.createVolume, sdUUID,
                          imgUUID, capacity, volFormat, preallocate, diskType,
                          volUUID, desc, srcImgUUID, srcVolUUID, initial_size)

    @public
    def deleteVolume(self, sdUUID, spUUID, imgUUID, volumes, postZero=False,
                     force=False, discard=False):
        argsStr = "sdUUID=%s, spUUID=%s, imgUUID=%s, volumes=%s, " \
                  "postZero=%s, force=%s, discard=%s" %\
                  (sdUUID, spUUID, imgUUID, volumes, postZero, force, discard)
        vars.task.setDefaultException(se.CannotDeleteVolume(argsStr))
        pool = self.getPool(spUUID)
        misc.validateUUID(imgUUID, 'imgUUID')

        vars.task.getSharedLock(STORAGE, sdUUID)
        self._spmSchedule(spUUID, "deleteVolume", pool.deleteVolume, sdUUID,
                          imgUUID, volumes, misc.parseBool(postZero),
                          misc.parseBool(force), discard)

    @public
    def deleteImage(self, sdUUID, spUUID, imgUUID, postZero=False,
                    force=False, discard=False):
        pool = self.getPool(spUUID)
        dom = sdCache.produce(sdUUID=sdUUID)

        vars.task.getExclusiveLock(STORAGE, "%s_%s" % (imgUUID, sdUUID))
        vars.task.getSharedLock(STORAGE, sdUUID)
        allVols = dom.getAllVolumes()
        volsByImg = sd.getVolsOfImage(allVols, imgUUID)
        if not volsByImg:
            self.log.error("Empty or not found image %s in SD %s. %s",
                           imgUUID, sdUUID, allVols)
            raise se.ImageDoesNotExistInSD(imgUUID, sdUUID)

        fakeTUUID = None
        for k, v in volsByImg.iteritems():
            if len(v.imgs) > 1 and v.imgs[0] == imgUUID:
                if dom.isBackup():
                    fakeTUUID = k
                else:
                    raise se.CannotDeleteSharedVolume("Cannot delete shared "
                                                      "image %s. volImgs: %s" %
                                                      (imgUUID, volsByImg))
                break

        if misc.parseBool(postZero):
            self._spmSchedule(spUUID, "zeroImage_%s" % imgUUID, dom.zeroImage,
                              sdUUID, imgUUID, volsByImg, discard)
        else:
            if fakeTUUID:
                tParams = dom.produceVolume(imgUUID, fakeTUUID).\
                    getVolumeParams()
            pool.deleteImage(dom, imgUUID, volsByImg)
            if fakeTUUID:
                img = image.Image(os.path.join(sc.REPO_DATA_CENTER, spUUID))
                img.createFakeTemplate(sdUUID=sdUUID, volParams=tParams)
            self._spmSchedule(spUUID, "purgeImage_%s" % imgUUID,
                              pool.purgeImage, sdUUID, imgUUID, volsByImg,
                              discard)

    @public
    def verify_untrusted_volume(self, spUUID, sdUUID, imgUUID, volUUID):
        dom = sdCache.produce(sdUUID=sdUUID).manifest
        vol = dom.produceVolume(imgUUID, volUUID)
        qemu_info = qemuimg.info(vol.getVolumePath(), trusted_image=False)

        meta_format = sc.FMT2STR[vol.getFormat()]
        qemu_format = qemu_info["format"]
        if meta_format != qemu_format:
            raise se.ImageVerificationError(
                "Volume's format specified by QEMU is %s, while the format "
                "specified in VDSM metadata is %s" %
                (qemu_format, meta_format))

        meta_size = vol.getCapacity()
        qemu_size = qemu_info["virtualsize"]
        if meta_size < qemu_size:
            raise se.ImageVerificationError(
                "Image virtual size %r is bigger than volume size %r"
                % (qemu_size, meta_size))
        elif meta_size > qemu_size:
            self.log.warning(
                "Image virtual size %r is smaller than volume size %r, image "
                "should be resized",
                qemu_size, meta_size)

        meta_parent = vol.getParent()
        qemu_parent = qemu_info.get("backingfile", sc.BLANK_UUID)
        if meta_parent != qemu_parent:
            raise se.ImageVerificationError(
                "Image backing file %r does not match volume parent uuid %r"
                % (qemu_parent, meta_parent))

        if qemu_format == qemuimg.FORMAT.QCOW2:
            qemu_compat = qemu_info["compat"]
            if not dom.supports_qcow2_compat(qemu_compat):
                raise se.ImageVerificationError(
                    "qcow2 compat %r not supported on this domain" %
                    qemu_compat)

    def validateImageMove(self, srcDom, dstDom, imgUUID):
        srcAllVols = srcDom.getAllVolumes()
        dstAllVols = dstDom.getAllVolumes()

        srcVolsImgs = sd.getVolsOfImage(srcAllVols, imgUUID)
        for volName, imgsPar in srcVolsImgs.iteritems():
            if len(imgsPar.imgs) > 1:
                tName, tImgs = volName, imgsPar.imgs
                if imgUUID != tImgs[0] and tName not in dstAllVols.keys():
                    self.log.error(
                        "img %s can't be moved to dom %s because template "
                        "%s is absent on it", imgUUID, dstDom.sdUUID, tName)
                    e = se.ImageDoesNotExistInSD(imgUUID, dstDom.sdUUID)
                    e.absentTemplateUUID = tName
                    e.absentTemplateImageUUID = tImgs[0]
                    raise e
                elif imgUUID == tImgs[0] and not srcDom.isBackup():
                    raise se.MoveTemplateImageError(imgUUID)
                break

        return True

    @public
    def moveImage(self, spUUID, srcDomUUID, dstDomUUID, imgUUID, vmUUID,
                  op, postZero=False, force=False, discard=False):
        argsStr = ("spUUID=%s, srcDomUUID=%s, dstDomUUID=%s, imgUUID=%s, "
                   "vmUUID=%s, op=%s, force=%s, postZero=%s, force=%s,"
                   "discard=%s" %
                   (spUUID, srcDomUUID, dstDomUUID, imgUUID, vmUUID, op,
                    force, postZero, force, discard))
        vars.task.setDefaultException(se.MoveImageError("%s" % argsStr))
        if srcDomUUID == dstDomUUID:
            raise se.InvalidParameterException(
                "srcDom", "must be different from dstDom: %s" % argsStr)

        srcDom = sdCache.produce(sdUUID=srcDomUUID)
        dstDom = sdCache.produce(sdUUID=dstDomUUID)
        pool = self.getPool(spUUID)
        try:
            self.validateImageMove(srcDom, dstDom, imgUUID)
        except se.ImageDoesNotExistInSD as e:
            if not dstDom.isBackup():
                raise
            else:
                tName = e.absentTemplateUUID
                tImgUUID = e.absentTemplateImageUUID
                tParams = srcDom.produceVolume(tImgUUID,
                                               tName).getVolumeParams()
                image.Image(os.path.join(sc.REPO_DATA_CENTER, spUUID)
                            ).createFakeTemplate(dstDom.sdUUID, tParams)

        domains = [srcDomUUID, dstDomUUID]
        domains.sort()

        for dom in domains:
            vars.task.getSharedLock(STORAGE, dom)

        self._spmSchedule(
            spUUID, "moveImage_%s" % imgUUID, pool.moveImage, srcDomUUID,
            dstDomUUID, imgUUID, vmUUID, op, misc.parseBool(postZero),
            misc.parseBool(force), discard)

    @public
    def sparsifyImage(self, spUUID, tmpSdUUID, tmpImgUUID, tmpVolUUID,
                      dstSdUUID, dstImgUUID, dstVolUUID):

        pool = self.getPool(spUUID)

        sdUUIDs = sorted(set((tmpSdUUID, dstSdUUID)))

        for dom in sdUUIDs:
            vars.task.getSharedLock(STORAGE, dom)

        self._spmSchedule(spUUID, "sparsifyImage", pool.sparsifyImage,
                          tmpSdUUID, tmpImgUUID, tmpVolUUID, dstSdUUID,
                          dstImgUUID, dstVolUUID)

    @public
    def cloneImageStructure(self, spUUID, sdUUID, imgUUID, dstSdUUID):
        sdCache.produce(sdUUID=sdUUID)
        sdCache.produce(sdUUID=dstSdUUID)

        for dom in sorted((sdUUID, dstSdUUID)):
            vars.task.getSharedLock(STORAGE, dom)

        pool = self.getPool(spUUID)
        self._spmSchedule(spUUID, "cloneImageStructure",
                          pool.cloneImageStructure, sdUUID, imgUUID, dstSdUUID)

    @public
    def syncImageData(self, spUUID, sdUUID, imgUUID, dstSdUUID, syncType):
        sdCache.produce(sdUUID=sdUUID)
        sdCache.produce(sdUUID=dstSdUUID)

        for dom in sorted((sdUUID, dstSdUUID)):
            vars.task.getSharedLock(STORAGE, dom)

        pool = self.getPool(spUUID)
        self._spmSchedule(spUUID, "syncImageData", pool.syncImageData,
                          sdUUID, imgUUID, dstSdUUID, syncType)

    @public
    def uploadImage(self, methodArgs, spUUID, sdUUID, imgUUID, volUUID=None):
        sdCache.produce(sdUUID)
        pool = self.getPool(spUUID)
        self._spmSchedule(spUUID, "uploadImage", pool.uploadImage,
                          methodArgs, sdUUID, imgUUID, volUUID)

    @public
    def downloadImage(self, methodArgs, spUUID, sdUUID, imgUUID, volUUID=None):
        sdCache.produce(sdUUID)
        pool = self.getPool(spUUID)
        self._spmSchedule(spUUID, "downloadImage", pool.downloadImage,
                          methodArgs, sdUUID, imgUUID, volUUID)

    @public
    def uploadImageToStream(self, methodArgs, callback, startEvent, spUUID,
                            sdUUID, imgUUID, volUUID=None):
        sdCache.produce(sdUUID)
        pool = self.getPool(spUUID)
        self._spmSchedule(spUUID, "uploadImageToStream",
                          pool.uploadImageToStream, methodArgs, callback,
                          startEvent, sdUUID, imgUUID, volUUID)

    @public
    def downloadImageFromStream(self, methodArgs, callback, spUUID, sdUUID,
                                imgUUID, volUUID=None):
        sdCache.produce(sdUUID)
        pool = self.getPool(spUUID)
        self._spmSchedule(spUUID, "downloadImageFromStream",
                          pool.downloadImageFromStream, methodArgs, callback,
                          sdUUID, imgUUID, volUUID)

    @public
    def copyImage(
            self, sdUUID, spUUID, vmUUID, srcImgUUID, srcVolUUID, dstImgUUID,
            dstVolUUID, description='', dstSdUUID=sd.BLANK_UUID,
            volType=sc.SHARED_VOL, volFormat=sc.UNKNOWN_VOL,
            preallocate=sc.UNKNOWN_VOL, postZero=False, force=False,
            discard=False):
        argsStr = ("sdUUID=%s, spUUID=%s, vmUUID=%s, srcImgUUID=%s, "
                   "srcVolUUID=%s, dstImgUUID=%s, dstVolUUID=%s, "
                   "description=%s, dstSdUUID=%s, volType=%s, volFormat=%s, "
                   "preallocate=%s force=%s, postZero=%s, discard=%s" %
                   (sdUUID, spUUID, vmUUID, srcImgUUID, srcVolUUID,
                    dstImgUUID, dstVolUUID, description, dstSdUUID, volType,
                    volFormat, preallocate, force, postZero, discard))
        vars.task.setDefaultException(se.TemplateCreationError("%s" % argsStr))
        if dstSdUUID in (sdUUID, sd.BLANK_UUID):
            if srcImgUUID == dstImgUUID:
                raise se.InvalidParameterException("dstImgUUID", dstImgUUID)
        pool = self.getPool(spUUID)
        sdCache.produce(sdUUID=sdUUID)

        pool.validateVolumeChain(sdUUID, srcImgUUID)
        if dstSdUUID != sd.BLANK_UUID:
            dom = dstSdUUID
        else:
            dom = sdUUID

        sdCache.produce(dom).validateCreateVolumeParams(
            volFormat, sc.BLANK_UUID, preallocate=preallocate)

        domains = [sdUUID]
        if dstSdUUID not in [sdUUID, sd.BLANK_UUID]:
            sdCache.produce(sdUUID=dstSdUUID)
            domains.append(dstSdUUID)
            domains.sort()

        for dom in domains:
            vars.task.getSharedLock(STORAGE, dom)

        self._spmSchedule(
            spUUID, "copyImage_%s" % dstImgUUID, pool.copyImage, sdUUID,
            vmUUID, srcImgUUID, srcVolUUID, dstImgUUID, dstVolUUID,
            description, dstSdUUID, volType, volFormat, preallocate,
            misc.parseBool(postZero), misc.parseBool(force), discard)

    @public
    def imageSyncVolumeChain(self, sdUUID, imgUUID, volUUID, newChain):
        argsStr = ("sdUUID=%s, imgUUID=%s, volUUID=%s, newChain=%s" %
                   (sdUUID, imgUUID, volUUID, newChain))
        vars.task.setDefaultException(se.StorageException("%s" % argsStr))
        sdDom = sdCache.produce(sdUUID=sdUUID)
        repoPath = os.path.join(sc.REPO_DATA_CENTER, sdDom.getPools()[0])

        imageResourcesNamespace = rm.getNamespace(sc.IMAGE_NAMESPACE, sdUUID)
        with rm.acquireResource(imageResourcesNamespace, imgUUID, rm.SHARED):
            image.Image(repoPath).syncVolumeChain(sdUUID, imgUUID, volUUID,
                                                  newChain)

    @public
    def reconcileVolumeChain(self, spUUID, sdUUID, imgUUID, leafVolUUID):
        argsStr = ("spUUID=%s, sdUUID=%s, imgUUID=%s, leafVolUUID=%s" %
                   (spUUID, sdUUID, imgUUID, leafVolUUID))
        vars.task.setDefaultException(se.StorageException("%s" % argsStr))
        pool = self.getPool(spUUID)
        sdCache.produce(sdUUID=sdUUID)
        vars.task.getSharedLock(STORAGE, sdUUID)
        return pool.reconcileVolumeChain(sdUUID, imgUUID, leafVolUUID)

    @deprecated
    @public
    def mergeSnapshots(self, sdUUID, spUUID, vmUUID, imgUUID, ancestor,
                       successor, postZero=False, discard=False):
        argsStr = ("sdUUID=%s, spUUID=%s, vmUUID=%s, imgUUID=%s, "
                   "ancestor=%s, successor=%s, postZero=%s, discard=%s" %
                   (sdUUID, spUUID, vmUUID, imgUUID, ancestor, successor,
                    postZero, discard))
        vars.task.setDefaultException(se.MergeSnapshotsError("%s" % argsStr))
        pool = self.getPool(spUUID)
        sdCache.produce(sdUUID=sdUUID)
        vars.task.getSharedLock(STORAGE, sdUUID)
        self._spmSchedule(
            spUUID, "mergeSnapshots", pool.mergeSnapshots, sdUUID, vmUUID,
            imgUUID, ancestor, successor, misc.parseBool(postZero), discard)

    @public
    def reconstructMaster(self, spUUID, poolName, masterDom, domDict,
                          masterVersion, lockPolicy, lockRenewalIntervalSec,
                          leaseTimeSec, ioOpTimeoutSec, leaseRetries, hostId,
                          options=None):
        leaseParams = sd.packLeaseParams(
            lockRenewalIntervalSec=lockRenewalIntervalSec,
            leaseTimeSec=leaseTimeSec,
            ioOpTimeoutSec=ioOpTimeoutSec,
            leaseRetries=leaseRetries
        )

        vars.task.setDefaultException(
            se.ReconstructMasterError(
                "spUUID=%s, masterDom=%s, masterVersion=%s, clusterlock "
                "params: (%s)" % (spUUID, masterDom, masterVersion,
                                  leaseParams)))

        self.log.info("spUUID=%s master=%s", spUUID, masterDom)

        try:
            pool = self.getPool(spUUID)
        except se.StoragePoolUnknown:
            pool = sp.StoragePool(spUUID, self.domainMonitor, self.taskMng)
            pool.setBackend(StoragePoolDiskBackend(pool))
        else:
            raise se.StoragePoolConnected(spUUID)

        self.validateSdUUID(masterDom)

        misc.validateN(hostId, 'hostId')

        vars.task.getExclusiveLock(STORAGE, spUUID)

        for d, status in domDict.iteritems():
            misc.validateUUID(d)
            try:
                sd.validateSDStatus(status)
            except:
                domDict[d] = sd.validateSDDeprecatedStatus(status)

        return pool.reconstructMaster(hostId, poolName, masterDom, domDict,
                                      masterVersion, leaseParams)

    @public
    def getDeviceList(self, storageType=None, guids=(), checkStatus=True,
                      options={}):
        vars.task.setDefaultException(se.BlockDeviceActionError())

        if checkStatus and not guids:
            self.log.warning(
                "Calling Host.getDeviceList with checkStatus=True without "
                "specifying guids is very slow. It is recommended to use "
                "checkStatus=False when getting all devices.")

        devices = self._getDeviceList(storageType=storageType, guids=guids,
                                      checkStatus=checkStatus)
        return dict(devList=devices)

    def _getDeviceList(self, storageType=None, guids=(), checkStatus=True):
        sdCache.refreshStorage()
        typeFilter = lambda dev: True
        if storageType:
            if sd.storageType(storageType) == sd.type2name(sd.ISCSI_DOMAIN):
                typeFilter = \
                    lambda dev: multipath.devIsiSCSI(dev.get("devtype"))
            elif sd.storageType(storageType) == sd.type2name(sd.FCP_DOMAIN):
                typeFilter = \
                    lambda dev: multipath.devIsFCP(dev.get("devtype"))

        devices = []
        pvs = {os.path.basename(pv.name): pv for pv in lvm.getAllPVs()}

        for dev in multipath.pathListIter(guids):
            if not typeFilter(dev):
                continue

            pv = pvs.get(dev.get('guid', ""))
            if pv is not None:
                pvuuid = pv.uuid
                pvsize = pv.size
                vguuid = pv.vg_uuid
            else:
                pvuuid = ""
                pvsize = ""
                vguuid = ""

            devInfo = {
                'GUID': dev.get("guid", ""),
                'capacity': dev.get("capacity", "0"),
                'devtype': dev.get("devtype", ""),
                'fwrev': dev.get("fwrev", ""),
                'logicalblocksize': dev.get("logicalblocksize", ""),
                'pathlist': dev.get("connections", []),
                'pathstatus': dev.get("paths", []),
                'physicalblocksize': dev.get("physicalblocksize", ""),
                'productID': dev.get("product", ""),
                'pvUUID': pvuuid,
                'pvsize': str(pvsize),
                'serial': dev.get("serial", ""),
                'vendorID': dev.get("vendor", ""),
                'vgUUID': vguuid,
                "discard_max_bytes": dev["discard_max_bytes"],
                "discard_zeroes_data": 0,
            }
            if not checkStatus:
                devInfo["status"] = "unknown"
            devices.append(devInfo)

        if checkStatus:
            devNamesToPVTest = tuple(dev["GUID"] for dev in devices)
            unusedDevs, usedDevs = lvm.testPVCreate(
                devNamesToPVTest, metadataSize=blockSD.VG_METADATASIZE)
            free = tuple(os.path.basename(d) for d in unusedDevs)
            used = tuple(os.path.basename(d) for d in usedDevs)
            for dev in devices:
                guid = dev['GUID']
                if guid in free:
                    dev['status'] = "free"
                elif guid in used:
                    dev['status'] = "used"
                else:
                    raise KeyError("pvcreate response foresight is "
                                   "can not be determined for %s", dev)

        return devices

    @public
    def getDevicesVisibility(self, guids, options=None):
        def _isVisible(guid):
            try:
                res = (os.stat('/dev/mapper/' + guid).st_mode &
                       stat.S_IRUSR != 0)
            except OSError as e:
                if e.errno != errno.ENOENT:
                    raise
                res = False
            return res

        visibility = [_isVisible(guid) for guid in guids]
        if not all(visibility):
            multipath.rescan()
            visibility = [_isVisible(guid) for guid in guids]
        visibility = dict(zip(guids, visibility))

        lvm.invalidateFilter()

        return {'visible': visibility}

    @public
    def createVG(self, vgname, devlist, force=False, options=None):
        force = force in (True, "true", "True")

        MINIMALVGSIZE = 10 * 1024 * constants.MEGAB

        vars.task.setDefaultException(
            se.VolumeGroupCreateError(str(vgname), str(devlist)))
        misc.validateUUID(vgname, 'vgname')
        knowndevs = set(os.path.basename(p) for p
                        in multipath.getMPDevNamesIter())
        size = 0
        devices = []
        unknowndevs = []

        for dev in devlist:
            if dev in knowndevs:
                devices.append(dev)
                size += multipath.getDeviceSize(devicemapper.getDmId(dev))
            else:
                unknowndevs.append(dev)

        if unknowndevs:
            raise se.InaccessiblePhysDev(unknowndevs)

        if size < MINIMALVGSIZE:
            raise se.VolumeGroupSizeError(
                "VG size must be more than %s MiB" %
                str(MINIMALVGSIZE / constants.MEGAB))

        lvm.createVG(vgname, devices, blockSD.STORAGE_UNREADY_DOMAIN_TAG,
                     metadataSize=blockSD.VG_METADATASIZE,
                     force=force)

        return dict(uuid=lvm.getVG(vgname).uuid)

    @deprecated
    @public
    def removeVG(self, vgUUID, options=None):
        vars.task.setDefaultException(se.VolumeGroupActionError("%s" % vgUUID))
        try:
            lvm.removeVGbyUUID(vgUUID)
        except se.VolumeGroupDoesNotExist:
            pass

    @public
    def getTaskStatus(self, taskID, spUUID=None, options=None):
        taskStatus = self.taskMng.getTaskStatus(taskID=taskID)
        return dict(taskStatus=taskStatus)

    @public
    def getAllTasksStatuses(self, spUUID=None, options=None):
        if not self._pool.is_connected():
            raise se.SpmStatusError()
        allTasksStatus = self._pool.getAllTasksStatuses()
        return dict(allTasksStatus=allTasksStatus)

    @public
    def getTaskInfo(self, taskID, spUUID=None, options=None):
        inf = self.taskMng.getTaskInfo(taskID=taskID)
        return dict(TaskInfo=inf)

    @public
    def getAllTasksInfo(self, spUUID=None, options=None):
        if not self._pool.is_connected():
            raise se.SpmStatusError()
        allTasksInfo = self._pool.getAllTasksInfo()
        return dict(allTasksInfo=allTasksInfo)

    @public
    def getAllTasks(self):
        ret = self.taskMng.getAllTasks()
        return dict(tasks=ret)

    @public
    def stopTask(self, taskID, spUUID=None, options=None):
        force = False
        if options:
            try:
                force = options.get("force", False)
            except:
                self.log.warning("options %s are ignored" % options)
        return self.taskMng.stopTask(taskID=taskID, force=force)

    @public
    def clearTask(self, taskID, spUUID=None, options=None):
        return self.taskMng.clearTask(taskID=taskID)

    @public
    def revertTask(self, taskID, spUUID=None, options=None):
        return self.taskMng.revertTask(taskID=taskID)

    @public
    def getFileStats(self, sdUUID, pattern='*', caseSensitive=False,
                     options=None):
        vars.task.setDefaultException(se.GetFileStatsError(sdUUID))
        vars.task.getSharedLock(STORAGE, sdUUID)

        dom = sdCache.produce(sdUUID=sdUUID)
        if not dom.isISO or dom.getStorageType() not in sd.FILE_DOMAIN_TYPES:
            raise se.GetFileStatsError(sdUUID)

        fileStats = dom.getFileList(pattern=pattern,
                                    caseSensitive=caseSensitive)
        return {'fileStats': fileStats}

    def _getSDTypeFindMethod(self, domType):
        SDTypeFindMethod = {sd.NFS_DOMAIN: nfsSD.findDomain,
                            sd.FCP_DOMAIN: blockSD.findDomain,
                            sd.ISCSI_DOMAIN: blockSD.findDomain,
                            sd.LOCALFS_DOMAIN: localFsSD.findDomain,
                            sd.POSIXFS_DOMAIN: nfsSD.findDomain,
                            sd.GLUSTERFS_DOMAIN: glusterSD.findDomain}
        return SDTypeFindMethod.get(domType)

    def _prefetchDomains(self, domType, conObj):
        uuidPatern = "????????-????-????-????-????????????"

        if domType in (sd.FCP_DOMAIN, sd.ISCSI_DOMAIN):
            uuids = tuple(blockSD.getStorageDomainsList())
        elif domType is sd.NFS_DOMAIN:
            lPath = conObj._mountCon._getLocalPath()
            self.log.debug("nfs local path: %s", lPath)
            goop = oop.getGlobalProcPool()
            uuids = tuple(os.path.basename(d) for d in
                          goop.glob.glob(os.path.join(lPath, uuidPatern)))
        elif domType is sd.POSIXFS_DOMAIN:
            lPath = conObj._getLocalPath()
            self.log.debug("posix local path: %s", lPath)
            goop = oop.getGlobalProcPool()
            uuids = tuple(os.path.basename(d) for d in
                          goop.glob.glob(os.path.join(lPath, uuidPatern)))
        elif domType is sd.GLUSTERFS_DOMAIN:
            glusterDomPath = os.path.join(sd.GLUSTERSD_DIR, "*")
            self.log.debug("glusterDomPath: %s", glusterDomPath)
            uuids = tuple(sdUUID for sdUUID, domainPath in
                          nfsSD.fileSD.scanDomains(glusterDomPath))
        elif domType is sd.LOCALFS_DOMAIN:
            lPath = conObj._path
            self.log.debug("local _path: %s", lPath)
            uuids = tuple(os.path.basename(d) for d in
                          glob.glob(os.path.join(lPath, uuidPatern)))
        else:
            uuids = tuple()
            self.log.warn("domType %s does not support prefetch")

        self.log.debug("Found SD uuids: %s", uuids)
        findMethod = self._getSDTypeFindMethod(domType)
        return dict.fromkeys(uuids, findMethod)

    @deprecated
    @public
    def connectStorageServer(self, domType, spUUID, conList, options=None):
        vars.task.setDefaultException(
            se.StorageServerConnectionError(
                "domType=%s, spUUID=%s, conList=%s" %
                (domType, spUUID, conList)))

        res = []
        connections = []
        for conDef in conList:
            conInfo = _connectionDict2ConnectionInfo(domType, conDef)
            conObj = storageServer.ConnectionFactory.createConnection(conInfo)
            try:
                self._connectStorageOverIser(conDef, conObj, domType)
                conObj.connect()
            except Exception as err:
                self.log.error(
                    "Could not connect to storageServer", exc_info=True)
                status, _ = self._translateConnectionError(err)
            else:
                status = 0
                connections.append(conObj)

            res.append({'id': conDef["id"], 'status': status})

        if domType in (sd.FCP_DOMAIN, sd.ISCSI_DOMAIN):
            sdCache.refreshStorage()

        for conObj in connections:
            try:
                doms = self._prefetchDomains(domType, conObj)
            except:
                self.log.debug("prefetch failed: %s",
                               sdCache.knownSDs, exc_info=True)
            else:
                for sdUUID in doms:
                    sdCache.manuallyRemoveDomain(sdUUID)
                sdCache.knownSDs.update(doms)

        self.log.debug("knownSDs: {%s}", ", ".join("%s: %s.%s" %
                       (k, v.__module__, v.__name__)
                       for k, v in six.iteritems(sdCache.knownSDs)))

        sdCache.invalidateStorage()
        return dict(statuslist=res)

    @deprecated
    def _connectStorageOverIser(self, conDef, conObj, conTypeId):
        typeName = CON_TYPE_ID_2_CON_TYPE[conTypeId]
        if typeName == 'iscsi' and 'initiatorName' not in conDef:
            ifaces = config.get('irs', 'iscsi_default_ifaces').split(',')
            if 'iser' in ifaces:
                conObj._iface = iscsi.IscsiInterface('iser')
                try:
                    conObj.connect()
                    conObj.disconnect()
                except:
                    conObj._iface = iscsi.IscsiInterface('default')

    @deprecated
    @public
    def disconnectStorageServer(self, domType, spUUID, conList, options=None):
        vars.task.setDefaultException(
            se.StorageServerDisconnectionError(
                "domType=%s, spUUID=%s, conList=%s" %
                (domType, spUUID, conList)))

        res = []
        for conDef in conList:
            conInfo = _connectionDict2ConnectionInfo(domType, conDef)
            conObj = storageServer.ConnectionFactory.createConnection(conInfo)
            try:
                conObj.disconnect()
                status = 0
            except Exception as err:
                self.log.error("Could not disconnect from storageServer",
                               exc_info=True)
                status, _ = self._translateConnectionError(err)

            res.append({'id': conDef["id"], 'status': status})

        sdCache.refreshStorage(resize=False)
        return dict(statuslist=res)

    def _translateConnectionError(self, e):
        if e is None:
            return 0, ""

        if isinstance(e, mount.MountError):
            return se.MountError.code, se.MountError.message
        if isinstance(e, iscsi.iscsiadm.IscsiAuthenticationError):
            return se.iSCSILoginAuthError.code, se.iSCSILoginAuthError.message
        if isinstance(e, iscsi.iscsiadm.IscsiInterfaceError):
            return se.iSCSIifaceError.code, se.iSCSIifaceError.message
        if isinstance(e, iscsi.iscsiadm.IscsiError):
            return se.iSCSISetupError.code, se.iSCSISetupError.message

        if hasattr(e, 'code'):
            return e.code, e.message

        return se.GeneralException.code, str(e)

    @public
    def getStoragePoolInfo(self, spUUID, options=None):
        vars.task.setDefaultException(
            se.StoragePoolActionError("spUUID=%s" % spUUID))
        vars.task.getSharedLock(STORAGE, spUUID)
        pool = self.getPool(spUUID)
        poolInfo = pool.getInfo()
        doms = pool.getDomains()
        domInfo = self._getDomsStats(pool.domainMonitor, doms)
        for sdUUID in doms.iterkeys():
            if domInfo[sdUUID]['isoprefix']:
                poolInfo['isoprefix'] = domInfo[sdUUID]['isoprefix']
                break
        else:

        return dict(info=poolInfo, dominfo=domInfo)

    @public
    def createStorageDomain(self, storageType, sdUUID, domainName,
                            typeSpecificArg, domClass,
                            domVersion=sc.SUPPORTED_DOMAIN_VERSIONS[0],
                            block_size=sc.BLOCK_SIZE_512,
                            max_hosts=sc.HOSTS_4K_1M, options=None):
        msg = ("storageType=%s, sdUUID=%s, domainName=%s, "
               "domClass=%s, typeSpecificArg=%s domVersion=%s"
               "block_size=%s, max_hosts=%s" %
               (storageType, sdUUID, domainName, domClass,
                typeSpecificArg, domVersion, block_size, max_hosts))
        domVersion = int(domVersion)
        vars.task.setDefaultException(se.StorageDomainCreationError(msg))
        misc.validateUUID(sdUUID, 'sdUUID')
        self.validateNonDomain(sdUUID)

        if domClass not in sd.DOMAIN_CLASSES.keys():
            raise se.StorageDomainClassError()

        sd.validateDomainVersion(domVersion)

        if storageType in sd.BLOCK_DOMAIN_TYPES:
            create = blockSD.BlockStorageDomain.create
        elif storageType in (sd.NFS_DOMAIN, sd.POSIXFS_DOMAIN):
            create = nfsSD.NfsStorageDomain.create
        elif storageType == sd.GLUSTERFS_DOMAIN:
            create = glusterSD.GlusterStorageDomain.create
        elif storageType == sd.LOCALFS_DOMAIN:
            create = localFsSD.LocalFsStorageDomain.create
        else:
            raise se.StorageDomainTypeError(storageType)

        newSD = create(
            sdUUID,
            domainName,
            domClass,
            typeSpecificArg,
            storageType,
            domVersion,
            block_size=block_size,
            max_hosts=max_hosts)

        findMethod = self._getSDTypeFindMethod(storageType)
        sdCache.knownSDs[sdUUID] = findMethod
        self.log.debug("knownSDs: {%s}", ", ".join("%s: %s.%s" %
                       (k, v.__module__, v.__name__)
                       for k, v in sdCache.knownSDs.iteritems()))

        sdCache.manuallyAddDomain(newSD)

    @public
    def validateStorageDomain(self, sdUUID, options=None):
        vars.task.setDefaultException(
            se.StorageDomainCreationError("sdUUID=%s" % sdUUID))
        return sdCache.produce(sdUUID=sdUUID).validate()

    def _recycle(self, dom):
        sdUUID = dom.sdUUID
        try:
            dom.format(dom.sdUUID)
            del dom
        finally:
            try:
                sdCache.manuallyRemoveDomain(sdUUID)
            except KeyError:
                self.log.warn("Storage domain %s doesn't exist in cache. "
                              "Leftovers are recycled.", sdUUID)

    @public
    def formatStorageDomain(self, sdUUID, autoDetach=False, options=None):
        multipath.rescan()
        vars.task.setDefaultException(
            se.StorageDomainActionError("sdUUID=%s" % sdUUID))

        vars.task.getExclusiveLock(STORAGE, sdUUID)
        try:
            domDict = self._pool.getDomains()
        except se.StoragePoolNotConnected:
            pass
        else:
            if sdUUID in domDict.keys():
                raise se.CannotFormatStorageDomainInConnectedPool(sdUUID)

        sd = sdCache.produce(sdUUID=sdUUID)
        try:
            sd.invalidateMetadata()
            if not misc.parseBool(autoDetach) and sd.getPools():
                raise se.CannotFormatAttachedStorageDomain(sdUUID)
        except (se.StorageDomainMetadataNotFound, se.MetaDataGeneralError,
                se.MiscFileReadException, se.MiscBlockReadException,
                se.MiscBlockReadIncomplete) as e:
            self.log.warn("Domain %s has problem with metadata. Continue "
                          "formatting... (%s)", sdUUID, e)

        self._recycle(sd)

    @public
    def setStorageDomainDescription(self, sdUUID, description, options=None):
        if len(description) > sd.MAX_DOMAIN_DESCRIPTION_SIZE:
            raise se.StorageDomainDescriptionTooLongError()

        vars.task.setDefaultException(
            se.StorageDomainActionError(
                "sdUUID=%s, description=%s" % (sdUUID, description)))
        dom = sdCache.produce(sdUUID=sdUUID)
        vars.task.getSharedLock(STORAGE, sdUUID)

        pool = self.getPool(dom.getPools()[0])
        pool.setSDDescription(dom, description)

    @public
    def getStorageDomainInfo(self, sdUUID, options=None):
        vars.task.setDefaultException(
            se.StorageDomainActionError("sdUUID=%s" % sdUUID))
        dom = self.validateSdUUID(sdUUID)

        vars.task.getSharedLock(STORAGE, sdUUID)
        return dict(info=dom.getInfo())

    @public
    def getStorageDomainStats(self, sdUUID, options=None):
        vars.task.setDefaultException(
            se.StorageDomainActionError("sdUUID=%s" % sdUUID))
        vars.task.getSharedLock(STORAGE, sdUUID)
        dom = sdCache.produce(sdUUID=sdUUID)
        dom.refresh()
        stats = dom.getStats()
        return dict(stats=stats)

    @public
    def getStorageDomainsList(
            self, spUUID=None, domainClass=None, storageType=None,
            remotePath=None, options=None):
        vars.task.setDefaultException(
            se.StorageDomainActionError("spUUID: %s" % spUUID))
        sdCache.refreshStorage()
        if remotePath:
            remotePath = fileUtils.normalize_path(remotePath)
            local_path = fileUtils.transformPath(remotePath)
        if spUUID and spUUID != sc.BLANK_UUID:
            domList = self.getPool(spUUID).getDomains()
            domains = domList.keys()
        else:
            domains = sdCache.getUUIDs()

        for sdUUID in domains[:]:
            try:
                dom = sdCache.produce(sdUUID=sdUUID)
                if storageType and storageType != dom.getStorageType():
                    domains.remove(sdUUID)
                    continue
                if domainClass and domainClass != dom.getDomainClass():
                    domains.remove(sdUUID)
                    continue
                if remotePath and local_path != dom.getRemotePath():
                    domains.remove(sdUUID)
                    continue
            except Exception:
                self.log.error("Unexpected error", exc_info=True)
                domains.remove(sdUUID)
                continue

        return dict(domlist=domains)

    def __fillPVDict(self, devInfo, pv, devtype):
        info = {}
        info["vendorID"] = devInfo["vendor"]
        info["productID"] = devInfo["product"]
        info["serial"] = devInfo["serial"]
        info["pathstatus"] = []
        for pathInfo in devInfo['paths']:
            info["pathstatus"].append(pathInfo)
        info["pathlist"] = devInfo["connections"]
        info["fwrev"] = "0000"
        info["devtype"] = devtype
        info["capacity"] = str(pv.size)
        info["devcapacity"] = str(pv.dev_size)
        info["vgUUID"] = str(pv.vg_uuid)
        info["pvUUID"] = str(pv.uuid)
        info["pe_count"] = str(pv.pe_count)
        info["pe_alloc_count"] = str(pv.pe_alloc_count)
        info["GUID"] = str(pv.guid)
        info["discard_max_bytes"] = devInfo["discard_max_bytes"]
        info["discard_zeroes_data"] = 0
        return info

    @deprecated
    @public
    def getVGList(self, storageType=None, options=None):
        vars.task.setDefaultException(se.VolumeGroupActionError())
        sdCache.refreshStorage()
        vglist = []
        vgs = self.__getVGsInfo()
        for vgInfo in vgs:
            del vgInfo["pvlist"]
            if storageType is not None:
                if vgInfo["type"] != storageType:
                    continue
            vglist.append(vgInfo)

        return dict(vglist=vglist)

    def __getVGsInfo(self, vgUUIDs=None):
        getGuid = lambda pvName: os.path.split(pvName)[-1]
        devNames = []
        vgInfos = []
        vgGuids = {}
        if vgUUIDs is None:
            vgList = lvm.getAllVGs()
        else:
            vgList = [lvm.getVGbyUUID(vgUUID) for vgUUID in vgUUIDs]

        for i, vg in enumerate(vgList):
            devNames.extend(map(getGuid, lvm.listPVNames(vg.name)))
            vgInfo = {'name': vg.name, 'vgUUID': vg.uuid,
                      'vgsize': str(vg.size), 'vgfree': str(vg.free),
                      'type': "", 'attr': dict(vg.attr._asdict()),
                      'state': vg.partial, "pvlist": []}
            vgInfos.append(vgInfo)
            vgGuids[vg.uuid] = i

        pathDict = {}
        for dev in multipath.pathListIter(devNames):
            pathDict[dev["guid"]] = dev

        self.__processVGInfos(vgInfos, pathDict, getGuid)

        return vgInfos

    def __processVGInfos(self, vgInfos, pathDict, getGuid):
        vgType = None
        for vgInfo in vgInfos:
            for pv in lvm.listPVNames(vgInfo['name']):
                dev = pathDict.get(getGuid(pv))
                if dev is None:
                    self.log.warn("dev %s was not found %s",
                                  getGuid(pv), pathDict)
                    continue
                if vgType is None:
                    vgType = dev["devtype"]
                elif (vgType != multipath.DEV_MIXED and
                      vgType != dev["devtype"]):
                    vgType = multipath.DEV_MIXED

                pvInfo = lvm.getPV(pv)
                vgInfo['pvlist'].append(self.__fillPVDict(dev, pvInfo, vgType))

            if vgType == multipath.DEV_FCP:
                vgType = sd.FCP_DOMAIN
            elif vgType == multipath.DEV_ISCSI:
                vgType = sd.ISCSI_DOMAIN
            else:
                vgType = sd.ISCSI_DOMAIN

            vgInfo["type"] = vgType

    @public
    def getVGInfo(self, vgUUID, options=None):
        vars.task.setDefaultException(se.VolumeGroupActionError("%s" % vgUUID))
        vg = lvm.getVGbyUUID(vgUUID)
        lvm.invalidateVG(vg.name, invalidateLVs=False, invalidatePVs=True)
        return dict(info=self.__getVGsInfo([vgUUID])[0])

    @public
    def discoverSendTargets(self, con, options=None):
        ip = con['connection']
        port = int(con['port'])
        ipv6_enabled = con['ipv6_enabled']
        username = con['user']
        password = con['password']
        if username == "":
            username = password = None

        iface = iscsi.IscsiInterface("default")
        portal = iscsi.IscsiPortal(ip, port)
        cred = None
        if username or password:
            cred = iscsi.ChapCredentials(username, password)

        try:
            targets = iscsi.discoverSendTargets(iface, portal, cred)
        except iscsi.iscsiadm.IscsiError as e:
            self.log.error("Discovery failed", exc_info=True)
            raise se.iSCSIDiscoveryError(portal, e)
        fullTargets = []
        partialTargets = []
        for target in targets:
            if ipv6_enabled or not target.portal.is_ipv6():
                fullTargets.append(str(target))
                partialTargets.append(target.iqn)

        return dict(targets=partialTargets, fullTargets=fullTargets)

    @public
    def cleanupUnusedConnections(self, options=None):
        pass

    @public
    def refreshVolume(self, sdUUID, spUUID, imgUUID, volUUID):
        return sdCache.produce(
            sdUUID=sdUUID).produceVolume(imgUUID=imgUUID,
                                         volUUID=volUUID).refreshVolume()

    @public
    def add_image_ticket(self, ticket):
        imagetickets.add_ticket(ticket)

    @public
    def get_image_ticket(self, uuid):
        return dict(result=imagetickets.get_ticket(uuid))

    @public
    def remove_image_ticket(self, uuid):
        imagetickets.remove_ticket(uuid)

    @public
    def extend_image_ticket(self, uuid, timeout):
        imagetickets.extend_ticket(uuid, timeout)

    @public
    def getVolumeSize(self, sdUUID, spUUID, imgUUID, volUUID, options=None):
        dom = sdCache.produce(sdUUID=sdUUID)
        apparentsize = str(dom.getVSize(imgUUID, volUUID))
        truesize = str(dom.getVAllocSize(imgUUID, volUUID))
        return dict(apparentsize=apparentsize, truesize=truesize)

    @public
    def setVolumeSize(self, sdUUID, spUUID, imgUUID, volUUID, capacity):
        capacity = int(capacity)
        vol = sdCache.produce(sdUUID).produceVolume(imgUUID, volUUID)
        capacity = utils.round(capacity, sc.BLOCK_SIZE_4K)
        vol.setCapacity(capacity)

    @public
    def getVolumeInfo(self, sdUUID, spUUID, imgUUID, volUUID, options=None):
        vars.task.getSharedLock(STORAGE, sdUUID)
        dom = sdCache.produce_manifest(sdUUID=sdUUID)
        try:
            vol = dom.produceVolume(imgUUID=imgUUID, volUUID=volUUID)
        except se.VolumeDoesNotExist as e:
            raise exception.expected(e)
        info = vol.getInfo()
        return dict(info=info)

    @public
    def getQemuImageInfo(self, sdUUID, spUUID, imgUUID, volUUID, options=None):
        vars.task.getSharedLock(STORAGE, sdUUID)
        sd = sdCache.produce(sdUUID)
        vol = sd.produceVolume(imgUUID, volUUID)
        info = vol.getQemuImageInfo()
        return dict(info=info)

    @public
    def appropriateDevice(self, guid, thiefId, deviceType):
        if deviceType == 'mpath':
            devPath = os.path.join(devicemapper.DMPATH_PREFIX, guid)
            supervdsm.getProxy().appropriateDevice(guid, thiefId, deviceType)
            supervdsm.getProxy().udevTrigger(guid, deviceType)
            size = str(multipath.getDeviceSize(devicemapper.getDmId(guid)))
            device = dict(truesize=size, apparentsize=size, path=devPath)
        elif deviceType == 'rbd':
            devPath = guid
            supervdsm.getProxy().appropriateDevice(guid, thiefId, deviceType)
            supervdsm.getProxy().udevTrigger(devPath, deviceType)
            device = dict(path=devPath)
        else:
            raise RuntimeError("Unsupported device type %r" % deviceType)

        function.retry(partial(fileUtils.validateQemuReadable, devPath),
                       expectedException=OSError,
                       timeout=QEMU_READABLE_TIMEOUT)


        return device

    @public
    def inappropriateDevices(self, thiefId):
        fails = supervdsm.getProxy().rmAppropriateMultipathRules(thiefId)
        if fails:
            self.log.error("Failed to remove the following rules: %s", fails)

    @public
    def prepareImage(self, sdUUID, spUUID, imgUUID, leafUUID,
                     allowIllegal=False):
        if spUUID != sd.BLANK_UUID:
            self.getPool(spUUID)

        vars.task.getSharedLock(STORAGE, sdUUID)

        imgVolumesInfo = []
        dom = sdCache.produce(sdUUID)
        allVols = dom.getAllVolumes()
        imgVolumes = sd.getVolsOfImage(allVols, imgUUID).keys()

        if leafUUID not in imgVolumes:
            raise se.VolumeDoesNotExist(leafUUID)

        for volUUID in imgVolumes:
            legality = dom.produceVolume(imgUUID, volUUID).getLegality()
            if legality == sc.ILLEGAL_VOL:
                if allowIllegal:
                    self.log.info("Preparing illegal volume %s", leafUUID)
                else:
                    raise se.prepareIllegalVolumeError(volUUID)

        imgPath = dom.activateVolumes(imgUUID, imgVolumes)
        try:
            for volUUID in imgVolumes:
                dom.produceVolume(imgUUID, volUUID).updateInvalidatedSize()

            if spUUID and spUUID != sd.BLANK_UUID:
                runImgPath = dom.linkBCImage(imgPath, imgUUID)
            else:
                runImgPath = imgPath

            leafInfo = dom.produceVolume(imgUUID, leafUUID).getVmVolumeInfo()

            leafPath = os.path.join(runImgPath, leafUUID)
            for volUUID in imgVolumes:
                path = os.path.join(dom.domaindir, sd.DOMAIN_IMAGES, imgUUID,
                                    volUUID)
                volInfo = {'domainID': sdUUID, 'imageID': imgUUID,
                           'volumeID': volUUID, 'path': path}

                lease = dom.getVolumeLease(imgUUID, volUUID)

                if lease.path and isinstance(lease.offset, numbers.Integral):
                    volInfo.update({
                        'leasePath': lease.path,
                        'leaseOffset': lease.offset,
                    })

                imgVolumesInfo.append(volInfo)
        except Exception:
            try:
                dom.unlinkBCImage(imgUUID)
                dom.deactivateImage(imgUUID)
            except Exception:
                self.log.exception("Error tearing down image")
            raise

        return {'path': leafPath, 'info': leafInfo,
                'imgVolumesInfo': imgVolumesInfo}

    @public
    def teardownImage(self, sdUUID, spUUID, imgUUID, volUUID=None):
        vars.task.getSharedLock(STORAGE, sdUUID)

        dom = sdCache.produce(sdUUID)
        dom.unlinkBCImage(imgUUID)
        dom.deactivateImage(imgUUID)

    @public
    def getVolumesList(self, sdUUID, spUUID, imgUUID=sc.BLANK_UUID,
                       options=None):
        vars.task.getSharedLock(STORAGE, sdUUID)
        dom = sdCache.produce(sdUUID=sdUUID)
        vols = dom.getAllVolumes()
        if imgUUID == sc.BLANK_UUID:
            volUUIDs = vols.keys()
        else:
            volUUIDs = [k for k, v in vols.iteritems() if imgUUID in v.imgs]
        return dict(uuidlist=volUUIDs)

    @public
    def getImagesList(self, sdUUID, options=None):
        vars.task.getSharedLock(STORAGE, sdUUID)
        dom = sdCache.produce(sdUUID=sdUUID)
        images = dom.getAllImages()
        return dict(imageslist=list(images))

    @deprecated
    @public
    def getImageDomainsList(self, spUUID, imgUUID, options=None):
        vars.task.setDefaultException(
            se.GetStorageDomainListError("spUUID=%s imgUUID=%s" %
                                         (spUUID, imgUUID)))
        vars.task.getSharedLock(STORAGE, spUUID)
        pool = self.getPool(spUUID)
        activeDoms = sorted(pool.getDomains(activeOnly=True).keys())
        imgDomains = []
        for sdUUID in activeDoms:
            dom = sdCache.produce(sdUUID=sdUUID)
            if dom.isData():
                with rm.acquireResource(STORAGE, sdUUID, rm.SHARED):
                    try:
                        imgs = dom.getAllImages()
                    except se.StorageDomainDoesNotExist:
                        self.log.error("domain %s can't be reached.",
                                       sdUUID, exc_info=True)
                    else:
                        if imgUUID in imgs:
                            imgDomains.append(sdUUID)

        return dict(domainslist=imgDomains)

    @public
    def prepareForShutdown(self, options=None):
        try:
            sp.StoragePool.cleanupMasterMount()
            self.__releaseLocks()

            try:
                if self._pool.spmMailer:
                    self._pool.spmMailer.stop()
                    self._pool.spmMailer.tp.joinAll(waitForTasks=False)

                if self._pool.hsmMailer:
                    self._pool.hsmMailer.stop()
            except se.StoragePoolNotConnected:
                pass

            try:
                self.domainMonitor.shutdown()
            except Exception:
                self.log.warning("Failed to stop RepoStats thread",
                                 exc_info=True)

            self.taskMng.prepareForShutdown()
            oop.stop()
            self.multipathListener.stop()
        except:
            pass

    @classmethod
    def __releaseLocks(cls):
        lockCmd = config.get('irs', 'lock_cmd')
        try:
            misc.killall(lockCmd, signal.SIGUSR1, group=True)
        except OSError as e:
            if e.errno == errno.ESRCH:
                return
            raise

        cls.log.warning("Found lease locks, releasing")
        for i in range(10):
            time.sleep(1)

            try:
                misc.killall(lockCmd, 0)
            except OSError as e:
                if e.errno == errno.ESRCH:
                    return

        cls.log.warning("Could not release locks, killing lock processes")
        misc.killall(lockCmd, signal.SIGKILL, group=True)

    @public
    def fenceSpmStorage(self, spUUID, lastOwner, lastLver, options=None):
        vars.task.setDefaultException(
            se.SpmFenceError("spUUID=%s, lastOwner=%s, lastLver=%s" %
                             (spUUID, lastOwner, lastLver)))
        pool = self.getPool(spUUID)
        if isinstance(pool.getBackend(), StoragePoolDiskBackend):
            pool.getBackend().invalidateMetadata()
            vars.task.getExclusiveLock(STORAGE, spUUID)
            pool.getBackend().forceFreeSpm()
        return dict(spm_st=self._getSpmStatusInfo(pool))

    @public
    def upgradeStoragePool(self, spUUID, targetDomVersion):
        targetDomVersion = int(targetDomVersion)
        vars.task.getExclusiveLock(STORAGE, spUUID)
        pool = self.getPool(spUUID)
        pool._upgradePool(targetDomVersion, lockTimeout=0)
        return {"upgradeStatus": "started"}

    def _getDomsStats(self, domainMonitor, doms):
        domInfo = {}
        repoStats = self._getRepoStats(domainMonitor)

        for sdUUID, sdStatus in doms.iteritems():
            domInfo[sdUUID] = {'status': sdStatus, 'alerts': [],
                               'isoprefix': ''}

            if sdStatus != sd.DOM_ACTIVE_STATUS or sdUUID not in repoStats:
                continue

            domInfo[sdUUID]['version'] = repoStats[sdUUID]['result']['version']

            if (repoStats[sdUUID]['disktotal'] is not None and
                    repoStats[sdUUID]['diskfree'] is not None):
                domInfo[sdUUID]['disktotal'] = repoStats[sdUUID]['disktotal']
                domInfo[sdUUID]['diskfree'] = repoStats[sdUUID]['diskfree']

            if not repoStats[sdUUID]['mdavalid']:
                domInfo[sdUUID]['alerts'].append({
                    'code': se.SmallVgMetadata.code,
                    'message': se.SmallVgMetadata.message,
                })
                self.log.warning("VG %s's metadata size too small %s",
                                 sdUUID, repoStats[sdUUID]['mdasize'])

            if not repoStats[sdUUID]['mdathreshold']:
                domInfo[sdUUID]['alerts'].append({
                    'code': se.VgMetadataCriticallyFull.code,
                    'message': se.VgMetadataCriticallyFull.message,
                })
                self.log.warning("VG %s's metadata size exceeded critical "
                                 "size: mdasize=%s mdafree=%s", sdUUID,
                                 repoStats[sdUUID]['mdasize'],
                                 repoStats[sdUUID]['mdafree'])

            if repoStats[sdUUID]['isoprefix'] is not None:
                domInfo[sdUUID]['isoprefix'] = repoStats[sdUUID]['isoprefix']

        return domInfo

    def _getRepoStats(self, domainMonitor, domains=()):
        domains = frozenset(domains)
        repoStats = {}
        statsGenTime = time.time()

        for sdUUID, domStatus in domainMonitor.getDomainsStatus():
            if domains and sdUUID not in domains:
                continue
            if domStatus.error is None:
                code = 0
            elif isinstance(domStatus.error, se.StorageException):
                code = domStatus.error.code
            else:
                code = se.StorageException.code

            disktotal, diskfree = domStatus.diskUtilization
            vgmdtotal, vgmdfree = domStatus.vgMdUtilization
            lastcheck = '%.1f' % (statsGenTime - domStatus.checkTime)

            repoStats[sdUUID] = {
                'finish': domStatus.checkTime,

                'result': {
                    'code': code,
                    'lastCheck': lastcheck,
                    'delay': str(domStatus.readDelay),
                    'valid': (domStatus.error is None),
                    'version': domStatus.version,
                    'acquired': domStatus.hasHostId is True,
                    'actual': domStatus.actual
                },

                'disktotal': disktotal,
                'diskfree': diskfree,

                'mdavalid': domStatus.vgMdHasEnoughFreeSpace,
                'mdathreshold': domStatus.vgMdFreeBelowThreashold,
                'mdasize': vgmdtotal,
                'mdafree': vgmdfree,

                'masterValidate': {
                    'mount': domStatus.masterMounted,
                    'valid': domStatus.masterValid
                },

                'isoprefix': domStatus.isoPrefix,
            }

        return repoStats

    @public
    def repoStats(self, domains=()):
        result = {}

        repo_stats = self._getRepoStats(self.domainMonitor, domains=domains)

        for d in repo_stats:
            result[d] = repo_stats[d]['result']

        return result

    @public
    def multipath_health(self):
        return self.mpathhealth_monitor.status()

    @deprecated
    @public
    def startMonitoringDomain(self, sdUUID, hostID, options=None):
        with rm.acquireResource(STORAGE, HSM_DOM_MON_LOCK, rm.EXCLUSIVE):
            self.domainMonitor.startMonitoring(sdUUID, int(hostID), False)

    @deprecated
    @public
    def stopMonitoringDomain(self, sdUUID, options=None):
        with rm.acquireResource(STORAGE, HSM_DOM_MON_LOCK, rm.EXCLUSIVE):
            if sdUUID in self.domainMonitor.poolDomains:
                raise se.StorageDomainIsMemberOfPool(sdUUID)
            self.domainMonitor.stopMonitoring([sdUUID])

    @public
    def getHostLeaseStatus(self, domains):
        return {'domains': self.domainMonitor.getHostStatus(domains)}

    @public
    def prepareMerge(self, spUUID, subchainInfo):
        msg = "spUUID=%s, subchainInfo=%s" % (spUUID, subchainInfo)
        vars.task.setDefaultException(se.StorageException(msg))
        subchain = merge.SubchainInfo(subchainInfo, self._pool.id)
        pool = self.getPool(spUUID)
        sdCache.produce(subchain.sd_id)
        vars.task.getSharedLock(STORAGE, subchain.sd_id)
        self._spmSchedule(spUUID, "prepareMerge", pool.prepareMerge,
                          subchain)

    @public
    def finalizeMerge(self, spUUID, subchainInfo):
        msg = "spUUID=%s, subchainInfo=%s" % (spUUID, subchainInfo)
        vars.task.setDefaultException(se.StorageException(msg))
        subchain = merge.SubchainInfo(subchainInfo, self._pool.id)
        pool = self.getPool(spUUID)
        sdCache.produce(subchain.sd_id)
        vars.task.getSharedLock(STORAGE, subchain.sd_id)
        self._spmSchedule(spUUID, "finalizeMerge", pool.finalizeMerge,
                          subchain)

    def sdm_schedule(self, job):
        jobs.add(job)
        self.taskMng.scheduleJob("sdm", None, vars.task,
                                 job.description, job.run)

    @public
    def sdm_copy_data(self, job_id, source, destination):
        job = copy_data.Job(job_id, self._pool.id, source, destination)
        self.sdm_schedule(job)

    @public
    def sdm_sparsify_volume(self, job_id, vol_info):
        job = sparsify_volume.Job(job_id, self._pool.id, vol_info)
        self.sdm_schedule(job)

    @public
    def sdm_amend_volume(self, job_id, vol_info, qcow2_attr):
        job = amend_volume.Job(job_id, self._pool.id, vol_info, qcow2_attr)
        self.sdm_schedule(job)

    @public
    def sdm_update_volume(self, job_id, vol_info, vol_attr):
        job = update_volume.Job(job_id, self._pool.id, vol_info, vol_attr)
        self.sdm_schedule(job)

    @public
    def sdm_merge(self, job_id, subchain_info):
        subchain = merge.SubchainInfo(subchain_info, self._pool.id)
        job = api_merge.Job(job_id, subchain)
        self.sdm_schedule(job)

    @public
    def sdm_move_domain_device(self, job_id, move_params):
        job = move_device.Job(job_id, DISCONNECTED_HOST_ID, move_params)
        self.sdm_schedule(job)

    @public
    def sdm_reduce_domain(self, job_id, reduce_params):
        job = reduce_domain.Job(job_id, DISCONNECTED_HOST_ID, reduce_params)
        self.sdm_schedule(job)


    @public
    def create_lease(self, lease):
        lease = types.Lease(lease)
        self._check_pool_connected()
        vars.task.getSharedLock(STORAGE, lease.sd_id)
        self._spmSchedule(self._pool.spUUID, "create_lease",
                          self._pool.create_lease, lease)

    @public
    def delete_lease(self, lease):
        lease = types.Lease(lease)
        self._check_pool_connected()
        vars.task.getSharedLock(STORAGE, lease.sd_id)
        self._spmSchedule(self._pool.spUUID, "delete_lease",
                          self._pool.delete_lease, lease)

    @public
    def lease_info(self, lease):
        lease = types.Lease(lease)
        self._check_pool_connected()
        with rm.acquireResource(STORAGE, lease.sd_id, rm.SHARED):
            dom = sdCache.produce_manifest(lease.sd_id)
            info = dom.lease_info(lease.lease_id)
        lease_info = dict(sd_id=info.lockspace,
                          lease_id=info.resource,
                          path=info.path,
                          offset=info.offset)
        return dict(result=lease_info)

    @public
    def rebuild_leases(self, sd_id):
        self._check_pool_connected()
        vars.task.getSharedLock(STORAGE, sd_id)
        self._spmSchedule(self._pool.spUUID, "rebuild_leases",
                          self._pool.rebuild_leases, sd_id)


    @public
    def lease_status(self, lease):
        raise NotImplementedError


    @public
    def start_nbd_server(self, server_id, config):
        self._check_pool_connected()
        url = nbd.start_server(server_id, config)
        return dict(result=url)

    @public
    def stop_nbd_server(self, server_id):
        nbd.stop_server(server_id)


    def _check_pool_connected(self):
        if not self._pool.is_connected():
            raise se.StoragePoolNotConnected
