
from __future__ import absolute_import

import os.path
import logging
from contextlib import contextmanager

from vdsm import utils
from vdsm.common import cmdutils
from vdsm.common import exception
from vdsm.common.marks import deprecated
from vdsm.common.threadlocal import vars

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 guarded
from vdsm.storage import misc
from vdsm.storage import qemuimg
from vdsm.storage import resourceManager as rm
from vdsm.storage import task
from vdsm.storage.sdc import sdCache
from vdsm.storage.volumemetadata import VolumeMetadata

log = logging.getLogger('storage.Volume')


def getBackingVolumePath(imgUUID, volUUID):
    return volUUID


def _next_generation(current_generation):
    return (current_generation + 1) % (sc.MAX_GENERATION + 1)


class VolumeManifest(object):
    log = logging.getLogger('storage.VolumeManifest')

    DISK_TYPE = None

    align_size = None

    def __init__(self, repoPath, sdUUID, imgUUID, volUUID):
        self.repoPath = repoPath
        self.sdUUID = sdUUID
        self.imgUUID = imgUUID
        self.volUUID = volUUID
        self._volumePath = None
        self._imagePath = None
        self.voltype = None

        if not imgUUID or imgUUID == sc.BLANK_UUID:
            raise se.InvalidParameterException("imgUUID", imgUUID)
        if not volUUID or volUUID == sc.BLANK_UUID:
            raise se.InvalidParameterException("volUUID", volUUID)
        self.validate()

    @property
    def imagePath(self):
        if self._imagePath is None:
            self.validateImagePath()
        return self._imagePath

    @property
    def volumePath(self):
        if self._volumePath is None:
            self.validateVolumePath()
        return self._volumePath

    @volumePath.setter
    def volumePath(self, value):
        self._volumePath = value

    def chunked(self):
        return False

    def validate(self):
        self.validateImagePath()
        self.validateVolumePath()

    def getMetaParam(self, key):
        meta = self.getMetadata()
        try:
            return meta[key]
        except KeyError:
            raise se.MetaDataKeyNotFoundError(str(meta) + ":" + str(key))

    def getVolumePath(self):
        if not self._volumePath:
            raise se.VolumeAccessError(self.volUUID)
        return self._volumePath

    def getVolType(self):
        if not self.voltype:
            self.voltype = self.getMetaParam(sc.VOLTYPE)
        return self.voltype

    def isLeaf(self):
        return self.getVolType() == sc.type2name(sc.LEAF_VOL)

    def isShared(self):
        return self.getVolType() == sc.type2name(sc.SHARED_VOL)

    def getDescription(self):
        return self.getMetaParam(sc.DESCRIPTION)

    def getLegality(self):
        try:
            legality = self.getMetaParam(sc.LEGALITY)
            return legality
        except se.MetaDataKeyNotFoundError:
            return sc.LEGAL_VOL

    def isLegal(self):
        try:
            legality = self.getMetaParam(sc.LEGALITY)
            return legality != sc.ILLEGAL_VOL
        except se.MetaDataKeyNotFoundError:
            return True

    def isFake(self):
        try:
            legality = self.getMetaParam(sc.LEGALITY)
            return legality == sc.FAKE_VOL
        except se.MetaDataKeyNotFoundError:
            return False

    def getCapacity(self):
        capacity = int(self.getMetaParam(sc.CAPACITY))
            raise se.MetaDataValidationError()
        return capacity

    def getFormat(self):
        return sc.name2type(self.getMetaParam(sc.FORMAT))

    def getType(self):
        return sc.name2type(self.getMetaParam(sc.TYPE))

    def getDiskType(self):
        return self.getMetaParam(sc.DISKTYPE)

    def isInternal(self):
        return self.getVolType() == sc.type2name(sc.INTERNAL_VOL)

    def isSparse(self):
        return self.getType() == sc.SPARSE_VOL

    def getLeaseStatus(self):
        sd_manifest = sdCache.produce_manifest(self.sdUUID)
        if not sd_manifest.hasVolumeLeases():
            return None

        try:
            version, host_id = sd_manifest.inquireVolumeLease(self.imgUUID,
                                                              self.volUUID)
        except clusterlock.InvalidLeaseName as e:
            self.log.warning("Cannot get lease status: %s", e)
            return None
        except clusterlock.TemporaryFailure as e:
            raise exception.expected(e)

        owners = [host_id] if host_id is not None else []
        return dict(owners=owners, version=version)

    def metadata2info(self, meta):
        return {
            "uuid": self.volUUID,
            "type": meta.get(sc.TYPE, ""),
            "format": meta.get(sc.FORMAT, ""),
            "disktype": meta.get(sc.DISKTYPE, ""),
            "voltype": meta.get(sc.VOLTYPE, ""),
            "capacity": meta.get(sc.CAPACITY, "0"),
            "parent": self.getParent(),
            "description": meta.get(sc.DESCRIPTION, ""),
            "domain": meta.get(sc.DOMAIN, ""),
            "image": self.getImage(),
            "ctime": meta.get(sc.CTIME, ""),
            "mtime": "0",
            "legality": meta.get(sc.LEGALITY, ""),
            "generation": meta.get(sc.GENERATION, sc.DEFAULT_GENERATION)
        }

    def getInfo(self):
        self.log.info("Info request: sdUUID=%s imgUUID=%s volUUID = %s ",
                      self.sdUUID, self.imgUUID, self.volUUID)
        info = {}
        try:
            meta = self.getMetadata()
            info = self.metadata2info(meta)
            vsize = self.getVolumeSize()
            avsize = self.getVolumeTrueSize()
            info['apparentsize'] = str(vsize)
            info['truesize'] = str(avsize)
            info['status'] = "OK"
            sd_manifest = sdCache.produce_manifest(self.sdUUID)
            _, path, offset = sd_manifest.getVolumeLease(self.imgUUID,
                                                         self.volUUID)
            if path is not None:
                leaseinfo = {"path": path, "offset": offset}
                leasestatus = self.getLeaseStatus()
                if leasestatus:
                    leaseinfo.update(leasestatus)
                info['lease'] = leaseinfo
        except se.StorageException as e:
            self.log.debug("exception: %s:%s" % (str(e.message), str(e.value)))
            info['apparentsize'] = "0"
            info['truesize'] = "0"
            info['status'] = "INVALID"

        info['children'] = []

        if info.get('legality', None) == sc.ILLEGAL_VOL:
            info['status'] = sc.ILLEGAL_VOL
        self.log.info("%s/%s/%s info is %s",
                      self.sdUUID, self.imgUUID, self.volUUID, str(info))
        return info

    def getQemuImageInfo(self):
        return qemuimg.info(self.getVolumePath(),
                            sc.fmt2str(self.getFormat()),
                            unsafe=True)

    def getVolumeParams(self):
        volParams = {}
        volParams['volUUID'] = self.volUUID
        volParams['imgUUID'] = self.getImage()
        volParams['path'] = self.getVolumePath()
        volParams['disktype'] = self.getDiskType()
        volParams['prealloc'] = self.getType()
        volParams['volFormat'] = self.getFormat()
        volParams['capacity'] = self.getCapacity()
        volParams['apparentsize'] = self.getVolumeSize()
        volParams['parent'] = self.getParent()
        volParams['descr'] = self.getDescription()
        volParams['legality'] = self.getLegality()
        return volParams

    def getVmVolumeInfo(self):
        return {"type": self.DISK_TYPE, "path": self.getVolumePath()}

    def setMetaParam(self, key, value):
        meta = self.getMetadata()
        try:
            meta[key] = value
            self.setMetadata(meta)
        except Exception:
            self.log.error("Volume.setMetaParam: %s: %s=%s" %
                           (self.volUUID, key, value))
            raise

    def setrw(self, rw):
        if int(sdCache.produce(self.sdUUID).getVersion()) < 3:
            self._setrw(rw=rw)

    def setLeaf(self):
        self.setMetaParam(sc.VOLTYPE, sc.type2name(sc.LEAF_VOL))
        self.voltype = sc.type2name(sc.LEAF_VOL)
        self.setrw(rw=True)
        return self.voltype

    def setInternal(self):
        self.setMetaParam(sc.VOLTYPE, sc.type2name(sc.INTERNAL_VOL))
        self.voltype = sc.type2name(sc.INTERNAL_VOL)
        self.setrw(rw=False)
        return self.voltype

    def recheckIfLeaf(self):

        if self.isShared():
            return False

        type = self.getVolType()
        childrenNum = len(self.getChildren())

        if childrenNum == 0 and type != sc.LEAF_VOL:
            self.setLeaf()
        elif childrenNum > 0 and type != sc.INTERNAL_VOL:
            self.setInternal()

        return self.isLeaf()

    def setDescription(self, descr):
        descr = VolumeMetadata.validate_description(descr)
        self.log.info("volUUID = %s descr = %s ", self.volUUID, descr)
        self.setMetaParam(sc.DESCRIPTION, descr)

    def setLegality(self, legality):
        self.log.info("sdUUID=%s imgUUID=%s volUUID = %s legality = %s ",
                      self.sdUUID, self.imgUUID, self.volUUID, legality)
        self.setMetaParam(sc.LEGALITY, legality)

    def setDomain(self, sdUUID):
        self.setMetaParam(sc.DOMAIN, sdUUID)
        self.sdUUID = sdUUID
        return self.sdUUID

    def setShared(self):
        self.setMetaParam(sc.VOLTYPE, sc.type2name(sc.SHARED_VOL))
        self.voltype = sc.type2name(sc.SHARED_VOL)
        self.setrw(rw=False)
        return self.voltype

    def update_attributes(self, generation, vol_attr):
        meta = self.getMetadata()

        if generation != meta[sc.GENERATION]:
            raise se.GenerationMismatch(generation, meta[sc.GENERATION])

        if vol_attr.type is not None:
            if meta[sc.VOLTYPE] != sc.type2name(sc.LEAF_VOL):
                raise se.InvalidVolumeUpdate(
                    self.volUUID, "%s Volume cannot be updated to %s"
                                  % (meta[sc.VOLTYPE], vol_attr.type))

            puuid = self.getParent()
            if puuid is not None and puuid != sc.BLANK_UUID:
                raise se.InvalidVolumeUpdate(
                    self.volUUID, "Volume with parent %s cannot update to %s"
                                  % (puuid, vol_attr.type))

            meta[sc.VOLTYPE] = vol_attr.type

        if vol_attr.legality is not None:
            if meta[sc.LEGALITY] == vol_attr.legality:
                raise se.InvalidVolumeUpdate(
                    self.volUUID, "%s Volume cannot be updated to %s"
                                  % (vol_attr.legality, vol_attr.legality))

            meta[sc.LEGALITY] = vol_attr.legality

        if vol_attr.description is not None:
            desc = VolumeMetadata.validate_description(vol_attr.description)
            meta[sc.DESCRIPTION] = desc

        if vol_attr.generation is not None:
            next_gen = vol_attr.generation
        else:
            next_gen = _next_generation(meta[sc.GENERATION])

        meta[sc.GENERATION] = next_gen

        self.log.info("Updating volume attributes on %s"
                      " (generation=%d, attributes=%s)",
                      self.volUUID, next_gen, vol_attr)
        self.setMetadata(meta)

        if vol_attr.type is not None:
            self.voltype = vol_attr.type

    def setCapacity(self, capacity):
        self.setMetaParam(sc.CAPACITY, capacity)

    def updateInvalidatedSize(self):
        if self.isShared():
            return

        capacity = self.getMetadata().capacity

        qemu_info = qemuimg.info(self.getVolumePath(), unsafe=True)
        virtual_size = qemu_info["virtualsize"]

        if capacity < virtual_size:
            self.log.warn(
                "Repairing wrong %s for volume %s stored=%d actual=%d",
                sc.CAPACITY, self.volUUID, capacity, virtual_size)
            self.setMetaParam(sc.CAPACITY, virtual_size)

    def setType(self, prealloc):
        self.setMetaParam(sc.TYPE, sc.type2name(prealloc))

    def setFormat(self, volFormat):
        self.setMetaParam(sc.FORMAT, sc.type2name(volFormat))

    def validateDelete(self):
        try:
            if self.isShared():
                raise se.CannotDeleteSharedVolume("img %s vol %s" %
                                                  (self.imgUUID, self.volUUID))
        except se.MetaDataKeyNotFoundError as e:
            self.log.warn("Volume %s metadata error (%s)",
                          self.volUUID, str(e))
        if self.getChildren():
            raise se.VolumeImageHasChildren(self)

    @classmethod
    def createMetadata(cls, metaId, meta):
        cls._putMetadata(metaId, meta)

    @classmethod
    def newMetadata(cls, metaId, sdUUID, imgUUID, puuid, capacity, format,
                    type, voltype, disktype, desc="", legality=sc.ILLEGAL_VOL):
        meta = VolumeMetadata(sdUUID, imgUUID, puuid, capacity, format, type,
                              voltype, disktype, desc, legality)
        cls.createMetadata(metaId, meta)
        return meta

    def refreshVolume(self):
        pass

    def _shareLease(self, dstImgPath):
        """
        Internal utility method used during the share process and by the
        domain V3 upgrade.
        """

    def getParentVolume(self):
        puuid = self.getParent()
        if puuid and puuid != sc.BLANK_UUID:
            sd_manifest = sdCache.produce(self.sdUUID).manifest
            return sd_manifest.produceVolume(self.imgUUID, puuid)
        return None

    def prepare(self, rw=True, justme=False,
                chainrw=False, setrw=False, force=False):
        self.log.info("Volume: preparing volume %s/%s",
                      self.sdUUID, self.volUUID)

        if not force:
            if not self.isLegal():
                raise se.prepareIllegalVolumeError(self.volUUID)

            if rw and self.isShared():
                if chainrw:
                else:
                    raise se.SharedVolumeNonWritable(self)

            if (not chainrw and rw and self.isInternal() and setrw and
                    not self.recheckIfLeaf()):
                raise se.InternalVolumeNonWritable(self)

        self.llPrepare(rw=rw, setrw=setrw)
        self.updateInvalidatedSize()

        try:
            if justme:
                return True
            pvol = self.getParentVolume()
            if pvol:
                pvol.prepare(rw=chainrw, justme=False,
                             chainrw=chainrw, setrw=setrw)
        except Exception:
            self.log.error("Unexpected error", exc_info=True)
            self.teardown(self.sdUUID, self.volUUID)
            raise

        return True

    @classmethod
    def teardown(cls, sdUUID, volUUID, justme=False):
        pass

    @contextmanager
    def operation(self, requested_gen=None, set_illegal=True):
        actual_gen = self.getMetaParam(sc.GENERATION)
        if requested_gen is not None and actual_gen != requested_gen:
            raise se.GenerationMismatch(requested_gen, actual_gen)
        self.log.info("Starting volume operation on %s (generation=%d, "
                      "set_illegal=%s)",
                      self.volUUID, actual_gen, set_illegal)
        if set_illegal:
            self.setLegality(sc.ILLEGAL_VOL)

        yield
        next_gen = _next_generation(actual_gen)
        metadata = self.getMetadata()
        if set_illegal:
            metadata[sc.LEGALITY] = sc.LEGAL_VOL
        metadata[sc.GENERATION] = next_gen
        self.setMetadata(metadata)
        self.log.info("Volume operation completed on %s (generation=%d)",
                      self.volUUID, next_gen)

    @classmethod
    def max_size(cls, virtual_size, format):
        if format == sc.RAW_FORMAT:
            return virtual_size

        return utils.round(virtual_size * sc.COW_OVERHEAD, cls.align_size)

    def removeMetadata(self, metaId=None):
        raise NotImplementedError

    @classmethod
    def _putMetadata(cls, metaId, meta, **overrides):
        raise NotImplementedError

    @classmethod
    def getImageVolumes(cls, sdUUID, imgUUID):
        raise NotImplementedError

    @classmethod
    def newVolumeLease(cls, metaId, sdUUID, volUUID):
        raise NotImplementedError

    @classmethod
    def leaseVolumePath(cls, vol_path):
        raise NotImplementedError

    @property
    def oop(self):
        raise NotImplementedError

    def validateImagePath(self):
        raise NotImplementedError

    def validateVolumePath(self):
        raise NotImplementedError

    def getMetadataId(self):
        raise NotImplementedError

    def getMetadata(self, metaId=None):
        raise NotImplementedError

    def getParent(self):
        raise NotImplementedError

    def getChildren(self):
        raise NotImplementedError

    def getImage(self):
        raise NotImplementedError

    def getVolumeSize(self):
        raise NotImplementedError

    def getVolumeTrueSize(self):
        raise NotImplementedError

    def setMetadata(self, meta, metaId=None, **overrides):
        raise NotImplementedError

    def _setrw(self, rw):
        raise NotImplementedError

    def llPrepare(self, rw=False, setrw=False):
        raise NotImplementedError

    def optimal_size(self):
        raise NotImplementedError

    def _share(self, dstImgPath):
        raise NotImplementedError


    def getDevPath(self):
        raise NotImplementedError

    def getVolumeTag(self, tagPrefix):
        raise NotImplementedError

    def changeVolumeTag(self, tagPrefix, uuid):
        raise NotImplementedError

    def getParentMeta(self):
        raise NotImplementedError

    def setParentMeta(self, puuid):
        raise NotImplementedError

    def getParentTag(self):
        raise NotImplementedError

    def setParentTag(self, puuid):
        raise NotImplementedError

    def getMetaSlot(self):
        raise NotImplementedError


