
from __future__ import absolute_import

import os
import logging
import threading
from collections import namedtuple
import codecs
from contextlib import contextmanager

import six

from vdsm import utils
from vdsm.common import exception
from vdsm.common.threadlocal import vars
from vdsm.config import config
from vdsm.storage import clusterlock
from vdsm.storage import constants as sc
from vdsm.storage import exception as se
from vdsm.storage import fileUtils
from vdsm.storage import misc
from vdsm.storage import outOfProcess as oop
from vdsm.storage import qemuimg
from vdsm.storage import resourceFactories
from vdsm.storage import resourceManager as rm
from vdsm.storage import rwlock
from vdsm.storage import task
from vdsm.storage import xlease
from vdsm.storage.persistent import unicodeEncoder, unicodeDecoder

DOMAIN_META_DATA = 'dom_md'
DOMAIN_IMAGES = 'images'
METADATA = "metadata"
MAX_DOMAIN_DESCRIPTION_SIZE = 50

GLUSTERSD_DIR = "glusterSD"

BLOCKSD_DIR = "blockSD"
LEASES = "leases"
IDS = "ids"
INBOX = "inbox"
OUTBOX = "outbox"

XLEASES = "xleases"

LEASES_SLOTS = 2048
XLEASES_SLOTS = 1024

SPECIAL_VOLUMES_V0 = (METADATA, LEASES, IDS, INBOX, OUTBOX)

SPECIAL_VOLUMES_V4 = SPECIAL_VOLUMES_V0 + (XLEASES,)

SPECIAL_VOLUME_SIZES_MIB = {
    IDS: 8,
    INBOX: 16,
    OUTBOX: 16,
}

UNKNOWN_DOMAIN = 0
NFS_DOMAIN = 1
FCP_DOMAIN = 2
ISCSI_DOMAIN = 3
LOCALFS_DOMAIN = 4
CIFS_DOMAIN = 5
POSIXFS_DOMAIN = 6
GLUSTERFS_DOMAIN = 7

BLOCK_DOMAIN_TYPES = [FCP_DOMAIN, ISCSI_DOMAIN]
FILE_DOMAIN_TYPES = [NFS_DOMAIN, LOCALFS_DOMAIN, CIFS_DOMAIN, POSIXFS_DOMAIN,
                     GLUSTERFS_DOMAIN]

DOMAIN_TYPES = {UNKNOWN_DOMAIN: 'UNKNOWN', NFS_DOMAIN: 'NFS',
                FCP_DOMAIN: 'FCP', ISCSI_DOMAIN: 'ISCSI',
                LOCALFS_DOMAIN: 'LOCALFS', CIFS_DOMAIN: 'CIFS',
                POSIXFS_DOMAIN: 'POSIXFS', GLUSTERFS_DOMAIN: 'GLUSTERFS'}

DEPRECATED_DOM_INACTIVE_STATUS = 'Inactive'
DOM_UNKNOWN_STATUS = 'Unknown'
DOM_ATTACHED_STATUS = 'Attached'
DOM_UNATTACHED_STATUS = 'Unattached'
DOM_ACTIVE_STATUS = 'Active'

DOMAIN_STATUSES = [DOM_UNKNOWN_STATUS, DOM_ATTACHED_STATUS,
                   DOM_UNATTACHED_STATUS, DOM_ACTIVE_STATUS]
DEPRECATED_STATUSES = {DEPRECATED_DOM_INACTIVE_STATUS: DOM_ATTACHED_STATUS}

MASTER_DOMAIN = 'Master'
REGULAR_DOMAIN = 'Regular'
DATA_DOMAIN = 1
ISO_DOMAIN = 2
BACKUP_DOMAIN = 3
DOMAIN_CLASSES = {DATA_DOMAIN: 'Data', ISO_DOMAIN: 'Iso',
                  BACKUP_DOMAIN: 'Backup'}

DMDK_VERSION = "VERSION"
DMDK_SDUUID = "SDUUID"
DMDK_TYPE = "TYPE"
DMDK_ROLE = "ROLE"
DMDK_DESCRIPTION = "DESCRIPTION"
DMDK_CLASS = "CLASS"
DMDK_POOLS = "POOL_UUID"
DMDK_BLOCK_SIZE = "BLOCK_SIZE"
DMDK_ALIGNMENT = "ALIGNMENT"

DMDK_LOCK_POLICY = 'LOCKPOLICY'
DMDK_LOCK_RENEWAL_INTERVAL_SEC = 'LOCKRENEWALINTERVALSEC'
DMDK_LEASE_TIME_SEC = 'LEASETIMESEC'
DMDK_IO_OP_TIMEOUT_SEC = 'IOOPTIMEOUTSEC'
DMDK_LEASE_RETRIES = 'LEASERETRIES'

