Adds ramdisk deploy driver
Adds a pxe deploy driver to support the concept of a deployment just consisting of a ramdisk. Ideally, as long as a kernel and ramdisk are defined, either by the operator or via a glance image, the PXE/iPXE template should point the booted kernel to using ramdisk as the root. In theory, this would allow deployment via nova, or directly using the parameters posted to the node's instance_info. There may be additional features realistically needed for this to be beyond minimally useful, but that would also depend on the contents of the ramdisk that is deployed by an API user. Change-Id: Id7067527cba27ed49753736f33ccb35e9b35bcba Story: 1753842 Task: 10666
This commit is contained in:
parent
bf76dd4fdc
commit
731af40129
@ -47,7 +47,7 @@ class GenericHardware(hardware_type.AbstractHardwareType):
|
||||
def supported_deploy_interfaces(self):
|
||||
"""List of supported deploy interfaces."""
|
||||
return [iscsi_deploy.ISCSIDeploy, agent.AgentDeploy,
|
||||
ansible_deploy.AnsibleDeploy]
|
||||
ansible_deploy.AnsibleDeploy, pxe.PXERamdiskDeploy]
|
||||
|
||||
@property
|
||||
def supported_inspect_interfaces(self):
|
||||
|
@ -58,7 +58,7 @@ LOG = logging.getLogger(__name__)
|
||||
METRICS = metrics_utils.get_metrics_logger(__name__)
|
||||
|
||||
SUPPORTED_CAPABILITIES = {
|
||||
'boot_option': ('local', 'netboot'),
|
||||
'boot_option': ('local', 'netboot', 'ramdisk'),
|
||||
'boot_mode': ('bios', 'uefi'),
|
||||
'secure_boot': ('true', 'false'),
|
||||
'trusted_boot': ('true', 'false'),
|
||||
@ -284,13 +284,16 @@ def _replace_root_uuid(path, root_uuid):
|
||||
|
||||
|
||||
def _replace_boot_line(path, boot_mode, is_whole_disk_image,
|
||||
trusted_boot=False, iscsi_boot=False):
|
||||
trusted_boot=False, iscsi_boot=False,
|
||||
ramdisk_boot=False):
|
||||
if is_whole_disk_image:
|
||||
boot_disk_type = 'boot_whole_disk'
|
||||
elif trusted_boot:
|
||||
boot_disk_type = 'trusted_boot'
|
||||
elif iscsi_boot:
|
||||
boot_disk_type = 'boot_iscsi'
|
||||
elif ramdisk_boot:
|
||||
boot_disk_type = 'boot_ramdisk'
|
||||
else:
|
||||
boot_disk_type = 'boot_partition'
|
||||
|
||||
@ -312,7 +315,7 @@ def _replace_disk_identifier(path, disk_identifier):
|
||||
|
||||
def switch_pxe_config(path, root_uuid_or_disk_id, boot_mode,
|
||||
is_whole_disk_image, trusted_boot=False,
|
||||
iscsi_boot=False):
|
||||
iscsi_boot=False, ramdisk_boot=False):
|
||||
"""Switch a pxe config from deployment mode to service mode.
|
||||
|
||||
:param path: path to the pxe config file in tftpboot.
|
||||
@ -324,14 +327,16 @@ def switch_pxe_config(path, root_uuid_or_disk_id, boot_mode,
|
||||
is_whole_disk_image and trusted_boot are mutually exclusive. You can
|
||||
have one or neither, but not both.
|
||||
:param iscsi_boot: if boot is from an iSCSI volume or not.
|
||||
:param ramdisk_boot: if the boot is to be to a ramdisk configuration.
|
||||
"""
|
||||
if not is_whole_disk_image:
|
||||
_replace_root_uuid(path, root_uuid_or_disk_id)
|
||||
else:
|
||||
_replace_disk_identifier(path, root_uuid_or_disk_id)
|
||||
if not ramdisk_boot:
|
||||
if not is_whole_disk_image:
|
||||
_replace_root_uuid(path, root_uuid_or_disk_id)
|
||||
else:
|
||||
_replace_disk_identifier(path, root_uuid_or_disk_id)
|
||||
|
||||
_replace_boot_line(path, boot_mode, is_whole_disk_image, trusted_boot,
|
||||
iscsi_boot)
|
||||
iscsi_boot, ramdisk_boot)
|
||||
|
||||
|
||||
def get_dev(address, port, iqn, lun):
|
||||
@ -365,7 +370,8 @@ def deploy_partition_image(
|
||||
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_option: Can be "local" or "netboot", or "ramdisk".
|
||||
"netboot" by default.
|
||||
:param boot_mode: Can be "bios" or "uefi". "bios" by default.
|
||||
:param disk_label: The disk label to be used when creating the
|
||||
partition table. Valid values are: "msdos", "gpt" or None; If None
|
||||
|
@ -30,6 +30,12 @@ imgfree
|
||||
kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.aki_path }} root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }} initrd=ramdisk || goto boot_partition
|
||||
initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.ari_path }} || goto boot_partition
|
||||
boot
|
||||
|
||||
:boot_ramdisk
|
||||
imgfree
|
||||
kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.aki_path }} root=/dev/ram0 text {{ pxe_options.pxe_append_params|default("", true) }} {{ pxe_options.ramdisk_opts|default('', true) }} initrd=ramdisk || goto boot_ramdisk
|
||||
initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.ari_path }} || goto boot_ramdisk
|
||||
boot
|
||||
{%- if pxe_options.boot_from_volume %}
|
||||
|
||||
:boot_iscsi
|
||||
|
@ -35,6 +35,7 @@ from ironic.common import states
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.conf import CONF
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import agent
|
||||
from ironic.drivers.modules import boot_mode_utils
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers.modules import image_cache
|
||||
@ -211,6 +212,18 @@ def _build_instance_pxe_options(task, pxe_info):
|
||||
pxe_opts.setdefault('aki_path', 'no_kernel')
|
||||
pxe_opts.setdefault('ari_path', 'no_ramdisk')
|
||||
|
||||
# TODO(TheJulia): We should only do this if we have a ramdisk interface.
|
||||
# We should check the capabilities of the class, but that becomes a bit
|
||||
# of a pain for unit testing. We can sort this out in Stein since we will
|
||||
# need to revisit a major portion of this file to effetively begin the
|
||||
# ipxe boot interface promotion.
|
||||
if isinstance(task.driver.deploy, PXERamdiskDeploy):
|
||||
i_info = task.node.instance_info
|
||||
try:
|
||||
pxe_opts['ramdisk_opts'] = i_info['ramdisk_kernel_arguments']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return pxe_opts
|
||||
|
||||
|
||||
@ -266,7 +279,8 @@ def _build_pxe_config_options(task, pxe_info, service=False):
|
||||
|
||||
|
||||
def _build_service_pxe_config(task, instance_image_info,
|
||||
root_uuid_or_disk_id):
|
||||
root_uuid_or_disk_id,
|
||||
ramdisk_boot=False):
|
||||
node = task.node
|
||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
|
||||
# NOTE(pas-ha) if it is takeover of ACTIVE node or node performing
|
||||
@ -283,7 +297,7 @@ def _build_service_pxe_config(task, instance_image_info,
|
||||
pxe_config_path, root_uuid_or_disk_id,
|
||||
boot_mode_utils.get_boot_mode_for_deploy(node),
|
||||
iwdi, deploy_utils.is_trusted_boot_requested(node),
|
||||
deploy_utils.is_iscsi_boot(task))
|
||||
deploy_utils.is_iscsi_boot(task), ramdisk_boot)
|
||||
|
||||
|
||||
def _get_volume_pxe_options(task):
|
||||
@ -417,7 +431,7 @@ def _clean_up_pxe_env(task, images_info):
|
||||
|
||||
class PXEBoot(base.BootInterface):
|
||||
|
||||
capabilities = ['iscsi_volume_boot']
|
||||
capabilities = ['iscsi_volume_boot', 'ramdisk_boot']
|
||||
|
||||
def __init__(self):
|
||||
if CONF.pxe.ipxe_enabled:
|
||||
@ -597,7 +611,6 @@ class PXEBoot(base.BootInterface):
|
||||
node = task.node
|
||||
boot_option = deploy_utils.get_boot_option(node)
|
||||
boot_device = None
|
||||
|
||||
if deploy_utils.is_iscsi_boot(task):
|
||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
||||
provider = dhcp_factory.DHCPFactory()
|
||||
@ -618,6 +631,22 @@ class PXEBoot(base.BootInterface):
|
||||
iscsi_boot=True)
|
||||
boot_device = boot_devices.PXE
|
||||
|
||||
elif boot_option == "ramdisk":
|
||||
instance_image_info = _get_instance_image_info(
|
||||
task.node, task.context)
|
||||
_cache_ramdisk_kernel(task.context, task.node,
|
||||
instance_image_info)
|
||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
||||
provider = dhcp_factory.DHCPFactory()
|
||||
provider.update_dhcp(task, dhcp_opts)
|
||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||
task.node.uuid)
|
||||
deploy_utils.switch_pxe_config(
|
||||
pxe_config_path, None,
|
||||
boot_mode_utils.get_boot_mode_for_deploy(node), False,
|
||||
iscsi_boot=False, ramdisk_boot=True)
|
||||
boot_device = boot_devices.PXE
|
||||
|
||||
elif boot_option != "local":
|
||||
if task.driver.storage.should_write_image(task):
|
||||
# Make sure that the instance kernel/ramdisk is cached.
|
||||
@ -702,3 +731,79 @@ class PXEBoot(base.BootInterface):
|
||||
parameters
|
||||
"""
|
||||
_parse_driver_info(task.node, mode='rescue')
|
||||
|
||||
|
||||
class PXERamdiskDeploy(agent.AgentDeploy, agent.AgentDeployMixin,
|
||||
base.DeployInterface):
|
||||
|
||||
def validate(self, task):
|
||||
# Initially this is likely okay, we can iterate on this and
|
||||
# enable other drivers that have similar functionality that
|
||||
# be invoked in a ramdisk friendly way.
|
||||
if not isinstance(task.driver.boot, PXEBoot):
|
||||
raise exception.InvalidParameterValue(
|
||||
err=('Invalid configuration: The ramdisk deploy '
|
||||
'interface requires the pxe boot interface.'))
|
||||
# Eventually we should be doing this.
|
||||
if 'ramdisk_boot' not in task.driver.boot.capabilities:
|
||||
raise exception.InvalidParameterValue(
|
||||
err=('Invalid configuration: The boot interface '
|
||||
'must have the `ramdisk_boot` capability. '
|
||||
'Not found.'))
|
||||
task.driver.boot.validate(task)
|
||||
|
||||
# Validate node capabilities
|
||||
deploy_utils.validate_capabilities(task.node)
|
||||
|
||||
def deploy(self, task):
|
||||
if 'configdrive' in task.node.instance_info:
|
||||
LOG.warning('A configuration drive is present with '
|
||||
'in the deployment request of node %(node)s. '
|
||||
'The configuration drive will be ignored for '
|
||||
'this deployment.',
|
||||
{'node': task.node})
|
||||
manager_utils.node_power_action(task, states.POWER_OFF)
|
||||
# Tenant neworks must enable connectivity to the boot
|
||||
# location, as reboot() can otherwise be very problematic.
|
||||
# IDEA(TheJulia): Maybe a "trusted environment" mode flag
|
||||
# that we otherwise fail validation on for drivers that
|
||||
# require explicit security postures.
|
||||
task.driver.network.configure_tenant_networks(task)
|
||||
|
||||
# calling boot.prepare_instance will also set the node
|
||||
# to PXE boot, and update PXE templates accordingly
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
# Power-on the instance, with PXE prepared, we're done.
|
||||
manager_utils.node_power_action(task, states.POWER_ON)
|
||||
LOG.info('Deployment setup for node %s done', task.node.uuid)
|
||||
# TODO(TheJulia): Update this in stein to support deploy steps.
|
||||
return states.DEPLOYDONE
|
||||
|
||||
def prepare(self, task):
|
||||
node = task.node
|
||||
# Log a warning if the boot_option is wrong... and
|
||||
# otherwise reset it.
|
||||
if deploy_utils.get_boot_option(node) != 'ramdisk':
|
||||
LOG.warning('Incorrect "boot_option" set for node %(node)s '
|
||||
'and will be overridden to "ramdisk" as the '
|
||||
'to match the deploy interface.',
|
||||
{'node': node.uuid})
|
||||
i_info = task.node.instance_info
|
||||
i_info.update({'capabilities': {'boot_option': 'ramdisk'}})
|
||||
node.instance_info = i_info
|
||||
node.save()
|
||||
|
||||
deploy_utils.populate_storage_driver_internal_info(task)
|
||||
if node.provision_state == states.DEPLOYING:
|
||||
# Ask the network interface to validate itself so
|
||||
# we can ensure we are able to proceed.
|
||||
task.driver.network.validate(task)
|
||||
|
||||
manager_utils.node_power_action(task, states.POWER_OFF)
|
||||
# NOTE(TheJulia): If this was any other interface, we would
|
||||
# unconfigure tenant networks, add provisioning networks, etc.
|
||||
task.driver.storage.attach_volumes(task)
|
||||
if node.provision_state in (states.ACTIVE, states.UNRESCUING):
|
||||
# In the event of takeover or unrescue.
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
@ -18,3 +18,7 @@ append mbr:{{ DISK_IDENTIFIER }}
|
||||
label trusted_boot
|
||||
kernel mboot
|
||||
append tboot.gz --- {{pxe_options.aki_path}} root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }} intel_iommu=on --- {{pxe_options.ari_path}}
|
||||
|
||||
label boot_ramdisk
|
||||
kernel {{ pxe_options.aki_path }}
|
||||
append initrd={{ pxe_options.ari_path }} root=/dev/ram0 text {{ pxe_options.pxe_append_params|default("", true) }} {{ pxe_options.ramdisk_opts|default('', true) }}
|
||||
|
@ -12,6 +12,11 @@ menuentry "boot_partition" {
|
||||
initrdefi {{ pxe_options.ari_path }}
|
||||
}
|
||||
|
||||
menuentry "boot_ramdisk" {
|
||||
linuxefi {{ pxe_options.deployment_aki_path }} root=/dev/ram0 text {{ pxe_options.pxe_append_params|default("", true) }} {{ pxe_options.ramdisk_opts|default('', true) }}
|
||||
initrdefi {{ pxe_options.deployment_ari_path }}
|
||||
}
|
||||
|
||||
menuentry "boot_whole_disk" {
|
||||
linuxefi chain.c32 mbr:{{ DISK_IDENTIFIER }}
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
u'f33c123/deploy_ramdisk',
|
||||
'ipa-api-url': 'http://192.168.122.184:6385',
|
||||
'ipxe_timeout': 0,
|
||||
'ramdisk_opts': 'ramdisk_param',
|
||||
}
|
||||
|
||||
self.ipxe_options = self.pxe_options.copy()
|
||||
|
@ -31,5 +31,11 @@ kernel http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_param initrd=ramd
|
||||
initrd http://1.2.3.4:1234/ramdisk || goto boot_partition
|
||||
boot
|
||||
|
||||
:boot_ramdisk
|
||||
imgfree
|
||||
kernel http://1.2.3.4:1234/kernel root=/dev/ram0 text test_param ramdisk_param initrd=ramdisk || goto boot_ramdisk
|
||||
initrd http://1.2.3.4:1234/ramdisk || goto boot_ramdisk
|
||||
boot
|
||||
|
||||
:boot_whole_disk
|
||||
sanboot --no-describe
|
||||
|
@ -31,6 +31,12 @@ kernel http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_param initrd=ramd
|
||||
initrd http://1.2.3.4:1234/ramdisk || goto boot_partition
|
||||
boot
|
||||
|
||||
:boot_ramdisk
|
||||
imgfree
|
||||
kernel http://1.2.3.4:1234/kernel root=/dev/ram0 text test_param ramdisk_param initrd=ramdisk || goto boot_ramdisk
|
||||
initrd http://1.2.3.4:1234/ramdisk || goto boot_ramdisk
|
||||
boot
|
||||
|
||||
:boot_iscsi
|
||||
imgfree
|
||||
set username fake_username
|
||||
|
@ -31,6 +31,12 @@ kernel http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_param initrd=ramd
|
||||
initrd http://1.2.3.4:1234/ramdisk || goto boot_partition
|
||||
boot
|
||||
|
||||
:boot_ramdisk
|
||||
imgfree
|
||||
kernel http://1.2.3.4:1234/kernel root=/dev/ram0 text test_param ramdisk_param initrd=ramdisk || goto boot_ramdisk
|
||||
initrd http://1.2.3.4:1234/ramdisk || goto boot_ramdisk
|
||||
boot
|
||||
|
||||
:boot_iscsi
|
||||
imgfree
|
||||
set username fake_username
|
||||
|
@ -31,5 +31,11 @@ kernel --timeout 120 http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_par
|
||||
initrd --timeout 120 http://1.2.3.4:1234/ramdisk || goto boot_partition
|
||||
boot
|
||||
|
||||
:boot_ramdisk
|
||||
imgfree
|
||||
kernel --timeout 120 http://1.2.3.4:1234/kernel root=/dev/ram0 text test_param ramdisk_param initrd=ramdisk || goto boot_ramdisk
|
||||
initrd --timeout 120 http://1.2.3.4:1234/ramdisk || goto boot_ramdisk
|
||||
boot
|
||||
|
||||
:boot_whole_disk
|
||||
sanboot --no-describe
|
||||
|
@ -1420,7 +1420,7 @@ class ParseInstanceInfoCapabilitiesTestCase(tests_base.TestCase):
|
||||
utils.validate_capabilities, self.node)
|
||||
|
||||
def test_all_supported_capabilities(self):
|
||||
self.assertEqual(('local', 'netboot'),
|
||||
self.assertEqual(('local', 'netboot', 'ramdisk'),
|
||||
utils.SUPPORTED_CAPABILITIES['boot_option'])
|
||||
self.assertEqual(('bios', 'uefi'),
|
||||
utils.SUPPORTED_CAPABILITIES['boot_mode'])
|
||||
|
@ -1254,7 +1254,7 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||
switch_pxe_config_mock.assert_called_once_with(
|
||||
pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50",
|
||||
'bios', False, False, False)
|
||||
'bios', False, False, False, False)
|
||||
set_boot_device_mock.assert_called_once_with(task,
|
||||
boot_devices.PXE,
|
||||
persistent=True)
|
||||
@ -1297,7 +1297,7 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
task, mock.ANY, CONF.pxe.pxe_config_template)
|
||||
switch_pxe_config_mock.assert_called_once_with(
|
||||
pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50",
|
||||
'bios', False, False, False)
|
||||
'bios', False, False, False, False)
|
||||
self.assertFalse(set_boot_device_mock.called)
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||
@ -1467,6 +1467,180 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
task.node, task.context)
|
||||
|
||||
|
||||
class PXEBootDeployTestCase(db_base.DbTestCase):
|
||||
|
||||
driver = 'fake-hardware'
|
||||
|
||||
def setUp(self):
|
||||
super(PXEBootDeployTestCase, self).setUp()
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.config(tftp_root=self.temp_dir, group='pxe')
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.config(images_path=self.temp_dir, group='pxe')
|
||||
self.config(enabled_deploy_interfaces=['ramdisk'])
|
||||
self.config(enabled_boot_interfaces=['pxe'])
|
||||
for iface in drivers_base.ALL_INTERFACES:
|
||||
impl = 'fake'
|
||||
if iface == 'network':
|
||||
impl = 'noop'
|
||||
if iface == 'deploy':
|
||||
impl = 'ramdisk'
|
||||
if iface == 'boot':
|
||||
impl = 'pxe'
|
||||
config_kwarg = {'enabled_%s_interfaces' % iface: [impl],
|
||||
'default_%s_interface' % iface: impl}
|
||||
self.config(**config_kwarg)
|
||||
self.config(enabled_hardware_types=[self.driver])
|
||||
instance_info = INST_INFO_DICT
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context,
|
||||
driver=self.driver,
|
||||
instance_info=instance_info,
|
||||
driver_info=DRV_INFO_DICT,
|
||||
driver_internal_info=DRV_INTERNAL_INFO_DICT)
|
||||
self.port = obj_utils.create_test_port(self.context,
|
||||
node_id=self.node.id)
|
||||
self.config(group='conductor', api_url='http://127.0.0.1:1234/')
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True)
|
||||
@mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True)
|
||||
@mock.patch.object(pxe, '_cache_ramdisk_kernel', autospec=True)
|
||||
@mock.patch.object(pxe, '_get_instance_image_info', autospec=True)
|
||||
def test_prepare_instance_ramdisk(
|
||||
self, get_image_info_mock, cache_mock,
|
||||
dhcp_factory_mock, switch_pxe_config_mock,
|
||||
set_boot_device_mock):
|
||||
provider_mock = mock.MagicMock()
|
||||
dhcp_factory_mock.return_value = provider_mock
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
image_info = {'kernel': ('', '/path/to/kernel'),
|
||||
'ramdisk': ('', '/path/to/ramdisk')}
|
||||
get_image_info_mock.return_value = image_info
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||
task.node.uuid)
|
||||
task.node.properties['capabilities'] = 'boot_option:netboot'
|
||||
task.node.driver_internal_info['is_whole_disk_image'] = False
|
||||
task.driver.deploy.prepare(task)
|
||||
task.driver.deploy.deploy(task)
|
||||
|
||||
get_image_info_mock.assert_called_once_with(
|
||||
task.node, task.context)
|
||||
cache_mock.assert_called_once_with(
|
||||
task.context, task.node, image_info)
|
||||
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||
switch_pxe_config_mock.assert_called_once_with(
|
||||
pxe_config_path, None,
|
||||
'bios', False, iscsi_boot=False, ramdisk_boot=True)
|
||||
set_boot_device_mock.assert_called_once_with(task,
|
||||
boot_devices.PXE,
|
||||
persistent=True)
|
||||
|
||||
@mock.patch.object(pxe.LOG, 'warning', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True)
|
||||
@mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True)
|
||||
@mock.patch.object(pxe, '_cache_ramdisk_kernel', autospec=True)
|
||||
@mock.patch.object(pxe, '_get_instance_image_info', autospec=True)
|
||||
def test_deploy(self, mock_image_info, mock_cache,
|
||||
mock_dhcp_factory, mock_switch_config, mock_warning):
|
||||
image_info = {'kernel': ('', '/path/to/kernel'),
|
||||
'ramdisk': ('', '/path/to/ramdisk')}
|
||||
mock_image_info.return_value = image_info
|
||||
i_info = self.node.instance_info
|
||||
i_info.update({'capabilities': {'boot_option': 'ramdisk'}})
|
||||
self.node.instance_info = i_info
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertEqual(states.DEPLOYDONE,
|
||||
task.driver.deploy.deploy(task))
|
||||
mock_image_info.assert_called_once_with(
|
||||
task.node, task.context)
|
||||
mock_cache.assert_called_once_with(
|
||||
task.context, task.node, image_info)
|
||||
self.assertFalse(mock_warning.called)
|
||||
i_info['configdrive'] = 'meow'
|
||||
self.node.instance_info = i_info
|
||||
self.node.save()
|
||||
mock_warning.reset_mock()
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertEqual(states.DEPLOYDONE,
|
||||
task.driver.deploy.deploy(task))
|
||||
self.assertTrue(mock_warning.called)
|
||||
|
||||
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
|
||||
def test_prepare(self, mock_prepare_instance):
|
||||
node = self.node
|
||||
node.provision_state = states.DEPLOYING
|
||||
node.instance_info = {}
|
||||
node.save()
|
||||
with task_manager.acquire(self.context, node.uuid) as task:
|
||||
task.driver.deploy.prepare(task)
|
||||
self.assertFalse(mock_prepare_instance.called)
|
||||
self.assertEqual({'boot_option': 'ramdisk'},
|
||||
task.node.instance_info['capabilities'])
|
||||
|
||||
node.provision_state = states.ACTIVE
|
||||
node.save()
|
||||
with task_manager.acquire(self.context, node.uuid) as task:
|
||||
task.driver.deploy.prepare(task)
|
||||
mock_prepare_instance.assert_called_once_with(mock.ANY, task)
|
||||
mock_prepare_instance.reset_mock()
|
||||
|
||||
node.provision_state = states.UNRESCUING
|
||||
node.save()
|
||||
with task_manager.acquire(self.context, node.uuid) as task:
|
||||
task.driver.deploy.prepare(task)
|
||||
mock_prepare_instance.assert_called_once_with(mock.ANY, task)
|
||||
|
||||
@mock.patch.object(pxe.LOG, 'warning', autospec=True)
|
||||
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
|
||||
def test_prepare_fixes_and_logs_boot_option_warning(
|
||||
self, mock_prepare_instance, mock_warning):
|
||||
node = self.node
|
||||
node.properties['capabilities'] = 'boot_option:ramdisk'
|
||||
node.provision_state = states.DEPLOYING
|
||||
node.instance_info = {}
|
||||
node.save()
|
||||
with task_manager.acquire(self.context, node.uuid) as task:
|
||||
task.driver.deploy.prepare(task)
|
||||
self.assertFalse(mock_prepare_instance.called)
|
||||
self.assertEqual({'boot_option': 'ramdisk'},
|
||||
task.node.instance_info['capabilities'])
|
||||
self.assertTrue(mock_warning.called)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
autospec=True)
|
||||
def test_validate(self, mock_validate_img):
|
||||
node = self.node
|
||||
node.properties['capabilities'] = 'boot_option:netboot'
|
||||
node.save()
|
||||
with task_manager.acquire(self.context, node.uuid) as task:
|
||||
task.driver.deploy.validate(task)
|
||||
self.assertTrue(mock_validate_img.called)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
autospec=True)
|
||||
def test_validate_interface_mismatch(self, mock_validate_image):
|
||||
node = self.node
|
||||
node.boot_interface = 'fake'
|
||||
node.save()
|
||||
self.config(enabled_boot_interfaces=['fake'],
|
||||
default_boot_interface='fake')
|
||||
with task_manager.acquire(self.context, node.uuid) as task:
|
||||
self.assertRaisesRegexp(exception.InvalidParameterValue,
|
||||
'requires the pxe boot interface',
|
||||
task.driver.deploy.validate, task)
|
||||
self.assertFalse(mock_validate_image.called)
|
||||
|
||||
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
||||
def test_validate_calls_boot_validate(self, mock_validate):
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
task.driver.deploy.validate(task)
|
||||
mock_validate.assert_called_once_with(mock.ANY, task)
|
||||
|
||||
|
||||
class PXEValidateRescueTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -18,3 +18,7 @@ append mbr:{{ DISK_IDENTIFIER }}
|
||||
label trusted_boot
|
||||
kernel mboot
|
||||
append tboot.gz --- /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/kernel root={{ ROOT }} ro text test_param intel_iommu=on --- /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/ramdisk
|
||||
|
||||
label boot_ramdisk
|
||||
kernel /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/kernel
|
||||
append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/ramdisk root=/dev/ram0 text test_param ramdisk_param
|
||||
|
@ -12,6 +12,11 @@ menuentry "boot_partition" {
|
||||
initrdefi /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/ramdisk
|
||||
}
|
||||
|
||||
menuentry "boot_ramdisk" {
|
||||
linuxefi /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_kernel root=/dev/ram0 text test_param ramdisk_param
|
||||
initrdefi /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk
|
||||
}
|
||||
|
||||
menuentry "boot_whole_disk" {
|
||||
linuxefi chain.c32 mbr:(( DISK_IDENTIFIER ))
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds a ``ramdisk`` deploy interface for deployments that wish to network
|
||||
boot to a ramdisk, as opposed to perform a complete
|
||||
traditional deployment to a physical media. This may be useful in
|
||||
scientific use cases or where ephemeral baremetal machines are desired.
|
||||
|
||||
The ``ramdisk`` deploy interface is intended for advanced users and has
|
||||
some particular operational caveats that the users should be aware of
|
||||
prior to use, such as network access list requirements and configuration
|
||||
drive architectural restrictions and the inability to leverage
|
||||
configuration drives.
|
@ -80,6 +80,7 @@ ironic.hardware.interfaces.deploy =
|
||||
iscsi = ironic.drivers.modules.iscsi_deploy:ISCSIDeploy
|
||||
oneview-direct = ironic.drivers.modules.oneview.deploy:OneViewAgentDeploy
|
||||
oneview-iscsi = ironic.drivers.modules.oneview.deploy:OneViewIscsiDeploy
|
||||
ramdisk = ironic.drivers.modules.pxe:PXERamdiskDeploy
|
||||
|
||||
ironic.hardware.interfaces.inspect =
|
||||
fake = ironic.drivers.modules.fake:FakeInspect
|
||||
|
Loading…
Reference in New Issue
Block a user