Refactor disk partitioner code from ironic and use ironic-lib.
The disk partitioner related code from ironic/common/disk_partitioner.py and ironic/drivers/modules/deploy_utils.py is being moved to ironic-lib. The code in ironic needs to be removed and use ironic-lib to perform disk related activities. Change-Id: I7b1b1d8b45b947a7b5715f3c6ab49d84e84b6b90
This commit is contained in:
parent
7b13f3b0a6
commit
7c138f01a0
@ -908,18 +908,6 @@
|
||||
# Options defined in ironic.drivers.modules.deploy_utils
|
||||
#
|
||||
|
||||
# Size of EFI system partition in MiB when configuring UEFI
|
||||
# systems for local boot. (integer value)
|
||||
#efi_system_partition_size=200
|
||||
|
||||
# Block size to use when writing to the nodes disk. (string
|
||||
# value)
|
||||
#dd_block_size=1M
|
||||
|
||||
# Maximum attempts to verify an iSCSI connection is active,
|
||||
# sleeping 1 second between attempts. (integer value)
|
||||
#iscsi_verify_attempts=3
|
||||
|
||||
# ironic-conductor node's HTTP server URL. Example:
|
||||
# http://192.1.2.3:8080 (string value)
|
||||
# Deprecated group/name - [pxe]/http_url
|
||||
@ -956,7 +944,7 @@
|
||||
[disk_partitioner]
|
||||
|
||||
#
|
||||
# Options defined in ironic.common.disk_partitioner
|
||||
# Options defined in ironic_lib.disk_partitioner
|
||||
#
|
||||
|
||||
# After Ironic has completed creating the partition table, it
|
||||
@ -972,6 +960,25 @@
|
||||
#check_device_max_retries=20
|
||||
|
||||
|
||||
[disk_utils]
|
||||
|
||||
#
|
||||
# Options defined in ironic_lib.disk_utils
|
||||
#
|
||||
|
||||
# Size of EFI system partition in MiB when configuring UEFI
|
||||
# systems for local boot. (integer value)
|
||||
#efi_system_partition_size=200
|
||||
|
||||
# Block size to use when writing to the nodes disk. (string
|
||||
# value)
|
||||
#dd_block_size=1M
|
||||
|
||||
# Maximum attempts to verify an iSCSI connection is active,
|
||||
# sleeping 1 second between attempts. (integer value)
|
||||
#iscsi_verify_attempts=3
|
||||
|
||||
|
||||
[drac]
|
||||
|
||||
#
|
||||
@ -1281,6 +1288,18 @@
|
||||
#sensor_method=ipmitool
|
||||
|
||||
|
||||
[ironic_lib]
|
||||
|
||||
#
|
||||
# Options defined in ironic_lib.utils
|
||||
#
|
||||
|
||||
# Command that is prefixed to commands that are run as root.
|
||||
# If not specified, no commands are run as root. (string
|
||||
# value)
|
||||
#root_helper=sudo ironic-rootwrap /etc/ironic/rootwrap.conf
|
||||
|
||||
|
||||
[keystone]
|
||||
|
||||
#
|
||||
|
19
etc/ironic/rootwrap.d/ironic-lib.filters
Normal file
19
etc/ironic/rootwrap.d/ironic-lib.filters
Normal file
@ -0,0 +1,19 @@
|
||||
# An ironic-lib.filters to be used with rootwrap command.
|
||||
# The following commands should be used in filters for disk manipulation.
|
||||
# This file should be owned by (and only-writeable by) the root user.
|
||||
|
||||
[Filters]
|
||||
# ironic_lib/disk_utils.py
|
||||
blkid: CommandFilter, blkid, root
|
||||
blockdev: CommandFilter, blockdev, root
|
||||
hexdump: CommandFilter, hexdump, root
|
||||
qemu-img: CommandFilter, qemu-img, root
|
||||
|
||||
# ironic_lib/utils.py
|
||||
mkswap: CommandFilter, mkswap, root
|
||||
mkfs: CommandFilter, mkfs, root
|
||||
dd: CommandFilter, dd, root
|
||||
|
||||
# ironic_lib/disk_partitioner.py
|
||||
fuser: CommandFilter, fuser, root
|
||||
parted: CommandFilter, parted, root
|
@ -4,9 +4,6 @@
|
||||
[Filters]
|
||||
# ironic/drivers/modules/deploy_utils.py
|
||||
iscsiadm: CommandFilter, iscsiadm, root
|
||||
blkid: CommandFilter, blkid, root
|
||||
blockdev: CommandFilter, blockdev, root
|
||||
hexdump: CommandFilter, hexdump, root
|
||||
|
||||
# ironic/common/utils.py
|
||||
mkswap: CommandFilter, mkswap, root
|
||||
@ -14,7 +11,3 @@ mkfs: CommandFilter, mkfs, root
|
||||
mount: CommandFilter, mount, root
|
||||
umount: CommandFilter, umount, root
|
||||
dd: CommandFilter, dd, root
|
||||
|
||||
# ironic/common/disk_partitioner.py
|
||||
fuser: CommandFilter, fuser, root
|
||||
parted: CommandFilter, parted, root
|
||||
|
@ -1,226 +0,0 @@
|
||||
# Copyright 2014 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 re
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import loopingcall
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common.i18n import _LW
|
||||
from ironic.common import utils
|
||||
|
||||
opts = [
|
||||
cfg.IntOpt('check_device_interval',
|
||||
default=1,
|
||||
help=_('After Ironic has completed creating the partition '
|
||||
'table, it continues to check for activity on the '
|
||||
'attached iSCSI device status at this interval prior '
|
||||
'to copying the image to the node, in seconds')),
|
||||
cfg.IntOpt('check_device_max_retries',
|
||||
default=20,
|
||||
help=_('The maximum number of times to check that the device '
|
||||
'is not accessed by another process. If the device is '
|
||||
'still busy after that, the disk partitioning will be '
|
||||
'treated as having failed.')),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
opt_group = cfg.OptGroup(name='disk_partitioner',
|
||||
title='Options for the disk partitioner')
|
||||
CONF.register_group(opt_group)
|
||||
CONF.register_opts(opts, opt_group)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DiskPartitioner(object):
|
||||
|
||||
def __init__(self, device, disk_label='msdos', alignment='optimal'):
|
||||
"""A convenient wrapper around the parted tool.
|
||||
|
||||
:param device: The device path.
|
||||
:param disk_label: The type of the partition table. Valid types are:
|
||||
"bsd", "dvh", "gpt", "loop", "mac", "msdos",
|
||||
"pc98", or "sun".
|
||||
:param alignment: Set alignment for newly created partitions.
|
||||
Valid types are: none, cylinder, minimal and
|
||||
optimal.
|
||||
|
||||
"""
|
||||
self._device = device
|
||||
self._disk_label = disk_label
|
||||
self._alignment = alignment
|
||||
self._partitions = []
|
||||
self._fuser_pids_re = re.compile(r'((\d)+\s*)+')
|
||||
|
||||
def _exec(self, *args):
|
||||
# NOTE(lucasagomes): utils.execute() is already a wrapper on top
|
||||
# of processutils.execute() which raises specific
|
||||
# exceptions. It also logs any failure so we don't
|
||||
# need to log it again here.
|
||||
utils.execute('parted', '-a', self._alignment, '-s', self._device,
|
||||
'--', 'unit', 'MiB', *args, check_exit_code=[0],
|
||||
use_standard_locale=True, run_as_root=True)
|
||||
|
||||
def add_partition(self, size, part_type='primary', fs_type='',
|
||||
bootable=False):
|
||||
"""Add a partition.
|
||||
|
||||
:param size: The size of the partition in MiB.
|
||||
:param part_type: The type of the partition. Valid values are:
|
||||
primary, logical, or extended.
|
||||
:param fs_type: The filesystem type. Valid types are: ext2, fat32,
|
||||
fat16, HFS, linux-swap, NTFS, reiserfs, ufs.
|
||||
If blank (''), it will create a Linux native
|
||||
partition (83).
|
||||
:param bootable: Boolean value; whether the partition is bootable
|
||||
or not.
|
||||
:returns: The partition number.
|
||||
|
||||
"""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please
|
||||
# also do the same modification in ironic-lib
|
||||
self._partitions.append({'size': size,
|
||||
'type': part_type,
|
||||
'fs_type': fs_type,
|
||||
'bootable': bootable})
|
||||
return len(self._partitions)
|
||||
|
||||
def get_partitions(self):
|
||||
"""Get the partitioning layout.
|
||||
|
||||
:returns: An iterator with the partition number and the
|
||||
partition layout.
|
||||
|
||||
"""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please
|
||||
# also do the same modification in ironic-lib
|
||||
return enumerate(self._partitions, 1)
|
||||
|
||||
def _wait_for_disk_to_become_available(self, retries, max_retries, pids,
|
||||
stderr):
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please
|
||||
# also do the same modification in ironic-lib
|
||||
retries[0] += 1
|
||||
if retries[0] > max_retries:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
try:
|
||||
# NOTE(ifarkas): fuser returns a non-zero return code if none of
|
||||
# the specified files is accessed
|
||||
out, err = utils.execute('fuser', self._device,
|
||||
check_exit_code=[0, 1], run_as_root=True)
|
||||
|
||||
if not out and not err:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
else:
|
||||
if err:
|
||||
stderr[0] = err
|
||||
if out:
|
||||
pids_match = re.search(self._fuser_pids_re, out)
|
||||
pids[0] = pids_match.group()
|
||||
except processutils.ProcessExecutionError as exc:
|
||||
LOG.warning(_LW('Failed to check the device %(device)s with fuser:'
|
||||
' %(err)s'), {'device': self._device, 'err': exc})
|
||||
|
||||
def commit(self):
|
||||
"""Write to the disk."""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please
|
||||
# also do the same modification in ironic-lib
|
||||
LOG.debug("Committing partitions to disk.")
|
||||
cmd_args = ['mklabel', self._disk_label]
|
||||
# NOTE(lucasagomes): Lead in with 1MiB to allow room for the
|
||||
# partition table itself.
|
||||
start = 1
|
||||
for num, part in self.get_partitions():
|
||||
end = start + part['size']
|
||||
cmd_args.extend(['mkpart', part['type'], part['fs_type'],
|
||||
str(start), str(end)])
|
||||
if part['bootable']:
|
||||
cmd_args.extend(['set', str(num), 'boot', 'on'])
|
||||
start = end
|
||||
|
||||
self._exec(*cmd_args)
|
||||
|
||||
retries = [0]
|
||||
pids = ['']
|
||||
fuser_err = ['']
|
||||
interval = CONF.disk_partitioner.check_device_interval
|
||||
max_retries = CONF.disk_partitioner.check_device_max_retries
|
||||
|
||||
timer = loopingcall.FixedIntervalLoopingCall(
|
||||
self._wait_for_disk_to_become_available,
|
||||
retries, max_retries, pids, fuser_err)
|
||||
timer.start(interval=interval).wait()
|
||||
|
||||
if retries[0] > max_retries:
|
||||
if pids[0]:
|
||||
raise exception.InstanceDeployFailure(
|
||||
_('Disk partitioning failed on device %(device)s. '
|
||||
'Processes with the following PIDs are holding it: '
|
||||
'%(pids)s. Time out waiting for completion.')
|
||||
% {'device': self._device, 'pids': pids[0]})
|
||||
else:
|
||||
raise exception.InstanceDeployFailure(
|
||||
_('Disk partitioning failed on device %(device)s. Fuser '
|
||||
'exited with "%(fuser_err)s". Time out waiting for '
|
||||
'completion.')
|
||||
% {'device': self._device, 'fuser_err': fuser_err[0]})
|
||||
|
||||
|
||||
_PARTED_PRINT_RE = re.compile(r"^(\d+):([\d\.]+)MiB:"
|
||||
"([\d\.]+)MiB:([\d\.]+)MiB:(\w*)::(\w*)")
|
||||
|
||||
|
||||
def list_partitions(device):
|
||||
"""Get partitions information from given device.
|
||||
|
||||
:param device: The device path.
|
||||
:returns: list of dictionaries (one per partition) with keys:
|
||||
number, start, end, size (in MiB), filesystem, flags
|
||||
"""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please also
|
||||
# do the same modification in ironic-lib
|
||||
output = utils.execute(
|
||||
'parted', '-s', '-m', device, 'unit', 'MiB', 'print',
|
||||
use_standard_locale=True, run_as_root=True)[0]
|
||||
if isinstance(output, bytes):
|
||||
output = output.decode("utf-8")
|
||||
lines = [line for line in output.split('\n') if line.strip()][2:]
|
||||
# Example of line: 1:1.00MiB:501MiB:500MiB:ext4::boot
|
||||
fields = ('number', 'start', 'end', 'size', 'filesystem', 'flags')
|
||||
result = []
|
||||
for line in lines:
|
||||
match = _PARTED_PRINT_RE.match(line)
|
||||
if match is None:
|
||||
LOG.warning(_LW("Partition information from parted for device "
|
||||
"%(device)s does not match "
|
||||
"expected format: %(line)s"),
|
||||
dict(device=device, line=line))
|
||||
continue
|
||||
# Cast int fields to ints (some are floats and we round them down)
|
||||
groups = [int(float(x)) if i < 4 else x
|
||||
for i, x in enumerate(match.groups())]
|
||||
result.append(dict(zip(fields, groups)))
|
||||
return result
|
@ -14,38 +14,28 @@
|
||||
# under the License.
|
||||
|
||||
|
||||
import base64
|
||||
import contextlib
|
||||
import gzip
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import stat
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from ironic_lib import disk_utils
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import units
|
||||
import requests
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from ironic.common import dhcp_factory
|
||||
from ironic.common import disk_partitioner
|
||||
from ironic.common import exception
|
||||
from ironic.common.glance_service import service_utils
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common.i18n import _LE
|
||||
from ironic.common.i18n import _LI
|
||||
from ironic.common.i18n import _LW
|
||||
from ironic.common import image_service
|
||||
from ironic.common import images
|
||||
from ironic.common import keystone
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
@ -57,17 +47,6 @@ from ironic import objects
|
||||
|
||||
|
||||
deploy_opts = [
|
||||
cfg.IntOpt('efi_system_partition_size',
|
||||
default=200,
|
||||
help=_('Size of EFI system partition in MiB when configuring '
|
||||
'UEFI systems for local boot.')),
|
||||
cfg.StrOpt('dd_block_size',
|
||||
default='1M',
|
||||
help=_('Block size to use when writing to the nodes disk.')),
|
||||
cfg.IntOpt('iscsi_verify_attempts',
|
||||
default=3,
|
||||
help=_('Maximum attempts to verify an iSCSI connection is '
|
||||
'active, sleeping 1 second between attempts.')),
|
||||
cfg.StrOpt('http_url',
|
||||
help='ironic-conductor node\'s HTTP server URL. '
|
||||
'Example: http://192.1.2.3:8080',
|
||||
@ -95,6 +74,14 @@ deploy_opts = [
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(deploy_opts, group='deploy')
|
||||
|
||||
# TODO(Faizan): Move this logic to common/utils.py and deprecate
|
||||
# rootwrap_config.
|
||||
# This is required to set the default value of ironic_lib option
|
||||
# only if rootwrap_config does not contain the default value.
|
||||
if CONF.rootwrap_config != '/etc/ironic/rootwrap.conf':
|
||||
root_helper = 'sudo ironic-rootwrap %s' % CONF.rootwrap_config
|
||||
CONF.set_default('root_helper', root_helper, 'ironic_lib')
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
VALID_ROOT_DEVICE_HINTS = set(('size', 'model', 'wwn', 'serial', 'vendor',
|
||||
@ -151,7 +138,7 @@ def check_file_system_for_iscsi_device(portal_address,
|
||||
check_dir = "/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-1" % (portal_address,
|
||||
portal_port,
|
||||
target_iqn)
|
||||
total_checks = CONF.deploy.iscsi_verify_attempts
|
||||
total_checks = CONF.disk_utils.iscsi_verify_attempts
|
||||
for attempt in range(total_checks):
|
||||
if os.path.exists(check_dir):
|
||||
break
|
||||
@ -171,7 +158,7 @@ def verify_iscsi_connection(target_iqn):
|
||||
"""Verify iscsi connection."""
|
||||
LOG.debug("Checking for iSCSI target to become active.")
|
||||
|
||||
for attempt in range(CONF.deploy.iscsi_verify_attempts):
|
||||
for attempt in range(CONF.disk_utils.iscsi_verify_attempts):
|
||||
out, _err = utils.execute('iscsiadm',
|
||||
'-m', 'node',
|
||||
'-S',
|
||||
@ -183,10 +170,10 @@ def verify_iscsi_connection(target_iqn):
|
||||
LOG.debug("iSCSI connection not active. Rechecking. Attempt "
|
||||
"%(attempt)d out of %(total)d",
|
||||
{"attempt": attempt + 1,
|
||||
"total": CONF.deploy.iscsi_verify_attempts})
|
||||
"total": CONF.disk_utils.iscsi_verify_attempts})
|
||||
else:
|
||||
msg = _("iSCSI connection did not become active after attempting to "
|
||||
"verify %d times.") % CONF.deploy.iscsi_verify_attempts
|
||||
"verify %d times.") % CONF.disk_utils.iscsi_verify_attempts
|
||||
LOG.error(msg)
|
||||
raise exception.InstanceDeployFailure(msg)
|
||||
|
||||
@ -231,163 +218,6 @@ def delete_iscsi(portal_address, portal_port, target_iqn):
|
||||
delay_on_retry=True)
|
||||
|
||||
|
||||
def get_disk_identifier(dev):
|
||||
"""Get the disk identifier from the disk being exposed by the ramdisk.
|
||||
|
||||
This disk identifier is appended to the pxe config which will then be
|
||||
used by chain.c32 to detect the correct disk to chainload. This is helpful
|
||||
in deployments to nodes with multiple disks.
|
||||
|
||||
http://www.syslinux.org/wiki/index.php/Comboot/chain.c32#mbr:
|
||||
|
||||
:param dev: Path for the already populated disk device.
|
||||
:returns The Disk Identifier.
|
||||
"""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please also
|
||||
# do the same modification in ironic-lib
|
||||
disk_identifier = utils.execute('hexdump', '-s', '440', '-n', '4',
|
||||
'-e', '''\"0x%08x\"''',
|
||||
dev,
|
||||
run_as_root=True,
|
||||
check_exit_code=[0],
|
||||
attempts=5,
|
||||
delay_on_retry=True)
|
||||
return disk_identifier[0]
|
||||
|
||||
|
||||
def make_partitions(dev, root_mb, swap_mb, ephemeral_mb,
|
||||
configdrive_mb, node_uuid, commit=True,
|
||||
boot_option="netboot", boot_mode="bios"):
|
||||
"""Partition the disk device.
|
||||
|
||||
Create partitions for root, swap, ephemeral and configdrive on a
|
||||
disk device.
|
||||
|
||||
:param root_mb: Size of the root partition in mebibytes (MiB).
|
||||
:param swap_mb: Size of the swap partition in mebibytes (MiB). If 0,
|
||||
no partition will be created.
|
||||
:param ephemeral_mb: Size of the ephemeral partition in mebibytes (MiB).
|
||||
If 0, no partition will be created.
|
||||
:param configdrive_mb: Size of the configdrive partition in
|
||||
mebibytes (MiB). If 0, no partition will be created.
|
||||
:param commit: True/False. Default for this setting is True. If False
|
||||
partitions will not be written to disk.
|
||||
:param boot_option: Can be "local" or "netboot". "netboot" by default.
|
||||
:param boot_mode: Can be "bios" or "uefi". "bios" by default.
|
||||
:param node_uuid: Node's uuid. Used for logging.
|
||||
:returns: A dictionary containing the partition type as Key and partition
|
||||
path as Value for the partitions created by this method.
|
||||
|
||||
"""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please also
|
||||
# do the same modification in ironic-lib
|
||||
LOG.debug("Starting to partition the disk device: %(dev)s "
|
||||
"for node %(node)s",
|
||||
{'dev': dev, 'node': node_uuid})
|
||||
part_template = dev + '-part%d'
|
||||
part_dict = {}
|
||||
|
||||
# For uefi localboot, switch partition table to gpt and create the efi
|
||||
# system partition as the first partition.
|
||||
if boot_mode == "uefi" and boot_option == "local":
|
||||
dp = disk_partitioner.DiskPartitioner(dev, disk_label="gpt")
|
||||
part_num = dp.add_partition(CONF.deploy.efi_system_partition_size,
|
||||
fs_type='fat32',
|
||||
bootable=True)
|
||||
part_dict['efi system partition'] = part_template % part_num
|
||||
else:
|
||||
dp = disk_partitioner.DiskPartitioner(dev)
|
||||
|
||||
if ephemeral_mb:
|
||||
LOG.debug("Add ephemeral partition (%(size)d MB) to device: %(dev)s "
|
||||
"for node %(node)s",
|
||||
{'dev': dev, 'size': ephemeral_mb, 'node': node_uuid})
|
||||
part_num = dp.add_partition(ephemeral_mb)
|
||||
part_dict['ephemeral'] = part_template % part_num
|
||||
if swap_mb:
|
||||
LOG.debug("Add Swap partition (%(size)d MB) to device: %(dev)s "
|
||||
"for node %(node)s",
|
||||
{'dev': dev, 'size': swap_mb, 'node': node_uuid})
|
||||
part_num = dp.add_partition(swap_mb, fs_type='linux-swap')
|
||||
part_dict['swap'] = part_template % part_num
|
||||
if configdrive_mb:
|
||||
LOG.debug("Add config drive partition (%(size)d MB) to device: "
|
||||
"%(dev)s for node %(node)s",
|
||||
{'dev': dev, 'size': configdrive_mb, 'node': node_uuid})
|
||||
part_num = dp.add_partition(configdrive_mb)
|
||||
part_dict['configdrive'] = part_template % part_num
|
||||
|
||||
# NOTE(lucasagomes): Make the root partition the last partition. This
|
||||
# enables tools like cloud-init's growroot utility to expand the root
|
||||
# partition until the end of the disk.
|
||||
LOG.debug("Add root partition (%(size)d MB) to device: %(dev)s "
|
||||
"for node %(node)s",
|
||||
{'dev': dev, 'size': root_mb, 'node': node_uuid})
|
||||
part_num = dp.add_partition(root_mb, bootable=(boot_option == "local" and
|
||||
boot_mode == "bios"))
|
||||
part_dict['root'] = part_template % part_num
|
||||
|
||||
if commit:
|
||||
# write to the disk
|
||||
dp.commit()
|
||||
return part_dict
|
||||
|
||||
|
||||
def is_block_device(dev):
|
||||
"""Check whether a device is block or not."""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please also
|
||||
# do the same modification in ironic-lib
|
||||
attempts = CONF.deploy.iscsi_verify_attempts
|
||||
for attempt in range(attempts):
|
||||
try:
|
||||
s = os.stat(dev)
|
||||
except OSError as e:
|
||||
LOG.debug("Unable to stat device %(dev)s. Attempt %(attempt)d "
|
||||
"out of %(total)d. Error: %(err)s",
|
||||
{"dev": dev, "attempt": attempt + 1,
|
||||
"total": attempts, "err": e})
|
||||
time.sleep(1)
|
||||
else:
|
||||
return stat.S_ISBLK(s.st_mode)
|
||||
msg = _("Unable to stat device %(dev)s after attempting to verify "
|
||||
"%(attempts)d times.") % {'dev': dev, 'attempts': attempts}
|
||||
LOG.error(msg)
|
||||
raise exception.InstanceDeployFailure(msg)
|
||||
|
||||
|
||||
def dd(src, dst):
|
||||
"""Execute dd from src to dst."""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please also
|
||||
# do the same modification in ironic-lib
|
||||
utils.dd(src, dst, 'bs=%s' % CONF.deploy.dd_block_size, 'oflag=direct')
|
||||
|
||||
|
||||
def populate_image(src, dst):
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please also
|
||||
# do the same modification in ironic-lib
|
||||
data = images.qemu_img_info(src)
|
||||
if data.file_format == 'raw':
|
||||
dd(src, dst)
|
||||
else:
|
||||
images.convert_image(src, dst, 'raw', True)
|
||||
|
||||
|
||||
def block_uuid(dev):
|
||||
"""Get UUID of a block device."""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please also
|
||||
# do the same modification in ironic-lib
|
||||
out, _err = utils.execute('blkid', '-s', 'UUID', '-o', 'value', dev,
|
||||
run_as_root=True,
|
||||
check_exit_code=[0])
|
||||
return out.strip()
|
||||
|
||||
|
||||
def _replace_lines_in_file(path, regex_pattern, replacement):
|
||||
with open(path) as f:
|
||||
lines = f.readlines()
|
||||
@ -468,278 +298,6 @@ def get_dev(address, port, iqn, lun):
|
||||
return dev
|
||||
|
||||
|
||||
def get_image_mb(image_path, virtual_size=True):
|
||||
"""Get size of an image in Megabyte."""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please also
|
||||
# do the same modification in ironic-lib
|
||||
mb = 1024 * 1024
|
||||
if not virtual_size:
|
||||
image_byte = os.path.getsize(image_path)
|
||||
else:
|
||||
image_byte = images.converted_size(image_path)
|
||||
# round up size to MB
|
||||
image_mb = int((image_byte + mb - 1) / mb)
|
||||
return image_mb
|
||||
|
||||
|
||||
def get_dev_block_size(dev):
|
||||
"""Get the device size in 512 byte sectors."""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please also
|
||||
# do the same modification in ironic-lib
|
||||
block_sz, cmderr = utils.execute('blockdev', '--getsz', dev,
|
||||
run_as_root=True, check_exit_code=[0])
|
||||
return int(block_sz)
|
||||
|
||||
|
||||
def destroy_disk_metadata(dev, node_uuid):
|
||||
"""Destroy metadata structures on node's disk.
|
||||
|
||||
Ensure that node's disk appears to be blank without zeroing the entire
|
||||
drive. To do this we will zero:
|
||||
- the first 18KiB to clear MBR / GPT data
|
||||
- the last 18KiB to clear GPT and other metadata like: LVM, veritas,
|
||||
MDADM, DMRAID, ...
|
||||
"""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please also
|
||||
# do the same modification in ironic-lib
|
||||
|
||||
# NOTE(NobodyCam): This is needed to work around bug:
|
||||
# https://bugs.launchpad.net/ironic/+bug/1317647
|
||||
LOG.debug("Start destroy disk metadata for node %(node)s.",
|
||||
{'node': node_uuid})
|
||||
try:
|
||||
utils.dd('/dev/zero', dev, 'bs=512', 'count=36')
|
||||
except processutils.ProcessExecutionError as err:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_LE("Failed to erase beginning of disk for node "
|
||||
"%(node)s. Command: %(command)s. Error: %(error)s."),
|
||||
{'node': node_uuid,
|
||||
'command': err.cmd,
|
||||
'error': err.stderr})
|
||||
|
||||
# now wipe the end of the disk.
|
||||
# get end of disk seek value
|
||||
try:
|
||||
block_sz = get_dev_block_size(dev)
|
||||
except processutils.ProcessExecutionError as err:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_LE("Failed to get disk block count for node %(node)s. "
|
||||
"Command: %(command)s. Error: %(error)s."),
|
||||
{'node': node_uuid,
|
||||
'command': err.cmd,
|
||||
'error': err.stderr})
|
||||
else:
|
||||
seek_value = block_sz - 36
|
||||
try:
|
||||
utils.dd('/dev/zero', dev, 'bs=512', 'count=36',
|
||||
'seek=%d' % seek_value)
|
||||
except processutils.ProcessExecutionError as err:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_LE("Failed to erase the end of the disk on node "
|
||||
"%(node)s. Command: %(command)s. "
|
||||
"Error: %(error)s."),
|
||||
{'node': node_uuid,
|
||||
'command': err.cmd,
|
||||
'error': err.stderr})
|
||||
LOG.info(_LI("Disk metadata on %(dev)s successfully destroyed for node "
|
||||
"%(node)s"), {'dev': dev, 'node': node_uuid})
|
||||
|
||||
|
||||
def _get_configdrive(configdrive, node_uuid):
|
||||
"""Get the information about size and location of the configdrive.
|
||||
|
||||
:param configdrive: Base64 encoded Gzipped configdrive content or
|
||||
configdrive HTTP URL.
|
||||
:param node_uuid: Node's uuid. Used for logging.
|
||||
:raises: InstanceDeployFailure if it can't download or decode the
|
||||
config drive.
|
||||
:returns: A tuple with the size in MiB and path to the uncompressed
|
||||
configdrive file.
|
||||
|
||||
"""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please also
|
||||
# do the same modification in ironic-lib
|
||||
# Check if the configdrive option is a HTTP URL or the content directly
|
||||
is_url = utils.is_http_url(configdrive)
|
||||
if is_url:
|
||||
try:
|
||||
data = requests.get(configdrive).content
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise exception.InstanceDeployFailure(
|
||||
_("Can't download the configdrive content for node %(node)s "
|
||||
"from '%(url)s'. Reason: %(reason)s") %
|
||||
{'node': node_uuid, 'url': configdrive, 'reason': e})
|
||||
else:
|
||||
data = configdrive
|
||||
|
||||
try:
|
||||
data = six.BytesIO(base64.b64decode(data))
|
||||
except TypeError:
|
||||
error_msg = (_('Config drive for node %s is not base64 encoded '
|
||||
'or the content is malformed.') % node_uuid)
|
||||
if is_url:
|
||||
error_msg += _(' Downloaded from "%s".') % configdrive
|
||||
raise exception.InstanceDeployFailure(error_msg)
|
||||
|
||||
configdrive_file = tempfile.NamedTemporaryFile(delete=False,
|
||||
prefix='configdrive',
|
||||
dir=CONF.tempdir)
|
||||
configdrive_mb = 0
|
||||
with gzip.GzipFile('configdrive', 'rb', fileobj=data) as gunzipped:
|
||||
try:
|
||||
shutil.copyfileobj(gunzipped, configdrive_file)
|
||||
except EnvironmentError as e:
|
||||
# Delete the created file
|
||||
utils.unlink_without_raise(configdrive_file.name)
|
||||
raise exception.InstanceDeployFailure(
|
||||
_('Encountered error while decompressing and writing '
|
||||
'config drive for node %(node)s. Error: %(exc)s') %
|
||||
{'node': node_uuid, 'exc': e})
|
||||
else:
|
||||
# Get the file size and convert to MiB
|
||||
configdrive_file.seek(0, os.SEEK_END)
|
||||
bytes_ = configdrive_file.tell()
|
||||
configdrive_mb = int(math.ceil(float(bytes_) / units.Mi))
|
||||
finally:
|
||||
configdrive_file.close()
|
||||
|
||||
return (configdrive_mb, configdrive_file.name)
|
||||
|
||||
|
||||
def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
|
||||
image_path, node_uuid, preserve_ephemeral=False,
|
||||
configdrive=None, boot_option="netboot",
|
||||
boot_mode="bios"):
|
||||
"""Create partitions and copy an image to the root partition.
|
||||
|
||||
:param dev: Path for the device to work on.
|
||||
:param root_mb: Size of the root partition in megabytes.
|
||||
:param swap_mb: Size of the swap partition in megabytes.
|
||||
:param ephemeral_mb: Size of the ephemeral partition in megabytes. If 0,
|
||||
no ephemeral partition will be created.
|
||||
:param ephemeral_format: The type of file system to format the ephemeral
|
||||
partition.
|
||||
:param image_path: Path for the instance's disk image.
|
||||
:param node_uuid: node's uuid. Used for logging.
|
||||
:param preserve_ephemeral: If True, no filesystem is written to the
|
||||
ephemeral block device, preserving whatever content it had (if the
|
||||
partition table has not changed).
|
||||
:param configdrive: Optional. Base64 encoded Gzipped configdrive content
|
||||
or configdrive HTTP URL.
|
||||
:param boot_option: Can be "local" or "netboot". "netboot" by default.
|
||||
:param boot_mode: Can be "bios" or "uefi". "bios" by default.
|
||||
:returns: a dictionary containing the following keys:
|
||||
'root uuid': UUID of root partition
|
||||
'efi system partition uuid': UUID of the uefi system partition
|
||||
(if boot mode is uefi).
|
||||
NOTE: If key exists but value is None, it means partition doesn't
|
||||
exist.
|
||||
"""
|
||||
# NOTE(jlvillal): This function has been moved to ironic-lib. And is
|
||||
# planned to be deleted here. If need to modify this function, please also
|
||||
# do the same modification in ironic-lib
|
||||
|
||||
# the only way for preserve_ephemeral to be set to true is if we are
|
||||
# rebuilding an instance with --preserve_ephemeral.
|
||||
commit = not preserve_ephemeral
|
||||
# now if we are committing the changes to disk clean first.
|
||||
if commit:
|
||||
destroy_disk_metadata(dev, node_uuid)
|
||||
|
||||
try:
|
||||
# If requested, get the configdrive file and determine the size
|
||||
# of the configdrive partition
|
||||
configdrive_mb = 0
|
||||
configdrive_file = None
|
||||
if configdrive:
|
||||
configdrive_mb, configdrive_file = _get_configdrive(configdrive,
|
||||
node_uuid)
|
||||
|
||||
part_dict = make_partitions(dev, root_mb, swap_mb, ephemeral_mb,
|
||||
configdrive_mb, node_uuid,
|
||||
commit=commit,
|
||||
boot_option=boot_option,
|
||||
boot_mode=boot_mode)
|
||||
LOG.info(_LI("Successfully completed the disk device"
|
||||
" %(dev)s partitioning for node %(node)s"),
|
||||
{'dev': dev, "node": node_uuid})
|
||||
|
||||
ephemeral_part = part_dict.get('ephemeral')
|
||||
swap_part = part_dict.get('swap')
|
||||
configdrive_part = part_dict.get('configdrive')
|
||||
root_part = part_dict.get('root')
|
||||
|
||||
if not is_block_device(root_part):
|
||||
raise exception.InstanceDeployFailure(
|
||||
_("Root device '%s' not found") % root_part)
|
||||
|
||||
for part in ('swap', 'ephemeral', 'configdrive',
|
||||
'efi system partition'):
|
||||
part_device = part_dict.get(part)
|
||||
LOG.debug("Checking for %(part)s device (%(dev)s) on node "
|
||||
"%(node)s.",
|
||||
{'part': part, 'dev': part_device, 'node': node_uuid})
|
||||
if part_device and not is_block_device(part_device):
|
||||
raise exception.InstanceDeployFailure(
|
||||
_("'%(partition)s' device '%(part_device)s' not found") %
|
||||
{'partition': part, 'part_device': part_device})
|
||||
|
||||
# If it's a uefi localboot, then we have created the efi system
|
||||
# partition. Create a fat filesystem on it.
|
||||
if boot_mode == "uefi" and boot_option == "local":
|
||||
efi_system_part = part_dict.get('efi system partition')
|
||||
utils.mkfs('vfat', efi_system_part, 'efi-part')
|
||||
|
||||
if configdrive_part:
|
||||
# Copy the configdrive content to the configdrive partition
|
||||
dd(configdrive_file, configdrive_part)
|
||||
LOG.info(_LI("Configdrive for node %(node)s successfully copied "
|
||||
"onto partition %(partition)s"),
|
||||
{'node': node_uuid, 'partition': configdrive_part})
|
||||
|
||||
finally:
|
||||
# If the configdrive was requested make sure we delete the file
|
||||
# after copying the content to the partition
|
||||
if configdrive_file:
|
||||
utils.unlink_without_raise(configdrive_file)
|
||||
|
||||
populate_image(image_path, root_part)
|
||||
LOG.info(_LI("Image for %(node)s successfully populated"),
|
||||
{'node': node_uuid})
|
||||
|
||||
if swap_part:
|
||||
utils.mkfs('swap', swap_part, 'swap1')
|
||||
LOG.info(_LI("Swap partition %(swap)s successfully formatted "
|
||||
"for node %(node)s"),
|
||||
{'swap': swap_part, 'node': node_uuid})
|
||||
|
||||
if ephemeral_part and not preserve_ephemeral:
|
||||
utils.mkfs(ephemeral_format, ephemeral_part, "ephemeral0")
|
||||
LOG.info(_LI("Ephemeral partition %(ephemeral)s successfully "
|
||||
"formatted for node %(node)s"),
|
||||
{'ephemeral': ephemeral_part, 'node': node_uuid})
|
||||
|
||||
uuids_to_return = {
|
||||
'root uuid': root_part,
|
||||
'efi system partition uuid': part_dict.get('efi system partition')
|
||||
}
|
||||
|
||||
try:
|
||||
for part, part_dev in uuids_to_return.items():
|
||||
if part_dev:
|
||||
uuids_to_return[part] = block_uuid(part_dev)
|
||||
|
||||
except processutils.ProcessExecutionError:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_LE("Failed to detect %s"), part)
|
||||
|
||||
return uuids_to_return
|
||||
|
||||
|
||||
def deploy_partition_image(
|
||||
address, port, iqn, lun, image_path,
|
||||
root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid,
|
||||
@ -775,7 +333,7 @@ def deploy_partition_image(
|
||||
NOTE: If key exists but value is None, it means partition doesn't
|
||||
exist.
|
||||
"""
|
||||
image_mb = get_image_mb(image_path)
|
||||
image_mb = disk_utils.get_image_mb(image_path)
|
||||
if image_mb > root_mb:
|
||||
msg = (_('Root partition is too small for requested image. Image '
|
||||
'virtual size: %(image_mb)d MB, Root size: %(root_mb)d MB')
|
||||
@ -783,7 +341,7 @@ def deploy_partition_image(
|
||||
raise exception.InstanceDeployFailure(msg)
|
||||
|
||||
with _iscsi_setup_and_handle_errors(address, port, iqn, lun) as dev:
|
||||
uuid_dict_returned = work_on_disk(
|
||||
uuid_dict_returned = disk_utils.work_on_disk(
|
||||
dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, image_path,
|
||||
node_uuid, preserve_ephemeral=preserve_ephemeral,
|
||||
configdrive=configdrive, boot_option=boot_option,
|
||||
@ -808,8 +366,8 @@ def deploy_disk_image(address, port, iqn, lun,
|
||||
"""
|
||||
with _iscsi_setup_and_handle_errors(address, port, iqn,
|
||||
lun) as dev:
|
||||
populate_image(image_path, dev)
|
||||
disk_identifier = get_disk_identifier(dev)
|
||||
disk_utils.populate_image(image_path, dev)
|
||||
disk_identifier = disk_utils.get_disk_identifier(dev)
|
||||
|
||||
return {'disk identifier': disk_identifier}
|
||||
|
||||
@ -826,7 +384,7 @@ def _iscsi_setup_and_handle_errors(address, port, iqn, lun):
|
||||
dev = get_dev(address, port, iqn, lun)
|
||||
discovery(address, port)
|
||||
login_iscsi(address, port, iqn)
|
||||
if not is_block_device(dev):
|
||||
if not disk_utils.is_block_device(dev):
|
||||
raise exception.InstanceDeployFailure(_("Parent device '%s' not found")
|
||||
% dev)
|
||||
try:
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
import os
|
||||
|
||||
from ironic_lib import disk_utils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import fileutils
|
||||
@ -241,7 +242,7 @@ def check_image_size(task):
|
||||
"""
|
||||
i_info = parse_instance_info(task.node)
|
||||
image_path = _get_image_file_path(task.node.uuid)
|
||||
image_mb = deploy_utils.get_image_mb(image_path)
|
||||
image_mb = disk_utils.get_image_mb(image_path)
|
||||
root_mb = 1024 * int(i_info['root_gb'])
|
||||
if image_mb > root_mb:
|
||||
msg = (_('Root partition is too small for requested image. Image '
|
||||
|
@ -1,198 +0,0 @@
|
||||
# Copyright 2014 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 eventlet
|
||||
import mock
|
||||
from testtools.matchers import HasLength
|
||||
|
||||
from ironic.common import disk_partitioner
|
||||
from ironic.common import exception
|
||||
from ironic.common import utils
|
||||
from ironic.tests import base
|
||||
|
||||
|
||||
@mock.patch.object(eventlet.greenthread, 'sleep', lambda seconds: None)
|
||||
class DiskPartitionerTestCase(base.TestCase):
|
||||
|
||||
def test_add_partition(self):
|
||||
dp = disk_partitioner.DiskPartitioner('/dev/fake')
|
||||
dp.add_partition(1024)
|
||||
dp.add_partition(512, fs_type='linux-swap')
|
||||
dp.add_partition(2048, bootable=True)
|
||||
expected = [(1, {'bootable': False,
|
||||
'fs_type': '',
|
||||
'type': 'primary',
|
||||
'size': 1024}),
|
||||
(2, {'bootable': False,
|
||||
'fs_type': 'linux-swap',
|
||||
'type': 'primary',
|
||||
'size': 512}),
|
||||
(3, {'bootable': True,
|
||||
'fs_type': '',
|
||||
'type': 'primary',
|
||||
'size': 2048})]
|
||||
partitions = [(n, p) for n, p in dp.get_partitions()]
|
||||
self.assertThat(partitions, HasLength(3))
|
||||
self.assertEqual(expected, partitions)
|
||||
|
||||
@mock.patch.object(disk_partitioner.DiskPartitioner, '_exec',
|
||||
autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_commit(self, mock_utils_exc, mock_disk_partitioner_exec):
|
||||
dp = disk_partitioner.DiskPartitioner('/dev/fake')
|
||||
fake_parts = [(1, {'bootable': False,
|
||||
'fs_type': 'fake-fs-type',
|
||||
'type': 'fake-type',
|
||||
'size': 1}),
|
||||
(2, {'bootable': True,
|
||||
'fs_type': 'fake-fs-type',
|
||||
'type': 'fake-type',
|
||||
'size': 1})]
|
||||
with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp:
|
||||
mock_gp.return_value = fake_parts
|
||||
mock_utils_exc.return_value = (None, None)
|
||||
dp.commit()
|
||||
|
||||
mock_disk_partitioner_exec.assert_called_once_with(
|
||||
mock.ANY, 'mklabel', 'msdos',
|
||||
'mkpart', 'fake-type', 'fake-fs-type', '1', '2',
|
||||
'mkpart', 'fake-type', 'fake-fs-type', '2', '3',
|
||||
'set', '2', 'boot', 'on')
|
||||
mock_utils_exc.assert_called_once_with(
|
||||
'fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1])
|
||||
|
||||
@mock.patch.object(disk_partitioner.DiskPartitioner, '_exec',
|
||||
autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_commit_with_device_is_busy_once(self, mock_utils_exc,
|
||||
mock_disk_partitioner_exec):
|
||||
dp = disk_partitioner.DiskPartitioner('/dev/fake')
|
||||
fake_parts = [(1, {'bootable': False,
|
||||
'fs_type': 'fake-fs-type',
|
||||
'type': 'fake-type',
|
||||
'size': 1}),
|
||||
(2, {'bootable': True,
|
||||
'fs_type': 'fake-fs-type',
|
||||
'type': 'fake-type',
|
||||
'size': 1})]
|
||||
fuser_outputs = iter([("/dev/fake: 10000 10001", None), (None, None)])
|
||||
|
||||
with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp:
|
||||
mock_gp.return_value = fake_parts
|
||||
mock_utils_exc.side_effect = fuser_outputs
|
||||
dp.commit()
|
||||
|
||||
mock_disk_partitioner_exec.assert_called_once_with(
|
||||
mock.ANY, 'mklabel', 'msdos',
|
||||
'mkpart', 'fake-type', 'fake-fs-type', '1', '2',
|
||||
'mkpart', 'fake-type', 'fake-fs-type', '2', '3',
|
||||
'set', '2', 'boot', 'on')
|
||||
mock_utils_exc.assert_called_with(
|
||||
'fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1])
|
||||
self.assertEqual(2, mock_utils_exc.call_count)
|
||||
|
||||
@mock.patch.object(disk_partitioner.DiskPartitioner, '_exec',
|
||||
autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_commit_with_device_is_always_busy(self, mock_utils_exc,
|
||||
mock_disk_partitioner_exec):
|
||||
dp = disk_partitioner.DiskPartitioner('/dev/fake')
|
||||
fake_parts = [(1, {'bootable': False,
|
||||
'fs_type': 'fake-fs-type',
|
||||
'type': 'fake-type',
|
||||
'size': 1}),
|
||||
(2, {'bootable': True,
|
||||
'fs_type': 'fake-fs-type',
|
||||
'type': 'fake-type',
|
||||
'size': 1})]
|
||||
|
||||
with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp:
|
||||
mock_gp.return_value = fake_parts
|
||||
mock_utils_exc.return_value = ("/dev/fake: 10000 10001", None)
|
||||
self.assertRaises(exception.InstanceDeployFailure, dp.commit)
|
||||
|
||||
mock_disk_partitioner_exec.assert_called_once_with(
|
||||
mock.ANY, 'mklabel', 'msdos',
|
||||
'mkpart', 'fake-type', 'fake-fs-type', '1', '2',
|
||||
'mkpart', 'fake-type', 'fake-fs-type', '2', '3',
|
||||
'set', '2', 'boot', 'on')
|
||||
mock_utils_exc.assert_called_with(
|
||||
'fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1])
|
||||
self.assertEqual(20, mock_utils_exc.call_count)
|
||||
|
||||
@mock.patch.object(disk_partitioner.DiskPartitioner, '_exec',
|
||||
autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_commit_with_device_disconnected(self, mock_utils_exc,
|
||||
mock_disk_partitioner_exec):
|
||||
dp = disk_partitioner.DiskPartitioner('/dev/fake')
|
||||
fake_parts = [(1, {'bootable': False,
|
||||
'fs_type': 'fake-fs-type',
|
||||
'type': 'fake-type',
|
||||
'size': 1}),
|
||||
(2, {'bootable': True,
|
||||
'fs_type': 'fake-fs-type',
|
||||
'type': 'fake-type',
|
||||
'size': 1})]
|
||||
|
||||
with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp:
|
||||
mock_gp.return_value = fake_parts
|
||||
mock_utils_exc.return_value = (None, "Specified filename /dev/fake"
|
||||
" does not exist.")
|
||||
self.assertRaises(exception.InstanceDeployFailure, dp.commit)
|
||||
|
||||
mock_disk_partitioner_exec.assert_called_once_with(
|
||||
mock.ANY, 'mklabel', 'msdos',
|
||||
'mkpart', 'fake-type', 'fake-fs-type', '1', '2',
|
||||
'mkpart', 'fake-type', 'fake-fs-type', '2', '3',
|
||||
'set', '2', 'boot', 'on')
|
||||
mock_utils_exc.assert_called_with(
|
||||
'fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1])
|
||||
self.assertEqual(20, mock_utils_exc.call_count)
|
||||
|
||||
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
class ListPartitionsTestCase(base.TestCase):
|
||||
|
||||
def test_correct(self, execute_mock):
|
||||
output = """
|
||||
BYT;
|
||||
/dev/sda:500107862016B:scsi:512:4096:msdos:ATA HGST HTS725050A7:;
|
||||
1:1.00MiB:501MiB:500MiB:ext4::boot;
|
||||
2:501MiB:476940MiB:476439MiB:::;
|
||||
"""
|
||||
expected = [
|
||||
{'number': 1, 'start': 1, 'end': 501, 'size': 500,
|
||||
'filesystem': 'ext4', 'flags': 'boot'},
|
||||
{'number': 2, 'start': 501, 'end': 476940, 'size': 476439,
|
||||
'filesystem': '', 'flags': ''},
|
||||
]
|
||||
execute_mock.return_value = (output, '')
|
||||
result = disk_partitioner.list_partitions('/dev/fake')
|
||||
self.assertEqual(expected, result)
|
||||
execute_mock.assert_called_once_with(
|
||||
'parted', '-s', '-m', '/dev/fake', 'unit', 'MiB', 'print',
|
||||
use_standard_locale=True, run_as_root=True)
|
||||
|
||||
@mock.patch.object(disk_partitioner.LOG, 'warning', autospec=True)
|
||||
def test_incorrect(self, log_mock, execute_mock):
|
||||
output = """
|
||||
BYT;
|
||||
/dev/sda:500107862016B:scsi:512:4096:msdos:ATA HGST HTS725050A7:;
|
||||
1:XX1076MiB:---:524MiB:ext4::boot;
|
||||
"""
|
||||
execute_mock.return_value = (output, '')
|
||||
self.assertEqual([], disk_partitioner.list_partitions('/dev/fake'))
|
||||
self.assertEqual(1, log_mock.call_count)
|
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,7 @@
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from ironic_lib import disk_utils
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import fileutils
|
||||
@ -344,7 +345,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
||||
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
|
||||
self.node = obj_utils.create_test_node(self.context, **n)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'get_image_mb', autospec=True)
|
||||
@mock.patch.object(disk_utils, 'get_image_mb', autospec=True)
|
||||
def test_check_image_size(self, get_image_mb_mock):
|
||||
get_image_mb_mock.return_value = 1000
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
@ -354,7 +355,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
||||
get_image_mb_mock.assert_called_once_with(
|
||||
iscsi_deploy._get_image_file_path(task.node.uuid))
|
||||
|
||||
@mock.patch.object(deploy_utils, 'get_image_mb', autospec=True)
|
||||
@mock.patch.object(disk_utils, 'get_image_mb', autospec=True)
|
||||
def test_check_image_size_fails(self, get_image_mb_mock):
|
||||
get_image_mb_mock.return_value = 1025
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
|
20
releasenotes/notes/refactor-ironic-lib-22939896d8d46a77.yaml
Normal file
20
releasenotes/notes/refactor-ironic-lib-22939896d8d46a77.yaml
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
Adds new configuration [ironic_lib]root_helper, to specify
|
||||
the command that is prefixed to commands that are run as root.
|
||||
Defaults to using the rootwrap config file at
|
||||
/etc/ironic/rootwrap.conf.
|
||||
- |
|
||||
Moves these configuration options from [deploy] group to the
|
||||
new [disk_utils] group: efi_system_partition_size, dd_block_size
|
||||
and iscsi_verify_attempts.
|
||||
deprecations:
|
||||
- |
|
||||
The following configuration options have been moved to
|
||||
the [disk_utils] group; they are deprecated from the
|
||||
[deploy] group: efi_system_partition_size, dd_block_size and
|
||||
iscsi_verify_attempts.
|
||||
other:
|
||||
- Code related to disk partitioning was moved to
|
||||
ironic-lib.
|
@ -13,6 +13,7 @@ paramiko>=1.13.0
|
||||
python-neutronclient>=2.6.0
|
||||
python-glanceclient>=0.18.0
|
||||
python-keystoneclient!=1.8.0,>=1.6.0
|
||||
ironic-lib>=0.5.0
|
||||
python-swiftclient>=2.2.0
|
||||
pytz>=2013.6
|
||||
stevedore>=1.5.0 # Apache-2.0
|
||||
|
@ -1,2 +1,2 @@
|
||||
export IRONIC_CONFIG_GENERATOR_EXTRA_LIBRARIES='oslo.db oslo.messaging oslo.middleware.cors keystonemiddleware.auth_token oslo.concurrency oslo.policy oslo.log oslo.service.service oslo.service.periodic_task oslo.service.sslutils'
|
||||
export IRONIC_CONFIG_GENERATOR_EXTRA_MODULES=
|
||||
export IRONIC_CONFIG_GENERATOR_EXTRA_MODULES='ironic_lib.disk_utils ironic_lib.disk_partitioner ironic_lib.utils'
|
||||
|
Loading…
Reference in New Issue
Block a user