DMDK_LOGBLKSIZE = "LOGBLKSIZE"
DMDK_PHYBLKSIZE = "PHYBLKSIZE"

DEFAULT_LEASE_PARAMS = {DMDK_LOCK_POLICY: "ON",
                        DMDK_LEASE_RETRIES: 3,
                        DMDK_LEASE_TIME_SEC: 60,
                        DMDK_LOCK_RENEWAL_INTERVAL_SEC: 5,
                        DMDK_IO_OP_TIMEOUT_SEC: 10}

MASTER_FS_DIR = 'master'
VMS_DIR = 'vms'
TASKS_DIR = 'tasks'

ImgsPar = namedtuple("ImgsPar", "imgs,parent")
ISO_IMAGE_UUID = '11111111-1111-1111-1111-111111111111'
BLANK_UUID = '00000000-0000-0000-0000-000000000000'

UNICODE_MINIMAL_VERSION = 3

SDM_LEASE_NAME = 'SDM'
SDM_LEASE_SLOT = 1


def getVolsOfImage(allVols, imgUUID):
    return dict((volName, vol) for volName, vol in allVols.iteritems()
                if imgUUID in vol.imgs)


def supportsUnicode(version):
    return version >= UNICODE_MINIMAL_VERSION


def packLeaseParams(lockRenewalIntervalSec, leaseTimeSec,
                    ioOpTimeoutSec, leaseRetries):
    if (lockRenewalIntervalSec and leaseTimeSec and
            ioOpTimeoutSec and leaseRetries):
        return {DMDK_LEASE_RETRIES: leaseRetries,
                DMDK_LEASE_TIME_SEC: leaseTimeSec,
                DMDK_LOCK_RENEWAL_INTERVAL_SEC: lockRenewalIntervalSec,
                DMDK_IO_OP_TIMEOUT_SEC: ioOpTimeoutSec}

    return DEFAULT_LEASE_PARAMS


def validateDomainVersion(version):
    if version not in sc.SUPPORTED_DOMAIN_VERSIONS:
        raise se.UnsupportedDomainVersion(version)


def validateSDDeprecatedStatus(status):
    if not status.capitalize() in DEPRECATED_STATUSES:
        raise se.StorageDomainStatusError(status)
    return DEPRECATED_STATUSES[status.capitalize()]


def validateSDStatus(status):
    if not status.capitalize() in DOMAIN_STATUSES:
        raise se.StorageDomainStatusError(status)


def storageType(t):
    if isinstance(t, (six.text_type, six.binary_type)):
        t = t.upper()
    if t in DOMAIN_TYPES.values():
        return t
    try:
        return type2name(int(t))
    except:
        raise se.StorageDomainTypeError(str(t))


def type2name(domType):
    return DOMAIN_TYPES[domType]


def name2type(name):
    for (k, v) in six.iteritems(DOMAIN_TYPES):
        if v == name.upper():
            return k
    raise KeyError(name)


def class2name(domClass):
    return DOMAIN_CLASSES[domClass]


def name2class(name):
    for (k, v) in six.iteritems(DOMAIN_CLASSES):
        if v == name:
            return k
    raise KeyError(name)


def sizeStr2Int(size_str):
    if size_str.endswith("M") or size_str.endswith("m"):
        size = int(size_str[:-1]) * (1 << 20)
    elif size_str.endswith("G") or size_str.endswith("g"):
        size = int(size_str[:-1]) * (1 << 30)
    else:
        size = int(size_str)

    return size


def intOrDefault(default, val):
    try:
        return int(val)
    except ValueError:
        return default


def intEncode(num):
    if num is None:
        return ""

    num = int(num)
    return str(num)


SD_MD_FIELDS = {
    DMDK_VERSION: (int, str),
    DMDK_DESCRIPTION: (unicodeDecoder, unicodeEncoder),
    DMDK_CLASS: (name2class, class2name),
    DMDK_POOLS: (lambda s: s.split(",") if s else [],
                 lambda poolUUIDs: ",".join(poolUUIDs)),
    DMDK_LOCK_POLICY: (str, str),
    DMDK_LOCK_RENEWAL_INTERVAL_SEC: (
        lambda val: intOrDefault(
            DEFAULT_LEASE_PARAMS[DMDK_LOCK_RENEWAL_INTERVAL_SEC], val),
        intEncode),
    DMDK_LEASE_TIME_SEC: (
        lambda val: intOrDefault(
            DEFAULT_LEASE_PARAMS[DMDK_LEASE_TIME_SEC], val),
        intEncode),
    DMDK_IO_OP_TIMEOUT_SEC: (
        lambda val: intOrDefault(
            DEFAULT_LEASE_PARAMS[DMDK_IO_OP_TIMEOUT_SEC], val),
        intEncode),
    DMDK_LEASE_RETRIES: (
        lambda val: intOrDefault(
            DEFAULT_LEASE_PARAMS[DMDK_LEASE_RETRIES], val),
        intEncode),
    DMDK_BLOCK_SIZE: (int, str),
    DMDK_ALIGNMENT: (int, str),
}


