Merge "Add anaconda support in the pxe boot driver"
This commit is contained in:
commit
ea6dff7d31
@ -14,6 +14,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import os
|
||||
|
||||
from ironic_lib import utils as ironic_utils
|
||||
@ -29,6 +30,7 @@ from ironic.common import image_service as service
|
||||
from ironic.common import images
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.conf import CONF
|
||||
from ironic.drivers.modules import boot_mode_utils
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
@ -244,6 +246,54 @@ def get_pxe_config_file_path(node_uuid, ipxe_enabled=False):
|
||||
return os.path.join(get_root_dir(), node_uuid, 'config')
|
||||
|
||||
|
||||
def get_file_path_from_label(node_uuid, root_dir, label):
|
||||
"""Generate absolute paths to various images from their name(label)
|
||||
|
||||
This method generates absolute file system path on the conductor where
|
||||
various images need to be placed. For example the kickstart template, file
|
||||
and stage2 squashfs.img needs to be placed in the ipxe_root_dir since they
|
||||
will be transferred by anaconda ramdisk over http(s). The generated paths
|
||||
will be added to the image_info dictionary as values.
|
||||
|
||||
:param node_uuid: the UUID of the node
|
||||
:param root_dir: Directory in which the image must be placed
|
||||
:param label: Name of the image
|
||||
"""
|
||||
if label == 'ks_template':
|
||||
return os.path.join(get_ipxe_root_dir(), node_uuid, 'ks.cfg.template')
|
||||
elif label == 'ks_cfg':
|
||||
return os.path.join(get_ipxe_root_dir(), node_uuid, 'ks.cfg')
|
||||
elif label == 'stage2':
|
||||
return os.path.join(get_ipxe_root_dir(), node_uuid, 'LiveOS',
|
||||
'squashfs.img')
|
||||
else:
|
||||
return os.path.join(root_dir, node_uuid, label)
|
||||
|
||||
|
||||
def get_http_url_path_from_label(http_url, node_uuid, label):
|
||||
"""Generate http url path to various image artifacts
|
||||
|
||||
This method generates http(s) urls for various image artifacts int the
|
||||
webserver root. The generated urls will be added to the pxe_options dict
|
||||
and used to render pxe/ipxe configuration templates.
|
||||
|
||||
:param http_url: URL to access the root of the webserver
|
||||
:param node_uuid: the UUID of the node
|
||||
:param label: Name of the image
|
||||
"""
|
||||
if label == 'ks_template':
|
||||
return '/'.join([http_url, node_uuid, 'ks.cfg.template'])
|
||||
elif label == 'ks_cfg':
|
||||
return '/'.join([http_url, node_uuid, 'ks.cfg'])
|
||||
elif label == 'stage2':
|
||||
# we store stage2 in http_root/node_uuid/LiveOS/squashfs.img
|
||||
# Specifying http://host/node_uuid as stage2 url will make anaconda
|
||||
# automatically load the squashfs.img from LiveOS directory.
|
||||
return '/'.join([http_url, node_uuid])
|
||||
else:
|
||||
return '/'.join([http_url, node_uuid, label])
|
||||
|
||||
|
||||
def create_pxe_config(task, pxe_options, template=None, ipxe_enabled=False):
|
||||
"""Generate PXE configuration file and MAC address links for it.
|
||||
|
||||
@ -642,10 +692,39 @@ def get_instance_image_info(task, ipxe_enabled=False):
|
||||
i_info[label] = str(iproperties[label + '_id'])
|
||||
node.instance_info = i_info
|
||||
node.save()
|
||||
for label in labels:
|
||||
|
||||
anaconda_labels = ()
|
||||
if deploy_utils.get_boot_option(node) == 'kickstart':
|
||||
# stage2 - Installer stage2 squashfs image
|
||||
# ks_template - Anaconda kickstart template
|
||||
# ks_cfg - rendered ks_template
|
||||
anaconda_labels = ('stage2', 'ks_template', 'ks_cfg')
|
||||
if not (i_info.get('stage2') and i_info.get('ks_template')):
|
||||
iproperties = glance_service.show(
|
||||
d_info['image_source']
|
||||
)['properties']
|
||||
for label in anaconda_labels:
|
||||
# ks_template is an optional property on the image
|
||||
if (label == 'ks_template'
|
||||
and not iproperties.get('ks_template')):
|
||||
i_info[label] = CONF.anaconda.default_ks_template
|
||||
elif label == 'ks_cfg':
|
||||
i_info[label] = ''
|
||||
elif label == 'stage2' and 'stage2_id' not in iproperties:
|
||||
msg = ("stage2_id property missing on the image. "
|
||||
"The anaconda deploy interface requires stage2_id "
|
||||
"property to be associated with the os image. ")
|
||||
raise exception.ImageUnacceptable(msg)
|
||||
else:
|
||||
i_info[label] = str(iproperties['stage2_id'])
|
||||
|
||||
node.instance_info = i_info
|
||||
node.save()
|
||||
|
||||
for label in labels + anaconda_labels:
|
||||
image_info[label] = (
|
||||
i_info[label],
|
||||
os.path.join(root_dir, node.uuid, label)
|
||||
get_file_path_from_label(node.uuid, root_dir, label)
|
||||
)
|
||||
|
||||
return image_info
|
||||
@ -705,15 +784,18 @@ def build_instance_pxe_options(task, pxe_info, ipxe_enabled=False):
|
||||
node = task.node
|
||||
|
||||
for label, option in (('kernel', 'aki_path'),
|
||||
('ramdisk', 'ari_path')):
|
||||
('ramdisk', 'ari_path'),
|
||||
('stage2', 'stage2_url'),
|
||||
('ks_template', 'ks_template_path'),
|
||||
('ks_cfg', 'ks_cfg_url')):
|
||||
if label in pxe_info:
|
||||
if ipxe_enabled:
|
||||
if ipxe_enabled or label in ('stage2', 'ks_template', 'ks_cfg'):
|
||||
# NOTE(pas-ha) do not use Swift TempURLs for kernel and
|
||||
# ramdisk of user image when boot_option is not local,
|
||||
# as this breaks instance reboot later when temp urls
|
||||
# have timed out.
|
||||
pxe_opts[option] = '/'.join(
|
||||
[CONF.deploy.http_url, node.uuid, label])
|
||||
pxe_opts[option] = get_http_url_path_from_label(
|
||||
CONF.deploy.http_url, node.uuid, label)
|
||||
else:
|
||||
# It is possible that we don't have kernel/ramdisk or even
|
||||
# image_source to determine if it's a whole disk image or not.
|
||||
@ -810,7 +892,8 @@ def build_service_pxe_config(task, instance_image_info,
|
||||
root_uuid_or_disk_id,
|
||||
ramdisk_boot=False,
|
||||
ipxe_enabled=False,
|
||||
is_whole_disk_image=None):
|
||||
is_whole_disk_image=None,
|
||||
anaconda_boot=False):
|
||||
node = task.node
|
||||
pxe_config_path = get_pxe_config_file_path(node.uuid,
|
||||
ipxe_enabled=ipxe_enabled)
|
||||
@ -844,7 +927,38 @@ def build_service_pxe_config(task, instance_image_info,
|
||||
is_whole_disk_image,
|
||||
deploy_utils.is_trusted_boot_requested(node),
|
||||
deploy_utils.is_iscsi_boot(task), ramdisk_boot,
|
||||
ipxe_enabled=ipxe_enabled)
|
||||
ipxe_enabled=ipxe_enabled, anaconda_boot=anaconda_boot)
|
||||
|
||||
|
||||
def _build_heartbeat_url(node_uuid):
|
||||
|
||||
api_version = 'v1'
|
||||
heartbeat_api = '%s/heartbeat/{node_uuid}' % api_version
|
||||
path = heartbeat_api.format(node_uuid=node_uuid)
|
||||
return "/".join([deploy_utils.get_ironic_api_url(), path])
|
||||
|
||||
|
||||
def build_kickstart_config_options(task):
|
||||
"""Build the kickstart template options for a node
|
||||
|
||||
This method builds the kickstart template options for a node,
|
||||
given all the required parameters.
|
||||
|
||||
The options should then be passed to pxe_utils.create_kickstart_config to
|
||||
create the actual config files.
|
||||
|
||||
:param task: A TaskManager object
|
||||
:returns: A dictionary of kickstart options to be used in the kickstart
|
||||
template.
|
||||
"""
|
||||
ks_options = {}
|
||||
node = task.node
|
||||
manager_utils.add_secret_token(node, pregenerated=True)
|
||||
node.save()
|
||||
ks_options['liveimg_url'] = node.instance_info['image_url']
|
||||
ks_options['agent_token'] = node.driver_internal_info['agent_secret_token']
|
||||
ks_options['heartbeat_url'] = _build_heartbeat_url(node.uuid)
|
||||
return ks_options
|
||||
|
||||
|
||||
def get_volume_pxe_options(task):
|
||||
@ -949,7 +1063,8 @@ def validate_boot_parameters_for_trusted_boot(node):
|
||||
def prepare_instance_pxe_config(task, image_info,
|
||||
iscsi_boot=False,
|
||||
ramdisk_boot=False,
|
||||
ipxe_enabled=False):
|
||||
ipxe_enabled=False,
|
||||
anaconda_boot=False):
|
||||
"""Prepares the config file for PXE boot
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
@ -959,6 +1074,7 @@ def prepare_instance_pxe_config(task, image_info,
|
||||
:param ramdisk_boot: if the boot is to a ramdisk configuration.
|
||||
:param ipxe_enabled: Default false boolean to indicate if ipxe
|
||||
is in use by the caller.
|
||||
:param anaconda_boot: if the boot is to a anaconda ramdisk configuration.
|
||||
:returns: None
|
||||
"""
|
||||
node = task.node
|
||||
@ -978,7 +1094,7 @@ def prepare_instance_pxe_config(task, image_info,
|
||||
node.uuid, ipxe_enabled=ipxe_enabled)
|
||||
if not os.path.isfile(pxe_config_path):
|
||||
pxe_options = build_pxe_config_options(
|
||||
task, image_info, service=ramdisk_boot,
|
||||
task, image_info, service=ramdisk_boot or anaconda_boot,
|
||||
ipxe_enabled=ipxe_enabled)
|
||||
if ipxe_enabled:
|
||||
pxe_config_template = (
|
||||
@ -993,7 +1109,23 @@ def prepare_instance_pxe_config(task, image_info,
|
||||
pxe_config_path, None,
|
||||
boot_mode_utils.get_boot_mode(node), False,
|
||||
iscsi_boot=iscsi_boot, ramdisk_boot=ramdisk_boot,
|
||||
ipxe_enabled=ipxe_enabled)
|
||||
ipxe_enabled=ipxe_enabled, anaconda_boot=anaconda_boot)
|
||||
|
||||
|
||||
def prepare_instance_kickstart_config(task, image_info, anaconda_boot=False):
|
||||
"""Prepare to boot anaconda ramdisk by generating kickstart file
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:param image_info: a dict of values of instance image
|
||||
metadata to set on the configuration file.
|
||||
:param anaconda_boot: if the boot is to a anaconda ramdisk configuration.
|
||||
"""
|
||||
if not anaconda_boot:
|
||||
return
|
||||
ks_options = build_kickstart_config_options(task)
|
||||
kickstart_template = image_info['ks_template'][1]
|
||||
ks_cfg = utils.render_template(kickstart_template, ks_options)
|
||||
utils.write_to_file(image_info['ks_cfg'][1], ks_cfg)
|
||||
|
||||
|
||||
@image_cache.cleanup(priority=25)
|
||||
@ -1012,14 +1144,30 @@ def cache_ramdisk_kernel(task, pxe_info, ipxe_enabled=False):
|
||||
"""Fetch the necessary kernels and ramdisks for the instance."""
|
||||
ctx = task.context
|
||||
node = task.node
|
||||
t_pxe_info = copy.copy(pxe_info)
|
||||
if ipxe_enabled:
|
||||
path = os.path.join(get_ipxe_root_dir(), node.uuid)
|
||||
else:
|
||||
path = os.path.join(get_root_dir(), node.uuid)
|
||||
fileutils.ensure_tree(path)
|
||||
# anconda deploy will have 'stage2' as one of the labels in pxe_info dict
|
||||
if 'stage2' in pxe_info.keys():
|
||||
# stage2 will be stored in ipxe http directory. So make sure they
|
||||
# exist.
|
||||
fileutils.ensure_tree(
|
||||
get_file_path_from_label(
|
||||
node.uuid,
|
||||
get_ipxe_root_dir(),
|
||||
'stage2'
|
||||
)
|
||||
)
|
||||
# ks_cfg is rendered later by the driver using ks_template. It cannot
|
||||
# be fetched and cached.
|
||||
t_pxe_info.pop('ks_cfg')
|
||||
|
||||
LOG.debug("Fetching necessary kernel and ramdisk for node %s",
|
||||
node.uuid)
|
||||
deploy_utils.fetch_images(ctx, TFTPImageCache(), list(pxe_info.values()),
|
||||
deploy_utils.fetch_images(ctx, TFTPImageCache(), list(t_pxe_info.values()),
|
||||
CONF.force_raw_images)
|
||||
|
||||
|
||||
|
@ -131,7 +131,8 @@ def _replace_root_uuid(path, root_uuid):
|
||||
|
||||
def _replace_boot_line(path, boot_mode, is_whole_disk_image,
|
||||
trusted_boot=False, iscsi_boot=False,
|
||||
ramdisk_boot=False, ipxe_enabled=False):
|
||||
ramdisk_boot=False, ipxe_enabled=False,
|
||||
anaconda_boot=False):
|
||||
if is_whole_disk_image:
|
||||
boot_disk_type = 'boot_whole_disk'
|
||||
elif trusted_boot:
|
||||
@ -140,6 +141,8 @@ def _replace_boot_line(path, boot_mode, is_whole_disk_image,
|
||||
boot_disk_type = 'boot_iscsi'
|
||||
elif ramdisk_boot:
|
||||
boot_disk_type = 'boot_ramdisk'
|
||||
elif anaconda_boot:
|
||||
boot_disk_type = 'boot_anaconda'
|
||||
else:
|
||||
boot_disk_type = 'boot_partition'
|
||||
|
||||
@ -163,7 +166,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, ramdisk_boot=False,
|
||||
ipxe_enabled=False):
|
||||
ipxe_enabled=False, anaconda_boot=False):
|
||||
"""Switch a pxe config from deployment mode to service mode.
|
||||
|
||||
:param path: path to the pxe config file in tftpboot.
|
||||
@ -178,15 +181,17 @@ def switch_pxe_config(path, root_uuid_or_disk_id, boot_mode,
|
||||
:param ramdisk_boot: if the boot is to be to a ramdisk configuration.
|
||||
:param ipxe_enabled: A default False boolean value to tell the method
|
||||
if the caller is using iPXE.
|
||||
:param anaconda_boot: if the boot is to be to an anaconda configuration.
|
||||
"""
|
||||
if not ramdisk_boot and root_uuid_or_disk_id is not None:
|
||||
if (not (ramdisk_boot or anaconda_boot)
|
||||
and root_uuid_or_disk_id is not None):
|
||||
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, ramdisk_boot, ipxe_enabled)
|
||||
iscsi_boot, ramdisk_boot, ipxe_enabled, anaconda_boot)
|
||||
|
||||
|
||||
def check_for_missing_params(info_dict, error_msg, param_prefix=''):
|
||||
|
@ -31,6 +31,12 @@ kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeou
|
||||
initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.ari_path }} || goto boot_partition
|
||||
boot
|
||||
|
||||
:boot_anaconda
|
||||
imgfree
|
||||
kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.aki_path }} text {{ pxe_options.pxe_append_params|default("", true) }} inst.ks={{ pxe_options.ks_cfg_url }} inst.stage2={{ pxe_options.stage2_url }} initrd=ramdisk || goto boot_anaconda
|
||||
initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.ari_path }} || goto boot_anaconda
|
||||
boot
|
||||
|
||||
:boot_ramdisk
|
||||
imgfree
|
||||
{%- if pxe_options.boot_iso_url %}
|
||||
|
@ -234,18 +234,23 @@ class PXEBaseMixin(object):
|
||||
boot_option = deploy_utils.get_boot_option(node)
|
||||
boot_device = None
|
||||
instance_image_info = {}
|
||||
if boot_option == "ramdisk":
|
||||
if boot_option == "ramdisk" or boot_option == "kickstart":
|
||||
instance_image_info = pxe_utils.get_instance_image_info(
|
||||
task, ipxe_enabled=self.ipxe_enabled)
|
||||
pxe_utils.cache_ramdisk_kernel(task, instance_image_info,
|
||||
ipxe_enabled=self.ipxe_enabled)
|
||||
|
||||
if deploy_utils.is_iscsi_boot(task) or boot_option == "ramdisk":
|
||||
if (deploy_utils.is_iscsi_boot(task) or boot_option == "ramdisk"
|
||||
or boot_option == "kickstart"):
|
||||
pxe_utils.prepare_instance_pxe_config(
|
||||
task, instance_image_info,
|
||||
iscsi_boot=deploy_utils.is_iscsi_boot(task),
|
||||
ramdisk_boot=(boot_option == "ramdisk"),
|
||||
anaconda_boot=(boot_option == "kickstart"),
|
||||
ipxe_enabled=self.ipxe_enabled)
|
||||
pxe_utils.prepare_instance_kickstart_config(
|
||||
task, instance_image_info,
|
||||
anaconda_boot=(boot_option == "kickstart"))
|
||||
boot_device = boot_devices.PXE
|
||||
|
||||
elif boot_option != "local":
|
||||
|
@ -22,3 +22,8 @@ append tboot.gz --- {{pxe_options.aki_path}} root={{ ROOT }} ro text {{ pxe_opti
|
||||
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) }}
|
||||
|
||||
label boot_anaconda
|
||||
kernel {{ pxe_options.aki_path }}
|
||||
append initrd={{ pxe_options.ari_path }} text {{ pxe_options.pxe_append_params|default("", true) }} inst.ks={{ pxe_options.ks_cfg_url }} inst.stage2={{ pxe_options.stage2_url }}
|
||||
ipappend 2
|
||||
|
@ -62,6 +62,8 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
'ipa-api-url': 'http://192.168.122.184:6385',
|
||||
'ipxe_timeout': 0,
|
||||
'ramdisk_opts': 'ramdisk_param',
|
||||
'ks_cfg_url': 'http://fake/ks.cfg',
|
||||
'stage2_url': 'http://fake/stage2'
|
||||
}
|
||||
|
||||
self.ipxe_options = self.pxe_options.copy()
|
||||
@ -1144,6 +1146,81 @@ class PXEInterfacesTestCase(db_base.DbTestCase):
|
||||
|
||||
boot_opt_mock.assert_called_once_with(task.node)
|
||||
|
||||
@mock.patch('ironic.drivers.modules.deploy_utils.get_boot_option',
|
||||
return_value='kickstart', autospec=True)
|
||||
@mock.patch.object(image_service.GlanceImageService, 'show', autospec=True)
|
||||
def test_get_instance_image_info_with_kickstart_boot_option(
|
||||
self, image_show_mock, boot_opt_mock):
|
||||
properties = {'properties': {u'kernel_id': u'instance_kernel_uuid',
|
||||
u'ramdisk_id': u'instance_ramdisk_uuid',
|
||||
u'stage2_id': u'instance_stage2_id'}}
|
||||
|
||||
expected_info = {'ramdisk':
|
||||
('instance_ramdisk_uuid',
|
||||
os.path.join(CONF.pxe.tftp_root,
|
||||
self.node.uuid,
|
||||
'ramdisk')),
|
||||
'kernel':
|
||||
('instance_kernel_uuid',
|
||||
os.path.join(CONF.pxe.tftp_root,
|
||||
self.node.uuid,
|
||||
'kernel')),
|
||||
'stage2':
|
||||
('instance_stage2_id',
|
||||
os.path.join(CONF.deploy.http_root,
|
||||
self.node.uuid,
|
||||
'LiveOS',
|
||||
'squashfs.img')),
|
||||
'ks_template':
|
||||
(CONF.anaconda.default_ks_template,
|
||||
os.path.join(CONF.deploy.http_root,
|
||||
self.node.uuid,
|
||||
'ks.cfg.template')),
|
||||
'ks_cfg':
|
||||
('',
|
||||
os.path.join(CONF.deploy.http_root,
|
||||
self.node.uuid,
|
||||
'ks.cfg'))}
|
||||
image_show_mock.return_value = properties
|
||||
self.context.auth_token = 'fake'
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
image_info = pxe_utils.get_instance_image_info(
|
||||
task, ipxe_enabled=False)
|
||||
self.assertEqual(expected_info, image_info)
|
||||
# In the absense of kickstart template in both instance_info and
|
||||
# image default kickstart template is used
|
||||
self.assertEqual(CONF.anaconda.default_ks_template,
|
||||
image_info['ks_template'][0])
|
||||
calls = [mock.call(task.node), mock.call(task.node)]
|
||||
boot_opt_mock.assert_has_calls(calls)
|
||||
# Instance info gets presedence over kickstart template on the
|
||||
# image
|
||||
properties['properties'] = {'ks_template': 'glance://template_id'}
|
||||
task.node.instance_info['ks_template'] = 'https://server/fake.tmpl'
|
||||
image_show_mock.return_value = properties
|
||||
image_info = pxe_utils.get_instance_image_info(
|
||||
task, ipxe_enabled=False)
|
||||
self.assertEqual('https://server/fake.tmpl',
|
||||
image_info['ks_template'][0])
|
||||
|
||||
@mock.patch('ironic.drivers.modules.deploy_utils.get_boot_option',
|
||||
return_value='kickstart', autospec=True)
|
||||
@mock.patch.object(image_service.GlanceImageService, 'show', autospec=True)
|
||||
def test_get_instance_image_info_kickstart_stage2_missing(
|
||||
self, image_show_mock, boot_opt_mock):
|
||||
properties = {'properties': {u'kernel_id': u'instance_kernel_uuid',
|
||||
u'ramdisk_id': u'instance_ramdisk_uuid'}}
|
||||
|
||||
image_show_mock.return_value = properties
|
||||
self.context.auth_token = 'fake'
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
self.assertRaises(
|
||||
exception.ImageUnacceptable, pxe_utils.get_instance_image_info,
|
||||
task, ipxe_enabled=False
|
||||
)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'fetch_images', autospec=True)
|
||||
def test__cache_tftp_images_master_path(self, mock_fetch_image):
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
@ -1242,6 +1319,68 @@ class PXEInterfacesTestCase(db_base.DbTestCase):
|
||||
self.assertFalse(mock_log.called)
|
||||
|
||||
|
||||
@mock.patch.object(pxe.PXEBoot, '__init__', lambda self: None)
|
||||
class PXEBuildKickstartConfigOptionsTestCase(db_base.DbTestCase):
|
||||
def setUp(self):
|
||||
super(PXEBuildKickstartConfigOptionsTestCase, self).setUp()
|
||||
n = {
|
||||
'driver': 'fake-hardware',
|
||||
'boot_interface': 'pxe',
|
||||
'instance_info': INST_INFO_DICT,
|
||||
'driver_info': DRV_INFO_DICT,
|
||||
'driver_internal_info': DRV_INTERNAL_INFO_DICT,
|
||||
}
|
||||
n['instance_info']['image_url'] = 'http://ironic/node/os_image.tar'
|
||||
self.config_temp_dir('http_root', group='deploy')
|
||||
self.node = object_utils.create_test_node(self.context, **n)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'get_ironic_api_url', autospec=True)
|
||||
def test_build_kickstart_config_options_pxe(self, api_url_mock):
|
||||
api_url_mock.return_value = 'http://ironic-api'
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
expected = {}
|
||||
expected['liveimg_url'] = task.node.instance_info['image_url']
|
||||
expected['heartbeat_url'] = (
|
||||
'http://ironic-api/v1/heartbeat/%s' % task.node.uuid
|
||||
)
|
||||
ks_options = pxe_utils.build_kickstart_config_options(task)
|
||||
self.assertTrue(ks_options.pop('agent_token'))
|
||||
self.assertEqual(expected, ks_options)
|
||||
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
def test_prepare_instance_kickstart_config_not_anaconda_boot(self,
|
||||
render_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
self.assertFalse(
|
||||
pxe_utils.prepare_instance_kickstart_config(task, {})
|
||||
)
|
||||
render_mock.assert_not_called()
|
||||
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
@mock.patch('ironic.common.pxe_utils.build_kickstart_config_options',
|
||||
autospec=True)
|
||||
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||
def test_prepare_instance_kickstart_config(self, write_mock,
|
||||
ks_options_mock, render_mock):
|
||||
image_info = {
|
||||
'ks_cfg': ['', '/http_root/node_uuid/ks.cfg'],
|
||||
'ks_template': ['tmpl_id', '/http_root/node_uuid/ks.cfg.template']
|
||||
}
|
||||
ks_options = {'liveimg_url': 'http://fake', 'agent_token': 'faketoken',
|
||||
'heartbeat_url': 'http://fake_hb'}
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
ks_options_mock.return_value = ks_options
|
||||
pxe_utils.prepare_instance_kickstart_config(task, image_info,
|
||||
anaconda_boot=True)
|
||||
render_mock.assert_called_with(image_info['ks_template'][1],
|
||||
ks_options)
|
||||
write_mock.assert_called_with(image_info['ks_cfg'][1],
|
||||
render_mock.return_value)
|
||||
|
||||
|
||||
@mock.patch.object(pxe.PXEBoot, '__init__', lambda self: None)
|
||||
class PXEBuildConfigOptionsTestCase(db_base.DbTestCase):
|
||||
def setUp(self):
|
||||
|
@ -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_anaconda
|
||||
imgfree
|
||||
kernel http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2 initrd=ramdisk || goto boot_anaconda
|
||||
initrd http://1.2.3.4:1234/ramdisk || goto boot_anaconda
|
||||
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
|
||||
|
@ -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_anaconda
|
||||
imgfree
|
||||
kernel http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2 initrd=ramdisk || goto boot_anaconda
|
||||
initrd http://1.2.3.4:1234/ramdisk || goto boot_anaconda
|
||||
boot
|
||||
|
||||
:boot_ramdisk
|
||||
imgfree
|
||||
sanboot http://1.2.3.4:1234/uuid/iso
|
||||
|
@ -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_anaconda
|
||||
imgfree
|
||||
kernel http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2 initrd=ramdisk || goto boot_anaconda
|
||||
initrd http://1.2.3.4:1234/ramdisk || goto boot_anaconda
|
||||
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
|
||||
|
@ -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_anaconda
|
||||
imgfree
|
||||
kernel http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2 initrd=ramdisk || goto boot_anaconda
|
||||
initrd http://1.2.3.4:1234/ramdisk || goto boot_anaconda
|
||||
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
|
||||
|
@ -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_anaconda
|
||||
imgfree
|
||||
kernel http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2 initrd=ramdisk || goto boot_anaconda
|
||||
initrd http://1.2.3.4:1234/ramdisk || goto boot_anaconda
|
||||
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
|
||||
|
@ -31,6 +31,12 @@ 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_anaconda
|
||||
imgfree
|
||||
kernel --timeout 120 http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2 initrd=ramdisk || goto boot_anaconda
|
||||
initrd --timeout 120 http://1.2.3.4:1234/ramdisk || goto boot_anaconda
|
||||
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
|
||||
|
@ -600,7 +600,8 @@ class iPXEBootTestCase(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, False, ipxe_enabled=True)
|
||||
'bios', False, False, False, False, ipxe_enabled=True,
|
||||
anaconda_boot=False)
|
||||
set_boot_device_mock.assert_called_once_with(task,
|
||||
boot_devices.PXE,
|
||||
persistent=True)
|
||||
@ -649,7 +650,8 @@ class iPXEBootTestCase(db_base.DbTestCase):
|
||||
ipxe_enabled=True)
|
||||
switch_pxe_config_mock.assert_called_once_with(
|
||||
pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50",
|
||||
'bios', False, False, False, False, ipxe_enabled=True)
|
||||
'bios', False, False, False, False, ipxe_enabled=True,
|
||||
anaconda_boot=False)
|
||||
self.assertFalse(set_boot_device_mock.called)
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||
@ -766,7 +768,8 @@ class iPXEBootTestCase(db_base.DbTestCase):
|
||||
ipxe_enabled=True)
|
||||
switch_pxe_config_mock.assert_called_once_with(
|
||||
pxe_config_path, None, boot_modes.LEGACY_BIOS, False,
|
||||
ipxe_enabled=True, iscsi_boot=True, ramdisk_boot=False)
|
||||
ipxe_enabled=True, iscsi_boot=True, ramdisk_boot=False,
|
||||
anaconda_boot=False)
|
||||
set_boot_device_mock.assert_called_once_with(task,
|
||||
boot_devices.PXE,
|
||||
persistent=True)
|
||||
@ -812,7 +815,8 @@ class iPXEBootTestCase(db_base.DbTestCase):
|
||||
ipxe_enabled=True)
|
||||
switch_pxe_config_mock.assert_called_once_with(
|
||||
pxe_config_path, None, boot_modes.LEGACY_BIOS, False,
|
||||
ipxe_enabled=True, iscsi_boot=False, ramdisk_boot=True)
|
||||
ipxe_enabled=True, iscsi_boot=False, ramdisk_boot=True,
|
||||
anaconda_boot=False)
|
||||
set_boot_device_mock.assert_called_once_with(task,
|
||||
boot_devices.PXE,
|
||||
persistent=True)
|
||||
@ -880,7 +884,8 @@ class iPXEBootTestCase(db_base.DbTestCase):
|
||||
persistent=True)
|
||||
switch_pxe_config_mock.assert_called_once_with(
|
||||
pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50",
|
||||
'bios', True, False, False, False, ipxe_enabled=True)
|
||||
'bios', True, False, False, False, ipxe_enabled=True,
|
||||
anaconda_boot=False)
|
||||
# No clean up
|
||||
self.assertFalse(clean_up_pxe_config_mock.called)
|
||||
# No netboot configuration beyond the PXE files
|
||||
|
@ -74,6 +74,7 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
group='anaconda')
|
||||
instance_info = INST_INFO_DICT
|
||||
instance_info['deploy_key'] = 'fake-56789'
|
||||
instance_info['image_url'] = 'http://fakeserver/os.tar.gz'
|
||||
|
||||
self.config(enabled_boot_interfaces=[self.boot_interface,
|
||||
'ipxe', 'fake'])
|
||||
@ -527,7 +528,8 @@ 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, False, ipxe_enabled=False)
|
||||
'bios', False, False, False, False, ipxe_enabled=False,
|
||||
anaconda_boot=False)
|
||||
set_boot_device_mock.assert_called_once_with(task,
|
||||
boot_devices.PXE,
|
||||
persistent=True)
|
||||
@ -575,7 +577,8 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
ipxe_enabled=False)
|
||||
switch_pxe_config_mock.assert_called_once_with(
|
||||
pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50",
|
||||
'bios', False, False, False, False, ipxe_enabled=False)
|
||||
'bios', False, False, False, False, ipxe_enabled=False,
|
||||
anaconda_boot=False)
|
||||
self.assertFalse(set_boot_device_mock.called)
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||
@ -727,7 +730,7 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
switch_pxe_config_mock.assert_called_once_with(
|
||||
pxe_config_path, None,
|
||||
'bios', False, ipxe_enabled=False, iscsi_boot=False,
|
||||
ramdisk_boot=True)
|
||||
ramdisk_boot=True, anaconda_boot=False)
|
||||
set_boot_device_mock.assert_called_once_with(task,
|
||||
boot_devices.PXE,
|
||||
persistent=True)
|
||||
@ -740,6 +743,63 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
def test_prepare_instance_ramdisk_pxe_conf_exists(self):
|
||||
self._test_prepare_instance_ramdisk(config_file_exits=False)
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True)
|
||||
@mock.patch.object(pxe_utils, 'create_pxe_config', autospec=True)
|
||||
@mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True)
|
||||
@mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True)
|
||||
@mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True)
|
||||
@mock.patch('ironic.drivers.modules.deploy_utils.get_boot_option',
|
||||
return_value='kickstart', autospec=True)
|
||||
@mock.patch('ironic.drivers.modules.deploy_utils.get_ironic_api_url',
|
||||
return_value='http://fakeserver/api', autospec=True)
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||
def test_prepare_instance_kickstart(
|
||||
self, write_file_mock, render_mock, api_url_mock, boot_opt_mock,
|
||||
get_image_info_mock, cache_mock, dhcp_factory_mock,
|
||||
create_pxe_config_mock, switch_pxe_config_mock,
|
||||
set_boot_device_mock):
|
||||
image_info = {'kernel': ['ins_kernel_id', '/path/to/kernel'],
|
||||
'ramdisk': ['ins_ramdisk_id', '/path/to/ramdisk'],
|
||||
'stage2': ['ins_stage2_id', '/path/to/stage2'],
|
||||
'ks_cfg': ['', '/path/to/ks.cfg'],
|
||||
'ks_template': ['template_id', '/path/to/ks_template']}
|
||||
get_image_info_mock.return_value = image_info
|
||||
provider_mock = mock.MagicMock()
|
||||
dhcp_factory_mock.return_value = provider_mock
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.config(http_url='http://fake_url', group='deploy')
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(
|
||||
task, ipxe_enabled=False)
|
||||
dhcp_opts += pxe_utils.dhcp_options_for_instance(
|
||||
task, ipxe_enabled=False, ip_version=6)
|
||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||
task.node.uuid)
|
||||
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
get_image_info_mock.assert_called_once_with(task,
|
||||
ipxe_enabled=False)
|
||||
cache_mock.assert_called_once_with(
|
||||
task, image_info, False)
|
||||
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||
render_mock.assert_called()
|
||||
write_file_mock.assert_called_with(
|
||||
'/path/to/ks.cfg', render_mock.return_value
|
||||
)
|
||||
create_pxe_config_mock.assert_called_once_with(
|
||||
task, mock.ANY, CONF.pxe.pxe_config_template,
|
||||
ipxe_enabled=False)
|
||||
switch_pxe_config_mock.assert_called_once_with(
|
||||
pxe_config_path, None,
|
||||
'bios', False, ipxe_enabled=False, iscsi_boot=False,
|
||||
ramdisk_boot=False, anaconda_boot=True)
|
||||
set_boot_device_mock.assert_called_once_with(task,
|
||||
boot_devices.PXE,
|
||||
persistent=True)
|
||||
|
||||
@mock.patch.object(boot_mode_utils, 'deconfigure_secure_boot_if_needed',
|
||||
autospec=True)
|
||||
@mock.patch.object(pxe_utils, 'clean_up_pxe_env', autospec=True)
|
||||
@ -826,7 +886,7 @@ class PXERamdiskDeployTestCase(db_base.DbTestCase):
|
||||
switch_pxe_config_mock.assert_called_once_with(
|
||||
pxe_config_path, None,
|
||||
'bios', False, ipxe_enabled=False, iscsi_boot=False,
|
||||
ramdisk_boot=True)
|
||||
ramdisk_boot=True, anaconda_boot=False)
|
||||
set_boot_device_mock.assert_called_once_with(task,
|
||||
boot_devices.PXE,
|
||||
persistent=True)
|
||||
|
@ -22,3 +22,8 @@ append tboot.gz --- /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/kernel root={
|
||||
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
|
||||
|
||||
label boot_anaconda
|
||||
kernel /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/kernel
|
||||
append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/ramdisk text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2
|
||||
ipappend 2
|
||||
|
Loading…
Reference in New Issue
Block a user