c473202d4d
The reason for that is to make sure that there will be enough space for creating logical volumes. Default lvm extension size is 4M. Nailgun volume manager does not care of it and if physical volume size is 4M * N + 3M and lvm metadata size is 4M * L then only 4M * (N-L) + 3M of space will be available for creating logical extensions. So only 4M * (N-L) of space will be available for logical volumes, while nailgun volume manager might reguire 4M * (N-L) + 3M logical volume. Besides, parted aligns partitions according to its own algorithm and actual partition might be a bit smaller than integer number of mebibytes. Implements: blueprint image-based-provisioning Change-Id: I14e5e8e37a52685a67a99fece630b368e6064c76
350 lines
12 KiB
Python
350 lines
12 KiB
Python
# Copyright 2014 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.
|
|
|
|
from fuel_agent import errors
|
|
from fuel_agent.openstack.common import log as logging
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class Parted(object):
|
|
def __init__(self, name, label):
|
|
self.name = name
|
|
self.label = label
|
|
self.partitions = []
|
|
self.install_bootloader = False
|
|
|
|
def add_partition(self, **kwargs):
|
|
# TODO(kozhukalov): validate before appending
|
|
# calculating partition name based on device name and partition count
|
|
kwargs['name'] = self.next_name()
|
|
kwargs['count'] = self.next_count()
|
|
kwargs['device'] = self.name
|
|
# if begin is given use its value else use end of last partition
|
|
kwargs['begin'] = kwargs.get('begin', self.next_begin())
|
|
# if end is given use its value else
|
|
# try to calculate it based on size kwarg or
|
|
# raise KeyError
|
|
# (kwargs.pop['size'] will raise error if size is not set)
|
|
kwargs['end'] = kwargs.get('end') or \
|
|
kwargs['begin'] + kwargs.pop('size')
|
|
# if partition_type is given use its value else
|
|
# try to calculate it automatically
|
|
kwargs['partition_type'] = \
|
|
kwargs.get('partition_type', self.next_type())
|
|
partition = Partition(**kwargs)
|
|
self.partitions.append(partition)
|
|
return partition
|
|
|
|
@property
|
|
def logical(self):
|
|
return filter(lambda x: x.type == 'logical', self.partitions)
|
|
|
|
@property
|
|
def primary(self):
|
|
return filter(lambda x: x.type == 'primary', self.partitions)
|
|
|
|
@property
|
|
def extended(self):
|
|
found = filter(lambda x: x.type == 'extended', self.partitions)
|
|
if found:
|
|
return found[0]
|
|
|
|
def next_type(self):
|
|
if self.label == 'gpt':
|
|
return 'primary'
|
|
elif self.label == 'msdos':
|
|
if self.extended:
|
|
return 'logical'
|
|
elif len(self.partitions) < 3 and not self.extended:
|
|
return 'primary'
|
|
elif len(self.partitions) == 3 and not self.extended:
|
|
return 'extended'
|
|
#NOTE(agordeev): how to reach that condition?
|
|
else:
|
|
return 'logical'
|
|
|
|
def next_count(self, next_type=None):
|
|
next_type = next_type or self.next_type()
|
|
if next_type == 'logical':
|
|
return len(self.logical) + 5
|
|
return len(self.partitions) + 1
|
|
|
|
def next_begin(self):
|
|
if not self.partitions:
|
|
return 1
|
|
if self.partitions[-1] == self.extended:
|
|
return self.partitions[-1].begin
|
|
return self.partitions[-1].end
|
|
|
|
def next_name(self):
|
|
if self.next_type() == 'extended':
|
|
return None
|
|
separator = ''
|
|
if 'cciss' in self.name or 'loop' in self.name:
|
|
separator = 'p'
|
|
return '%s%s%s' % (self.name, separator, self.next_count())
|
|
|
|
|
|
class Partition(object):
|
|
def __init__(self, name, count, device, begin, end, partition_type,
|
|
flags=None, guid=None, configdrive=False):
|
|
self.name = name
|
|
self.count = count
|
|
self.device = device
|
|
self.name = name
|
|
self.begin = begin
|
|
self.end = end
|
|
self.type = partition_type
|
|
self.flags = flags or []
|
|
self.guid = guid
|
|
self.configdrive = configdrive
|
|
|
|
def set_flag(self, flag):
|
|
if flag not in self.flags:
|
|
self.flags.append(flag)
|
|
|
|
def set_guid(self, guid):
|
|
self.guid = guid
|
|
|
|
|
|
class Pv(object):
|
|
def __init__(self, name, metadatasize=16, metadatacopies=2):
|
|
self.name = name
|
|
self.metadatasize = metadatasize
|
|
self.metadatacopies = metadatacopies
|
|
|
|
|
|
class Vg(object):
|
|
def __init__(self, name, pvnames=None):
|
|
self.name = name
|
|
self.pvnames = pvnames or []
|
|
|
|
def add_pv(self, pvname):
|
|
if pvname not in self.pvnames:
|
|
self.pvnames.append(pvname)
|
|
|
|
|
|
class Lv(object):
|
|
def __init__(self, name, vgname, size):
|
|
self.name = name
|
|
self.vgname = vgname
|
|
self.size = size
|
|
|
|
@property
|
|
def device_name(self):
|
|
return '/dev/mapper/%s-%s' % (self.vgname.replace('-', '--'),
|
|
self.name.replace('-', '--'))
|
|
|
|
|
|
class Md(object):
|
|
def __init__(self, name, level,
|
|
devices=None, spares=None):
|
|
self.name = name
|
|
self.level = level
|
|
self.devices = devices or []
|
|
self.spares = spares or []
|
|
|
|
def add_device(self, device):
|
|
if device in self.devices or device in self.spares:
|
|
raise errors.MDDeviceDuplicationError(
|
|
'Error while attaching device to md: '
|
|
'device %s is already attached' % device)
|
|
self.devices.append(device)
|
|
|
|
def add_spare(self, device):
|
|
if device in self.devices or device in self.spares:
|
|
raise errors.MDDeviceDuplicationError(
|
|
'Error while attaching device to md: '
|
|
'device %s is already attached' % device)
|
|
self.spares.append(device)
|
|
|
|
|
|
class Fs(object):
|
|
def __init__(self, device, mount=None,
|
|
fs_type=None, fs_options=None, fs_label=None):
|
|
self.device = device
|
|
self.mount = mount
|
|
self.type = fs_type or 'xfs'
|
|
self.options = fs_options or ''
|
|
self.label = fs_label or ''
|
|
|
|
|
|
class PartitionScheme(object):
|
|
def __init__(self):
|
|
self.parteds = []
|
|
self.mds = []
|
|
self.pvs = []
|
|
self.vgs = []
|
|
self.lvs = []
|
|
self.fss = []
|
|
self.kernel_params = ''
|
|
|
|
def add_parted(self, **kwargs):
|
|
parted = Parted(**kwargs)
|
|
self.parteds.append(parted)
|
|
return parted
|
|
|
|
def add_pv(self, **kwargs):
|
|
pv = Pv(**kwargs)
|
|
self.pvs.append(pv)
|
|
return pv
|
|
|
|
def add_vg(self, **kwargs):
|
|
vg = Vg(**kwargs)
|
|
self.vgs.append(vg)
|
|
return vg
|
|
|
|
def add_lv(self, **kwargs):
|
|
lv = Lv(**kwargs)
|
|
self.lvs.append(lv)
|
|
return lv
|
|
|
|
def add_fs(self, **kwargs):
|
|
fs = Fs(**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'
|
|
md = Md(**mdkwargs)
|
|
self.mds.append(md)
|
|
return md
|
|
|
|
def md_by_name(self, name):
|
|
found = filter(lambda x: x.name == name, self.mds)
|
|
if found:
|
|
return found[0]
|
|
|
|
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 vg_by_name(self, vgname):
|
|
found = filter(lambda x: (x.name == vgname), self.vgs)
|
|
if found:
|
|
return found[0]
|
|
|
|
def pv_by_name(self, pvname):
|
|
found = filter(lambda x: (x.name == pvname), self.pvs)
|
|
if found:
|
|
return found[0]
|
|
|
|
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):
|
|
found = filter(lambda x: (x.mount and x.mount == mount), self.fss)
|
|
if found:
|
|
return found[0]
|
|
|
|
def fs_by_device(self, device):
|
|
found = filter(lambda x: x.device == device, self.fss)
|
|
if found:
|
|
return found[0]
|
|
|
|
def lv_by_device_name(self, device_name):
|
|
found = filter(lambda x: x.device_name == device_name, self.lvs)
|
|
if found:
|
|
return found[0]
|
|
|
|
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 append_kernel_params(self, kernel_params):
|
|
self.kernel_params += ' ' + kernel_params
|