class StorageDomainManifest(object):
    log = logging.getLogger("storage.StorageDomainManifest")
    mountpoint = None

    _domainLockTable = {
        0: clusterlock.SafeLease,
        2: clusterlock.SafeLease,
        3: clusterlock.SANLock,
        4: clusterlock.SANLock,
        5: clusterlock.SANLock,
    }

    def __init__(self, sdUUID, domaindir, metadata):
        self.sdUUID = sdUUID
        self.domaindir = domaindir
        self.replaceMetadata(metadata)
        self._external_leases_lock = rwlock.RWLock()
        self._alignment = metadata.get(DMDK_ALIGNMENT, sc.ALIGNMENT_1M)
        self._block_size = metadata.get(DMDK_BLOCK_SIZE, sc.BLOCK_SIZE_512)


        version = self.getVersion()
        if version < 5:
            if self.alignment != sc.ALIGNMENT_1M:
                raise se.MetaDataValidationError(
                    "Storage domain version {} does not support alignment {}"
                        .format(version, self.alignment))

            if self.block_size != sc.BLOCK_SIZE_512:
                raise se.MetaDataValidationError(
                    "Storage domain version {} does not support block size {}"
                        .format(version, self.block_size))

        self._domainLock = self._makeDomainLock()

    @classmethod
    def special_volumes(cls, version):
        raise NotImplementedError

    @property
    def supportsSparseness(self):
        return False

    def recommends_unordered_writes(self, format):
        return format == sc.RAW_FORMAT and not self.supportsSparseness

    @property
    def oop(self):
        return oop.getProcessPool(self.sdUUID)

    def qcow2_compat(self):
        if self.getVersion() >= 4:
            return "1.1"
        return qemuimg.default_qcow2_compat()

    def supports_qcow2_compat(self, value):
        if self.getVersion() >= 4:
            return qemuimg.supports_compat(value)
        else:
            return value in ("0.10", qemuimg.default_qcow2_compat())

    def supports_device_reduce(self):
        return False

    def replaceMetadata(self, md):
        self._metadata = md

    def getDomainRole(self):
        return self.getMetaParam(DMDK_ROLE)

    def getDomainClass(self):
        return self.getMetaParam(DMDK_CLASS)

    def getStorageType(self):
        return self.getMetaParam(DMDK_TYPE)

    def getRepoPath(self):
        if self.getDomainClass() == ISO_DOMAIN:
            raise se.ImagesNotSupportedError()

        return os.path.join(sc.REPO_DATA_CENTER, self.getPools()[0])

    def getImageDir(self, imgUUID):
        return os.path.join(self.domaindir, DOMAIN_IMAGES, imgUUID)

    def getIsoDomainImagesDir(self):
        return os.path.join(self.domaindir, DOMAIN_IMAGES, ISO_IMAGE_UUID)

    def getMDPath(self):
        if self.domaindir:
            return os.path.join(self.domaindir, DOMAIN_META_DATA)
        return None

    def getMetadata(self):
        return self._metadata.copy()

    def getMetaParam(self, key):
        return self._metadata[key]

    def getVersion(self):
        return self.getMetaParam(DMDK_VERSION)

    @property
    def alignment(self):
        return self._alignment

    @property
    def block_size(self):
        return self._block_size

    def resizePV(self, guid):
        pass

    def movePV(self, src_device, dst_devices):
        raise exception.UnsupportedOperation()

    def reduceVG(self, guid):
        raise exception.UnsupportedOperation()

    def getFormat(self):
        return str(self.getVersion())

    def getPools(self):
        try:
            pools = self.getMetaParam(key=DMDK_POOLS)
        except KeyError:
            pools = []
        else:
            if BLANK_UUID in pools:
                pools.remove(BLANK_UUID)
        return pools

    def getIdsFilePath(self):
        raise NotImplementedError

    def getLeasesFilePath(self):
        raise NotImplementedError

    def produceVolume(self, imgUUID, volUUID):
        return self.getVolumeClass()(self.mountpoint, self.sdUUID, imgUUID,
                                     volUUID)

    def isISO(self):
        return self.getMetaParam(DMDK_CLASS) == ISO_DOMAIN

    def isBackup(self):
        return self.getMetaParam(DMDK_CLASS) == BACKUP_DOMAIN

    def isData(self):
        return self.getMetaParam(DMDK_CLASS) == DATA_DOMAIN

    def getReservedId(self):
        return self._domainLock.getReservedId()

    def acquireHostId(self, hostId, wait=True):
        self._domainLock.acquireHostId(hostId, wait)

    def releaseHostId(self, hostId, wait=True, unused=False):
        self._domainLock.releaseHostId(hostId, wait, unused)

    def hasHostId(self, hostId):
        return self._domainLock.hasHostId(hostId)

    def getHostStatus(self, hostId):
        return self._domainLock.getHostStatus(hostId)

    def hasVolumeLeases(self):
        return self._domainLock.supports_multiple_leases

    def getVolumeLease(self, imgUUID, volUUID):
        return clusterlock.Lease(None, None, None)

    def acquireVolumeLease(self, hostId, imgUUID, volUUID):
        lease = self.getVolumeLease(imgUUID, volUUID)
        self._domainLock.acquire(hostId, lease)

    def releaseVolumeLease(self, imgUUID, volUUID):
        lease = self.getVolumeLease(imgUUID, volUUID)
        self._domainLock.release(lease)

    def inquireVolumeLease(self, imgUUID, volUUID):
        lease = self.getVolumeLease(imgUUID, volUUID)
        return self._domainLock.inquire(lease)

    def getDomainLease(self):
        return clusterlock.Lease(SDM_LEASE_NAME,
                                 self.getLeasesFilePath(),
                                 SDM_LEASE_SLOT * self.alignment)

    def acquireDomainLock(self, hostID):
        self.refresh()
        self._domainLock.setParams(
            self.getMetaParam(DMDK_LOCK_RENEWAL_INTERVAL_SEC),
            self.getMetaParam(DMDK_LEASE_TIME_SEC),
            self.getMetaParam(DMDK_LEASE_RETRIES),
            self.getMetaParam(DMDK_IO_OP_TIMEOUT_SEC)
        )
        self._domainLock.acquire(hostID, self.getDomainLease())

    def releaseDomainLock(self):
        self._domainLock.release(self.getDomainLease())

    @contextmanager
    def domain_lock(self, host_id):
        self.acquireDomainLock(host_id)
        try:
            yield
        finally:
            self.releaseDomainLock()

    @contextmanager
    def domain_id(self, host_id):
        self.acquireHostId(host_id)
        try:
            yield
        finally:
            self.releaseHostId(host_id)

    def inquireDomainLock(self):
        return self._domainLock.inquire(self.getDomainLease())

    def _makeDomainLock(self, domVersion=None):
        if not domVersion:
            domVersion = self.getVersion()

        try:
            lockClass = self._domainLockTable[domVersion]
        except KeyError:
            raise se.UnsupportedDomainVersion(domVersion)


        leaseParams = (
            DEFAULT_LEASE_PARAMS[DMDK_LOCK_RENEWAL_INTERVAL_SEC],
            DEFAULT_LEASE_PARAMS[DMDK_LEASE_TIME_SEC],
            DEFAULT_LEASE_PARAMS[DMDK_LEASE_RETRIES],
            DEFAULT_LEASE_PARAMS[DMDK_IO_OP_TIMEOUT_SEC],
        )

        kwargs = {
            "alignment": self._alignment,
            "block_size": self._block_size,
        }

        return lockClass(self.sdUUID, self.getIdsFilePath(),
                         self.getDomainLease(), *leaseParams, **kwargs)

    def initDomainLock(self):
        self._domainLock.initLock(self.getDomainLease())
        self.log.debug("lease initialized successfully")

    def refreshDirTree(self):
        pass

    def refresh(self):
        pass

    @classmethod
    def validateCreateVolumeParams(cls, volFormat, srcVolUUID, diskType=None,
                                   preallocate=None):
        if volFormat not in sc.VOL_FORMAT:
            raise se.IncorrectFormat(volFormat)

        if srcVolUUID != sc.BLANK_UUID and volFormat != sc.COW_FORMAT:
            raise se.IncorrectFormat(sc.type2name(volFormat))

        if diskType is not None and diskType not in sc.VOL_DISKTYPE:
            raise se.InvalidParameterException("DiskType", diskType)

        if preallocate is not None and preallocate not in sc.VOL_TYPE:
            raise se.IncorrectType(preallocate)

    def teardownVolume(self, imgUUID, volUUID):
        """
        Called when a volume is detached from a prepared image during live
        merge flow. In this case, the volume will not be torn down when
        the image is torn down.
        This does nothing, subclass should override this if needed.
        """

    def getVolumeClass(self):
        raise NotImplementedError


    @classmethod
    def supports_external_leases(cls, version):
        return version >= 4

    @classmethod
    @contextmanager
    def external_leases_backend(cls, lockspace, path):
        backend = xlease.DirectFile(path)
        with utils.closing(backend):
            yield backend

    def external_leases_path(self):
        raise NotImplementedError

    @property
    def external_leases_lock(self):
        return self._external_leases_lock

    @contextmanager
    def external_leases_volume(self):
        path = self.external_leases_path()
        with self.external_leases_backend(self.sdUUID, path) as backend:
            vol = xlease.LeasesVolume(
                backend,
                alignment=self._alignment,
                block_size=self._block_size)
            with utils.closing(vol):
                yield vol

    def lease_info(self, lease_id):
        with self.external_leases_lock.shared:
            with self.external_leases_volume() as vol:
                return vol.lookup(lease_id)