class Volume(object):
    log = logging.getLogger('storage.Volume')
    manifestClass = VolumeManifest

    @classmethod
    def _create(cls, dom, imgUUID, volUUID, capacity, volFormat, preallocate,
                volParent, srcImgUUID, srcVolUUID, volPath, initial_size=None):
        raise NotImplementedError

    def __init__(self, repoPath, sdUUID, imgUUID, volUUID):
        self._manifest = self.manifestClass(repoPath, sdUUID, imgUUID, volUUID)

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

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

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

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

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

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

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

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

    def getMetadata(self, metaId=None):
        return self._manifest.getMetadata(metaId)

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

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

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

    def _setrw(self, rw):
        self._manifest._setrw(rw)

    def _share(self, dstImgPath):
        return self._manifest._share(dstImgPath)

    @classmethod
    def _putMetadata(cls, metaId, meta, **overrides):
        cls.manifestClass._putMetadata(metaId, meta, **overrides)

    def setMetadata(self, meta, metaId=None, **overrides):
        return self._manifest.setMetadata(meta, metaId, **overrides)

    @classmethod
    def _getModuleAndClass(cls):
        clsName = cls.__name__
        clsModule = cls.__module__.split(".").pop()
        return clsModule, clsName

    def validate(self):
        self._manifest.validateImagePath()
        self._manifest.validateVolumePath()

    def __str__(self):
        return str(self.volUUID)

    @classmethod
    def killProcRollback(cls, taskObj, pid, ctime):
        cls.log.info('ignoring killProcRollback request for pid %s and '
                     'ctime %s', pid, ctime)

    @classmethod
    def renameLeaseRollback(cls, taskObj, metaID0, leaseOffset, sdUUID,
                            volUUID):
        cls.log.info("Rolling back lease rename (metaID0=%s, "
                     "leaseOffset=%s, sdUUID=%s, volUUID=%s)",
                     metaID0, leaseOffset, sdUUID, volUUID)
        try:
            metaID = (metaID0, int(leaseOffset))
            cls.newVolumeLease(metaID, sdUUID, volUUID)
        except Exception:
            cls.log.exception("Could not rollback lease rename (metaID=%s, "
                              "sdUUID=%s, volUUID=%s)",
                              metaID, sdUUID, volUUID)

    def renameLease(self, metaID, newUUID, recovery=True):
        self.log.debug("Renaming volume lease %s to %s",
                       self.volUUID, newUUID)
        if recovery:
            clsModule, clsName = self._getModuleAndClass()
            vars.task.pushRecovery(
                task.Recovery(
                    "Rename lease rollback: " + newUUID,
                    clsModule,
                    clsName,
                    "renameLeaseRollback",
                    [metaID[0], str(metaID[1]), self.sdUUID, self.volUUID]))
        self.newVolumeLease(metaID, self.sdUUID, newUUID)

    @classmethod
    def rebaseVolumeRollback(cls, taskObj, sdUUID, srcImg,
                             srcVol, dstFormat, srcParent, unsafe):
        cls.log.info('rebase volume rollback (sdUUID=%s srcImg=%s srcVol=%s '
                     'dstFormat=%s srcParent=%s)', sdUUID, srcImg, srcVol,
                     dstFormat, srcParent)

        imageResourcesNamespace = rm.getNamespace(sc.IMAGE_NAMESPACE, sdUUID)
        with rm.acquireResource(imageResourcesNamespace, srcImg, rm.EXCLUSIVE):
            vol = sdCache.produce(sdUUID).produceVolume(srcImg, srcVol)
            vol.prepare(rw=True, chainrw=True, setrw=True)

            volumePath = vol.getVolumePath()
            backingVolPath = getBackingVolumePath(srcImg, srcParent)

            operation = qemuimg.rebase(volumePath,
                                       backingVolPath,
                                       sc.fmt2str(vol.getFormat()),
                                       sc.fmt2str(int(dstFormat)),
                                       misc.parseBool(unsafe))
            try:
                with vars.task.abort_callback(operation.abort):
                    operation.run()
                vol.setParent(srcParent)
                vol.recheckIfLeaf()
            except cmdutils.Error as e:
                cls.log.exception('Rollback rebase failed: %s', e)
                raise se.MergeVolumeRollbackError(srcVol)
            finally:
                vol.teardown(sdUUID, srcVol)

    def rebase(self, backingVol, backingVolPath, backingFormat, unsafe,
               rollback):
        if rollback:
            pvol = self.getParentVolume()
            if not pvol:
                self.log.warn("Can't rebase volume %s, parent missing",
                              self.volUUID)
                return

            name = "Merge volume: " + self.volUUID
            vars.task.pushRecovery(
                task.Recovery(name, "volume", "Volume",
                              "rebaseVolumeRollback",
                              [self.sdUUID, self.getImage(),
                                  self.volUUID, str(pvol.getFormat()),
                                  pvol.volUUID, str(True)]))

        volumePath = self.getVolumePath()

        operation = qemuimg.rebase(volumePath,
                                   backingVolPath,
                                   sc.fmt2str(self.getFormat()),
                                   sc.fmt2str(backingFormat),
                                   unsafe)
        try:
            with vars.task.abort_callback(operation.abort):
                operation.run()
        except cmdutils.Error as e:
            self.log.exception('Rebase failed: %s', e)
            raise se.MergeSnapshotsError(self.volUUID)

        self.setParent(backingVol)
        self.recheckIfLeaf()

    def clone(self, dstPath, volFormat, capacity):
        wasleaf = False
        taskName = "parent volume rollback: " + self.volUUID
        vars.task.pushRecovery(
            task.Recovery(taskName, "volume", "Volume",
                          "parentVolumeRollback",
                          [self.sdUUID, self.imgUUID, self.volUUID]))
        if self.isLeaf():
            wasleaf = True
            self.setInternal()
        try:
            self.log.debug('cloning volume %s to %s', self.volumePath,
                           dstPath)
            parent = getBackingVolumePath(self.imgUUID, self.volUUID)
            domain = sdCache.produce(self.sdUUID)
            operation = qemuimg.create(
                dstPath,
                size=capacity,
                backing=parent,
                format=sc.fmt2str(volFormat),
                qcow2Compat=domain.qcow2_compat(),
                backingFormat=sc.fmt2str(self.getFormat()),
                unsafe=True)
            operation.run()
        except Exception as e:
            self.log.exception('cannot clone image %s volume %s to %s',
                               self.imgUUID, self.volUUID, dstPath)
            if wasleaf:
                self.setLeaf()
            raise se.CannotCloneVolume(self.volumePath, dstPath, str(e))

    def _shareLease(self, dstImgPath):
        self._manifest._shareLease(dstImgPath)

    def share(self, dstImgPath):
        self.log.debug("Share volume %s to %s", self.volUUID, dstImgPath)

        if not self.isShared():
            raise se.VolumeNonShareable(self)

        if os.path.basename(dstImgPath) == os.path.basename(self.imagePath):
            raise se.VolumeOwnershipError(self)

        dstPath = os.path.join(dstImgPath, self.volUUID)
        clsModule, clsName = self._getModuleAndClass()

        try:
            vars.task.pushRecovery(
                task.Recovery("Share volume rollback: %s" % dstPath, clsModule,
                              clsName, "shareVolumeRollback", [dstPath])
            )

            self._share(dstImgPath)

        except Exception as e:
            raise se.CannotShareVolume(self.getVolumePath(), dstPath, str(e))

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

    @classmethod
    def parentVolumeRollback(cls, taskObj, sdUUID, pimgUUID, pvolUUID):
        cls.log.info("parentVolumeRollback: sdUUID=%s pimgUUID=%s"
                     " pvolUUID=%s" % (sdUUID, pimgUUID, pvolUUID))
        if pvolUUID != sc.BLANK_UUID and pimgUUID != sc.BLANK_UUID:
            pvol = sdCache.produce(sdUUID).produceVolume(pimgUUID,
                                                         pvolUUID)
            pvol.prepare()
            try:
                pvol.recheckIfLeaf()
            except Exception:
                cls.log.error("Unexpected error", exc_info=True)
            finally:
                pvol.teardown(sdUUID, pvolUUID)

    @classmethod
    def startCreateVolumeRollback(cls, taskObj, sdUUID, imgUUID, volUUID):
        cls.log.info("startCreateVolumeRollback: sdUUID=%s imgUUID=%s "
                     "volUUID=%s " % (sdUUID, imgUUID, volUUID))
        pass

    @classmethod
    def createVolumeRollback(cls, taskObj, repoPath,
                             sdUUID, imgUUID, volUUID, imageDir):
        cls.log.info("createVolumeRollback: repoPath=%s sdUUID=%s imgUUID=%s "
                     "volUUID=%s imageDir=%s" %
                     (repoPath, sdUUID, imgUUID, volUUID, imageDir))
        vol = sdCache.produce(sdUUID).produceVolume(imgUUID, volUUID)
        pvol = vol.getParentVolume()
        vol.delete(postZero=False, force=True, discard=False)
        if len(cls.getImageVolumes(sdUUID, imgUUID)):
            return

        if not pvol or pvol.isShared():
            if os.path.exists(imageDir):
                cls.log.info("Removing image directory %r", imageDir)
                fileUtils.cleanupdir(imageDir)

    @classmethod
    def create(cls, repoPath, sdUUID, imgUUID, capacity, volFormat,
               preallocate, diskType, volUUID, desc, srcImgUUID, srcVolUUID,
               initial_size=None):
        if initial_size is not None:
            if initial_size < 0:
                cls.log.error("initial_size %d is negative", initial_size)
                raise se.InvalidParameterException(
                    "initial size", initial_size)

        capacity = utils.round(capacity, sc.BLOCK_SIZE_4K)
        if initial_size is not None:
            initial_size = utils.round(initial_size, sc.BLOCK_SIZE_4K)

        dom = sdCache.produce(sdUUID)
        dom.validateCreateVolumeParams(
            volFormat, srcVolUUID, diskType=diskType, preallocate=preallocate)

        imgPath = dom.create_image(imgUUID)

        volPath = os.path.join(imgPath, volUUID)
        volParent = None
        volType = sc.type2name(sc.LEAF_VOL)

        clsModule, clsName = cls._getModuleAndClass()

        try:
            if srcVolUUID != sc.BLANK_UUID:
                if srcImgUUID == sc.BLANK_UUID:
                    srcImgUUID = imgUUID

                volParent = cls(repoPath, sdUUID, srcImgUUID, srcVolUUID)

                if not volParent.isLegal():
                    raise se.createIllegalVolumeSnapshotError(
                        volParent.volUUID)

                if imgUUID != srcImgUUID:
                    volParent.share(imgPath)
                    volParent = cls(repoPath, sdUUID, imgUUID, srcVolUUID)

        except se.StorageException:
            cls.log.error("Unexpected error", exc_info=True)
            raise
        except Exception as e:
            cls.log.error("Unexpected error", exc_info=True)
            raise se.VolumeCannotGetParent(
                "Couldn't get parent %s for volume %s: %s" %
                (srcVolUUID, volUUID, e))

        if volParent:
            if capacity < volParent.getCapacity():
                cls.log.error(
                    "Requested capacity %d < parent capacity %d",
                    capacity, volParent.getCapacity())
                raise se.InvalidParameterException("capacity", capacity)

        try:
            cls.log.info("Creating volume %s", volUUID)

            vars.task.pushRecovery(
                task.Recovery(task.ROLLBACK_SENTINEL, clsModule, clsName,
                              "startCreateVolumeRollback",
                              [sdUUID, imgUUID, volUUID])
            )

            vars.task.pushRecovery(
                task.Recovery("Halfbaked volume rollback", clsModule, clsName,
                              "halfbakedVolumeRollback",
                              [sdUUID, volUUID, volPath])
            )

            try:
                metaId = cls._create(dom, imgUUID, volUUID, capacity,
                                     volFormat, preallocate, volParent,
                                     srcImgUUID, srcVolUUID, volPath,
                                     initial_size=initial_size)
            except (se.VolumeAlreadyExists, se.CannotCreateLogicalVolume,
                    se.VolumeCreationError, se.InvalidParameterException) as e:
                cls.log.error("Failed to create volume %s: %s", volPath, e)
                vars.task.popRecovery()
                raise
            if volFormat == sc.RAW_FORMAT:
                apparent_size = dom.getVSize(imgUUID, volUUID)
                if apparent_size < capacity:
                    cls.log.error("The volume %s apparent size %s is "
                                  "smaller than the requested capacity %s",
                                  volUUID, apparent_size, capacity)
                    raise se.VolumeCreationError()
                if apparent_size > capacity:
                    cls.log.info("The requested size for volume %s doesn't "
                                 "match the granularity on domain %s, updating"
                                 " the volume capacity from %s to %s",
                                 volUUID, sdUUID, capacity, apparent_size)
                    capacity = apparent_size

            vars.task.pushRecovery(
                task.Recovery("Create volume metadata rollback", clsModule,
                              clsName, "createVolumeMetadataRollback",
                              map(str, metaId))
            )

            cls.newMetadata(metaId, sdUUID, imgUUID, srcVolUUID, capacity,
                            sc.type2name(volFormat), sc.type2name(preallocate),
                            volType, diskType, desc, sc.LEGAL_VOL)

            if dom.hasVolumeLeases():
                cls.newVolumeLease(metaId, sdUUID, volUUID)

        except se.StorageException:
            cls.log.error("Unexpected error", exc_info=True)
            raise
        except Exception as e:
            cls.log.error("Unexpected error", exc_info=True)
            raise se.VolumeCreationError("Volume creation %s failed: %s" %
                                         (volUUID, e))

        vars.task.replaceRecoveries(
            task.Recovery("Create volume rollback", clsModule, clsName,
                          "createVolumeRollback",
                          [repoPath, sdUUID, imgUUID, volUUID, imgPath])
        )

        return volUUID

    def validateDelete(self):
        self._manifest.validateDelete()

    def extend(self, new_size):
        pass

    def reduce(self, new_size, allowActive=False):
        pass

    def syncMetadata(self):
        volFormat = self.getFormat()
        if volFormat != sc.RAW_FORMAT:
            self.log.error("impossible to update metadata for volume %s "
                           "its format is not RAW", self.volUUID)
            return

        new_vol_capacity = self.getVolumeSize()
        old_vol_capacity = self.getCapacity()

        if old_vol_capacity == new_vol_capacity:
            self.log.debug("capacity metadata %s is up to date for volume %s",
                           old_vol_capacity, self.volUUID)
        else:
            self.log.debug("updating metadata for volume %s changing the "
                           "capacity %s to %s", self.volUUID, old_vol_capacity,
                           new_vol_capacity)
            self.setCapacity(new_vol_capacity)

    @classmethod
    def extendSizeFinalize(cls, taskObj, sdUUID, imgUUID, volUUID):
        cls.log.debug("finalizing size extension for volume %s on domain "
                      "%s", volUUID, sdUUID)
        sdCache.produce(sdUUID) \
               .produceVolume(imgUUID, volUUID).syncMetadata()

    def extendSize(self, new_capacity):
        if self.isShared():
            raise se.VolumeNonWritable(self.volUUID)

        volFormat = self.getFormat()
        if volFormat == sc.COW_FORMAT:
            self.log.debug("skipping cow size extension for volume %s to "
                           "capacity %s", self.volUUID, new_capacity)
            return
        elif volFormat != sc.RAW_FORMAT:
            raise se.IncorrectFormat(self.volUUID)

        isBase = self.getParent() == sc.BLANK_UUID
        if not (isBase or self.isLeaf()):
            raise se.VolumeNonWritable(self.volUUID)

        cur_raw_capacity = self.getVolumeSize()

        if new_capacity < cur_raw_capacity:
            self.log.error("current capacity of volume %s is larger than the "
                           "capacity requested in the extension (%s > %s)",
                           self.volUUID, cur_raw_capacity, new_capacity)
            raise se.VolumeResizeValueError(new_capacity)

        if new_capacity == cur_raw_capacity:
            self.log.debug("the requested capacity %s is equal to the current "
                           "capacity %s, skipping extension", new_capacity,
                           cur_raw_capacity)
        else:
            self.log.info("executing a raw capacity extension for volume %s "
                          "from capacity %s to capacity %s", self.volUUID,
                          cur_raw_capacity, new_capacity)
            vars.task.pushRecovery(task.Recovery(
                "Extend size for volume: " + self.volUUID, "volume",
                "Volume", "extendSizeFinalize",
                [self.sdUUID, self.imgUUID, self.volUUID]))
            self._extendSizeRaw(new_capacity)


    def setDescription(self, descr):
        self._manifest.setDescription(descr)

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

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

    def setLegality(self, legality):
        self._manifest.setLegality(legality)

    def setDomain(self, sdUUID):
        return self._manifest.setDomain(sdUUID)

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

    def setrw(self, rw):
        self._manifest.setrw(rw)

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

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

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

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

    @classmethod
    def max_size(cls, virtual_size, format):
        return cls.manifestClass.max_size(virtual_size, format)

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

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

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

    def setCapacity(self, capacity):
        self._manifest.setCapacity(capacity)

    def updateInvalidatedSize(self):
        self._manifest.updateInvalidatedSize()

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

    def setType(self, prealloc):
        self._manifest.setType(prealloc)

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

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

    def setFormat(self, volFormat):
        self._manifest.setFormat(volFormat)

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

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

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

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

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

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

    def recheckIfLeaf(self):
        """
        Recheck if I am a leaf.
        """
        return self._manifest.recheckIfLeaf()

    @contextmanager
    def scopedPrepare(self, rw=True, justme=False, chainrw=False, setrw=False,
                      force=False):
        self.prepare(rw=True, justme=False, chainrw=False, setrw=False,
                     force=False)
        try:
            yield self
        finally:
            self.teardown(self.sdUUID, self.volUUID, justme)

    def prepare(self, rw=True, justme=False,
                chainrw=False, setrw=False, force=False):
        return self._manifest.prepare(rw, justme, chainrw, setrw, force)

    @classmethod
    def teardown(cls, sdUUID, volUUID, justme=False):
        return cls.manifestClass.teardown(sdUUID, volUUID, justme)

    def metadata2info(self, meta):
        return self._manifest.metadata2info(meta)

    @classmethod
    def newMetadata(cls, metaId, sdUUID, imgUUID, puuid, capacity, format,
                    type, voltype, disktype, desc="", legality=sc.ILLEGAL_VOL):
        return cls.manifestClass.newMetadata(
            metaId, sdUUID, imgUUID, puuid, capacity, format, type, voltype,
            disktype, desc, legality)

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

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

    def getParentVolume(self):
        puuid = self.getParent()
        if puuid and puuid != sc.BLANK_UUID:
            return sdCache.produce(self.sdUUID).produceVolume(self.imgUUID,
                                                              puuid)
        return None

    def setParent(self, puuid):
        self.setParentTag(puuid)
        self.setParentMeta(puuid)

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

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

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

    def setMetaParam(self, key, value):
        self._manifest.setMetaParam(key, value)

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

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

    @classmethod
    def createMetadata(cls, metaId, meta):
        return cls.manifestClass.createMetadata(metaId, meta)

    @classmethod
    def newVolumeLease(cls, metaId, sdUUID, volUUID):
        return cls.manifestClass.newVolumeLease(metaId, sdUUID, volUUID)

    @classmethod
    def getImageVolumes(cls, sdUUID, imgUUID):
        return cls.manifestClass.getImageVolumes(sdUUID, imgUUID)

    def _extendSizeRaw(self, newSize):
        raise NotImplementedError


    def setParentMeta(self, puuid):
        raise NotImplementedError

    def setParentTag(self, puuid):
        raise NotImplementedError


class VolumeLease(guarded.AbstractLock):

    def __init__(self, host_id, sd_id, img_id, vol_id):
        self._host_id = host_id
        self._sd_id = sd_id
        self._img_id = img_id
        self._vol_id = vol_id

    @property
    def ns(self):
        return rm.getNamespace(sc.VOLUME_LEASE_NAMESPACE, self._sd_id)

    @property
    def name(self):
        return self._vol_id

    @property
    def mode(self):

    def acquire(self):
        dom = sdCache.produce_manifest(self._sd_id)
        dom.acquireVolumeLease(self._host_id, self._img_id, self._vol_id)

    def release(self):
        dom = sdCache.produce_manifest(self._sd_id)
        dom.releaseVolumeLease(self._img_id, self._vol_id)
