Add support for local boot
This patch implements the part that reads the specified boot_option in the node.instance_info['capabilities'] that have been passed to Ironic by the Nova Ironic Driver and pass that information to the deploy ramdisk by adding it to the kernel cmdline in the PXE configuration file that will be generated to that node. This patch also makes sure that we clean the PXE configuration files for the node marked to local boot after it's deployed, so that any attempt to boot it from the network will not work. This patch only apply to the pxe_* drivers, because the blueprint is about adding local boot support for deployments that uses partition images. The agent driver right now supports full disk images only. Implements: blueprint local-boot-support-with-partition-images Change-Id: Ide08e2b41dcf74c69dfbce242112da701fa15187
This commit is contained in:
parent
c14e3dc46e
commit
a4cf7149fb
@ -29,6 +29,7 @@ from oslo.utils import excutils
|
||||
from oslo.utils import units
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
from oslo_serialization import jsonutils
|
||||
import requests
|
||||
import six
|
||||
|
||||
@ -677,3 +678,39 @@ def get_single_nic_with_vif_port_id(task):
|
||||
for port in task.ports:
|
||||
if port.extra.get('vif_port_id'):
|
||||
return port.address
|
||||
|
||||
|
||||
def parse_instance_info_capabilities(node):
|
||||
"""Parse the instance_info capabilities.
|
||||
|
||||
These capabilities are defined in the Flavor extra_spec and passed
|
||||
to Ironic by the Nova Ironic driver.
|
||||
|
||||
NOTE: Although our API fully supports JSON fields, to maintain the
|
||||
backward compatibility with Juno the Nova Ironic driver is sending
|
||||
it as a string.
|
||||
|
||||
:param node: a single Node.
|
||||
:raises: InvalidParameterValue if the capabilities string is not a
|
||||
dictionary or is malformed.
|
||||
:returns: A dictionary with the capabilities if found, otherwise an
|
||||
empty dictionary.
|
||||
"""
|
||||
|
||||
def parse_error():
|
||||
error_msg = (_("Error parsing capabilities from Node %s instance_info "
|
||||
"field. A dictionary or a dictionary string is "
|
||||
"expected.") % node.uuid)
|
||||
raise exception.InvalidParameterValue(error_msg)
|
||||
|
||||
capabilities = node.instance_info.get('capabilities', {})
|
||||
if isinstance(capabilities, six.string_types):
|
||||
try:
|
||||
capabilities = jsonutils.loads(capabilities)
|
||||
except (ValueError, TypeError):
|
||||
parse_error()
|
||||
|
||||
if not isinstance(capabilities, dict):
|
||||
parse_error()
|
||||
|
||||
return capabilities
|
||||
|
@ -5,7 +5,7 @@ dhcp
|
||||
goto deploy
|
||||
|
||||
:deploy
|
||||
kernel {{ pxe_options.deployment_aki_path }} selinux=0 disk={{ pxe_options.disk }} iscsi_target_iqn={{ pxe_options.iscsi_target_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} ip=${ip}:${next-server}:${gateway}:${netmask} BOOTIF=${mac} {% if pxe_options.root_device %}root_device={{ pxe_options.root_device }}{% endif %}
|
||||
kernel {{ pxe_options.deployment_aki_path }} selinux=0 disk={{ pxe_options.disk }} iscsi_target_iqn={{ pxe_options.iscsi_target_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} boot_option={{ pxe_options.boot_option }} ip=${ip}:${next-server}:${gateway}:${netmask} BOOTIF=${mac} {% if pxe_options.root_device %}root_device={{ pxe_options.root_device }}{% endif %}
|
||||
initrd {{ pxe_options.deployment_ari_path }}
|
||||
boot
|
||||
|
||||
|
@ -316,6 +316,18 @@ def parse_root_device_hints(node):
|
||||
return ','.join(hints)
|
||||
|
||||
|
||||
def get_boot_option(node):
|
||||
"""Get the boot mode.
|
||||
|
||||
:param node: A single Node.
|
||||
:raises: InvalidParameterValue if the capabilities string is not a
|
||||
dict or is malformed.
|
||||
:returns: A string representing the boot mode type. Defaults to 'netboot'.
|
||||
"""
|
||||
capabilities = deploy_utils.parse_instance_info_capabilities(node)
|
||||
return capabilities.get('boot_option', 'netboot').lower()
|
||||
|
||||
|
||||
def build_deploy_ramdisk_options(node):
|
||||
"""Build the ramdisk config options for a node
|
||||
|
||||
@ -343,6 +355,7 @@ def build_deploy_ramdisk_options(node):
|
||||
'iscsi_target_iqn': "iqn-%s" % node.uuid,
|
||||
'ironic_api_url': ironic_api,
|
||||
'disk': CONF.pxe.disk_devices,
|
||||
'boot_option': get_boot_option(node),
|
||||
}
|
||||
|
||||
root_device = parse_root_device_hints(node)
|
||||
|
@ -22,6 +22,7 @@ import shutil
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import dhcp_factory
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
@ -267,6 +268,24 @@ def _destroy_token_file(node):
|
||||
utils.unlink_without_raise(token_file_path)
|
||||
|
||||
|
||||
def try_set_boot_device(task, device, persistent=True):
|
||||
# NOTE(faizan): Under UEFI boot mode, setting of boot device may differ
|
||||
# between different machines. IPMI does not work for setting boot
|
||||
# devices in UEFI mode for certain machines.
|
||||
# Expected IPMI failure for uefi boot mode. Logging a message to
|
||||
# set the boot device manually and continue with deploy.
|
||||
try:
|
||||
manager_utils.node_set_boot_device(task, device, persistent=persistent)
|
||||
except exception.IPMIFailure:
|
||||
if driver_utils.get_node_capability(task.node,
|
||||
'boot_mode') == 'uefi':
|
||||
LOG.warning(_LW("ipmitool is unable to set boot device while "
|
||||
"the node %s is in UEFI boot mode. Please set "
|
||||
"the boot device manually.") % task.node.uuid)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
class PXEDeploy(base.DeployInterface):
|
||||
"""PXE Deploy Interface for deploy-related actions."""
|
||||
|
||||
@ -280,9 +299,24 @@ class PXEDeploy(base.DeployInterface):
|
||||
:raises: InvalidParameterValue.
|
||||
:raises: MissingParameterValue
|
||||
"""
|
||||
node = task.node
|
||||
|
||||
# Check the boot_mode capability parameter value.
|
||||
driver_utils.validate_boot_mode_capability(task.node)
|
||||
# Check the boot_mode and boot_option capabilities values.
|
||||
driver_utils.validate_boot_mode_capability(node)
|
||||
driver_utils.validate_boot_option_capability(node)
|
||||
|
||||
boot_mode = driver_utils.get_node_capability(node, 'boot_mode')
|
||||
boot_option = driver_utils.get_node_capability(node, 'boot_option')
|
||||
|
||||
# NOTE(lucasagomes): We don't support UEFI + localboot because
|
||||
# we do not support creating an EFI boot partition, including the
|
||||
# EFI modules and managing the bootloader variables via efibootmgr.
|
||||
if boot_mode == 'uefi' and boot_option == 'local':
|
||||
error_msg = (_("Local boot is requested, but can't be "
|
||||
"used with node %s because it's configured "
|
||||
"to use UEFI boot") % node.uuid)
|
||||
LOG.error(error_msg)
|
||||
raise exception.InvalidParameterValue(error_msg)
|
||||
|
||||
if CONF.pxe.ipxe_enabled:
|
||||
if not CONF.pxe.http_url or not CONF.pxe.http_root:
|
||||
@ -290,16 +324,15 @@ class PXEDeploy(base.DeployInterface):
|
||||
"iPXE boot is enabled but no HTTP URL or HTTP "
|
||||
"root was specified."))
|
||||
# iPXE and UEFI should not be configured together.
|
||||
if driver_utils.get_node_capability(task.node,
|
||||
'boot_mode') == 'uefi':
|
||||
if boot_mode == 'uefi':
|
||||
LOG.error(_LE("UEFI boot mode is not supported with "
|
||||
"iPXE boot enabled."))
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Conflict: iPXE is enabled, but cannot be used with node"
|
||||
"%(node_uuid)s configured to use UEFI boot") %
|
||||
{'node_uuid': task.node.uuid})
|
||||
{'node_uuid': node.uuid})
|
||||
|
||||
d_info = _parse_deploy_info(task.node)
|
||||
d_info = _parse_deploy_info(node)
|
||||
|
||||
iscsi_deploy.validate(task)
|
||||
|
||||
@ -331,22 +364,7 @@ class PXEDeploy(base.DeployInterface):
|
||||
provider = dhcp_factory.DHCPFactory()
|
||||
provider.update_dhcp(task, dhcp_opts)
|
||||
|
||||
# NOTE(faizan): Under UEFI boot mode, setting of boot device may differ
|
||||
# between different machines. IPMI does not work for setting boot
|
||||
# devices in UEFI mode for certain machines.
|
||||
# Expected IPMI failure for uefi boot mode. Logging a message to
|
||||
# set the boot device manually and continue with deploy.
|
||||
try:
|
||||
manager_utils.node_set_boot_device(task, 'pxe', persistent=True)
|
||||
except exception.IPMIFailure:
|
||||
if driver_utils.get_node_capability(task.node,
|
||||
'boot_mode') == 'uefi':
|
||||
LOG.warning(_LW("ipmitool is unable to set boot device while "
|
||||
"the node is in UEFI boot mode."
|
||||
"Please set the boot device manually."))
|
||||
else:
|
||||
raise
|
||||
|
||||
try_set_boot_device(task, boot_devices.PXE)
|
||||
manager_utils.node_power_action(task, states.REBOOT)
|
||||
|
||||
return states.DEPLOYWAIT
|
||||
@ -390,6 +408,9 @@ class PXEDeploy(base.DeployInterface):
|
||||
|
||||
pxe_utils.create_pxe_config(task, pxe_options,
|
||||
pxe_config_template)
|
||||
|
||||
# FIXME(lucasagomes): If it's local boot we should not cache
|
||||
# the image kernel and ramdisk (Or even require it).
|
||||
_cache_ramdisk_kernel(task.context, task.node, pxe_info)
|
||||
|
||||
def clean_up(self, task):
|
||||
@ -425,6 +446,13 @@ class PXEDeploy(base.DeployInterface):
|
||||
provider = dhcp_factory.DHCPFactory()
|
||||
provider.update_dhcp(task, dhcp_opts)
|
||||
|
||||
if iscsi_deploy.get_boot_option(task.node) == "local":
|
||||
# If it's going to boot from the local disk, we don't need
|
||||
# PXE config files. They still need to be generated as part
|
||||
# of the prepare() because the deployment does PXE boot the
|
||||
# deploy ramdisk
|
||||
pxe_utils.clean_up_pxe_config(task)
|
||||
|
||||
|
||||
class VendorPassthru(base.VendorInterface):
|
||||
"""Interface to mix IPMI and PXE vendor-specific interfaces."""
|
||||
@ -444,6 +472,7 @@ class VendorPassthru(base.VendorInterface):
|
||||
:param kwargs: kwargs containins the method's parameters.
|
||||
:raises: InvalidParameterValue if any parameters is invalid.
|
||||
"""
|
||||
driver_utils.validate_boot_option_capability(task.node)
|
||||
iscsi_deploy.get_deploy_info(task.node, **kwargs)
|
||||
|
||||
@base.passthru(['POST'], method='pass_deploy_info')
|
||||
@ -469,12 +498,17 @@ class VendorPassthru(base.VendorInterface):
|
||||
return
|
||||
|
||||
try:
|
||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
|
||||
deploy_utils.switch_pxe_config(pxe_config_path, root_uuid,
|
||||
driver_utils.get_node_capability(node, 'boot_mode'))
|
||||
if iscsi_deploy.get_boot_option(node) == "local":
|
||||
try_set_boot_device(task, boot_devices.DISK)
|
||||
# If it's going to boot from the local disk, get rid of
|
||||
# the PXE configuration files used for the deployment
|
||||
pxe_utils.clean_up_pxe_config(task)
|
||||
else:
|
||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
|
||||
deploy_utils.switch_pxe_config(pxe_config_path, root_uuid,
|
||||
driver_utils.get_node_capability(node, 'boot_mode'))
|
||||
|
||||
deploy_utils.notify_deploy_complete(kwargs['address'])
|
||||
|
||||
LOG.info(_LI('Deployment to node %s done'), node.uuid)
|
||||
task.process_event('done')
|
||||
except Exception as e:
|
||||
|
@ -2,7 +2,7 @@ default deploy
|
||||
|
||||
label deploy
|
||||
kernel {{ pxe_options.deployment_aki_path }}
|
||||
append initrd={{ pxe_options.deployment_ari_path }} rootfstype=ramfs selinux=0 disk={{ pxe_options.disk }} iscsi_target_iqn={{ pxe_options.iscsi_target_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} {% if pxe_options.root_device %}root_device={{ pxe_options.root_device }}{% endif %}
|
||||
append initrd={{ pxe_options.deployment_ari_path }} rootfstype=ramfs selinux=0 disk={{ pxe_options.disk }} iscsi_target_iqn={{ pxe_options.iscsi_target_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} boot_option={{ pxe_options.boot_option }} {% if pxe_options.root_device %}root_device={{ pxe_options.root_device }}{% endif %}
|
||||
ipappend 3
|
||||
|
||||
|
||||
|
@ -198,16 +198,44 @@ def add_node_capability(task, capability, value):
|
||||
node.save()
|
||||
|
||||
|
||||
def validate_capability(node, capability_name, valid_values):
|
||||
"""Validate a capabability set in node property
|
||||
|
||||
:param node: an ironic node object.
|
||||
:param capability_name: the name of the capability.
|
||||
:parameter valid_values: an iterable with valid values expected for
|
||||
that capability.
|
||||
:raises: InvalidParameterValue, if the capability is not set to the
|
||||
expected values.
|
||||
"""
|
||||
value = get_node_capability(node, capability_name)
|
||||
|
||||
if value and value not in valid_values:
|
||||
valid_value_str = ', '.join(valid_values)
|
||||
raise exception.InvalidParameterValue(
|
||||
_("Invalid %(capability)s parameter '%(value)s'. "
|
||||
"Acceptable values are: %(valid_values)s.") %
|
||||
{'capability': capability_name, 'value': value,
|
||||
'valid_values': valid_value_str})
|
||||
|
||||
|
||||
def validate_boot_mode_capability(node):
|
||||
"""Validate the boot_mode capability set in node property.
|
||||
"""Validate the boot_mode capability set in node properties.
|
||||
|
||||
:param node: an ironic node object.
|
||||
:raises: InvalidParameterValue, if 'boot_mode' capability is set
|
||||
other than 'bios' or 'uefi' or None.
|
||||
|
||||
"""
|
||||
boot_mode = get_node_capability(node, 'boot_mode')
|
||||
validate_capability(node, 'boot_mode', ('bios', 'uefi'))
|
||||
|
||||
if boot_mode and boot_mode not in ['bios', 'uefi']:
|
||||
raise exception.InvalidParameterValue(_("Invalid boot_mode "
|
||||
"parameter '%s'.") % boot_mode)
|
||||
|
||||
def validate_boot_option_capability(node):
|
||||
"""Validate the boot_option capability set in node properties.
|
||||
|
||||
:param node: an ironic node object.
|
||||
:raises: InvalidParameterValue, if 'boot_option' capability is set
|
||||
other than 'local' or 'netboot' or None.
|
||||
|
||||
"""
|
||||
validate_capability(node, 'boot_option', ('local', 'netboot'))
|
||||
|
@ -2,7 +2,7 @@ default deploy
|
||||
|
||||
label deploy
|
||||
kernel /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_kernel
|
||||
append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk rootfstype=ramfs selinux=0 disk=cciss/c0d0,sda,hda,vda iscsi_target_iqn=iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_id=1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_key=0123456789ABCDEFGHIJKLMNOPQRSTUV ironic_api_url=http://192.168.122.184:6385 troubleshoot=0 text test_param root_device=vendor=fake,size=123
|
||||
append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk rootfstype=ramfs selinux=0 disk=cciss/c0d0,sda,hda,vda iscsi_target_iqn=iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_id=1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_key=0123456789ABCDEFGHIJKLMNOPQRSTUV ironic_api_url=http://192.168.122.184:6385 troubleshoot=0 text test_param boot_option=netboot root_device=vendor=fake,size=123
|
||||
ipappend 3
|
||||
|
||||
|
||||
|
@ -1081,3 +1081,27 @@ class VirtualMediaDeployUtilsTestCase(db_base.DbTestCase):
|
||||
shared=False) as task:
|
||||
address = utils.get_single_nic_with_vif_port_id(task)
|
||||
self.assertEqual('aa:bb:cc', address)
|
||||
|
||||
|
||||
class ParseInstanceInfoCapabilitiesTestCase(tests_base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ParseInstanceInfoCapabilitiesTestCase, self).setUp()
|
||||
self.node = obj_utils.get_test_node(self.context, driver='fake')
|
||||
|
||||
def test_parse_instance_info_capabilities_string(self):
|
||||
self.node.instance_info = {'capabilities': '{"cat": "meow"}'}
|
||||
expected_result = {"cat": "meow"}
|
||||
result = utils.parse_instance_info_capabilities(self.node)
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
def test_parse_instance_info_capabilities(self):
|
||||
self.node.instance_info = {'capabilities': {"dog": "wuff"}}
|
||||
expected_result = {"dog": "wuff"}
|
||||
result = utils.parse_instance_info_capabilities(self.node)
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
def test_parse_instance_info_invalid_type(self):
|
||||
self.node.instance_info = {'capabilities': 'not-a-dict'}
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
utils.parse_instance_info_capabilities, self.node)
|
||||
|
@ -214,7 +214,8 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
||||
mock_rmtree.assert_called_once_with('/path/uuid')
|
||||
|
||||
def _test_build_deploy_ramdisk_options(self, mock_alnum, api_url,
|
||||
expected_root_device=None):
|
||||
expected_root_device=None,
|
||||
expected_boot_option='netboot'):
|
||||
fake_key = '0123456789ABCDEFGHIJKLMNOPQRSTUV'
|
||||
fake_disk = 'fake-disk'
|
||||
|
||||
@ -226,7 +227,8 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
||||
'deployment_id': self.node.uuid,
|
||||
'deployment_key': fake_key,
|
||||
'disk': fake_disk,
|
||||
'ironic_api_url': api_url}
|
||||
'ironic_api_url': api_url,
|
||||
'boot_option': expected_boot_option}
|
||||
|
||||
if expected_root_device:
|
||||
expected_opts['root_device'] = expected_root_device
|
||||
@ -272,6 +274,17 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
||||
self._test_build_deploy_ramdisk_options(mock_alnum, fake_api_url,
|
||||
expected_root_device=expected)
|
||||
|
||||
@mock.patch.object(keystone, 'get_service_url')
|
||||
@mock.patch.object(utils, 'random_alnum')
|
||||
def test_build_deploy_ramdisk_options_boot_option(self, mock_alnum,
|
||||
mock_get_url):
|
||||
self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
|
||||
expected = 'local'
|
||||
fake_api_url = 'http://127.0.0.1:6385'
|
||||
self.config(api_url=fake_api_url, group='conductor')
|
||||
self._test_build_deploy_ramdisk_options(mock_alnum, fake_api_url,
|
||||
expected_boot_option=expected)
|
||||
|
||||
def test_parse_root_device_hints(self):
|
||||
self.node.properties['root_device'] = {'wwn': 123456}
|
||||
expected = 'wwn=123456'
|
||||
@ -288,3 +301,13 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
||||
self.node.properties = {}
|
||||
result = iscsi_deploy.parse_root_device_hints(self.node)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_get_boot_option(self):
|
||||
self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
|
||||
result = iscsi_deploy.get_boot_option(self.node)
|
||||
self.assertEqual("local", result)
|
||||
|
||||
def test_get_boot_option_default_value(self):
|
||||
self.node.instance_info = {}
|
||||
result = iscsi_deploy.get_boot_option(self.node)
|
||||
self.assertEqual("netboot", result)
|
||||
|
@ -25,6 +25,7 @@ import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_serialization import jsonutils as json
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import dhcp_factory
|
||||
from ironic.common import exception
|
||||
from ironic.common.glance_service import base_image_service
|
||||
@ -158,7 +159,8 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
'deployment_id': 'fake-deploy-id',
|
||||
'deployment_key': 'fake-deploy-key',
|
||||
'disk': 'fake-disk',
|
||||
'ironic_api_url': 'fake-api-url'}
|
||||
'ironic_api_url': 'fake-api-url',
|
||||
'boot_option': 'netboot'}
|
||||
|
||||
deploy_opts_mock.return_value = fake_deploy_opts
|
||||
|
||||
@ -193,7 +195,8 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
'pxe_append_params': 'test_param',
|
||||
'aki_path': kernel,
|
||||
'deployment_aki_path': deploy_kernel,
|
||||
'tftp_server': tftp_server
|
||||
'tftp_server': tftp_server,
|
||||
'boot_option': 'netboot'
|
||||
}
|
||||
|
||||
expected_options.update(fake_deploy_opts)
|
||||
@ -353,6 +356,28 @@ class PXEDriverTestCase(db_base.DbTestCase):
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.deploy.validate, task)
|
||||
|
||||
@mock.patch.object(base_image_service.BaseImageService, '_show')
|
||||
def test_validate_fail_invalid_boot_option(self, mock_glance):
|
||||
properties = {'capabilities': 'boot_option:foo,dog:wuff'}
|
||||
mock_glance.return_value = {'properties': {'kernel_id': 'fake-kernel',
|
||||
'ramdisk_id': 'fake-initr'}}
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.properties = properties
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.deploy.validate, task)
|
||||
|
||||
@mock.patch.object(base_image_service.BaseImageService, '_show')
|
||||
def test_validate_fail_invalid_uefi_and_localboot(self, mock_glance):
|
||||
properties = {'capabilities': 'boot_mode:uefi,boot_option:local'}
|
||||
mock_glance.return_value = {'properties': {'kernel_id': 'fake-kernel',
|
||||
'ramdisk_id': 'fake-initr'}}
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.properties = properties
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.deploy.validate, task)
|
||||
|
||||
def test_validate_fail_no_port(self):
|
||||
new_node = obj_utils.create_test_node(
|
||||
self.context,
|
||||
@ -580,12 +605,34 @@ class PXEDriverTestCase(db_base.DbTestCase):
|
||||
update_dhcp_mock.assert_called_once_with(
|
||||
task, dhcp_opts)
|
||||
|
||||
@mock.patch.object(pxe_utils, 'clean_up_pxe_config')
|
||||
@mock.patch.object(dhcp_factory.DHCPFactory, 'update_dhcp')
|
||||
def test_take_over_localboot(self, update_dhcp_mock, clean_pxe_mock):
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=True) as task:
|
||||
task.node.instance_info['capabilities'] = {"boot_option": "local"}
|
||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
||||
task.driver.deploy.take_over(task)
|
||||
update_dhcp_mock.assert_called_once_with(
|
||||
task, dhcp_opts)
|
||||
clean_pxe_mock.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(pxe_utils, 'clean_up_pxe_config')
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device')
|
||||
@mock.patch.object(deploy_utils, 'notify_deploy_complete')
|
||||
@mock.patch.object(deploy_utils, 'switch_pxe_config')
|
||||
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
|
||||
def test_continue_deploy_good(self, mock_image_cache, mock_switch_config,
|
||||
notify_mock):
|
||||
def _test_continue_deploy(self, is_localboot, mock_image_cache,
|
||||
mock_switch_config, notify_mock,
|
||||
mock_node_boot_dev, mock_clean_pxe):
|
||||
token_path = self._create_token_file()
|
||||
|
||||
# set local boot
|
||||
if is_localboot:
|
||||
i_info = self.node.instance_info
|
||||
i_info['capabilities'] = '{"boot_option": "local"}'
|
||||
self.node.instance_info = i_info
|
||||
|
||||
self.node.power_state = states.POWER_ON
|
||||
self.node.provision_state = states.DEPLOYWAIT
|
||||
self.node.target_provision_state = states.ACTIVE
|
||||
@ -614,9 +661,23 @@ class PXEDriverTestCase(db_base.DbTestCase):
|
||||
mock_image_cache.assert_called_once_with()
|
||||
mock_image_cache.return_value.clean_up.assert_called_once_with()
|
||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
||||
mock_switch_config.assert_called_once_with(pxe_config_path, root_uuid,
|
||||
boot_mode)
|
||||
notify_mock.assert_called_once_with('123456')
|
||||
if is_localboot:
|
||||
mock_node_boot_dev.assert_called_once_with(
|
||||
mock.ANY, boot_devices.DISK, persistent=True)
|
||||
mock_clean_pxe.assert_called_once_with(mock.ANY)
|
||||
self.assertFalse(mock_switch_config.called)
|
||||
else:
|
||||
mock_switch_config.assert_called_once_with(
|
||||
pxe_config_path, root_uuid, boot_mode)
|
||||
self.assertFalse(mock_node_boot_dev.called)
|
||||
self.assertFalse(mock_clean_pxe.called)
|
||||
|
||||
def test_continue_deploy(self):
|
||||
self._test_continue_deploy(False)
|
||||
|
||||
def test_continue_deploy_localboot(self):
|
||||
self._test_continue_deploy(True)
|
||||
|
||||
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
|
||||
def test_continue_deploy_fail(self, mock_image_cache):
|
||||
|
@ -133,6 +133,22 @@ class UtilsTestCase(db_base.DbTestCase):
|
||||
self.assertIsNone(driver_utils.rm_node_capability(task, 'x'))
|
||||
self.assertEqual('a:b', task.node.properties['capabilities'])
|
||||
|
||||
def test_validate_capability(self):
|
||||
properties = {'capabilities': 'cat:meow,cap2:value2'}
|
||||
self.node.properties = properties
|
||||
|
||||
result = driver_utils.validate_capability(
|
||||
self.node, 'cat', ['meow', 'purr'])
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_validate_capability_with_exception(self):
|
||||
properties = {'capabilities': 'cat:bark,cap2:value2'}
|
||||
self.node.properties = properties
|
||||
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
driver_utils.validate_capability,
|
||||
self.node, 'cat', ['meow', 'purr'])
|
||||
|
||||
def test_validate_boot_mode_capability(self):
|
||||
properties = {'capabilities': 'boot_mode:uefi,cap2:value2'}
|
||||
self.node.properties = properties
|
||||
@ -146,3 +162,17 @@ class UtilsTestCase(db_base.DbTestCase):
|
||||
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
driver_utils.validate_boot_mode_capability, self.node)
|
||||
|
||||
def test_validate_boot_option_capability(self):
|
||||
properties = {'capabilities': 'boot_option:netboot,cap2:value2'}
|
||||
self.node.properties = properties
|
||||
|
||||
result = driver_utils.validate_boot_option_capability(self.node)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_validate_boot_option_capability_with_exception(self):
|
||||
properties = {'capabilities': 'boot_option:foo,cap2:value2'}
|
||||
self.node.properties = properties
|
||||
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
driver_utils.validate_boot_option_capability, self.node)
|
||||
|
@ -49,7 +49,8 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
'deployment_aki_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-'
|
||||
u'c02d7f33c123/deploy_kernel',
|
||||
'disk': 'cciss/c0d0,sda,hda,vda',
|
||||
'root_device': 'vendor=fake,size=123'
|
||||
'root_device': 'vendor=fake,size=123',
|
||||
'boot_option': 'netboot',
|
||||
}
|
||||
self.agent_pxe_options = {
|
||||
'deployment_ari_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7'
|
||||
|
Loading…
x
Reference in New Issue
Block a user