Implement new partioning mechanism

This mechanism is capable to work with new objects model and new block
device utils.

Change-Id: I16cb7ec25aa4c6a6fabffc047f50a5758bb19c06
This commit is contained in:
Dmitry Bogun 2016-12-26 17:02:24 +02:00 committed by Andrii Ostapenko
parent 2bd7569719
commit a70b832447
2 changed files with 361 additions and 0 deletions

View File

@ -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)

View File

@ -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)