def calculate_volume_alloc_size(cls, preallocate, capacity, initial_size): """ Calculate the allocation size in bytes of the volume 'preallocate' - Sparse or Preallocated 'capacity' - the volume size in bytes 'initial_size' - optional, if provided the initial allocated size in bytes for sparse volumes """ if initial_size and preallocate == sc.PREALLOCATED_VOL: log.error("Initial size is not supported for preallocated volumes") raise se.InvalidParameterException("initial size", initial_size) if initial_size: max_size = cls.max_size(capacity, sc.COW_FORMAT) if initial_size > max_size: log.error( "The requested initial %s is bigger " "than the max size %s", initial_size, max_size) raise se.InvalidParameterException("initial_size", initial_size) if preallocate == sc.SPARSE_VOL: if initial_size: # TODO: if initial_size == max_size, we exceed the max_size # here. This should be fixed, but first we must check that # engine is not assuming that vdsm will increase initial size # like this. alloc_size = int(initial_size * QCOW_OVERHEAD_FACTOR) else: chunk_size_mb = config.getint("irs", "volume_utilization_chunk_mb") alloc_size = chunk_size_mb * constants.MEGAB else: alloc_size = capacity return alloc_size
def calculate_volume_alloc_size(cls, preallocate, capacity, initial_size): """ Calculate the allocation size in mb of the volume 'preallocate' - Sparse or Preallocated 'capacity' - the volume size in sectors 'initial_size' - optional, if provided the initial allocated size in sectors for sparse volumes """ if initial_size and preallocate == sc.PREALLOCATED_VOL: log.error("Initial size is not supported for preallocated volumes") raise se.InvalidParameterException("initial size", initial_size) if initial_size: capacity_bytes = capacity * sc.BLOCK_SIZE initial_size_bytes = initial_size * sc.BLOCK_SIZE max_size = cls.max_size(capacity_bytes, sc.COW_FORMAT) if initial_size_bytes > max_size: log.error("The requested initial %s is bigger " "than the max size %s", initial_size_bytes, max_size) raise se.InvalidParameterException("initial size", initial_size) if preallocate == sc.SPARSE_VOL: if initial_size: initial_size = int(initial_size * QCOW_OVERHEAD_FACTOR) alloc_size = ((initial_size + SECTORS_TO_MB - 1) / SECTORS_TO_MB) else: alloc_size = config.getint("irs", "volume_utilization_chunk_mb") else: alloc_size = (capacity + SECTORS_TO_MB - 1) / SECTORS_TO_MB return alloc_size
def ddWatchCopy(src, dst, stop, size, offset=0): """ Copy src to dst using dd command with stop abilities """ try: size = int(size) except ValueError: raise se.InvalidParameterException("size", "size = %s" % (size, )) try: offset = int(offset) except ValueError: raise se.InvalidParameterException("offset", "offset = %s" % (offset, )) left = size baseoffset = offset while left > 0: (iounit, count, iooffset) = _alignData(left, offset) oflag = None conv = "notrunc" if (iounit % 512) == 0: oflag = DIRECTFLAG else: conv += ",%s" % DATASYNCFLAG cmd = [ constants.EXT_DD, "if=%s" % src, "of=%s" % dst, "bs=%d" % iounit, "seek=%s" % iooffset, "skip=%s" % iooffset, "conv=%s" % conv, 'count=%s' % count ] if oflag: cmd.append("oflag=%s" % oflag) if not stop: (rc, out, err) = execCmd(cmd, nice=utils.NICENESS.HIGH, ioclass=utils.IOCLASS.IDLE) else: (rc, out, err) = watchCmd(cmd, stop=stop, nice=utils.NICENESS.HIGH, ioclass=utils.IOCLASS.IDLE) if rc: raise se.MiscBlockWriteException(dst, offset, size) if not validateDDBytes(err, iounit * count): raise se.MiscBlockWriteIncomplete(dst, offset, size) left = left % iounit offset = baseoffset + size - left return (rc, out, err)
def _validate_block_size(cls, block_size, version): """ Validate that block size can be used with this storage domain class. """ if version < 5: if block_size != sc.BLOCK_SIZE_512: raise se.InvalidParameterException('block_size', block_size) else: if block_size not in cls.supported_block_size: raise se.InvalidParameterException('block_size', block_size)
def _validate_size(self, size, initial_size, vol_format): if size % sc.BLOCK_SIZE != 0: self.log.debug("size %s not a multiple of the block size", size) raise se.InvalidParameterException("size", size) if initial_size is not None and vol_format != sc.COW_FORMAT: self.log.debug("initial_size is supported only for COW volumes") raise se.InvalidParameterException("initial_size", initial_size) if initial_size and initial_size % sc.BLOCK_SIZE != 0: self.log.debug("initial_size %s not a multiple of the block size", initial_size) raise se.InvalidParameterException("initial_size", initial_size)
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()
def _validate_block_and_alignment(cls, block_size, alignment, version): # For domain version prior version 5 block size has to ve 512b and # alignment has to be 1M. if version < 5: if block_size != sc.BLOCK_SIZE_512: raise se.InvalidParameterException('block_size', block_size) if alignment != sc.ALIGNMENT_1M: raise se.InvalidParameterException('alignment', alignment) if block_size not in cls.supported_block_size: raise se.InvalidParameterException('block_size', block_size) if alignment not in cls.supported_alignment: raise se.InvalidParameterException('alignment', alignment)
def validateCreateVolumeParams(self, volFormat, srcVolUUID, diskType=None, preallocate=None, add_bitmaps=False): """ Validate create volume parameters """ if volFormat not in sc.VOL_FORMAT: raise se.IncorrectFormat(volFormat) # Volumes with a parent must be cow if srcVolUUID != sc.BLANK_UUID and volFormat != sc.COW_FORMAT: raise se.IncorrectFormat(sc.type2name(volFormat)) if diskType is not None and diskType not in sc.VOL_DISKTYPE: raise se.InvalidParameterException("DiskType", diskType) if preallocate is not None and preallocate not in sc.VOL_TYPE: raise se.IncorrectType(preallocate) if add_bitmaps: if srcVolUUID == sc.BLANK_UUID: raise se.UnsupportedOperation( "Cannot add bitmaps for volume without parent volume", srcVolUUID=srcVolUUID, add_bitmaps=add_bitmaps) if not self.supports_bitmaps_operations(): raise se.UnsupportedOperation( "Cannot perform bitmaps operations on " "storage domain version < 4", domain_version=self.getVersion(), add_bitmaps=add_bitmaps)
def zero(device_path, size=None, task=_NullTask()): """ Zero a block device. Arguments: device_path (str): Path to block device to wipe size (int): Number of bytes to write. If not specified, use the device size. Size must be aligned to `vdsm.storage.constants.BLOCK_SIZE`. task (`storage.task.Task`): Task running this operation. If specified, the zero operation will be aborted if the task is aborted. Raises: `vdsm.common.exception.ActionStopped` if the wipe was aborted `vdsm.storage.exception.VolumesZeroingError` if writing to storage failed. `vdsm.storage.exception.InvalidParameterException` if size is not aligned to `vdsm.storage.constants.BLOCK_SIZE`. """ if size is None: # Always aligned to LVM extent size (128MiB). size = fsutils.size(device_path) elif size % sc.BLOCK_SIZE: raise se.InvalidParameterException("size", size) log.info("Zeroing device %s (size=%d)", device_path, size) with utils.stopwatch("Zero device %s" % device_path, level=logging.INFO, log=log): try: op = blkdiscard.zeroout_operation(device_path, size) with task.abort_callback(op.abort): op.run() except se.StorageException as e: raise se.VolumesZeroingError("Zeroing device %s failed: %s" % (device_path, e))
def _create_cow_volume( cls, dom, vol_id, capacity, vol_path, initial_size, vol_parent, img_id, src_img_id, src_vol_id): """ specific implementation of _create() for COW volumes. All the exceptions are properly handled and logged in volume.create() """ if initial_size: cls.log.error("initial size is not supported " "for file-based volumes") raise se.InvalidParameterException("initial size", initial_size) cls._truncate_volume(vol_path, 0, vol_id, dom) if not vol_parent: cls.log.info("Request to create COW volume %s with capacity = %s", vol_path, capacity) operation = qemuimg.create(vol_path, size=capacity, format=sc.fmt2str(sc.COW_FORMAT), qcow2Compat=dom.qcow2_compat()) operation.run() else: # Create hardlink to template and its meta file cls.log.info("Request to create snapshot %s/%s of volume %s/%s " "with capacity %s", img_id, vol_id, src_img_id, src_vol_id, capacity) vol_parent.clone(vol_path, sc.COW_FORMAT, capacity) # Forcing the volume permissions in case one of the tools we use # (dd, qemu-img, etc.) will mistakenly change the file permissions. cls._set_permissions(vol_path, dom) return (vol_path,)
def create(self, size, vol_format, disk_type, desc, parent=None, initial_size=None): """ Create metadata file artifact, lease file, and volume file on storage. """ prealloc = self._get_volume_preallocation(vol_format) self._validate_create_params(vol_format, parent, prealloc) if initial_size is not None: self.log.debug("initial_size is not supported for file volumes") raise se.InvalidParameterException("initial_size", initial_size) self._validate_size(size, initial_size, vol_format) if not self.is_image(): self._create_image_artifact() self._create_metadata_artifact(size, vol_format, prealloc, disk_type, desc, parent) self._create_lease_file() self._create_volume_file(vol_format, size) self._initialize_volume(vol_format, size)
def _create(cls, dom, imgUUID, volUUID, size, volFormat, preallocate, volParent, srcImgUUID, srcVolUUID, volPath, initialSize=None): """ Class specific implementation of volumeCreate. All the exceptions are properly handled and logged in volume.create() """ if initialSize: cls.log.error("initialSize is not supported for file-based " "volumes") raise se.InvalidParameterException("initial size", initialSize) sizeBytes = size * BLOCK_SIZE truncSize = sizeBytes if volFormat == sc.RAW_FORMAT else 0 try: oop.getProcessPool(dom.sdUUID).truncateFile( volPath, truncSize, mode=sc.FILE_VOLUME_PERMISSIONS, creatExcl=True) except OSError as e: if e.errno == errno.EEXIST: raise se.VolumeAlreadyExists(volUUID) raise if preallocate == sc.PREALLOCATED_VOL: try: operation = fallocate.allocate(volPath, sizeBytes) with vars.task.abort_callback(operation.abort): with utils.stopwatch("Preallocating volume %s" % volPath): operation.run() except exception.ActionStopped: raise except Exception: cls.log.error("Unexpected error", exc_info=True) raise se.VolumesZeroingError(volPath) if not volParent: cls.log.info("Request to create %s volume %s with size = %s " "sectors", sc.type2name(volFormat), volPath, size) if volFormat == sc.COW_FORMAT: qemuimg.create(volPath, size=sizeBytes, format=sc.fmt2str(volFormat), qcow2Compat=dom.qcow2_compat()) else: # Create hardlink to template and its meta file cls.log.info("Request to create snapshot %s/%s of volume %s/%s", imgUUID, volUUID, srcImgUUID, srcVolUUID) volParent.clone(volPath, volFormat) # Forcing the volume permissions in case one of the tools we use # (dd, qemu-img, etc.) will mistakenly change the file permissiosn. dom.oop.os.chmod(volPath, sc.FILE_VOLUME_PERMISSIONS) return (volPath,)
def validateDDBytes(ddstderr, size): log.debug("err: %s, size: %s" % (ddstderr, size)) try: size = int(size) except (ValueError, ): raise se.InvalidParameterException("size", str(size)) if len(ddstderr) != 3: raise se.InvalidParameterException("len(ddstderr)", ddstderr) try: xferred = int(ddstderr[2].split()[0]) except (ValueError, ): raise se.InvalidParameterException("ddstderr", ddstderr[2]) if xferred != size: return False return True
def getIntParam(optDict, key, default): res = optDict.get(key, default) if res is None: return res try: return int(res) except ValueError: raise se.InvalidParameterException(key, res)
def validateSize(capacity, name): """ Validate number of bytes as string. Raises InvalidParameterException if value is not a string or if it could not be converted to integer. """ if not isinstance(capacity, six.string_types): log.error("Number of blocks as int is not supported, use size in " "bytes as string") raise se.InvalidParameterException(name, capacity) return validateN(capacity, name)
def alignment(block_size, max_hosts): if max_hosts < 1 or max_hosts > sc.HOSTS_MAX: raise se.InvalidParameterException('max_hosts', max_hosts) if block_size not in (sc.BLOCK_SIZE_512, sc.BLOCK_SIZE_4K): raise se.InvalidParameterException('block_size', block_size) if block_size == sc.BLOCK_SIZE_512: # Only this block size is supported on 512b blocks # Supports up to 2000 hosts. return sc.ALIGNMENT_1M if max_hosts > sc.HOSTS_4K_4M: return sc.ALIGNMENT_8M if max_hosts > sc.HOSTS_4K_2M: return sc.ALIGNMENT_4M if max_hosts > sc.HOSTS_4K_1M: return sc.ALIGNMENT_2M return sc.ALIGNMENT_1M
def validateUUID(uuid, name="uuid", blank=True): """ Ensure that uuid structure is 32 bytes long and is of the form: 8-4-4-4-12 (where each number depicts the amount of hex digits) Even though UUIDs can contain capital letters (because HEX strings are case insensitive) we usually compare uuids with the `==` operator, having uuids with upper case letters will cause unexpected bug so we filter them out. The blank argument specifies if it's allowed for the uuid to be blank or not. """ try: m = UUID_REGEX.match(uuid) except TypeError: raise se.InvalidParameterException(name, uuid) if m is None: raise se.InvalidParameterException(name, uuid) if not blank and uuid == UUID_BLANK: raise se.InvalidParameterException(name, uuid)
def _create_raw_volume( cls, dom, vol_id, capacity, vol_path, initial_size, preallocate): """ Specific implementation of _create() for RAW volumes. All the exceptions are properly handled and logged in volume.create() """ if initial_size is None: alloc_size = capacity else: if preallocate == sc.SPARSE_VOL: cls.log.error("initial size is not supported for file-based " "sparse volumes") raise se.InvalidParameterException( "initial size", initial_size) if initial_size > capacity: cls.log.error("initial_size %d out of range 0-%s", initial_size, capacity) raise se.InvalidParameterException( "initial size", initial_size) # Always allocate at least 4k, so qemu-img can allocated the first # block of the image, helping qemu to probe the alignment later. alloc_size = max(initial_size, sc.BLOCK_SIZE_4K) cls._truncate_volume(vol_path, 0, vol_id, dom) cls._allocate_volume(vol_path, alloc_size, preallocate=preallocate) if alloc_size < capacity: qemuimg.resize(vol_path, capacity, format=qemuimg.FORMAT.RAW) cls.log.info("Request to create RAW volume %s with capacity = %s", vol_path, capacity) # Forcing volume permissions in case qemu-img changed the permissions. cls._set_permissions(vol_path, dom) return (vol_path,)
def _validate_create_params(self, vol_format, parent, prealloc): # XXX: Remove these when support is added: if parent: raise NotImplementedError("parent_vol_id not supported") if self.is_image() and not parent: self.log.debug("parent not provided when creating a volume in an" "existing image.") raise se.InvalidParameterException("parent", parent) parent_vol_id = parent.vol_id if parent else sc.BLANK_UUID self.sd_manifest.validateCreateVolumeParams( vol_format, parent_vol_id, preallocate=prealloc)
def validateSize(size, name): """ Validate number of bytes as string and convert to number of sectors, rounding up to next sectors. Raises InvalidParameterException if value is not a string or if it could not be converted to integer. """ if not isinstance(size, basestring): log.error("Number of sectors as int is not supported, use size in " "bytes as string") raise se.InvalidParameterException("size", size) size = validateN(size, name) return (size + SECTOR_SIZE - 1) / SECTOR_SIZE
def _create_raw_volume(cls, dom, vol_id, capacity, vol_path, initial_size, preallocate): """ Specific implementation of _create() for RAW volumes. All the exceptions are properly handled and logged in volume.create() """ if initial_size is not None: if preallocate == sc.SPARSE_VOL: cls.log.error("initial size is not supported for file-based " "sparse volumes") raise se.InvalidParameterException("initial size", initial_size) if initial_size > capacity: cls.log.error("initial_size %d out of range 0-%s", initial_size, capacity) raise se.InvalidParameterException("initial size", initial_size) # In the past engine called with initial_size=0 to deffer # preallocation to qemu-img convert, but we learned that this is a # bad idea on older NFS versions. We ignore initial_size now, and # always allocate the entire image. qemu-img convert is always # using -n so it does not affect image allocation. cls.log.warning("Ignoring initial_size=%s", initial_size) cls.log.info("Request to create RAW volume %s with capacity = %s", vol_path, capacity) cls._truncate_volume(vol_path, 0, vol_id, dom) cls._allocate_volume(vol_path, capacity, preallocate=preallocate) # Forcing volume permissions in case qemu-img changed the permissions. cls._set_permissions(vol_path, dom) return (vol_path, )
def _create_raw_volume(cls, dom, vol_id, size, vol_path, initial_size, preallocate): """ Specific implementation of _create() for RAW volumes. All the exceptions are properly handled and logged in volume.create() """ if initial_size is None: alloc_size = size else: if preallocate == sc.SPARSE_VOL: cls.log.error("initial size is not supported for file-based " "sparse volumes") raise se.InvalidParameterException("initial size", initial_size) if initial_size < 0 or initial_size > size: cls.log.error("initial_size %d out of range 0-%s", initial_size, size) raise se.InvalidParameterException("initial size", initial_size) alloc_size = initial_size cls._truncate_volume(vol_path, size, vol_id, dom) if preallocate == sc.PREALLOCATED_VOL and alloc_size != 0: cls._fallocate_volume(vol_path, alloc_size) cls.log.info("Request to create RAW volume %s with size = %s bytes", vol_path, size) # Forcing the volume permissions in case one of the tools we use # (dd, qemu-img, etc.) will mistakenly change the file permissions. cls._set_permissions(vol_path, dom) return (vol_path, )
def validateCreateVolumeParams(cls, volFormat, srcVolUUID, diskType=None, preallocate=None): """ Validate create volume parameters """ if volFormat not in sc.VOL_FORMAT: raise se.IncorrectFormat(volFormat) # Volumes with a parent must be cow if srcVolUUID != sc.BLANK_UUID and volFormat != sc.COW_FORMAT: raise se.IncorrectFormat(sc.type2name(volFormat)) if diskType is not None and diskType not in sc.VOL_DISKTYPE: raise se.InvalidParameterException("DiskType", diskType) if preallocate is not None and preallocate not in sc.VOL_TYPE: raise se.IncorrectType(preallocate)
def zero(device_path, size=None, task=None): """ Zero a block device. Arguments: device_path (str): Path to block device to wipe size (int): Number of bytes to write. If not specified, use the device size. Size must be aligned to `vdsm.storage.constants.BLOCK_SIZE`. task (`storage.task.Task`): Task running this operation. If specified, the zero operation will be aborted if the task is aborted. Raises: `vdsm.common.exception.ActionStopped` if the wipe was aborted `vdsm.storage.exception.VolumesZeroingError` if writing to storage failed. `vdsm.storage.exception.InvalidParameterException` if size is not aligned to `vdsm.storage.constants.BLOCK_SIZE`. """ if size is None: # Always aligned to LVM extent size (128MiB). size = fsutils.size(device_path) elif size % sc.BLOCK_SIZE: raise se.InvalidParameterException("size", size) log.info("Zeroing device %s (size=%d)", device_path, size) with utils.stopwatch("Zero device %s" % device_path, level=logging.INFO, log=log): try: # Write optimal size blocks. Images are always aligned to # optimal size blocks, so we typically have only one call. blocks = size // OPTIMAL_BLOCK_SIZE if blocks > 0: _zero(device_path, 0, OPTIMAL_BLOCK_SIZE, blocks, task=task) # When zeroing special volumes size may not be aligned to # optimal block size, so we need to write the last block. rest = size % OPTIMAL_BLOCK_SIZE if rest > 0: offset = blocks * OPTIMAL_BLOCK_SIZE _zero(device_path, offset, rest, 1, task=task) except se.StorageException as e: raise se.VolumesZeroingError("Zeroing device %s failed: %s" % (device_path, e))
def zero(device_path, size=None, task=_NullTask()): """ Zero a block device. Arguments: device_path (str): Path to block device to wipe size (int): Number of bytes to write. If not specified, use the device size. Size must be aligned to `vdsm.storage.constants.BLOCK_SIZE`. task (`storage.task.Task`): Task running this operation. If specified, the zero operation will be aborted if the task is aborted. Raises: `vdsm.common.exception.ActionStopped` if the wipe was aborted `vdsm.storage.exception.VolumesZeroingError` if writing to storage failed. `vdsm.storage.exception.InvalidParameterException` if size is not aligned to `vdsm.storage.constants.BLOCK_SIZE`. """ if size is None: # Always aligned to LVM extent size (128MiB). size = fsutils.size(device_path) elif size % sc.BLOCK_SIZE: raise se.InvalidParameterException("size", size) log.info("Zeroing device %s (size=%d)", device_path, size) with utils.stopwatch("Zero device %s" % device_path, level=logging.INFO, log=log): zero_method = config.get('irs', 'zero_method') try: if zero_method == "blkdiscard": _zero_blkdiscard(device_path, size, task) elif zero_method == "dd": _zero_dd(device_path, size, task) else: raise exception.InvalidConfiguration( reason="Unsupported value for irs:zero_method", zero_method=zero_method) except se.StorageException as e: raise se.VolumesZeroingError("Zeroing device %s failed: %s" % (device_path, e))
def validateCreateVolumeParams(cls, volFormat, srcVolUUID, diskType=None, preallocate=None, add_bitmaps=False): """ Validate create volume parameters """ if volFormat not in sc.VOL_FORMAT: raise se.IncorrectFormat(volFormat) # Volumes with a parent must be cow if srcVolUUID != sc.BLANK_UUID and volFormat != sc.COW_FORMAT: raise se.IncorrectFormat(sc.type2name(volFormat)) if diskType is not None and diskType not in sc.VOL_DISKTYPE: raise se.InvalidParameterException("DiskType", diskType) if preallocate is not None and preallocate not in sc.VOL_TYPE: raise se.IncorrectType(preallocate) if add_bitmaps and srcVolUUID == sc.BLANK_UUID: raise se.UnsupportedOperation( "Cannot add bitmaps for volume without parent volume", srcVolUUID=srcVolUUID, add_bitmaps=add_bitmaps)
def validateID(cls, taskID): if not taskID or "." in taskID: raise se.InvalidParameterException("taskID", taskID)
def validateN(number, name): n = validateInt(number, name) if n < 0: raise se.InvalidParameterException(name, number) return n
def validateInt(number, name): try: return int(number) except: raise se.InvalidParameterException(name, number)
def _enum(params, name, values): value = _required(params, name) if value not in values: raise se.InvalidParameterException(name, value) return value