diff --git a/Makefile b/Makefile index cbbf5012..28b8e1c0 100755 --- a/Makefile +++ b/Makefile @@ -34,6 +34,7 @@ SM_LIBS += util SM_LIBS += verifyVHDsOnSR SM_LIBS += scsiutil SM_LIBS += scsi_host_rescan +SM_LIBS += cowutil SM_LIBS += vhdutil SM_LIBS += linstorjournaler SM_LIBS += linstorvhdutil diff --git a/drivers/CephFSSR.py b/drivers/CephFSSR.py index 574190d6..8e7c173d 100644 --- a/drivers/CephFSSR.py +++ b/drivers/CephFSSR.py @@ -39,7 +39,6 @@ import cleanup import lock import util -import vhdutil import xs_errors CAPABILITIES = ["SR_PROBE", "SR_UPDATE", diff --git a/drivers/FileSR.py b/drivers/FileSR.py index 05371ee0..2a1ca7e9 100755 --- a/drivers/FileSR.py +++ b/drivers/FileSR.py @@ -24,7 +24,6 @@ import SRCommand import util import scsiutil -import vhdutil import lock import os import errno @@ -109,7 +108,7 @@ def load(self, sr_uuid) -> None: @override def create(self, sr_uuid, size) -> None: - """ Create the SR. The path must not already exist, or if it does, + """ Create the SR. The path must not already exist, or if it does, it must be empty. (This accounts for the case where the user has mounted a device onto a directory manually and want to use this as the root of a file-based SR.) """ @@ -279,7 +278,7 @@ def _loadvdis(self): pattern = os.path.join(self.path, "*%s" % VdiTypeExtension.VHD) try: - self.vhds = vhdutil.getAllVHDs(pattern, FileVDI.extractUuid) + self.vhds = CowUtil.getAllInfoFromVG(pattern, FileVDI.extractUuid) except util.CommandException as inst: raise xs_errors.XenError('SRScan', opterr="error VHD-scanning " \ "path %s (%s)" % (self.path, inst)) @@ -296,7 +295,7 @@ def _loadvdis(self): self.vdis[uuid] = self.vdi(uuid) # Get the key hash of any encrypted VDIs: vhd_path = os.path.join(self.path, self.vhds[uuid].path) - key_hash = vhdutil.getKeyHash(vhd_path) + key_hash = cowutil.getKeyHash(vhd_path) self.vdis[uuid].sm_config_override['key_hash'] = key_hash # raw VDIs and CBT log files @@ -418,18 +417,18 @@ def _check_hardlinks(self) -> bool: return True class FileVDI(VDI.VDI): - PARAM_VHD = "vhd" PARAM_RAW = "raw" + PARAM_VHD = "vhd" VDI_TYPE = { - PARAM_VHD: VdiType.VHD, - PARAM_RAW: VdiType.RAW + PARAM_RAW: VdiType.RAW, + PARAM_VHD: VdiType.VHD } def _find_path_with_retries(self, vdi_uuid, maxretry=5, period=2.0): - vhd_path = os.path.join(self.sr.path, "%s.%s" % \ - (vdi_uuid, self.PARAM_VHD)) raw_path = os.path.join(self.sr.path, "%s.%s" % \ (vdi_uuid, self.PARAM_RAW)) + vhd_path = os.path.join(self.sr.path, "%s.%s" % \ + (vdi_uuid, self.PARAM_VHD)) cbt_path = os.path.join(self.sr.path, "%s.%s" % (vdi_uuid, CBTLOG_TAG)) found = False @@ -581,7 +580,7 @@ def create(self, sr_uuid, vdi_uuid, size) -> str: if VdiType.isCowImage(self.vdi_type): try: - size = vhdutil.validate_and_round_vhd_size(int(size)) + size = self._cowutil.validateAndRoundImageSize(int(size)) mb = 1024 * 1024 size_mb = size // mb util.ioretry(lambda: self._create(str(size_mb), self.path)) @@ -679,19 +678,19 @@ def resize(self, sr_uuid, vdi_uuid, size) -> str: if size == self.size: return VDI.VDI.get_params(self) - # We already checked it is a VHD - size = vhdutil.validate_and_round_vhd_size(int(size)) - + # We already checked it is a cow image. + size = self._cowutil.validateAndRoundImageSize(int(size)) + jFile = JOURNAL_FILE_PREFIX + self.uuid try: - vhdutil.setSizeVirt(self.path, size, jFile) + self._cowutil.setSizeVirt(self.path, size, jFile) except: # Revert the operation - vhdutil.revert(self.path, jFile) + self._cowutil.revert(self.path, jFile) raise xs_errors.XenError('VDISize', opterr='resize operation failed') old_size = self.size - self.size = vhdutil.getSizeVirt(self.path) + self.size = self._cowutil.getSizeVirt(self.path) st = util.ioretry(lambda: os.stat(self.path)) self.utilisation = int(st.st_size) @@ -711,11 +710,9 @@ def compose(self, sr_uuid, vdi1, vdi2) -> None: parent_fn = vdi1 + VDI_TYPE_TO_EXTENSION[VdiType.VHD] parent_path = os.path.join(self.sr.path, parent_fn) assert(util.pathexists(parent_path)) - vhdutil.setParent(self.path, parent_path, False) - vhdutil.setHidden(parent_path) + self._cowutil.setParent(self.path, parent_path, False) + self._cowutil.setHidden(parent_path) self.sr.session.xenapi.VDI.set_managed(self.sr.srcmd.params['args'][0], False) - util.pread2([vhdutil.VHD_UTIL, "modify", "-p", parent_path, - "-n", self.path]) # Tell tapdisk the chain has changed if not blktap2.VDI.tap_refresh(self.session, sr_uuid, vdi2): raise util.SMException("failed to refresh VDI %s" % self.uuid) @@ -726,11 +723,11 @@ def reset_leaf(self, sr_uuid, vdi_uuid): raise xs_errors.XenError('Unimplemented') # safety check - if not vhdutil.hasParent(self.path): + if not self._cowutil.hasParent(self.path): raise util.SMException("ERROR: VDI %s has no parent, " + \ "will not reset contents" % self.uuid) - vhdutil.killData(self.path) + self._cowutil.killData(self.path) @override def _do_snapshot(self, sr_uuid, vdi_uuid, snapType, @@ -796,11 +793,11 @@ def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None): if self.hidden: raise xs_errors.XenError('VDIClone', opterr='hidden VDI') - depth = vhdutil.getDepth(self.path) + depth = self._cowutil.getDepth(self.path) if depth == -1: raise xs_errors.XenError('VDIUnavailable', \ - opterr='failed to get VHD depth') - elif depth >= vhdutil.MAX_CHAIN_SIZE: + opterr='failed to get image depth') + elif depth >= self._cowutil.getMaxChainLength(): raise xs_errors.XenError('SnapshotChainTooLong') newuuid = util.gen_uuid() @@ -1004,14 +1001,14 @@ def _create(self, size, path): cmd = [SR.TAPDISK_UTIL, "create", VdiType.VHD, size, path] text = util.pread(cmd) if self.key_hash: - vhdutil.setKey(path, self.key_hash) + self._cowutil.setKey(path, self.key_hash) def _mark_hidden(self, path): - vhdutil.setHidden(path, True) + self._cowutil.setHidden(path, True) self.hidden = 1 def _is_hidden(self, path): - return vhdutil.getHidden(path) == 1 + return self._cowutil.getHidden(path) == 1 def extractUuid(path): fileName = os.path.basename(path) diff --git a/drivers/LVHDSR.py b/drivers/LVHDSR.py index f866bfb2..beae0f1a 100755 --- a/drivers/LVHDSR.py +++ b/drivers/LVHDSR.py @@ -732,17 +732,16 @@ def scan(self, uuid) -> None: util.roundup(lvutil.LVM_SIZE_INCREMENT, int(size)) else: - parent = \ - vhdutil._getVHDParentNoCheck(lvPath) + parent = cowutil.getParentNoCheck(lvPath) if parent is not None: sm_config['vhd-parent'] = parent[len( \ lvhdutil.LV_PREFIX[VdiType.VHD]):] - size = vhdutil.getSizeVirt(lvPath) + size = cowutil.getSizeVirt(lvPath) if self.provision == "thin": utilisation = \ util.roundup(lvutil.LVM_SIZE_INCREMENT, - vhdutil.calcOverheadEmpty(lvhdutil.MSIZE)) + cowutil.calcOverheadEmpty(lvhdutil.MSIZE)) else: utilisation = lvhdutil.calcSizeVHDLV(int(size)) @@ -962,7 +961,7 @@ def _handleInterruptedCloneOp(self, origUuid, jval, forceUndo=False): parent = vdis[orig.parentUuid] self.lvActivator.activate(parent.uuid, parent.lvName, False) origPath = os.path.join(self.path, orig.lvName) - if not vhdutil.check(origPath): + if cowutil.check(origPath) != cowutil.CheckResult.Success: util.SMlog("Orig VHD invalid => revert") self._undoCloneOp(lvs, origUuid, baseUuid, clonUuid) return @@ -971,7 +970,7 @@ def _handleInterruptedCloneOp(self, origUuid, jval, forceUndo=False): clon = vdis[clonUuid] clonPath = os.path.join(self.path, clon.lvName) self.lvActivator.activate(clonUuid, clon.lvName, False) - if not vhdutil.check(clonPath): + if cowutil.check(clonPath) != cowutil.CheckResult.Success: util.SMlog("Clon VHD invalid => revert") self._undoCloneOp(lvs, origUuid, baseUuid, clonUuid) return @@ -995,10 +994,10 @@ def _undoCloneOp(self, lvs, origUuid, baseUuid, clonUuid): if VdiType.isCowImage(base.vdiType): self.lvActivator.activate(baseUuid, base.name, False) origRefcountNormal = 1 - vhdInfo = vhdutil.getVHDInfo(basePath, lvhdutil.extractUuid, False) - if vhdInfo.hidden: - vhdutil.setHidden(basePath, False) - elif base.vdiType == VdiType.RAW and base.hidden: + cow_info = cowutil.getInfo(basePath, lvhdutil.extractUuid, False) + if cow_info.hidden: + cowutil.setHidden(basePath, False) + elif base.hidden: self.lvmCache.setHidden(base.name, False) # remove the child nodes @@ -1054,7 +1053,7 @@ def _completeCloneOp(self, vdis, origUuid, baseUuid, clonUuid): self.lvmCache.setHidden(base.lvName) else: basePath = os.path.join(self.path, base.lvName) - vhdutil.setHidden(basePath) + cowutil.setHidden(basePath) if not base.lvReadonly: self.lvmCache.setReadonly(base.lvName, True) @@ -1206,13 +1205,13 @@ def _undoAllVHDJournals(self): lvhdutil.inflate(self.journaler, self.uuid, vdi.uuid, fullSize) try: jFile = os.path.join(self.path, jlvName) - vhdutil.revert(vdi.path, jFile) + cowutil.revert(vdi.path, jFile) except util.CommandException: util.logException("VHD journal revert") vhdutil.check(vdi.path) util.SMlog("VHD revert failed but VHD ok: removing journal") # Attempt to reclaim unused space - vhdInfo = vhdutil.getVHDInfo(vdi.path, lvhdutil.extractUuid, False) + vhdInfo = cowutil.getInfo(vdi.path, lvhdutil.extractUuid, False) NewSize = lvhdutil.calcSizeVHDLV(vhdInfo.sizeVirt) if NewSize < fullSize: lvhdutil.deflate(self.lvmCache, vdi.lvname, int(NewSize)) @@ -1374,7 +1373,8 @@ def create(self, sr_uuid, vdi_uuid, size) -> str: if self.exists: raise xs_errors.XenError('VDIExists') - size = vhdutil.validate_and_round_vhd_size(int(size)) + + size = self._cowutil.validateAndRoundImageSize(int(size)) util.SMlog("LVHDVDI.create: type = %s, %s (size=%s)" % \ (self.vdi_type, self.path, size)) @@ -1396,8 +1396,8 @@ def create(self, sr_uuid, vdi_uuid, size) -> str: if self.vdi_type == VdiType.RAW: self.size = self.sr.lvmCache.getSize(self.lvname) else: - vhdutil.create(self.path, int(size), False, lvhdutil.MSIZE_MB) - self.size = vhdutil.getSizeVirt(self.path) + self._cowutil.create(self.path, int(size), False, lvhdutil.MSIZE_MB) + self.size = self._cowutil.getSizeVirt(self.path) self.sr.lvmCache.deactivateNoRefcount(self.lvname) except util.CommandException as e: util.SMlog("Unable to create VDI") @@ -1555,7 +1555,7 @@ def resize(self, sr_uuid, vdi_uuid, size) -> str: '(current size: %d, new size: %d)' % (self.size, size)) raise xs_errors.XenError('VDISize', opterr='shrinking not allowed') - size = vhdutil.validate_and_round_vhd_size(int(size)) + size = self._cowutil.validateAndRoundImageSize(int(size)) if size == self.size: return VDI.VDI.get_params(self) @@ -1582,8 +1582,8 @@ def resize(self, sr_uuid, vdi_uuid, size) -> str: if lvSizeNew != lvSizeOld: lvhdutil.inflate(self.sr.journaler, self.sr.uuid, self.uuid, lvSizeNew) - vhdutil.setSizeVirtFast(self.path, size) - self.size = vhdutil.getSizeVirt(self.path) + self._cowutil.setSizeVirtFast(self.path, size) + self.size = self._cowutil.getSizeVirt(self.path) self.utilisation = self.sr.lvmCache.getSize(self.lvname) vdi_ref = self.sr.srcmd.params['vdi_ref'] @@ -1613,8 +1613,8 @@ def compose(self, sr_uuid, vdi1, vdi2) -> None: self.sr.lvActivator.activate(self.uuid, self.lvname, False) self.sr.lvActivator.activate(parent_uuid, parent_lvname, False) - vhdutil.setParent(self.path, parent_path, False) - vhdutil.setHidden(parent_path) + self._cowutil.setParent(self.path, parent_path, False) + self._cowutil.setHidden(parent_path) self.sr.session.xenapi.VDI.set_managed(self.sr.srcmd.params['args'][0], False) if not blktap2.VDI.tap_refresh(self.session, self.sr.uuid, self.uuid, @@ -1631,11 +1631,11 @@ def reset_leaf(self, sr_uuid, vdi_uuid): self.sr.lvActivator.activate(self.uuid, self.lvname, False) # safety check - if not vhdutil.hasParent(self.path): + if not self._cowutil.hasParent(self.path): raise util.SMException("ERROR: VDI %s has no parent, " + \ "will not reset contents" % self.uuid) - vhdutil.killData(self.path) + self._cowutil.killData(self.path) def _attach(self): self._chainSetActive(True, True, True) @@ -1723,11 +1723,11 @@ def _snapshot(self, snapType, cloneOp=False, cbtlog=None, cbt_consistency=None): opterr='VDI unavailable: %s' % (self.path)) if VdiType.isCowImage(self.vdi_type): - depth = vhdutil.getDepth(self.path) + depth = self._cowutil.getDepth(self.path) if depth == -1: raise xs_errors.XenError('VDIUnavailable', \ opterr='failed to get VHD depth') - elif depth >= vhdutil.MAX_CHAIN_SIZE: + elif depth >= self._cowutil.getMaxChainLength(): raise xs_errors.XenError('SnapshotChainTooLong') self.issnap = self.session.xenapi.VDI.get_is_a_snapshot( \ @@ -1816,7 +1816,7 @@ def _snapshot(self, snapType, cloneOp=False, cbtlog=None, cbt_consistency=None): if self.vdi_type == VdiType.RAW: self.sr.lvmCache.setHidden(self.lvname) else: - vhdutil.setHidden(self.path) + self._cowutil.setHidden(self.path) util.fistpoint.activate("LVHDRT_clone_vdi_after_parent_hidden", self.sr.uuid) # set the base copy to ReadOnly @@ -1860,8 +1860,8 @@ def _createSnap(self, snapUuid, snapSizeLV, isNew): RefCounter.set(snapUuid, 1, 0, lvhdutil.NS_PREFIX_LVM + self.sr.uuid) self.sr.lvActivator.add(snapUuid, snapLV, False) parentRaw = (self.vdi_type == VdiType.RAW) - vhdutil.snapshot(snapPath, self.path, parentRaw, lvhdutil.MSIZE_MB) - snapParent = vhdutil.getParent(snapPath, lvhdutil.extractUuid) + self._cowutil.snapshot(snapPath, self.path, parentRaw, lvhdutil.MSIZE_MB) + snapParent = self._cowutil.getParent(snapPath, lvhdutil.extractUuid) snapVDI = LVHDVDI(self.sr, snapUuid) snapVDI.read_only = False @@ -2068,15 +2068,15 @@ def _determineType(self): # LVM commands can be costly, so check the file directly first in case # the LV is active found = False - for t in lvhdutil.VDI_TYPES: - lvname = "%s%s" % (lvhdutil.LV_PREFIX[t], self.uuid) + for vdiType, prefix in lvhdutil.LV_PREFIX: + lvname = "%s%s" % (prefix, self.uuid) path = os.path.join(self.sr.path, lvname) if util.pathexists(path): if found: raise xs_errors.XenError('VDILoad', opterr="multiple VDI's: uuid %s" % self.uuid) found = True - self.vdi_type = t + self.vdi_type = vdiType self.lvname = lvname self.path = path if found: @@ -2110,7 +2110,7 @@ def _loadThis(self): self._initFromLVInfo(lvs[self.uuid]) if VdiType.isCowImage(self.vdi_type): self.sr.lvActivator.activate(self.uuid, self.lvname, False) - vhdInfo = vhdutil.getVHDInfo(self.path, lvhdutil.extractUuid, False) + vhdInfo = self._cowutil.getInfo(self.path, lvhdutil.extractUuid, False) if not vhdInfo: raise xs_errors.XenError('VDIUnavailable', \ opterr='getVHDInfo failed') @@ -2154,7 +2154,7 @@ def _markHidden(self): if self.vdi_type == VdiType.RAW: self.sr.lvmCache.setHidden(self.lvname) else: - vhdutil.setHidden(self.path) + self._cowutil.setHidden(self.path) self.hidden = 1 def _prepareThin(self, attach): diff --git a/drivers/LinstorSR.py b/drivers/LinstorSR.py index 07639d8e..3834cdcc 100755 --- a/drivers/LinstorSR.py +++ b/drivers/LinstorSR.py @@ -1292,7 +1292,7 @@ def _get_vdi_path_and_parent(self, vdi_uuid, volume_name): return (device_path, None) # Otherwise it's a VHD and a parent can exist. - if not self._vhdutil.check(vdi_uuid): + if self._cowutil.check(vdi_uuid) != cowutil.CheckResult.Success: return (None, None) vhd_info = self._vhdutil.get_vhd_info(vdi_uuid) @@ -1668,7 +1668,7 @@ def create(self, sr_uuid, vdi_uuid, size) -> str: assert self.vdi_type # 2. Compute size and check space available. - size = vhdutil.validate_and_round_vhd_size(int(size)) + size = self._cowutil.validateAndRoundImageSize(int(size)) volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type) util.SMlog( 'LinstorVDI.create: type={}, vhd-size={}, volume-size={}' @@ -1702,13 +1702,13 @@ def create(self, sr_uuid, vdi_uuid, size) -> str: if self.vdi_type == VdiType.RAW: self.size = volume_info.virtual_size else: - self.sr._vhdutil.create( + self._cowutil.create( self.path, size, False, self.MAX_METADATA_VIRT_SIZE ) - self.size = self.sr._vhdutil.get_size_virt(self.uuid) + self.size = self._cowutil.get_size_virt(self.uuid) if self._key_hash: - self.sr._vhdutil.set_key(self.path, self._key_hash) + self._cowutil.set_key(self.path, self._key_hash) # Because vhdutil commands modify the volume data, # we must retrieve a new time the utilization size. @@ -1940,7 +1940,7 @@ def resize(self, sr_uuid, vdi_uuid, size) -> str: raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI') # Compute the virtual VHD and DRBD volume size. - size = vhdutil.validate_and_round_vhd_size(int(size)) + size = self._cowutil.validateAndRoundImageSize(int(size)) volume_size = LinstorVhdUtil.compute_volume_size(size, self.vdi_type) util.SMlog( 'LinstorVDI.resize: type={}, vhd-size={}, volume-size={}' @@ -2293,12 +2293,12 @@ def _create_snapshot(self, snap_uuid, snap_of_uuid=None): # 2. Write the snapshot content. is_raw = (self.vdi_type == VdiType.RAW) - self.sr._vhdutil.snapshot( + self._cowutil.snapshot( snap_path, self.path, is_raw, self.MAX_METADATA_VIRT_SIZE ) # 3. Get snapshot parent. - snap_parent = self.sr._vhdutil.get_parent(snap_uuid) + snap_parent = self._cowutil.get_parent(snap_uuid) # 4. Update metadata. util.SMlog('Set VDI {} metadata of snapshot'.format(snap_uuid)) @@ -2391,7 +2391,7 @@ def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None): 'VDIUnavailable', opterr='failed to get VHD depth' ) - elif depth >= vhdutil.MAX_CHAIN_SIZE: + elif depth >= self._cowutil.getMaxChainLength(): raise xs_errors.XenError('SnapshotChainTooLong') # Ensure we have a valid path if we don't have a local diskful. diff --git a/drivers/blktap2.py b/drivers/blktap2.py index f7696ac2..24f4d90e 100755 --- a/drivers/blktap2.py +++ b/drivers/blktap2.py @@ -47,7 +47,7 @@ import nfs import resetvdis -import vhdutil +from cowutil import CowUtil, ImageType import lvhdutil import VDI as sm @@ -1678,7 +1678,7 @@ def _activate_locked(self, sr_uuid, vdi_uuid, options): # When we attach a static VDI for HA, we cannot communicate with # xapi, because has not started yet. These VDIs are raw. - if vdi_type != VdiType.RAW: + if CowUtil.isCowImage(vdi_type): session = self.target.vdi.session vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid) # pylint: disable=used-before-assignment @@ -1931,7 +1931,7 @@ def _setup_cache(self, session, sr_uuid, vdi_uuid, local_sr_uuid, from lock import Lock from FileSR import FileVDI - parent_uuid = vhdutil.getParent(self.target.vdi.path, + parent_uuid = self._cowutil.getParent(self.target.vdi.path, FileVDI.extractUuid) if not parent_uuid: util.SMlog("ERROR: VDI %s has no parent, not enabling" % \ @@ -1960,14 +1960,14 @@ def _setup_cache(self, session, sr_uuid, vdi_uuid, local_sr_uuid, read_cache_path) else: try: - vhdutil.snapshot(read_cache_path, shared_target.path, False) + self._cowutil.snapshot(read_cache_path, shared_target.path, False) except util.CommandException as e: util.SMlog("Error creating parent cache: %s" % e) self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code) return None # local write node - leaf_size = vhdutil.getSizeVirt(self.target.vdi.path) + leaf_size = self._cowutil.getSizeVirt(self.target.vdi.path) local_leaf_path = "%s/%s.vhdcache" % \ (local_sr.path, self.target.vdi.uuid) if util.pathexists(local_leaf_path): @@ -1975,18 +1975,18 @@ def _setup_cache(self, session, sr_uuid, vdi_uuid, local_sr_uuid, local_leaf_path) os.unlink(local_leaf_path) try: - vhdutil.snapshot(local_leaf_path, read_cache_path, False, + self._cowutil.snapshot(local_leaf_path, read_cache_path, False, msize=leaf_size // 1024 // 1024, checkEmpty=False) except util.CommandException as e: util.SMlog("Error creating leaf cache: %s" % e) self.alert_no_cache(session, vdi_uuid, local_sr_uuid, e.code) return None - local_leaf_size = vhdutil.getSizeVirt(local_leaf_path) + local_leaf_size = self._cowutil.getSizeVirt(local_leaf_path) if leaf_size > local_leaf_size: util.SMlog("Leaf size %d > local leaf cache size %d, resizing" % (leaf_size, local_leaf_size)) - vhdutil.setSizeVirtFast(local_leaf_path, leaf_size) + self._cowutil.setSizeVirtFast(local_leaf_path, leaf_size) vdi_type = self.target.get_vdi_type() @@ -2078,7 +2078,7 @@ def _remove_cache(self, session, local_sr_uuid): from lock import Lock from FileSR import FileVDI - parent_uuid = vhdutil.getParent(self.target.vdi.path, + parent_uuid = self._cowutil.getParent(self.target.vdi.path, FileVDI.extractUuid) if not parent_uuid: util.SMlog("ERROR: No parent for VDI %s, ignore" % \ diff --git a/drivers/cleanup.py b/drivers/cleanup.py index 4250754a..15b5d073 100755 --- a/drivers/cleanup.py +++ b/drivers/cleanup.py @@ -37,7 +37,7 @@ import XenAPI # pylint: disable=import-error import util import lvutil -import vhdutil +from cowutil import ImageType import lvhdutil import lvmcache import journaler @@ -549,6 +549,7 @@ def __init__(self, sr, uuid, vdi_type): self.parent = None self.children = [] self._vdiRef = None + self._cowutil = None self._clearRef() @staticmethod @@ -771,10 +772,10 @@ def delete(self) -> None: self._clear() def getParent(self) -> str: - return vhdutil.getParent(self.path, lambda x: x.strip()) + return self._cowutil.getParent(self.path, lambda x: x.strip()) def repair(self, parent) -> None: - vhdutil.repair(parent) + self._cowutil.repair(parent) @override def __str__(self) -> str: @@ -799,7 +800,7 @@ def __str__(self) -> str: strSizeVHD, strSizeAllocated, strType) def validate(self, fast=False) -> None: - if not vhdutil.check(self.path, fast=fast): + if not self._cowutil.check(self.path, fast=fast) != cowutil.CheckResult.Success: raise util.SMException("VHD %s corrupted" % self) def _clear(self): @@ -896,7 +897,7 @@ def _reportCoalesceError(vdi, ce): def coalesce(self) -> int: # size is returned in sectors - return vhdutil.coalesce(self.path) * 512 + return self._cowutil.coalesce(self.path) * 512 @staticmethod def _doCoalesceVHD(vdi): @@ -1022,12 +1023,12 @@ def _tagChildrenForRelink(self): child._tagChildrenForRelink() def _loadInfoParent(self): - ret = vhdutil.getParent(self.path, lvhdutil.extractUuid) + ret = self._cowutil.getParent(self.path, lvhdutil.extractUuid) if ret: self.parentUuid = ret def _setParent(self, parent) -> None: - vhdutil.setParent(self.path, parent.path, False) + self._cowutil.setParent(self.path, parent.path, False) self.parent = parent self.parentUuid = parent.uuid parent.children.append(self) @@ -1040,11 +1041,11 @@ def _setParent(self, parent) -> None: (self.uuid, self.parentUuid)) def _loadInfoHidden(self) -> None: - hidden = vhdutil.getHidden(self.path) + hidden = self._cowutil.getHidden(self.path) self.hidden = (hidden != 0) def _setHidden(self, hidden=True) -> None: - vhdutil.setHidden(self.path, hidden) + self._cowutil.setHidden(self.path, hidden) self.hidden = hidden def _increaseSizeVirt(self, size, atomic=True) -> None: @@ -1059,9 +1060,9 @@ def _increaseSizeVirt(self, size, atomic=True) -> None: Util.log(" Expanding VHD virt size for VDI %s: %s -> %s" % \ (self, Util.num2str(self.sizeVirt), Util.num2str(size))) - msize = vhdutil.getMaxResizeSize(self.path) * 1024 * 1024 + msize = self._cowutil.getMaxResizeSize(self.path) if (size <= msize): - vhdutil.setSizeVirtFast(self.path, size) + self._cowutil.setSizeVirtFast(self.path, size) else: if atomic: vdiList = self._getAllSubtree() @@ -1077,17 +1078,17 @@ def _increaseSizeVirt(self, size, atomic=True) -> None: else: self._setSizeVirt(size) - self.sizeVirt = vhdutil.getSizeVirt(self.path) + self.sizeVirt = self._cowutil.getSizeVirt(self.path) def _setSizeVirt(self, size) -> None: """WARNING: do not call this method directly unless all VDIs in the subtree are guaranteed to be unplugged (and remain so for the duration of the operation): this operation is only safe for offline VHDs""" jFile = os.path.join(self.sr.path, self.uuid) - vhdutil.setSizeVirt(self.path, size, jFile) + self._cowutil.setSizeVirt(self.path, size, jFile) def _queryVHDBlocks(self) -> bytes: - return vhdutil.getBlockBitmap(self.path) + return self._cowutil.getBlockBitmap(self.path) def _getCoalescedSizeData(self): """Get the data size of the resulting VHD if we coalesce self onto @@ -1101,14 +1102,14 @@ def _getCoalescedSizeData(self): blocksParent = self.parent.getVHDBlocks() numBlocks = Util.countBits(blocksChild, blocksParent) Util.log("Num combined blocks = %d" % numBlocks) - sizeData = numBlocks * vhdutil.VHD_BLOCK_SIZE + sizeData = numBlocks * self._cowutil.getBlockSize(self.path) assert(sizeData <= self.sizeVirt) return sizeData def _calcExtraSpaceForCoalescing(self) -> int: sizeData = self._getCoalescedSizeData() - sizeCoalesced = sizeData + vhdutil.calcOverheadBitmap(sizeData) + \ - vhdutil.calcOverheadEmpty(self.sizeVirt) + sizeCoalesced = sizeData + self._cowutil.calcOverheadBitmap(sizeData) + \ + self._cowutil.calcOverheadEmpty(self.sizeVirt) Util.log("Coalesced size = %s" % Util.num2str(sizeCoalesced)) return sizeCoalesced - self.parent.getSizeVHD() @@ -1122,7 +1123,7 @@ def _calcExtraSpaceForSnapshotCoalescing(self) -> int: """How much extra space in the SR will be required to snapshot-coalesce this VDI""" return self._calcExtraSpaceForCoalescing() + \ - vhdutil.calcOverheadEmpty(self.sizeVirt) # extra snap leaf + self._cowutil.calcOverheadEmpty(self.sizeVirt) # extra snap leaf def _getAllSubtree(self): """Get self and all VDIs in the subtree of self as a flat list""" @@ -1159,7 +1160,7 @@ def load(self, info=None) -> None: if not util.pathexists(self.path): raise util.SMException("%s not found" % self.path) try: - info = vhdutil.getVHDInfo(self.path, self.extractUuid) + info = self._cowutil.getInfo(self.path, self.extractUuid) except util.SMException: Util.log(" [VDI %s: failed to read VHD metadata]" % self.uuid) return @@ -1203,7 +1204,7 @@ def delete(self) -> None: @override def getAllocatedSize(self) -> int: if self._sizeAllocated == -1: - self._sizeAllocated = vhdutil.getAllocatedSize(self.path) + self._sizeAllocated = self._cowutil.getAllocatedSize(self.path) return self._sizeAllocated @@ -1295,6 +1296,7 @@ def rename(self, uuid) -> None: oldUuid = self.uuid oldLVName = self.fileName VDI.rename(self, uuid) + self.fileName = lvhdutil.LV_PREFIX[VdiType.VHD] + self.uuid if self.vdi_type == VdiType.RAW: self.fileName = lvhdutil.LV_PREFIX[VdiType.RAW] + self.uuid @@ -1356,7 +1358,7 @@ def _loadInfoSizeAllocated(self): if self.vdi_type == VdiType.RAW: return self._activate() - self._sizeAllocated = vhdutil.getAllocatedSize(self.path) + self._sizeAllocated = self._cowutil.getAllocatedSize(self.path) @override def _loadInfoHidden(self) -> None: @@ -1422,7 +1424,7 @@ def _setParent(self, parent) -> None: self.sr.lvmCache.setReadonly(self.fileName, False) try: - vhdutil.setParent(self.path, parent.path, parent.vdi_type == VdiType.RAW) + self._cowutil.setParent(self.path, parent.path, parent.vdi_type == VdiType.RAW) finally: if self.lvReadonly: self.sr.lvmCache.setReadonly(self.fileName, True) @@ -1559,6 +1561,7 @@ def load(self, info=None) -> None: self.drbd_size = -1 self.hidden = info.hidden self.scanError = False + self.vdi_type = VdiType.VHD @override @@ -1637,7 +1640,7 @@ def delete(self) -> None: @override def validate(self, fast=False) -> None: - if self.vdi_type != VdiType.RAW and not self.sr._vhdutil.check(self.uuid, fast=fast): + if self.vdi_type != VdiType.RAW and self._cowutil.check(self.uuid, fast=fast) != cowutil.CheckResult.Success: raise util.SMException('VHD {} corrupted'.format(self)) @override @@ -2780,7 +2783,7 @@ def _scan(self, force): for i in range(SR.SCAN_RETRY_ATTEMPTS): error = False pattern = os.path.join(self.path, "*%s" % VdiTypeExtension.VHD) - vhds = vhdutil.getAllVHDs(pattern, FileVDI.extractUuid) + vhds = CowUtil.getAllInfoFromVG(pattern, FileVDI.extractUuid) for uuid, vhdInfo in vhds.items(): if vhdInfo.error: error = True diff --git a/drivers/cowutil.py b/drivers/cowutil.py new file mode 100644 index 00000000..90ad8edf --- /dev/null +++ b/drivers/cowutil.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 Vates SAS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from sm_typing import Callable, Dict, Final, Optional, Sequence, override + +from abc import ABC, abstractmethod +from enum import IntEnum + +import errno +import time + +import util + +# ------------------------------------------------------------------------------ + +IMAGE_FORMAT_COW_FLAG: Final = 1 << 8 + +class ImageFormat(IntEnum): + RAW = 1 + VHD = 2 | IMAGE_FORMAT_COW_FLAG + + @override + def __str__(self) -> str: + return IMAGE_FORMAT_TO_STR[self] + +IMAGE_FORMAT_TO_STR: Final = { + ImageFormat.RAW: "raw", + ImageFormat.VHD: "vhd" +} + +# ------------------------------------------------------------------------------ + +class CowImageInfo(object): + uuid = "" + path = "" + sizeVirt = -1 + sizePhys = -1 + sizeAllocated = -1 + hidden = False + parentUuid = "" + parentPath = "" + error = 0 + + def __init__(self, uuid): + self.uuid = uuid + +# ------------------------------------------------------------------------------ + +class CowUtil(ABC): + class CheckResult(IntEnum): + Success = 0 + Fail = 1 + Unavailable = 2 + + @abstractmethod + def getMinImageSize(self) -> int: + pass + + @abstractmethod + def getMaxImageSize(self) -> int: + pass + + @abstractmethod + def getBlockSize(self, path: str) -> int: + pass + + @abstractmethod + def getMaxChainLength(self) -> int: + pass + + @abstractmethod + def calcOverheadEmpty(self, virtual_size: int) -> int: + pass + + @abstractmethod + def calcOverheadBitmap(self, virtual_size: int) -> int: + pass + + @abstractmethod + def getInfo( + self, + path: str, + extractUuidFunction: Callable[[str], str], + includeParent: bool = True, + resolveParent: bool = True + ) -> CowImageInfo: + pass + + @abstractmethod + def getInfoFromLVM(self, lvName: str, extractUuidFunction: Callable[[str], str], vgName: str) -> CowImageInfo: + pass + + @abstractmethod + def getParent(self, path: str, extractUuidFunction: Callable[[str], str]) -> Optional[str]: + pass + + @abstractmethod + def getParentNoCheck(self, path: str) -> Optional[str]: + pass + + @abstractmethod + def hasParent(self, path: str) -> bool: + pass + + @abstractmethod + def setParent(self, path: str, parentPath: str, parentRaw: bool) -> None: + pass + + @abstractmethod + def getHidden(self, path: str) -> bool: + pass + + @abstractmethod + def setHidden(self, path: str, hidden: bool = True) -> None: + pass + + @abstractmethod + def getSizeVirt(self, path: str) -> int: + pass + + @abstractmethod + def setSizeVirt(self, path: str, size: int, jFile: str) -> None: + pass + + @abstractmethod + def setSizeVirtFast(self, path: str, size: int) -> None: + pass + + @abstractmethod + def getMaxResizeSize(self, path: str) -> int: + pass + + @abstractmethod + def getSizePhys(self, path: str) -> int: + pass + + @abstractmethod + def setSizePhys(self, path: str, size: int, debug: bool = True) -> None: + pass + + @abstractmethod + def getAllocatedSize(self, path: str) -> int: + pass + + @abstractmethod + def killData(self, path: str) -> None: + pass + + @abstractmethod + def getDepth(self, path: str) -> int: + pass + + @abstractmethod + def getBlockBitmap(self, path: str) -> bytes: + pass + + @abstractmethod + def coalesce(self, path: str) -> int: + pass + + @abstractmethod + def create(self, path: str, size: int, static: bool, msize: int = 0) -> None: + pass + + @abstractmethod + def snapshot( + self, + path: str, + parent: str, + parentRaw: bool, + msize: int = 0, + checkEmpty: Optional[bool] = True + ) -> None: + pass + + @abstractmethod + def check( + self, + path: str, + ignoreMissingFooter: Optional[bool] = False, + fast: Optional[bool] = False + ) -> CheckResult: + pass + + @abstractmethod + def revert(self, path: str, jFile: str) -> None: + pass + + @abstractmethod + def repair(self, path: str) -> None: + pass + + @abstractmethod + def validateAndRoundImageSize(self, size: int) -> int: + pass + + @abstractmethod + def getKeyHash(self, path: str) -> Optional[str]: + pass + + @abstractmethod + def setKey(self, path: str, key_hash: str) -> None: + pass + + @staticmethod + def getAllInfoFromVG( + pattern: str, + extractUuidFunction: Callable[[str], str], + vgName: Optional[str] = None, + parents: bool = False, + exitOnError: bool = False + ) -> Dict[str, CowImageInfo]: + raise Exception('Not implemented') # TODO + + @classmethod + def getParentChain(cls, lvName: str, extractUuidFunction: Callable[[str], str], vgName: str) -> Dict[str, str]: + """ + Get the chain of all parents of 'path'. Safe to call for raw VDI's as well. + """ + chain = {} + vdis: Dict[str, CowImageInfo] = {} + retries = 0 + while (not vdis): + if retries > 60: + util.SMlog('ERROR: getAllInfoFromVG returned 0 VDIs after %d retries' % retries) + util.SMlog('ERROR: the image metadata might be corrupted') + break + vdis = cls.getAllInfoFromVG(lvName, extractUuidFunction, vgName, True, True) + if (not vdis): + retries = retries + 1 + time.sleep(1) + for uuid, vdi in vdis.items(): + chain[uuid] = vdi.path + #util.SMlog("Parent chain for %s: %s" % (lvName, chain)) + return chain + + @staticmethod + def isCowImage(image_format: ImageFormat) -> bool: + return bool(image_format & IMAGE_FORMAT_COW_FLAG) + + @staticmethod + def _ioretry(cmd: Sequence[str], text: bool = True) -> str | bytes: + return util.ioretry( + lambda: util.pread2(cmd, text=text), + errlist=[errno.EIO, errno.EAGAIN] + ) diff --git a/drivers/linstor-manager b/drivers/linstor-manager index 4366057e..784a3080 100755 --- a/drivers/linstor-manager +++ b/drivers/linstor-manager @@ -417,10 +417,10 @@ def get_vhd_info(session, args): device_path.rstrip('\n') ) - vhd_info = vhdutil.getVHDInfo( + cow_info = cowutil.getInfo( device_path, extract_uuid, include_parent, False ) - return json.dumps(vhd_info.__dict__) + return json.dumps(cow_info.__dict__) except Exception as e: util.SMlog('linstor-manager:get_vhd_info error: {}'.format(e)) raise @@ -429,7 +429,7 @@ def get_vhd_info(session, args): def has_parent(session, args): try: device_path = args['devicePath'] - return str(vhdutil.hasParent(device_path)) + return str(cowutil.hasParent(device_path)) except Exception as e: util.SMlog('linstor-manager:has_parent error: {}'.format(e)) raise @@ -452,7 +452,7 @@ def get_parent(session, args): device_path.rstrip('\n') ) - return vhdutil.getParent(device_path, extract_uuid) + return cowutil.getParent(device_path, extract_uuid) except Exception as e: util.SMlog('linstor-manager:get_parent error: {}'.format(e)) raise @@ -461,7 +461,7 @@ def get_parent(session, args): def get_size_virt(session, args): try: device_path = args['devicePath'] - return str(vhdutil.getSizeVirt(device_path)) + return str(cowutil.getSizeVirt(device_path)) except Exception as e: util.SMlog('linstor-manager:get_size_virt error: {}'.format(e)) raise @@ -479,7 +479,7 @@ def get_size_phys(session, args): def get_allocated_size(session, args): try: device_path = args['devicePath'] - return str(vhdutil.getAllocatedSize(device_path)) + return str(cowutil.getAllocatedSize(device_path)) except Exception as e: util.SMlog('linstor-manager:get_allocated_size error: {}'.format(e)) raise @@ -488,7 +488,7 @@ def get_allocated_size(session, args): def get_depth(session, args): try: device_path = args['devicePath'] - return str(vhdutil.getDepth(device_path)) + return str(cowutil.getDepth(device_path)) except Exception as e: util.SMlog('linstor-manager:get_depth error: {}'.format(e)) raise @@ -497,7 +497,7 @@ def get_depth(session, args): def get_key_hash(session, args): try: device_path = args['devicePath'] - return vhdutil.getKeyHash(device_path) or '' + return cowutil.getKeyHash(device_path) or '' except Exception as e: util.SMlog('linstor-manager:get_key_hash error: {}'.format(e)) raise @@ -506,7 +506,7 @@ def get_key_hash(session, args): def get_block_bitmap(session, args): try: device_path = args['devicePath'] - return base64.b64encode(vhdutil.getBlockBitmap(device_path)).decode('ascii') + return base64.b64encode(cowutil.getBlockBitmap(device_path)).decode('ascii') except Exception as e: util.SMlog('linstor-manager:get_block_bitmap error: {}'.format(e)) raise @@ -528,7 +528,7 @@ def set_parent(session, args): try: device_path = args['devicePath'] parent_path = args['parentPath'] - vhdutil.setParent(device_path, parent_path, False) + cowutil.setParent(device_path, parent_path, False) return '' except Exception as e: util.SMlog('linstor-manager:set_parent error: {}'.format(e)) @@ -538,7 +538,7 @@ def set_parent(session, args): def coalesce(session, args): try: device_path = args['devicePath'] - return str(vhdutil.coalesce(device_path)) + return str(cowutil.coalesce(device_path)) except Exception as e: util.SMlog('linstor-manager:coalesce error: {}'.format(e)) raise diff --git a/drivers/linstorvhdutil.py b/drivers/linstorvhdutil.py index 5e109a62..4f3943f0 100644 --- a/drivers/linstorvhdutil.py +++ b/drivers/linstorvhdutil.py @@ -50,11 +50,11 @@ def call_remote_method(session, host_ref, method, device_path, args): def check_ex(path, ignoreMissingFooter = False, fast = False): - cmd = [vhdutil.VHD_UTIL, "check", vhdutil.OPT_LOG_ERR, "-n", path] - if ignoreMissingFooter: - cmd.append("-i") - if fast: - cmd.append("-B") + result = self._cowutil.check(path, ignoreMissingFooter, fast) + if result == cowutil.CheckResult.Success: + return True + if result == cowutil.CheckResult.Fail: + return False vhdutil.ioretry(cmd) @@ -398,7 +398,7 @@ def _force_deflate(self, path, newSize, oldSize, zeroize): self.deflate(path, newSize, oldSize, zeroize) # -------------------------------------------------------------------------- - # Static helpers. + # Helpers. # -------------------------------------------------------------------------- @classmethod @@ -406,18 +406,14 @@ def compute_volume_size(cls, virtual_size, image_type): if VdiType.isCowImage(image_type): # All LINSTOR VDIs have the metadata area preallocated for # the maximum possible virtual size (for fast online VDI.resize). - meta_overhead = vhdutil.calcOverheadEmpty(cls.MAX_SIZE) - bitmap_overhead = vhdutil.calcOverheadBitmap(virtual_size) + meta_overhead = self._cowutil.calcOverheadEmpty(self.MAX_SIZE) + bitmap_overhead = self._cowutil.calcOverheadBitmap(virtual_size) virtual_size += meta_overhead + bitmap_overhead elif image_type != VdiType.RAW: raise Exception('Invalid image type: {}'.format(image_type)) return LinstorVolumeManager.round_up_volume_size(virtual_size) - # -------------------------------------------------------------------------- - # Helpers. - # -------------------------------------------------------------------------- - def _extract_uuid(self, device_path): # TODO: Remove new line in the vhdutil module. Not here. return self._linstor.get_volume_uuid_from_device_path( diff --git a/drivers/lvhdutil.py b/drivers/lvhdutil.py index 484260b8..4f0b1933 100755 --- a/drivers/lvhdutil.py +++ b/drivers/lvhdutil.py @@ -36,10 +36,10 @@ LVM_SIZE_INCREMENT = 4 * 1024 * 1024 LV_PREFIX = { - VdiType.VHD: "VHD-", - VdiType.RAW: "LV-", + VdiType.RAW: "LV-", + VdiType.VHD: "VHD-" } -VDI_TYPES = [VdiType.VHD, VdiType.RAW] +VDI_TYPES = [VdiType.RAW, VdiType.VHD] JRN_INFLATE = "inflate" @@ -72,8 +72,7 @@ def __init__(self, uuid): def matchLV(lvName): """given LV name, return the VDI type and the UUID, or (None, None) if the name doesn't match any known type""" - for vdiType in VDI_TYPES: - prefix = LV_PREFIX[vdiType] + for vdiType, prefix in LV_PREFIX.items(): if lvName.startswith(prefix): return (vdiType, lvName.replace(prefix, "")) return (None, None) @@ -85,9 +84,9 @@ def extractUuid(path): # we are dealing with realpath uuid = uuid.replace("--", "-") uuid.replace(VG_PREFIX, "") - for t in VDI_TYPES: - if uuid.find(LV_PREFIX[t]) != -1: - uuid = uuid.split(LV_PREFIX[t])[-1] + for prefix in LV_PREFIX.values(): + if uuid.find(prefix) != -1: + uuid = uuid.split(prefix)[-1] uuid = uuid.strip() # TODO: validate UUID format return uuid @@ -101,8 +100,8 @@ def calcSizeLV(sizeVHD): def calcSizeVHDLV(sizeVirt): # all LVHD VDIs have the metadata area preallocated for the maximum # possible virtual size (for fast online VDI.resize) - metaOverhead = vhdutil.calcOverheadEmpty(MSIZE) - bitmapOverhead = vhdutil.calcOverheadBitmap(sizeVirt) + metaOverhead = self._cowutil.calcOverheadEmpty(MSIZE) + bitmapOverhead = self._cowutil.calcOverheadBitmap(sizeVirt) return calcSizeLV(sizeVirt + metaOverhead + bitmapOverhead) @@ -144,7 +143,7 @@ def getVDIInfo(lvmCache): if haveVHDs: pattern = "%s*" % LV_PREFIX[VdiType.VHD] - vhds = vhdutil.getAllVHDs(pattern, extractUuid, lvmCache.vgName) + vhds = CowUtil.getAllInfoFromVG(pattern, extractUuid, lvmCache.vgName) uuids = vdis.keys() for uuid in uuids: vdi = vdis[uuid] @@ -201,7 +200,7 @@ def deflate(lvmCache, lvName, size): return path = os.path.join(VG_LOCATION, lvmCache.vgName, lvName) # no undo necessary if this fails at any point between now and the end - vhdutil.setSizePhys(path, newSize) + self._cowutil.setSizePhys(path, newSize) lvmCache.setSize(lvName, newSize) @@ -212,7 +211,7 @@ def setSizeVirt(journaler, srUuid, vdiUuid, size, jFile): vgName = VG_PREFIX + srUuid path = os.path.join(VG_LOCATION, vgName, lvName) inflate(journaler, srUuid, vdiUuid, calcSizeVHDLV(size)) - vhdutil.setSizeVirt(path, size, jFile) + self._cowutil.setSizeVirt(path, size, jFile) def _tryAcquire(lock): @@ -235,7 +234,7 @@ def attachThin(journaler, srUuid, vdiUuid): lvmCache = journaler.lvmCache _tryAcquire(sr_lock) lvmCache.refresh() - vhdInfo = vhdutil.getVHDInfoLVM(lvName, extractUuid, vgName) + vhdInfo = self._cowutil.getInfoFromLVM(lvName, extractUuid, vgName) newSize = calcSizeVHDLV(vhdInfo.sizeVirt) currSizeLV = lvmCache.getSize(lvName) if newSize <= currSizeLV: diff --git a/drivers/tapdisk-pause b/drivers/tapdisk-pause index 40c6a71e..128ad729 100755 --- a/drivers/tapdisk-pause +++ b/drivers/tapdisk-pause @@ -213,8 +213,7 @@ class Tapdisk: ns = lvhdutil.NS_PREFIX_LVM + self.sr_uuid lvm_cache = lvmcache.LVMCache(vg_name) lv_name = lvhdutil.LV_PREFIX[VdiType.VHD] + self.vdi_uuid - vdi_list = vhdutil.getParentChain(lv_name, - lvhdutil.extractUuid, vg_name) + vdi_list = cowutil.getParentChain(lv_name, lvhdutil.extractUuid, vg_name) for uuid, lv_name in vdi_list.items(): if uuid == self.vdi_uuid: continue diff --git a/drivers/verifyVHDsOnSR.py b/drivers/verifyVHDsOnSR.py index e0a56f46..0ba06bdc 100755 --- a/drivers/verifyVHDsOnSR.py +++ b/drivers/verifyVHDsOnSR.py @@ -55,7 +55,7 @@ def activateVdiChainAndCheck(vhd_info, vg_name): activated_list.append([vhd_info.uuid, vhd_path]) # Do a vhdutil check with -i option, to ignore error in primary - if not vhdutil.check(vhd_path, True): + if cowutil.check(vhd_path, True) != cowutil.CheckResult.Success: util.SMlog("VHD check for %s failed, continuing with the rest!" % vg_name) VHDs_failed += 1 else: @@ -115,7 +115,7 @@ def checkAllVHD(sr_uuid): pattern = "%s*" % lvhdutil.LV_PREFIX[VdiType.VHD] # Do a vhd scan and gets all the VHDs - vhds = vhdutil.getAllVHDs(pattern, lvhdutil.extractUuid, vg_name) + vhds = CowUtil.getAllInfoFromVG(pattern, lvhdutil.extractUuid, vg_name) VHDs_total = len(vhds) # Build VHD chain, that way it will be easier to activate all the VHDs diff --git a/drivers/vhdutil.py b/drivers/vhdutil.py index 5484ad2e..c05f8a49 100755 --- a/drivers/vhdutil.py +++ b/drivers/vhdutil.py @@ -16,425 +16,396 @@ # Helper functions pertaining to VHD operations # -import os -import util +from sm_typing import Callable, Dict, Final, Optional, Sequence, cast, override + import errno -import zlib +import os import re -import xs_errors -import time -from vditype import VdiType - -MIN_VHD_SIZE = 2 * 1024 * 1024 -MAX_VHD_SIZE = 2040 * 1024 * 1024 * 1024 -MAX_VHD_JOURNAL_SIZE = 6 * 1024 * 1024 # 2MB VHD block size, max 2TB VHD size -MAX_CHAIN_SIZE = 30 # max VHD parent chain size -VHD_UTIL = "/usr/bin/vhd-util" -OPT_LOG_ERR = "--debug" -VHD_BLOCK_SIZE = 2 * 1024 * 1024 -VHD_FOOTER_SIZE = 512 - -# lock to lock the entire SR for short ops -LOCK_TYPE_SR = "sr" - - -class VHDInfo: - uuid = "" - path = "" - sizeVirt = -1 - sizePhys = -1 - sizeAllocated = -1 - hidden = False - parentUuid = "" - parentPath = "" - error = 0 - - def __init__(self, uuid): - self.uuid = uuid - - -def calcOverheadEmpty(virtual_size): - """Calculate the VHD space overhead (metadata size) for an empty VDI of - size virtual_size""" - overhead = 0 - size_mb = virtual_size // (1024 * 1024) - - # Footer + footer copy + header + possible CoW parent locator fields - overhead = 3 * 1024 - - # BAT 4 Bytes per block segment - overhead += (size_mb // 2) * 4 - overhead = util.roundup(512, overhead) - - # BATMAP 1 bit per block segment - overhead += (size_mb // 2) // 8 - overhead = util.roundup(4096, overhead) - - return overhead - - -def calcOverheadBitmap(virtual_size): - num_blocks = virtual_size // VHD_BLOCK_SIZE - if virtual_size % VHD_BLOCK_SIZE: - num_blocks += 1 - return num_blocks * 4096 - - -def ioretry(cmd, text=True): - return util.ioretry(lambda: util.pread2(cmd, text=text), - errlist=[errno.EIO, errno.EAGAIN]) - - -def convertAllocatedSizeToBytes(size): - # Assume we have standard 2MB allocation blocks - return size * 2 * 1024 * 1024 - - -def getVHDInfo(path, extractUuidFunction, includeParent=True, resolveParent=True): - """Get the VHD info. The parent info may optionally be omitted: vhd-util - tries to verify the parent by opening it, which results in error if the VHD - resides on an inactive LV""" - opts = "-vsaf" - if includeParent: - opts += "p" - if not resolveParent: - opts += "u" - - cmd = [VHD_UTIL, "query", OPT_LOG_ERR, opts, "-n", path] - ret = ioretry(cmd) - fields = ret.strip().split('\n') - uuid = extractUuidFunction(path) - vhdInfo = VHDInfo(uuid) - vhdInfo.sizeVirt = int(fields[0]) * 1024 * 1024 - vhdInfo.sizePhys = int(fields[1]) - nextIndex = 2 - if includeParent: - if fields[nextIndex].find("no parent") == -1: - vhdInfo.parentPath = fields[nextIndex] - vhdInfo.parentUuid = extractUuidFunction(fields[nextIndex]) - nextIndex += 1 - vhdInfo.hidden = int(fields[nextIndex].replace("hidden: ", "")) - vhdInfo.sizeAllocated = convertAllocatedSizeToBytes(int(fields[nextIndex+1])) - vhdInfo.path = path - return vhdInfo - - -def getVHDInfoLVM(lvName, extractUuidFunction, vgName): - """Get the VHD info. This function does not require the container LV to be - active, but uses lvs & vgs""" - vhdInfo = None - cmd = [VHD_UTIL, "scan", "-f", "-l", vgName, "-m", lvName] - ret = ioretry(cmd) - return _parseVHDInfo(ret, extractUuidFunction) - - -def getAllVHDs(pattern, extractUuidFunction, vgName=None, \ - parentsOnly=False, exitOnError=False): - vhds = dict() - cmd = [VHD_UTIL, "scan", "-f", "-m", pattern] - if vgName: - cmd.append("-l") - cmd.append(vgName) - if parentsOnly: - cmd.append("-a") - try: - ret = ioretry(cmd) - except Exception as e: - util.SMlog("WARN: vhd scan failed: output: %s" % e) - ret = ioretry(cmd + ["-c"]) - util.SMlog("WARN: vhd scan with NOFAIL flag, output: %s" % ret) - for line in ret.split('\n'): - if len(line.strip()) == 0: - continue - vhdInfo = _parseVHDInfo(line, extractUuidFunction) - if vhdInfo: - if vhdInfo.error != 0 and exitOnError: - # Just return an empty dict() so the scan will be done - # again by getParentChain. See CA-177063 for details on - # how this has been discovered during the stress tests. - return dict() - vhds[vhdInfo.uuid] = vhdInfo - else: - util.SMlog("WARN: vhdinfo line doesn't parse correctly: %s" % line) - return vhds - - -def getParentChain(lvName, extractUuidFunction, vgName): - """Get the chain of all VHD parents of 'path'. Safe to call for raw VDI's - as well""" - chain = dict() - vdis = dict() - retries = 0 - while (not vdis): - if retries > 60: - util.SMlog('ERROR: getAllVHDs returned 0 VDIs after %d retries' % retries) - util.SMlog('ERROR: the VHD metadata might be corrupted') - break - vdis = getAllVHDs(lvName, extractUuidFunction, vgName, True, True) - if (not vdis): - retries = retries + 1 - time.sleep(1) - for uuid, vdi in vdis.items(): - chain[uuid] = vdi.path - #util.SMlog("Parent chain for %s: %s" % (lvName, chain)) - return chain - - -def getParent(path, extractUuidFunction): - cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-p", "-n", path] - ret = ioretry(cmd) - if ret.find("query failed") != -1 or ret.find("Failed opening") != -1: - raise util.SMException("VHD query returned %s" % ret) - if ret.find("no parent") != -1: - return None - return extractUuidFunction(ret) - - -def hasParent(path): - """Check if the VHD has a parent. A VHD has a parent iff its type is - 'Differencing'. This function does not need the parent to actually - be present (e.g. the parent LV to be activated).""" - cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-p", "-n", path] - ret = ioretry(cmd) - # pylint: disable=no-member - m = re.match(r".*Disk type\s+: (\S+) hard disk.*", ret, flags=re.S) - vhd_type = m.group(1) - assert(vhd_type == "Differencing" or vhd_type == "Dynamic") - return vhd_type == "Differencing" - - -def setParent(path, parentPath, parentRaw): - normpath = os.path.normpath(parentPath) - cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-p", normpath, "-n", path] - if parentRaw: - cmd.append("-m") - ioretry(cmd) - - -def getHidden(path): - cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-f", "-n", path] - ret = ioretry(cmd) - hidden = int(ret.split(':')[-1].strip()) - return hidden - - -def setHidden(path, hidden=True): - opt = "1" - if not hidden: - opt = "0" - cmd = [VHD_UTIL, "set", OPT_LOG_ERR, "-n", path, "-f", "hidden", "-v", opt] - ret = ioretry(cmd) - - -def getSizeVirt(path): - cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-v", "-n", path] - ret = ioretry(cmd) - size = int(ret) * 1024 * 1024 - return size - - -def setSizeVirt(path, size, jFile): - "resize VHD offline" - size_mb = size // (1024 * 1024) - cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path, - "-j", jFile] - ioretry(cmd) - - -def setSizeVirtFast(path, size): - "resize VHD online" - size_mb = size // (1024 * 1024) - cmd = [VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path, "-f"] - ioretry(cmd) - - -def getMaxResizeSize(path): - """get the max virtual size for fast resize""" - cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-S", "-n", path] - ret = ioretry(cmd) - return int(ret) - - -def getSizePhys(path): - cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-s", "-n", path] - ret = ioretry(cmd) - return int(ret) - - -def setSizePhys(path, size, debug=True): - "set physical utilisation (applicable to VHD's on fixed-size files)" - if debug: - cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-s", str(size), "-n", path] - else: - cmd = [VHD_UTIL, "modify", "-s", str(size), "-n", path] - ioretry(cmd) - - -def getAllocatedSize(path): - cmd = [VHD_UTIL, "query", OPT_LOG_ERR, '-a', '-n', path] - ret = ioretry(cmd) - return convertAllocatedSizeToBytes(int(ret)) - -def killData(path): - "zero out the disk (kill all data inside the VHD file)" - cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-z", "-n", path] - ioretry(cmd) - - -def getDepth(path): - "get the VHD parent chain depth" - cmd = [VHD_UTIL, "query", OPT_LOG_ERR, "-d", "-n", path] - text = ioretry(cmd) - depth = -1 - if text.startswith("chain depth:"): - depth = int(text.split(':')[1].strip()) - return depth - - -def getBlockBitmap(path): - cmd = [VHD_UTIL, "read", OPT_LOG_ERR, "-B", "-n", path] - text = ioretry(cmd, text=False) - return zlib.compress(text) - - -def coalesce(path): - """ - Coalesce the VHD, on success it returns the number of sectors coalesced - """ - cmd = [VHD_UTIL, "coalesce", OPT_LOG_ERR, "-n", path] - text = ioretry(cmd) - match = re.match(r'^Coalesced (\d+) sectors', text) - if match: - return int(match.group(1)) - - return 0 +import zlib +from cowutil import CowImageInfo, CowUtil +import util +import XenAPI # pylint: disable=import-error +import xs_errors -def create(path, size, static, msize=0): - size_mb = size // (1024 * 1024) - cmd = [VHD_UTIL, "create", OPT_LOG_ERR, "-n", path, "-s", str(size_mb)] - if static: - cmd.append("-r") - if msize: - cmd.append("-S") - cmd.append(str(msize)) - ioretry(cmd) - - -def snapshot(path, parent, parentRaw, msize=0, checkEmpty=True): - cmd = [VHD_UTIL, "snapshot", OPT_LOG_ERR, "-n", path, "-p", parent] - if parentRaw: - cmd.append("-m") - if msize: - cmd.append("-S") - cmd.append(str(msize)) - if not checkEmpty: - cmd.append("-e") - ioretry(cmd) - - -def check(path, ignoreMissingFooter=False, fast=False): - cmd = [VHD_UTIL, "check", OPT_LOG_ERR, "-n", path] - if ignoreMissingFooter: - cmd.append("-i") - if fast: - cmd.append("-B") - try: - ioretry(cmd) - return True - except util.CommandException: - return False - - -def revert(path, jFile): - cmd = [VHD_UTIL, "revert", OPT_LOG_ERR, "-n", path, "-j", jFile] - ioretry(cmd) - - -def _parseVHDInfo(line, extractUuidFunction): - vhdInfo = None - valueMap = line.split() - if len(valueMap) < 1 or valueMap[0].find("vhd=") == -1: - return None - for keyval in valueMap: - (key, val) = keyval.split('=') - if key == "vhd": - uuid = extractUuidFunction(val) - if not uuid: - util.SMlog("***** malformed output, no UUID: %s" % valueMap) - return None - vhdInfo = VHDInfo(uuid) - vhdInfo.path = val - elif key == "scan-error": - vhdInfo.error = line - util.SMlog("***** VHD scan error: %s" % line) - break - elif key == "capacity": - vhdInfo.sizeVirt = int(val) - elif key == "size": - vhdInfo.sizePhys = int(val) - elif key == "hidden": - vhdInfo.hidden = int(val) - elif key == "parent" and val != "none": - vhdInfo.parentPath = val - vhdInfo.parentUuid = extractUuidFunction(val) - return vhdInfo - - -def _getVHDParentNoCheck(path): - cmd = ["vhd-util", "read", "-p", "-n", "%s" % path] - text = util.pread(cmd) - util.SMlog(text) - for line in text.split('\n'): - if line.find("decoded name :") != -1: - val = line.split(':')[1].strip() - vdi = val.replace("--", "-")[-40:] - if vdi[1:].startswith("LV-"): - vdi = vdi[1:] - return vdi - return None - - -def repair(path): - """Repairs the VHD.""" - ioretry([VHD_UTIL, 'repair', '-n', path]) - - -def validate_and_round_vhd_size(size): - """ Take the supplied vhd size, in bytes, and check it is positive and less - that the maximum supported size, rounding up to the next block boundary - """ - if size < 0 or size > MAX_VHD_SIZE: - raise xs_errors.XenError( - 'VDISize', opterr='VDI size ' + - 'must be between 1 MB and %d MB' % - (MAX_VHD_SIZE // (1024 * 1024))) - - if size < MIN_VHD_SIZE: - size = MIN_VHD_SIZE - - size = util.roundup(VHD_BLOCK_SIZE, size) - - return size - - -def getKeyHash(path): - """Extract the hash of the encryption key from the header of an encrypted VHD""" - cmd = ["vhd-util", "key", "-p", "-n", path] - ret = ioretry(cmd) - ret = ret.strip() - if ret == 'none': +# ------------------------------------------------------------------------------ + +MIN_VHD_SIZE: Final = 2 * 1024 * 1024 +MAX_VHD_SIZE: Final = 2040 * 1024 * 1024 * 1024 + +VHD_BLOCK_SIZE: Final = 2 * 1024 * 1024 + +MAX_VHD_CHAIN_LENGTH: Final = 30 + +VHD_UTIL: Final = "/usr/bin/vhd-util" + +OPT_LOG_ERR: Final = "--debug" + +# ------------------------------------------------------------------------------ + +class VhdUtil(CowUtil): + @override + def getMinImageSize(self) -> int: + return MIN_VHD_SIZE + + @override + def getMaxImageSize(self) -> int: + return MAX_VHD_SIZE + + @override + def getBlockSize(self, path: str) -> int: + return VHD_BLOCK_SIZE + + @override + def getMaxChainLength(self) -> int: + return MAX_VHD_CHAIN_LENGTH + + @override + def calcOverheadEmpty(self, virtual_size: int) -> int: + """ + Calculate the VHD space overhead (metadata size) for an empty VDI of + size virtual_size. + """ + overhead = 0 + size_mb = virtual_size // (1024 * 1024) + + # Footer + footer copy + header + possible CoW parent locator fields + overhead = 3 * 1024 + + # BAT 4 Bytes per block segment + overhead += (size_mb // 2) * 4 + overhead = util.roundup(512, overhead) + + # BATMAP 1 bit per block segment + overhead += (size_mb // 2) // 8 + overhead = util.roundup(4096, overhead) + + return overhead + + @override + def calcOverheadBitmap(self, virtual_size: int) -> int: + num_blocks = virtual_size // VHD_BLOCK_SIZE + if virtual_size % VHD_BLOCK_SIZE: + num_blocks += 1 + return num_blocks * 4096 + + @override + def getInfo( + self, + path: str, + extractUuidFunction: Callable[[str], str], + includeParent: bool = True, + resolveParent: bool = True + ) -> CowImageInfo: + """ + Get the VHD info. The parent info may optionally be omitted: vhd-util + tries to verify the parent by opening it, which results in error if the VHD + resides on an inactive LV. + """ + opts = "-vsaf" + if includeParent: + opts += "p" + if not resolveParent: + opts += "u" + + ret = cast(str, self._ioretry([VHD_UTIL, "query", OPT_LOG_ERR, opts, "-n", path])) + fields = ret.strip().split("\n") + uuid = extractUuidFunction(path) + vhdInfo = CowImageInfo(uuid) + vhdInfo.sizeVirt = int(fields[0]) * 1024 * 1024 + vhdInfo.sizePhys = int(fields[1]) + nextIndex = 2 + if includeParent: + if fields[nextIndex].find("no parent") == -1: + vhdInfo.parentPath = fields[nextIndex] + vhdInfo.parentUuid = extractUuidFunction(fields[nextIndex]) + nextIndex += 1 + vhdInfo.hidden = bool(int(fields[nextIndex].replace("hidden: ", ""))) + vhdInfo.sizeAllocated = self._convertAllocatedSizeToBytes(int(fields[nextIndex+1])) + vhdInfo.path = path + return vhdInfo + + @override + def getInfoFromLVM(self, lvName: str, extractUuidFunction: Callable[[str], str], vgName: str) -> CowImageInfo: + """ + Get the VHD info. This function does not require the container LV to be + active, but uses LVs & VGs. + """ + ret = self._ioretry([VHD_UTIL, "scan", "-f", "-l", vgName, "-m", lvName]) + return self._parseVHDInfo(ret, extractUuidFunction) + + @override + def getParent(self, path: str, extractUuidFunction: Callable[[str], str]) -> Optional[str]: + ret = cast(str, self._ioretry([VHD_UTIL, "query", OPT_LOG_ERR, "-p", "-n", path])) + if ret.find("query failed") != -1 or ret.find("Failed opening") != -1: + raise util.SMException("VHD query returned %s" % ret) + if ret.find("no parent") != -1: + return None + return extractUuidFunction(ret) + + @override + def getParentNoCheck(self, path: str) -> Optional[str]: + text = util.pread([VHD_UTIL, "read", "-p", "-n", "%s" % path]) + util.SMlog(text) + for line in text.split("\n"): + if line.find("decoded name :") != -1: + val = line.split(":")[1].strip() + vdi = val.replace("--", "-")[-40:] + if vdi[1:].startswith("LV-"): + vdi = vdi[1:] + return vdi return None - vals = ret.split() - if len(vals) != 2: - util.SMlog('***** malformed output from vhd-util' - ' for VHD {}: "{}"'.format(path, ret)) - return None - [_nonce, key_hash] = vals - return key_hash - -def setKey(path, key_hash): - """Set the encryption key for a VHD""" - cmd = ["vhd-util", "key", "-s", "-n", path, "-H", key_hash] - ioretry(cmd) + @override + def hasParent(self, path: str) -> bool: + """ + Check if the VHD has a parent. A VHD has a parent iff its type is + 'Differencing'. This function does not need the parent to actually + be present (e.g. the parent LV to be activated). + """ + ret = cast(str, self._ioretry([VHD_UTIL, "read", OPT_LOG_ERR, "-p", "-n", path])) + # pylint: disable=no-member + m = re.match(r".*Disk type\s+: (\S+) hard disk.*", ret, flags=re.S) + if m: + vhd_type = m.group(1) + assert vhd_type == "Differencing" or vhd_type == "Dynamic" + return vhd_type == "Differencing" + assert False, f"Ill-formed {VHD_UTIL} output detected during VHD parent parsing" + + @override + def setParent(self, path: str, parentPath: str, parentRaw: bool) -> None: + normpath = os.path.normpath(parentPath) + cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-p", normpath, "-n", path] + if parentRaw: + cmd.append("-m") + self._ioretry(cmd) + + @override + def getHidden(self, path: str) -> bool: + ret = cast(str, self._ioretry([VHD_UTIL, "query", OPT_LOG_ERR, "-f", "-n", path])) + return bool(int(ret.split(":")[-1].strip())) + + @override + def setHidden(self, path: str, hidden: bool = True) -> None: + opt = "1" + if not hidden: + opt = "0" + self._ioretry([VHD_UTIL, "set", OPT_LOG_ERR, "-n", path, "-f", "hidden", "-v", opt]) + + @override + def getSizeVirt(self, path: str) -> int: + ret = self._ioretry([VHD_UTIL, "query", OPT_LOG_ERR, "-v", "-n", path]) + return int(ret) * 1024 * 1024 + + @override + def setSizeVirt(self, path: str, size: int, jFile: str) -> None: + """ + Resize VHD offline + """ + size_mb = size // (1024 * 1024) + self._ioretry([VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path, "-j", jFile]) + + @override + def setSizeVirtFast(self, path: str, size: int) -> None: + """ + Resize VHD online. + """ + size_mb = size // (1024 * 1024) + self._ioretry([VHD_UTIL, "resize", OPT_LOG_ERR, "-s", str(size_mb), "-n", path, "-f"]) + + @override + def getMaxResizeSize(self, path: str) -> int: + """ + Get the max virtual size for fast resize. + """ + ret = self._ioretry([VHD_UTIL, "query", OPT_LOG_ERR, "-S", "-n", path]) + return int(ret) * 1024 * 1024 + + @override + def getSizePhys(self, path: str) -> int: + return int(self._ioretry([VHD_UTIL, "query", OPT_LOG_ERR, "-s", "-n", path])) + + @override + def setSizePhys(self, path: str, size: int, debug: bool = True) -> None: + """ + Set physical utilisation (applicable to VHD's on fixed-size files). + """ + if debug: + cmd = [VHD_UTIL, "modify", OPT_LOG_ERR, "-s", str(size), "-n", path] + else: + cmd = [VHD_UTIL, "modify", "-s", str(size), "-n", path] + self._ioretry(cmd) + + @override + def getAllocatedSize(self, path: str) -> int: + ret = self._ioretry([VHD_UTIL, "query", OPT_LOG_ERR, "-a", "-n", path]) + return self._convertAllocatedSizeToBytes(int(ret)) + + @override + def killData(self, path: str) -> None: + """ + Zero out the disk (kill all data inside the VHD file). + """ + self._ioretry([VHD_UTIL, "modify", OPT_LOG_ERR, "-z", "-n", path]) + + @override + def getDepth(self, path: str) -> int: + """ + Get the VHD parent chain depth. + """ + text = cast(str, self._ioretry([VHD_UTIL, "query", OPT_LOG_ERR, "-d", "-n", path])) + depth = -1 + if text.startswith("chain depth:"): + depth = int(text.split(":")[1].strip()) + return depth + + @override + def getBlockBitmap(self, path: str) -> bytes: + text = cast(bytes, self._ioretry([VHD_UTIL, "read", OPT_LOG_ERR, "-B", "-n", path], text=False)) + return zlib.compress(text) + + @override + def coalesce(self, path: str) -> int: + """ + Coalesce the VHD, on success it returns the number of sectors coalesced. + """ + text = cast(str, self._ioretry([VHD_UTIL, "coalesce", OPT_LOG_ERR, "-n", path])) + match = re.match(r"^Coalesced (\d+) sectors", text) + if match: + return int(match.group(1)) + return 0 + + @override + def create(self, path: str, size: int, static: bool, msize: int = 0) -> None: + size_mb = size // (1024 * 1024) + cmd = [VHD_UTIL, "create", OPT_LOG_ERR, "-n", path, "-s", str(size_mb)] + if static: + cmd.append("-r") + if msize: + cmd.append("-S") + cmd.append(str(msize)) + self._ioretry(cmd) + + @override + def snapshot( + self, + path: str, + parent: str, + parentRaw: bool, + msize: int = 0, + checkEmpty: Optional[bool] = True + ) -> None: + cmd = [VHD_UTIL, "snapshot", OPT_LOG_ERR, "-n", path, "-p", parent] + if parentRaw: + cmd.append("-m") + if msize: + cmd.append("-S") + cmd.append(str(msize)) + if not checkEmpty: + cmd.append("-e") + self._ioretry(cmd) + + @override + def check( + self, + path: str, + ignoreMissingFooter: Optional[bool] = False, + fast: Optional[bool] = False + ) -> CowUtil.CheckResult: + cmd = [VHD_UTIL, "check", OPT_LOG_ERR, "-n", path] + if ignoreMissingFooter: + cmd.append("-i") + if fast: + cmd.append("-B") + try: + self._ioretry(cmd) + return CowUtil.CheckResult.Success + except util.CommandException as e: + if e.code in (errno.ENOENT, errno.EROFS, errno.EMEDIUMTYPE): + return CowUtil.CheckResult.Unavailable + return CowUtil.CheckResult.Fail + + @override + def revert(self, path: str, jFile: str) -> None: + self._ioretry([VHD_UTIL, "revert", OPT_LOG_ERR, "-n", path, "-j", jFile]) + + @override + def repair(self, path: str) -> None: + """ + Repairs a VHD. + """ + self._ioretry([VHD_UTIL, "repair", "-n", path]) + + @override + def validateAndRoundImageSize(self, size: int) -> int: + """ + Take the supplied vhd size, in bytes, and check it is positive and less + that the maximum supported size, rounding up to the next block boundary. + """ + if size < 0 or size > MAX_VHD_SIZE: + raise xs_errors.XenError( + "VDISize", + opterr="VDI size must be between 1 MB and %d MB" % (MAX_VHD_SIZE // (1024 * 1024)) + ) + + if size < MIN_VHD_SIZE: + size = MIN_VHD_SIZE + + return util.roundup(VHD_BLOCK_SIZE, size) + + @override + def getKeyHash(self, path: str) -> Optional[str]: + """ + Extract the hash of the encryption key from the header of an encrypted VHD. + """ + ret = cast(str, self._ioretry([VHD_UTIL, "key", "-p", "-n", path])).strip() + if ret == "none": + return None + vals = ret.split() + if len(vals) != 2: + util.SMlog("***** malformed output from vhd-util for VHD {}: \"{}\"".format(path, ret)) + return None + [_nonce, key_hash] = vals + return key_hash + + @override + def setKey(self, path: str, key_hash: str) -> None: + """ + Set the encryption key for a VHD. + """ + self._ioretry([VHD_UTIL, "key", "-s", "-n", path, "-H", key_hash]) + + @staticmethod + def _convertAllocatedSizeToBytes(size): + # Assume we have standard 2MB allocation blocks + return size * 2 * 1024 * 1024 + + @staticmethod + def _parseVHDInfo(line, extractUuidFunction): + vhdInfo = None + valueMap = line.split() + if len(valueMap) < 1 or valueMap[0].find("vhd=") == -1: + return None + for keyval in valueMap: + (key, val) = keyval.split("=") + if key == "vhd": + uuid = extractUuidFunction(val) + if not uuid: + util.SMlog("***** malformed output, no UUID: %s" % valueMap) + return None + vhdInfo = CowImageInfo(uuid) + vhdInfo.path = val + elif key == "scan-error": + vhdInfo.error = line + util.SMlog("***** VHD scan error: %s" % line) + break + elif key == "capacity": + vhdInfo.sizeVirt = int(val) + elif key == "size": + vhdInfo.sizePhys = int(val) + elif key == "hidden": + vhdInfo.hidden = int(val) + elif key == "parent" and val != "none": + vhdInfo.parentPath = val + vhdInfo.parentUuid = extractUuidFunction(val) + return vhdInfo diff --git a/tests/test_vhdutil.py b/tests/test_vhdutil.py index 6aa91e15..d32da18a 100644 --- a/tests/test_vhdutil.py +++ b/tests/test_vhdutil.py @@ -18,27 +18,27 @@ class TestVhdUtil(unittest.TestCase): def test_validate_and_round_min_size(self): - size = vhdutil.validate_and_round_vhd_size(2 * 1024 * 1024) + size = vhdutil.validateAndRoundImageSize(2 * 1024 * 1024) self.assertTrue(size == 2 * 1024 * 1024) def test_validate_and_round_max_size(self): - size = vhdutil.validate_and_round_vhd_size(vhdutil.MAX_VHD_SIZE) + size = vhdutil.validateAndRoundImageSize(vhdutil.MAX_VHD_SIZE) self.assertTrue(size == vhdutil.MAX_VHD_SIZE) def test_validate_and_round_odd_size_up_to_next_boundary(self): - size = vhdutil.validate_and_round_vhd_size(vhdutil.MAX_VHD_SIZE - 1) + size = vhdutil.validateAndRoundImageSize(vhdutil.MAX_VHD_SIZE - 1) self.assertTrue(size == vhdutil.MAX_VHD_SIZE) def test_validate_and_round_negative(self): with self.assertRaises(xs_errors.SROSError): - vhdutil.validate_and_round_vhd_size(-1) + vhdutil.validateAndRoundImageSize(-1) def test_validate_and_round_too_large(self): with self.assertRaises(xs_errors.SROSError): - vhdutil.validate_and_round_vhd_size(vhdutil.MAX_VHD_SIZE + 1) + vhdutil.validateAndRoundImageSize(vhdutil.MAX_VHD_SIZE + 1) @testlib.with_context def test_calc_overhead_empty_small(self, context):