From a70b83244744619ca5bbc014a0da18f9ef9613c6 Mon Sep 17 00:00:00 2001 From: Dmitry Bogun Date: Mon, 26 Dec 2016 17:02:24 +0200 Subject: [PATCH] Implement new partioning mechanism This mechanism is capable to work with new objects model and new block device utils. Change-Id: I16cb7ec25aa4c6a6fabffc047f50a5758bb19c06 --- bareon/drivers/deploy/generic.py | 322 +++++++++++++++++++++++++++++++ bareon/utils/block_device.py | 39 ++++ 2 files changed, 361 insertions(+) diff --git a/bareon/drivers/deploy/generic.py b/bareon/drivers/deploy/generic.py index b7cf5a0..6bce23d 100644 --- a/bareon/drivers/deploy/generic.py +++ b/bareon/drivers/deploy/generic.py @@ -13,18 +13,26 @@ # See the License for the specific language governing permissions and # limitations under the License. +import abc +import itertools import json import os import re from oslo_config import cfg from oslo_log import log as logging +import six from bareon.actions import partitioning from bareon.drivers.deploy.base import BaseDeployDriver from bareon.drivers.deploy import mixins from bareon import errors +from bareon import objects +from bareon.utils import block_device +from bareon.utils import fs as fu from bareon.utils import grub as gu +from bareon.utils import lvm as lu +from bareon.utils import md as mu from bareon.utils import partition as pu from bareon.utils import utils @@ -447,3 +455,317 @@ class PartitionSchemaCompareTool(object): p['size'] = p['end'] - p['begin'] del p['begin'] del p['end'] + + +@six.add_metaclass(abc.ABCMeta) +class AbstractPartitionPolicy(object): + space_allocation_accuracy = block_device.SizeUnit(1, 'MiB') + + def __init__(self, deploy, storage): + self.deploy_driver = deploy + self.storage_claim = storage + + self.dev_finder = block_device.DeviceFinder() + + self.partition = self._make_partition_plan() + + @abc.abstractmethod + def __call__(self): + pass + + def dev_by_guid(self, guid): + needle = 'disk/by-partuuid/{}'.format(guid.lower()) + dev_info = self.dev_finder('path', needle) + return dev_info['device'] + + def _make_partition_plan(self): + disks = {} + for claim in self.storage_claim.items_by_kind( + objects.block_device.Disk): + LOG.info('Make partition plan for "%s"', claim.dev) + disks[claim.dev] = self._disk_partition(claim) + + return disks + + def _disk_partition(self, claim): + disk = block_device.Disk.new_by_scan(claim.dev, partitions=False) + disk.allocate_accuracy = self.space_allocation_accuracy + + remaining = None + from_tail = [] + for idx, claim in enumerate(claim.items): + if claim.size.kind == claim.size.KIND_BIGGEST: + remaining = claim + from_tail = claim.items[idx + 1:] + break + self._apply_claim(disk, claim) + + from_tail.reverse() + for claim in from_tail: + self._apply_claim(disk, claim, from_tail=True) + + if remaining is not None: + self._apply_claim(disk, remaining) + + return disk + + def _lvm_vg_partition(self, claim): + vg = block_device.LVM.new_by_scan(claim.idnr, lv=False) + vg.allocate_accuracy = self.space_allocation_accuracy + + remaining = None + for lv in claim.items_by_kind(objects.block_device.LVMlv): + if lv.size.kind == lv.size.KIND_BIGGEST: + remaining = lv + continue + self._apply_claim(vg, lv) + + if remaining is not None: + self._apply_claim(vg, remaining) + + return vg + + def _handle_filesystems(self): + for claim in self.storage_claim.items_by_kind( + objects.block_device.Disk): + self._resolv_disk_partitions(claim) + + for claim in self.storage_claim.items_by_kind( + objects.block_device.FileSystemMixin, recursion=True): + self._make_filesystem(claim) + + def _resolv_disk_partitions(self, claim_disk): + partition_disk = self.partition[claim_disk.dev] + actual_disk = block_device.Disk.new_by_scan(claim_disk.dev) + + fuzzy_factor = actual_disk.sizeunit_to_blocks( + self.space_allocation_accuracy) + + claim_segments = [] + actual_segments = [] + for storage, target in ( + (partition_disk, claim_segments), + (actual_disk, actual_segments)): + for segment in storage.segments: + if segment.kind != segment.KIND_BUSY: + continue + + segment.set_fuzzy_cmp_factor(fuzzy_factor) + target.append(segment) + + idx_iter = itertools.count() + for claim, actual in itertools.izip_longest( + claim_segments, actual_segments): + idx = next(idx_iter) + if claim == actual: + if isinstance( + claim.payload, objects.block_device.Partition): + claim.payload.guid = actual.payload.guid + continue + + raise errors.PartitionSchemaMismatchError( + 'Unable to resolv claim devices into physical devices. ' + 'Claim and physical devices partitions are different. ' + '(dev={}, {}: {!r} != {!r})'.format( + claim_disk.dev, idx, claim, actual)) + + def _make_filesystem(self, claim): + if not claim.file_system: + return + + if isinstance(claim, objects.block_device.Partition): + dev = self.dev_by_guid(claim.guid) + elif isinstance(claim, objects.block_device.MDRaid): + dev = claim.name + elif isinstance(claim, objects.block_device.LVMlv): + dev = claim.dev + else: + raise errors.InternalError(exc_info=False) + + # FIXME(dbogun): label + fu.make_fs(claim.file_system, '', '', dev) + + def _apply_claim(self, storage, claim, from_tail=False): + segment = claim.size(storage, from_tail=from_tail) + if isinstance(claim, objects.block_device.BlockDevice): + if claim.is_service: + segment.set_is_service() + segment.payload = claim + + +class PartitionPolicyClean(AbstractPartitionPolicy): + def __call__(self): + LOG.info('Apply "clean" partitions policy') + + self._remove_all_compound_devices() + + for dev in sorted(self.partition): + self._handle_disk(self.partition[dev]) + + # update dev finder after all changes to disks + self.dev_finder = block_device.DeviceFinder() + + for md in self.storage_claim.items_by_kind( + objects.block_device.MDRaid): + self._handle_mdraid(md) + for vg in self.storage_claim.items_by_kind(objects.block_device.LVMvg): + self._handle_lvm(vg) + + self._handle_filesystems() + + def _remove_all_compound_devices(self): + mu.mdclean_all() + lu.lvremove_all() + lu.vgremove_all() + lu.pvremove_all() + + def _handle_disk(self, disk): + gdisk = block_device.GDisk(disk.dev) + + gdisk.zap() + try: + idx = itertools.count(1) + for segment in disk.segments: + if segment.is_free(): + continue + partition = block_device.Partition.new_by_disk_segment( + segment, next(idx), segment.payload.guid_code) + partition.guid = segment.payload.guid + segment.payload.guid = gdisk.new(partition) + finally: + pu.reread_partitions(disk.dev) + + def _handle_mdraid(self, md): + components = set() + for item in md.items: + components.add(self.dev_by_guid(item.guid)) + + mu.mdcreate(md.name, md.level, sorted(components)) + + def _handle_lvm(self, vg): + components = set() + for pv in vg.items_by_kind(objects.block_device.LVMpv): + dev = self.dev_by_guid(pv.guid) + components.add(dev) + + args = {} + if pv.meta_size is not None: + args['metadatasize'] = pv.meta_size.size.in_unit( + 'MiB').value_int + lu.pvcreate(dev, **args) + + lu.vgcreate(vg.idnr, *sorted(components)) + + partition = self._lvm_vg_partition(vg) + for segment in partition.segments: + if segment.kind != segment.KIND_BUSY: + continue + lu.lvcreate(vg.idnr, segment.payload.name, segment.size) + + def _resolv_disk_partitions(self, claim_dist): + """Dummy to avoid already done operation + + Actual disk partition resolv have happened in __call__ method, during + disks partitioning. + """ + + +class PartitionPolicyNailgun(PartitionPolicyClean): + _respect_keep_data = True + + def __call__(self): + self._respect_keep_data = self._check_keep_data_claim() + if self._respect_keep_data: + LOG.debug('Some of fs has keep_data (preserve) flag, ' + 'skipping partitioning') + + self._handle_filesystems() + return + + super(PartitionPolicyNailgun, self).__call__() + + def _check_keep_data_claim(self): + for item in itertools.chain( + self.storage_claim.items_by_kind( + objects.block_device.FileSystemMixin, recursion=True), + self.storage_claim.items_by_kind(objects.block_device.LVMvg)): + if not item.keep_data_flag: + continue + break + else: + return False + return True + + def _make_filesystem(self, claim): + if self._respect_keep_data and claim.keep_data_flag: + return + + super(PartitionPolicyNailgun, self)._make_filesystem(claim) + + +class PartitionPolicyVerify(AbstractPartitionPolicy): + _lvm_fuzzy_cmp_factor = 2 + + def __call__(self): + LOG.info('Apply "verify" partitions policy') + + for dev in self.partition: + self._handle_disk(self.partition[dev]) + + for vg in self.storage_claim.items_by_kind(objects.block_device.LVMvg): + self._handle_lvm(vg) + + self._handle_filesystems() + + def _handle_disk(self, disk): + actual_disk = block_device.Disk.new_by_scan(disk.dev) + actual_partition = self._grab_storage_segments(actual_disk) + desired_partition = self._grab_storage_segments(disk) + + if actual_partition == desired_partition: + return + + self._report_mismatch(disk.dev, desired_partition, actual_partition) + + def _handle_lvm(self, vg_claim): + try: + vg = block_device.LVM.new_by_scan(vg_claim.idnr) + actual_partition = self._grab_storage_segments( + vg, self._lvm_fuzzy_cmp_factor) + + vg = self._lvm_vg_partition(vg_claim) + desired_partition = self._grab_storage_segments( + vg, self._lvm_fuzzy_cmp_factor) + except errors.VGNotFoundError: + raise errors.PartitionSchemaMismatchError( + 'There is no LVMvg {}'.format(vg_claim.idnr)) + + if actual_partition == desired_partition: + return + + self._report_mismatch( + vg_claim.idnr, desired_partition, actual_partition) + + def _grab_storage_segments(self, storage, factor=None): + if factor is None: + factor = storage.sizeunit_to_blocks(self.space_allocation_accuracy) + + result = [] + for segment in storage.segments: + if segment.kind != segment.KIND_BUSY: + continue + segment.set_fuzzy_cmp_factor(factor) + result.append(segment) + + return result + + # TODO(dbogun): increase verbosity + def _report_mismatch(self, dev, desired, actual): + raise errors.PartitionSchemaMismatchError( + 'Partition mismatch on {}'.format(dev)) + + def _make_filesystem(self, claim): + if claim.keep_data_flag: + return + + super(PartitionPolicyVerify, self)._make_filesystem(claim) diff --git a/bareon/utils/block_device.py b/bareon/utils/block_device.py index f94926d..ae1be28 100644 --- a/bareon/utils/block_device.py +++ b/bareon/utils/block_device.py @@ -32,6 +32,38 @@ from bareon.utils import utils LOG = logging.getLogger(__name__) +class GDisk(object): + def __init__(self, dev): + self.dev = dev + + def zap(self): + LOG.info('Erase block device "%s"', self.dev) + utils.execute('sgdisk', '--zap-all', self.dev) + + def new(self, partition): + LOG.info( + 'Create new partition %d:%d (0x%04x) on %s', + partition.begin, partition.end, partition.code, self.dev) + utils.execute( + 'sgdisk', '--new={}:{}:{}'.format( + partition.index, partition.begin, partition.end), self.dev) + utils.execute('sgdisk', '--typecode={}:{:04x}'.format( + partition.index, partition.code), self.dev) + if partition.index < 5: + utils.execute('sgdisk', '--change-name={}:{}'.format( + partition.index, 'primary'), self.dev) + + if partition.guid is not None: + guid = partition.guid + utils.execute('sgdisk', '--disk-guid={}'.format(guid)) + else: + output = utils.execute( + 'sgdisk', '--info', '{}'.format(partition.index), + self.dev)[0] + guid = _GDiskInfo(output).guid + return guid + + class DeviceFinder(object): def __init__(self): self.dev_list = [] @@ -833,6 +865,13 @@ class LVMSegment(AbstractSegment): class Partition(BlockDevicePayload): suffix_number = None + @classmethod + def new_by_disk_segment(cls, space, index, code): + block = _BlockDevice( + None, space.size, space.owner.block_size, + physical_block_size=space.owner.physical_block_size) + return cls(space.owner, block, space.begin, index, code) + def __init__(self, disk, block, begin, index, code, guid=None, attributes=0): super(Partition, self).__init__(block, guid=guid)