class StorageDomain(object):
    log = logging.getLogger("storage.StorageDomain")
    mdBackupVersions = config.get('irs', 'md_backup_versions')
    mdBackupDir = config.get('irs', 'md_backup_dir')
    manifestClass = StorageDomainManifest

    supported_block_size = ()

    def __init__(self, manifest):
        self._manifest = manifest
        self._lock = threading.Lock()

    @property
    def sdUUID(self):
        return self._manifest.sdUUID

    @property
    def alignment(self):
        return self._manifest.alignment

    @property
    def block_size(self):
        return self._manifest.block_size

    @property
    def domaindir(self):
        return self._manifest.domaindir

    @property
    def _metadata(self):
        return self._manifest._metadata

    @property
    def mountpoint(self):
        return self._manifest.mountpoint

    @property
    def manifest(self):
        return self._manifest

    def replaceMetadata(self, md):
        self._manifest.replaceMetadata(md)

    def getMonitoringPath(self):
        return self._manifest.getMonitoringPath()

    def getVSize(self, imgUUID, volUUID):
        return self._manifest.getVSize(imgUUID, volUUID)

    def getVAllocSize(self, imgUUID, volUUID):
        return self._manifest.getVAllocSize(imgUUID, volUUID)

    def deleteImage(self, sdUUID, imgUUID, volsImgs):
        self._manifest.deleteImage(sdUUID, imgUUID, volsImgs)

    def purgeImage(self, sdUUID, imgUUID, volsImgs, discard):
        self._manifest.purgeImage(sdUUID, imgUUID, volsImgs, discard)

    def getAllImages(self):
        return self._manifest.getAllImages()

    def getAllVolumes(self):
        return self._manifest.getAllVolumes()

    def iter_volumes(self):
        all_volumes = self.getAllVolumes()
        for vol_id, (img_ids, _) in six.iteritems(all_volumes):
            img_id = img_ids[0]

            yield self.produceVolume(img_id, vol_id)

    def prepareMailbox(self):
        """
        This method has been introduced in order to prepare the mailbox
        on those domains where the metadata for the inbox and outbox
        wasn't allocated on creation.
        """

    @property
    def supportsMailbox(self):
        return True

    @property
    def supportsSparseness(self):
        return self._manifest.supportsSparseness

    def recommends_unordered_writes(self, format):
        return self._manifest.recommends_unordered_writes(format)

    @property
    def oop(self):
        return self._manifest.oop

    def qcow2_compat(self):
        return self._manifest.qcow2_compat()

    def _makeClusterLock(self, domVersion=None):
        return self._manifest._makeDomainLock(domVersion)

    @classmethod
    def create(cls, sdUUID, domainName, domClass, typeSpecificArg, version,
               block_size=sc.BLOCK_SIZE_512, max_hosts=sc.HOSTS_4K_1M):
        pass

    @classmethod
    def _validate_block_size(cls, block_size, version):
        if version < 5:
            if block_size != sc.BLOCK_SIZE_512:
                raise se.InvalidParameterException('block_size', block_size)
        else:
            if block_size not in cls.supported_block_size:
                raise se.InvalidParameterException('block_size', block_size)

    @classmethod
    def _validate_storage_block_size(cls, block_size, storage_block_size):
        if storage_block_size == sc.BLOCK_SIZE_NONE:
            if block_size != sc.BLOCK_SIZE_AUTO:
                return block_size
            else:
                return sc.BLOCK_SIZE_512

        if block_size == sc.BLOCK_SIZE_AUTO:
            return storage_block_size

        if block_size == storage_block_size:
            return block_size

        raise se.StorageDomainBlockSizeMismatch(block_size, storage_block_size)

    def _registerResourceNamespaces(self):
        imageResourceFactory = \
            resourceFactories.ImageResourceFactory(self.sdUUID)
        imageResourcesNamespace = rm.getNamespace(sc.IMAGE_NAMESPACE,
                                                  self.sdUUID)
        try:
            rm.registerNamespace(imageResourcesNamespace, imageResourceFactory)
        except rm.NamespaceRegistered:
            self.log.debug("Resource namespace %s already registered",
                           imageResourcesNamespace)

        volumeResourcesNamespace = rm.getNamespace(sc.VOLUME_NAMESPACE,
                                                   self.sdUUID)
        try:
            rm.registerNamespace(volumeResourcesNamespace,
                                 rm.SimpleResourceFactory())
        except rm.NamespaceRegistered:
            self.log.debug("Resource namespace %s already registered",
                           volumeResourcesNamespace)

    def produceVolume(self, imgUUID, volUUID):
        return self.getVolumeClass()(self.mountpoint, self.sdUUID, imgUUID,
                                     volUUID)

    @classmethod
    def validateCreateVolumeParams(cls, volFormat, srcVolUUID, diskType=None,
                                   preallocate=None):
        return cls.manifestClass.validateCreateVolumeParams(
            volFormat, srcVolUUID, diskType=diskType, preallocate=preallocate)

    def createVolume(self, imgUUID, capacity, volFormat, preallocate, diskType,
                     volUUID, desc, srcImgUUID, srcVolUUID,
                     initial_size=None):
        return self.getVolumeClass().create(
            self._getRepoPath(), self.sdUUID, imgUUID, capacity, volFormat,
            preallocate, diskType, volUUID, desc, srcImgUUID, srcVolUUID,
            initial_size=initial_size)

    def getMDPath(self):
        return self._manifest.getMDPath()

    def initSPMlease(self):
        return self._manifest.initDomainLock()

    def getVersion(self):
        return self._manifest.getVersion()

    def getFormat(self):
        return self._manifest.getFormat()

    def getPools(self):
        return self._manifest.getPools()

    def getIdsFilePath(self):
        return self._manifest.getIdsFilePath()

    def getLeasesFilePath(self):
        return self._manifest.getLeasesFilePath()

    def getReservedId(self):
        return self._manifest.getReservedId()

    def acquireHostId(self, hostId, wait=True):
        self._manifest.acquireHostId(hostId, wait)

    def releaseHostId(self, hostId, wait=True, unused=False):
        self._manifest.releaseHostId(hostId, wait, unused)

    def hasHostId(self, hostId):
        return self._manifest.hasHostId(hostId)

    def getHostStatus(self, hostId):
        return self._manifest.getHostStatus(hostId)

    def hasVolumeLeases(self):
        return self._manifest.hasVolumeLeases()

    def getVolumeLease(self, imgUUID, volUUID):
        return self._manifest.getVolumeLease(imgUUID, volUUID)

    def getClusterLease(self):
        return self._manifest.getDomainLease()

    def acquireClusterLock(self, hostID):
        self._manifest.acquireDomainLock(hostID)

    def releaseClusterLock(self):
        self._manifest.releaseDomainLock()

    def inquireClusterLock(self):
        return self._manifest.inquireDomainLock()

    def attach(self, spUUID):
        self.invalidateMetadata()
        pools = self.getPools()
        if spUUID in pools:
            self.log.warn("domain `%s` is already attached to pool `%s`",
                          self.sdUUID, spUUID)
            return

        if len(pools) > 0 and not self.isISO():
            raise se.StorageDomainAlreadyAttached(pools[0], self.sdUUID)

        pools.append(spUUID)
        self.setMetaParam(DMDK_POOLS, pools)

    def detach(self, spUUID):
        self.log.info('detaching storage domain %s from pool %s',
                      self.sdUUID, spUUID)
        self.invalidateMetadata()
        pools = self.getPools()
        try:
            pools.remove(spUUID)
        except ValueError:
            self.log.error(
                "Can't remove pool %s from domain %s pool list %s, "
                "it does not exist",
                spUUID, self.sdUUID, str(pools))
            return
        with self._metadata.transaction():
            self.changeRole(REGULAR_DOMAIN)
            self.setMetaParam(DMDK_POOLS, pools)

    def changeLeaseParams(self, leaseParamPack):
        self.setMetaParams(leaseParamPack)

    def getLeaseParams(self):
        keys = [DMDK_LOCK_RENEWAL_INTERVAL_SEC, DMDK_LEASE_TIME_SEC,
                DMDK_IO_OP_TIMEOUT_SEC, DMDK_LEASE_RETRIES]
        params = {}
        for key in keys:
            params[key] = self.getMetaParam(key)
        return params

    def getMasterDir(self):
        return os.path.join(self.domaindir, MASTER_FS_DIR)

    def invalidate(self):
        pass

    def validateMaster(self):
        stat = {'mount': True, 'valid': True}
        if not self.isMaster():
            return stat

        if not self.validateMasterMount():
            stat['mount'] = False
            return stat

        pdir = self.getVMsDir()
        if not self.oop.fileUtils.pathExists(pdir):
            stat['valid'] = False
            return stat
        pdir = self.getTasksDir()
        if not self.oop.fileUtils.pathExists(pdir):
            stat['valid'] = False
            return stat

        return stat

    def getVMsDir(self):
        return os.path.join(self.domaindir, MASTER_FS_DIR, VMS_DIR)

    def getTasksDir(self):
        return os.path.join(self.domaindir, MASTER_FS_DIR, TASKS_DIR)

    def getVMsList(self):
        vmsPath = self.getVMsDir()
        VM_PATTERN = os.path.join(vmsPath, sc.UUID_GLOB_PATTERN)
        vms = self.oop.glob.glob(VM_PATTERN)
        vmList = [os.path.basename(i) for i in vms]
        self.log.info("vmList=%s", str(vmList))

        return vmList

    def getVMsInfo(self, vmList=None):
        vmsInfo = {}
        vmsPath = self.getVMsDir()

        if not vmList:
            vmList = self.getVMsList()

        self.log.info("vmList=%s", str(vmList))

        for vm in vmList:
            vm_path = os.path.join(vmsPath, vm)
            if not os.path.exists(vm_path):
                continue
            ovfPath = os.path.join(vm_path, vm + '.ovf')
            if not os.path.lexists(ovfPath):
                raise se.MissingOvfFileFromVM(vm)

            ovf = codecs.open(ovfPath, encoding='utf8').read()
            vmsInfo[vm] = ovf

        return vmsInfo

    def createMasterTree(self):
        vmsDir = self.getVMsDir()
        self.log.info("Creating vms dir: %s" % vmsDir)
        self.oop.fileUtils.createdir(vmsDir)
        tasksDir = self.getTasksDir()
        self.log.info("Creating task dir: %s" % tasksDir)
        self.oop.fileUtils.createdir(tasksDir)

    def activate(self):
        if self.isBackup():
            self.mountMaster()
            self.createMasterTree()

    def _getRepoPath(self):
        return self._manifest.getRepoPath()

    def getImageDir(self, imgUUID):
        return self._manifest.getImageDir(imgUUID)

    getLinkBCImagePath = getImageDir

    def getImageRundir(self, imgUUID):
        return os.path.join(sc.P_VDSM_STORAGE, self.sdUUID, imgUUID)

    def getIsoDomainImagesDir(self):
        return self._manifest.getIsoDomainImagesDir()

    def supportsUnicode(self):
        return supportsUnicode(self.getVersion())

    def setDescription(self, descr):
        self.log.info("sdUUID=%s descr=%s", self.sdUUID, descr)
        if not misc.isAscii(descr) and not self.supportsUnicode():
            raise se.UnicodeArgumentException()

        self.setMetaParam(DMDK_DESCRIPTION, descr)

    def getInfo(self):
        info = {}
        info['uuid'] = self.sdUUID
        info['type'] = type2name(self.getMetaParam(DMDK_TYPE))
        info['class'] = class2name(self.getMetaParam(DMDK_CLASS))
        info['name'] = self.getMetaParam(DMDK_DESCRIPTION)
        info['role'] = self.getMetaParam(DMDK_ROLE)
        info['pool'] = self.getPools()
        info['version'] = str(self.getMetaParam(DMDK_VERSION))
        info['block_size'] = self.block_size
        info['alignment'] = self.alignment

        return info

    def getStats(self):
        pass

    def validateMasterMount(self):
        raise NotImplementedError

    def mountMaster(self):
        pass

    def unmountMaster(self):
        pass

    def extendVolume(self, volumeUUID, size, isShuttingDown=None):
        pass

    def reduceVolume(self, imgUUID, volumeUUID, allowActive=False):
        pass

    @staticmethod
    def findDomainPath(sdUUID):
        raise NotImplementedError

    def getMetadata(self):
        return self._manifest.getMetadata()

    def setMetadata(self, newMetadata):
        with self._metadata.transaction():
            self._metadata.clear()
            self._metadata.update(newMetadata)

    def invalidateMetadata(self):
        self._metadata.invalidate()

    def getMetaParam(self, key):
        return self._manifest.getMetaParam(key)

    def getStorageType(self):
        return self._manifest.getStorageType()

    def getDomainRole(self):
        return self._manifest.getDomainRole()

    def getDomainClass(self):
        return self._manifest.getDomainClass()

    def getRemotePath(self):
        pass

    def templateRelink(self, imgUUID, volUUID):
        self.log.debug("Skipping relink of template, domain %s is not file "
                       "based", self.sdUUID)

    def changeRole(self, newRole):
        if newRole not in [REGULAR_DOMAIN, MASTER_DOMAIN]:
            raise ValueError(newRole)

        self.setMetaParam(DMDK_ROLE, newRole)

    def setMetaParams(self, params):
        self._metadata.update(params)

    def setMetaParam(self, key, value):
        self.setMetaParams({key: value})

    def refreshDirTree(self):
        self._manifest.refreshDirTree()

    def refresh(self):
        self._manifest.refresh()

    def extend(self, devlist, force):
        pass

    def isMaster(self):
        return self.getMetaParam(DMDK_ROLE).capitalize() == MASTER_DOMAIN

    def initMaster(self, spUUID, leaseParams):
        self.invalidateMetadata()
        pools = self.getPools()

        if len(pools) > 1 or (len(pools) == 1 and pools[0] != spUUID):
            raise se.StorageDomainAlreadyAttached(pools[0], self.sdUUID)

        with self._metadata.transaction():
            self.changeLeaseParams(leaseParams)
            self.setMetaParam(DMDK_POOLS, [spUUID])
            self.changeRole(MASTER_DOMAIN)

    def isISO(self):
        return self._manifest.isISO()

    def isBackup(self):
        return self._manifest.isBackup()

    def isData(self):
        return self._manifest.isData()

    def imageGarbageCollector(self):
        pass

    def getVolumeClass(self):
        raise NotImplementedError


    @classmethod
    def supports_external_leases(cls, version):
        return cls.manifestClass.supports_external_leases(version)

    @classmethod
    def format_external_leases(
            cls, lockspace, path, alignment=sc.ALIGNMENT_1M,
            block_size=sc.BLOCK_SIZE_512):
        with cls.manifestClass.external_leases_backend(
                lockspace, path) as backend:
            xlease.format_index(
                lockspace,
                backend,
                alignment=alignment,
                block_size=block_size)

    def external_leases_path(self):
        return self._manifest.external_leases_path()

    def create_external_leases(self):
        raise NotImplementedError

    def create_lease(self, lease_id):
        with self._manifest.external_leases_lock.exclusive:
            with self._manifest.external_leases_volume() as vol:
                vol.add(lease_id)

    def delete_lease(self, lease_id):
        with self._manifest.external_leases_lock.exclusive:
            with self._manifest.external_leases_volume() as vol:
                vol.remove(lease_id)

    def rebuild_external_leases(self):
        with self._manifest.external_leases_lock.exclusive:
            path = self.external_leases_path()
            backend = xlease.DirectFile(path)
            with utils.closing(backend):
                xlease.rebuild_index(
                    self.sdUUID,
                    backend,
                    alignment=self._manifest.alignment,
                    block_size=self._manifest.block_size)


    def create_image(self, imgUUID):
        image_dir = self.getImageDir(imgUUID)
        if not os.path.isdir(image_dir):
            self.log.info("Create placeholder %s for image's volumes",
                          image_dir)
            task_name = "create image rollback: " + imgUUID
            recovery = task.Recovery(task_name, "sd", "StorageDomain",
                                     "create_image_rollback", [image_dir])
            vars.task.pushRecovery(recovery)
            os.mkdir(image_dir)
        return image_dir

    @classmethod
    def create_image_rollback(cls, task, image_dir):
        cls.log.info("create image rollback (image_dir=%s)", image_dir)
        if os.path.exists(image_dir):
            if not len(os.listdir(image_dir)):
                cls.log.info("Removing image directory %r", image_dir)
                fileUtils.cleanupdir(image_dir)
            else:
                cls.log.error("create image rollback: Cannot remove dirty "
                              "image (image_dir=%s)",
                              image_dir)


    def convert_volumes_metadata(self, target_version):
        raise NotImplementedError

    def convert_metadata(self, target_version):
        current_version = self.getVersion()

        if not (current_version == 4 and target_version == 5):
            raise RuntimeError(
                "Cannot convert domain {} from version {} to version {}"
                .format(self.sdUUID, current_version, target_version))

        self.log.info(
            "Converting domain %s metadata from version %s to version %s",
            self.sdUUID, current_version, target_version)

        with self._metadata.transaction():
            self._metadata[DMDK_VERSION] = target_version

            self._metadata[DMDK_BLOCK_SIZE] = sc.BLOCK_SIZE_512
            self._metadata[DMDK_ALIGNMENT] = sc.ALIGNMENT_1M

            if DMDK_LOGBLKSIZE in self._metadata:
                del self._metadata[DMDK_LOGBLKSIZE]
            if DMDK_PHYBLKSIZE in self._metadata:
                del self._metadata[DMDK_PHYBLKSIZE]

    def finalize_volumes_metadata(self, target_version):
        raise NotImplementedError
