8fe4fff91f
This is a contribution of features made in the scope of Cray Bareon adoption. Even though changes affect a lot of code, they are not breaking. To proove that we have created a functional test that covers existing nailgun deployment flow (see /tests_functional/test_nailgun.py) We have made Manager a deploy_driver. Current nailgun's manager has been moved to nailgun deploy driver, which is default. Most of the changes made in the scope of the project are enclosed within Ironic data driver and Ironic (swift & rsync) deploy drivers. To make this review easier I propose the following order: Review changes to existing code: - review changes to object model - review changes related to splitting deploy driver / base drivers - review changes to common utils Review the new code: - review Ironic data driver - review Ironic deploy drivers Change-Id: Id2d32a7574e6fcafee09490c39fb114c80407db7 Implements: blueprint size-unit-conversion-and-relative-sizing Implements: blueprint policy-based-partitioning Implements: blueprint multi-image-deployment Implements: blueprint rsync-image-deployment
275 lines
9.8 KiB
Python
275 lines
9.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2015 Mirantis, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
import os
|
|
|
|
from bareon import errors
|
|
from bareon.objects.partition.fs import FileSystem
|
|
from bareon.objects.partition.lv import LogicalVolume
|
|
from bareon.objects.partition.md import MultipleDevice
|
|
from bareon.objects.partition.parted import Parted
|
|
from bareon.objects.partition.pv import PhysicalVolume
|
|
from bareon.objects.partition.vg import VolumeGroup
|
|
|
|
from bareon.openstack.common import log as logging
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class PartitionScheme(object):
|
|
def __init__(self):
|
|
self.parteds = []
|
|
self.mds = []
|
|
self.pvs = []
|
|
self.vgs = []
|
|
self.lvs = []
|
|
self.fss = []
|
|
|
|
def add_parted(self, **kwargs):
|
|
parted = Parted(**kwargs)
|
|
self.parteds.append(parted)
|
|
return parted
|
|
|
|
def add_pv(self, **kwargs):
|
|
pv = PhysicalVolume(**kwargs)
|
|
self.pvs.append(pv)
|
|
return pv
|
|
|
|
def add_vg(self, **kwargs):
|
|
vg = VolumeGroup(**kwargs)
|
|
self.vgs.append(vg)
|
|
return vg
|
|
|
|
def add_lv(self, **kwargs):
|
|
lv = LogicalVolume(**kwargs)
|
|
self.lvs.append(lv)
|
|
return lv
|
|
|
|
def add_fs(self, **kwargs):
|
|
fs = FileSystem(**kwargs)
|
|
self.fss.append(fs)
|
|
return fs
|
|
|
|
def add_md(self, **kwargs):
|
|
mdkwargs = {}
|
|
mdkwargs['name'] = kwargs.get('name') or self.md_next_name()
|
|
mdkwargs['level'] = kwargs.get('level') or 'mirror'
|
|
mdkwargs['metadata'] = kwargs.get('metadata') or 'default'
|
|
md = MultipleDevice(**mdkwargs)
|
|
self.mds.append(md)
|
|
return md
|
|
|
|
def md_by_name(self, name):
|
|
return next((x for x in self.mds if x.name == name), None)
|
|
|
|
def md_by_mount(self, mount):
|
|
fs = self.fs_by_mount(mount)
|
|
if fs:
|
|
return self.md_by_name(fs.device)
|
|
|
|
def md_attach_by_mount(self, device, mount, spare=False, **kwargs):
|
|
md = self.md_by_mount(mount)
|
|
if not md:
|
|
md = self.add_md(**kwargs)
|
|
fskwargs = {}
|
|
fskwargs['device'] = md.name
|
|
fskwargs['mount'] = mount
|
|
fskwargs['fs_type'] = kwargs.pop('fs_type', None)
|
|
fskwargs['fs_options'] = kwargs.pop('fs_options', None)
|
|
fskwargs['fs_label'] = kwargs.pop('fs_label', None)
|
|
self.add_fs(**fskwargs)
|
|
md.add_spare(device) if spare else md.add_device(device)
|
|
return md
|
|
|
|
def md_next_name(self):
|
|
count = 0
|
|
while True:
|
|
name = '/dev/md%s' % count
|
|
if name not in [md.name for md in self.mds]:
|
|
return name
|
|
if count >= 127:
|
|
raise errors.MDAlreadyExistsError(
|
|
'Error while generating md name: '
|
|
'names from /dev/md0 to /dev/md127 seem to be busy, '
|
|
'try to generate md name manually')
|
|
count += 1
|
|
|
|
def partition_by_name(self, name):
|
|
return next((parted.partition_by_name(name)
|
|
for parted in self.parteds
|
|
if parted.partition_by_name(name)), None)
|
|
|
|
def vg_by_name(self, vgname):
|
|
return next((x for x in self.vgs if x.name == vgname), None)
|
|
|
|
def pv_by_name(self, pvname):
|
|
return next((x for x in self.pvs if x.name == pvname), None)
|
|
|
|
def vg_attach_by_name(self, pvname, vgname,
|
|
metadatasize=16, metadatacopies=2):
|
|
vg = self.vg_by_name(vgname) or self.add_vg(name=vgname)
|
|
pv = self.pv_by_name(pvname) or self.add_pv(
|
|
name=pvname, metadatasize=metadatasize,
|
|
metadatacopies=metadatacopies)
|
|
vg.add_pv(pv.name)
|
|
|
|
def fs_by_mount(self, mount, os_id=None):
|
|
found = filter(lambda x: (x.mount and x.mount == mount), self.fss)
|
|
if os_id:
|
|
found = filter(lambda x: (x.os_id and os_id in x.os_id), found)
|
|
if found:
|
|
return found[0]
|
|
|
|
def fs_by_device(self, device):
|
|
return next((x for x in self.fss if x.device == device), None)
|
|
|
|
def fs_sorted_by_depth(self, os_id=None, reverse=False):
|
|
"""Getting file systems sorted by path length.
|
|
|
|
Shorter paths earlier.
|
|
['/', '/boot', '/var', '/var/lib/mysql']
|
|
:param reverse: Sort backward (Default: False)
|
|
"""
|
|
def key(x):
|
|
return x.mount.rstrip(os.path.sep).count(os.path.sep)
|
|
|
|
sorted_fss = sorted(self.fss, key=key, reverse=reverse)
|
|
return filter(lambda fs: self._os_filter(fs, os_id), sorted_fss)
|
|
|
|
def _os_filter(self, file_system, os_id):
|
|
if os_id:
|
|
return os_id in file_system.os_id
|
|
else:
|
|
return True
|
|
|
|
def fs_by_os_id(self, os_id):
|
|
return filter(lambda fs: self._os_filter(fs, os_id), self.fss)
|
|
|
|
def lv_by_device_name(self, device_name):
|
|
return next((x for x in self.lvs if x.device_name == device_name),
|
|
None)
|
|
|
|
def root_device(self):
|
|
fs = self.fs_by_mount('/')
|
|
if not fs:
|
|
raise errors.WrongPartitionSchemeError(
|
|
'Error while trying to find root device: '
|
|
'root file system not found')
|
|
return fs.device
|
|
|
|
def boot_device(self, grub_version=2):
|
|
# We assume /boot is a separate partition. If it is not
|
|
# then we try to use root file system
|
|
boot_fs = self.fs_by_mount('/boot') or self.fs_by_mount('/')
|
|
if not boot_fs:
|
|
raise errors.WrongPartitionSchemeError(
|
|
'Error while trying to find boot device: '
|
|
'boot file system not fount, '
|
|
'it must be a separate mount point')
|
|
|
|
if grub_version == 1:
|
|
# Legacy GRUB has a limitation. It is not able to mount MD devices.
|
|
# If it is MD compatible it is only able to ignore MD metadata
|
|
# and to mount one of those devices which are parts of MD device,
|
|
# but it is possible only if MD device is a MIRROR.
|
|
md = self.md_by_name(boot_fs.device)
|
|
if md:
|
|
try:
|
|
return md.devices[0]
|
|
except IndexError:
|
|
raise errors.WrongPartitionSchemeError(
|
|
'Error while trying to find boot device: '
|
|
'md device %s does not have devices attached' %
|
|
md.name)
|
|
# Legacy GRUB is not able to mount LVM devices.
|
|
if self.lv_by_device_name(boot_fs.device):
|
|
raise errors.WrongPartitionSchemeError(
|
|
'Error while trying to find boot device: '
|
|
'found device is %s but legacy grub is not able to '
|
|
'mount logical volumes' %
|
|
boot_fs.device)
|
|
|
|
return boot_fs.device
|
|
|
|
def configdrive_device(self):
|
|
# Configdrive device must be a small (about 10M) partition
|
|
# on one of node hard drives. This partition is necessary
|
|
# only if one uses cloud-init with configdrive.
|
|
for parted in self.parteds:
|
|
for prt in parted.partitions:
|
|
if prt.configdrive:
|
|
return prt.name
|
|
|
|
def elevate_keep_data(self):
|
|
LOG.debug('Elevate keep_data flag from partitions')
|
|
|
|
for vg in self.vgs:
|
|
for pvname in vg.pvnames:
|
|
partition = self.partition_by_name(pvname)
|
|
if partition and partition.keep_data:
|
|
partition.keep_data = False
|
|
vg.keep_data = True
|
|
LOG.debug('Set keep_data to vg=%s' % vg.name)
|
|
|
|
for lv in self.lvs:
|
|
vg = self.vg_by_name(lv.vgname)
|
|
if vg.keep_data:
|
|
lv.keep_data = True
|
|
|
|
# Need to loop over lv again to remove keep flag from vg
|
|
for lv in self.lvs:
|
|
vg = self.vg_by_name(lv.vgname)
|
|
if vg.keep_data and lv.keep_data:
|
|
vg.keep_data = False
|
|
|
|
for fs in self.fss:
|
|
lv = self.lv_by_device_name(fs.device)
|
|
if lv:
|
|
if lv.keep_data:
|
|
lv.keep_data = False
|
|
fs.keep_data = True
|
|
LOG.debug('Set keep_data to fs=%s from lv=%s' %
|
|
(fs.mount, lv.name))
|
|
continue
|
|
partition = self.partition_by_name(fs.device)
|
|
if partition and partition.keep_data:
|
|
partition.keep_data = False
|
|
fs.keep_data = True
|
|
LOG.debug('Set keep flag to fs=%s from partition=%s' %
|
|
(fs.mount, partition.name))
|
|
|
|
@property
|
|
def skip_partitioning(self):
|
|
if any(fs.keep_data for fs in self.fss):
|
|
return True
|
|
if any(lv.keep_data for lv in self.lvs):
|
|
return True
|
|
if any(vg.keep_data for vg in self.vgs):
|
|
return True
|
|
for parted in self.parteds:
|
|
if any(prt.keep_data for prt in parted.partitions):
|
|
return True
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'parteds': [parted.to_dict() for parted in self.parteds],
|
|
'mds': [md.to_dict() for md in self.mds],
|
|
'pvs': [pv.to_dict() for pv in self.pvs],
|
|
'vgs': [vg.to_dict() for vg in self.vgs],
|
|
'lvs': [lv.to_dict() for lv in self.lvs],
|
|
'fss': [fs.to_dict() for fs in self.fss],
|
|
}
|