Merge "Convert iPXE boot script to Jinja template"
This commit is contained in:
commit
ab79b3eff2
@ -17,7 +17,6 @@
|
||||
import os
|
||||
|
||||
from ironic_lib import utils as ironic_utils
|
||||
import jinja2
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import fileutils
|
||||
@ -54,28 +53,6 @@ def _ensure_config_dirs_exist(node_uuid):
|
||||
fileutils.ensure_tree(os.path.join(root_dir, PXE_CFG_DIR_NAME))
|
||||
|
||||
|
||||
def _build_pxe_config(pxe_options, template, root_tag, disk_ident_tag):
|
||||
"""Build the PXE boot configuration file.
|
||||
|
||||
This method builds the PXE boot configuration file by rendering the
|
||||
template with the given parameters.
|
||||
|
||||
:param pxe_options: A dict of values to set on the configuration file.
|
||||
:param template: The PXE configuration template.
|
||||
:param root_tag: Root tag used in the PXE config file.
|
||||
:param disk_ident_tag: Disk identifier tag used in the PXE config file.
|
||||
:returns: A formatted string with the file content.
|
||||
|
||||
"""
|
||||
tmpl_path, tmpl_file = os.path.split(template)
|
||||
env = jinja2.Environment(loader=jinja2.FileSystemLoader(tmpl_path))
|
||||
template = env.get_template(tmpl_file)
|
||||
return template.render({'pxe_options': pxe_options,
|
||||
'ROOT': root_tag,
|
||||
'DISK_IDENTIFIER': disk_ident_tag,
|
||||
})
|
||||
|
||||
|
||||
def _link_mac_pxe_configs(task):
|
||||
"""Link each MAC address with the PXE configuration file.
|
||||
|
||||
@ -237,8 +214,11 @@ def create_pxe_config(task, pxe_options, template=None):
|
||||
pxe_config_root_tag = '{{ ROOT }}'
|
||||
pxe_config_disk_ident = '{{ DISK_IDENTIFIER }}'
|
||||
|
||||
pxe_config = _build_pxe_config(pxe_options, template, pxe_config_root_tag,
|
||||
pxe_config_disk_ident)
|
||||
params = {'pxe_options': pxe_options,
|
||||
'ROOT': pxe_config_root_tag,
|
||||
'DISK_IDENTIFIER': pxe_config_disk_ident}
|
||||
|
||||
pxe_config = utils.render_template(template, params)
|
||||
utils.write_to_file(pxe_config_file_path, pxe_config)
|
||||
|
||||
if is_uefi_boot_mode and not CONF.pxe.ipxe_enabled:
|
||||
|
@ -27,6 +27,7 @@ import re
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import jinja2
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import netutils
|
||||
@ -283,6 +284,22 @@ def hash_file(file_like_object, hash_algo='md5'):
|
||||
return checksum.hexdigest()
|
||||
|
||||
|
||||
def file_has_content(path, content, hash_algo='md5'):
|
||||
"""Checks that content of the file is the same as provided reference.
|
||||
|
||||
:param path: path to file
|
||||
:param content: reference content to check against
|
||||
:param hash_algo: hashing algo from hashlib to use, default is 'md5'
|
||||
:returns: True if the hash of reference content is the same as
|
||||
the hash of file's content, False otherwise
|
||||
"""
|
||||
with open(path) as existing:
|
||||
file_hash_hex = hash_file(existing, hash_algo=hash_algo)
|
||||
ref_hash = _get_hash_object(hash_algo)
|
||||
ref_hash.update(content)
|
||||
return file_hash_hex == ref_hash.hexdigest()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def tempdir(**kwargs):
|
||||
tempfile.tempdir = CONF.tempdir
|
||||
@ -500,3 +517,22 @@ def validate_network_port(port, port_name="Port"):
|
||||
'numbers must be between 1 and 65535.') %
|
||||
{'port_name': port_name, 'port': port})
|
||||
return port
|
||||
|
||||
|
||||
def render_template(template, params, is_file=True):
|
||||
"""Renders Jinja2 template file with given parameters.
|
||||
|
||||
:param template: full path to the Jinja2 template file
|
||||
:param params: dictionary with parameters to use when rendering
|
||||
:param is_file: whether template is file or string with template itself
|
||||
:returns: the rendered template as a string
|
||||
"""
|
||||
if is_file:
|
||||
tmpl_path, tmpl_name = os.path.split(template)
|
||||
loader = jinja2.FileSystemLoader(tmpl_path)
|
||||
else:
|
||||
tmpl_name = 'template'
|
||||
loader = jinja2.DictLoader({tmpl_name: template})
|
||||
env = jinja2.Environment(loader=loader)
|
||||
tmpl = env.get_template(tmpl_name)
|
||||
return tmpl.render(params)
|
||||
|
@ -5,10 +5,10 @@
|
||||
# https://bugs.launchpad.net/ironic/+bug/1504482
|
||||
set netid:int32 -1
|
||||
:loop
|
||||
inc netid || chain pxelinux.cfg/${mac:hexhyp} || goto old_rom
|
||||
inc netid || chain {{ ipxe_for_mac_uri }}${mac:hexhyp} || goto old_rom
|
||||
isset ${net${netid}/mac} || goto loop_done
|
||||
echo Attempting to boot from MAC ${net${netid}/mac:hexhyp}
|
||||
chain pxelinux.cfg/${net${netid}/mac:hexhyp} || goto loop
|
||||
chain {{ ipxe_for_mac_uri }}${net${netid}/mac:hexhyp} || goto loop
|
||||
|
||||
:loop_done
|
||||
echo PXE boot failed! No configuration found for any of the present NICs.
|
||||
|
@ -15,9 +15,7 @@
|
||||
PXE Boot Interface
|
||||
"""
|
||||
|
||||
import filecmp
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from ironic_lib import metrics_utils
|
||||
from ironic_lib import utils as ironic_utils
|
||||
@ -33,6 +31,7 @@ from ironic.common import image_service as service
|
||||
from ironic.common import images
|
||||
from ironic.common import pxe_utils
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.conf import CONF
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
@ -382,13 +381,20 @@ class PXEBoot(base.BootInterface):
|
||||
node = task.node
|
||||
|
||||
if CONF.pxe.ipxe_enabled:
|
||||
# Copy the iPXE boot script to HTTP root directory
|
||||
# Render the iPXE boot script template and save it
|
||||
# to HTTP root directory
|
||||
boot_script = utils.render_template(
|
||||
CONF.pxe.ipxe_boot_script,
|
||||
{'ipxe_for_mac_uri': pxe_utils.PXE_CFG_DIR_NAME + '/'})
|
||||
bootfile_path = os.path.join(
|
||||
CONF.deploy.http_root,
|
||||
os.path.basename(CONF.pxe.ipxe_boot_script))
|
||||
# NOTE(pas-ha) to prevent unneeded writes,
|
||||
# only write to file if its content is different from required,
|
||||
# which should be rather rare
|
||||
if (not os.path.isfile(bootfile_path) or
|
||||
not filecmp.cmp(CONF.pxe.ipxe_boot_script, bootfile_path)):
|
||||
shutil.copyfile(CONF.pxe.ipxe_boot_script, bootfile_path)
|
||||
not utils.file_has_content(bootfile_path, boot_script)):
|
||||
utils.write_to_file(bootfile_path, boot_script)
|
||||
|
||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
||||
provider = dhcp_factory.DHCPFactory()
|
||||
|
@ -22,6 +22,7 @@ from oslo_utils import uuidutils
|
||||
import six
|
||||
|
||||
from ironic.common import pxe_utils
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.tests.unit.conductor import mgr_utils
|
||||
from ironic.tests.unit.db import base as db_base
|
||||
@ -65,18 +66,30 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
|
||||
self.node = object_utils.create_test_node(self.context)
|
||||
|
||||
def test__build_pxe_config(self):
|
||||
def test_default_pxe_config(self):
|
||||
|
||||
rendered_template = pxe_utils._build_pxe_config(
|
||||
self.pxe_options, CONF.pxe.pxe_config_template,
|
||||
'{{ ROOT }}', '{{ DISK_IDENTIFIER }}')
|
||||
rendered_template = utils.render_template(
|
||||
CONF.pxe.pxe_config_template,
|
||||
{'pxe_options': self.pxe_options,
|
||||
'ROOT': '{{ ROOT }}',
|
||||
'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'})
|
||||
|
||||
expected_template = open(
|
||||
'ironic/tests/unit/drivers/pxe_config.template').read().rstrip()
|
||||
|
||||
self.assertEqual(six.text_type(expected_template), rendered_template)
|
||||
|
||||
def test__build_ipxe_config(self):
|
||||
def test_default_ipxe_boot_script(self):
|
||||
rendered_template = utils.render_template(
|
||||
CONF.pxe.ipxe_boot_script,
|
||||
{'ipxe_for_mac_uri': 'pxelinux.cfg/'})
|
||||
|
||||
expected_template = open(
|
||||
'ironic/tests/unit/drivers/boot.ipxe').read().rstrip()
|
||||
|
||||
self.assertEqual(six.text_type(expected_template), rendered_template)
|
||||
|
||||
def test_default_ipxe_config(self):
|
||||
# NOTE(lucasagomes): iPXE is just an extension of the PXE driver,
|
||||
# it doesn't have it's own configuration option for template.
|
||||
# More info:
|
||||
@ -86,16 +99,18 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
group='pxe'
|
||||
)
|
||||
self.config(http_url='http://1.2.3.4:1234', group='deploy')
|
||||
rendered_template = pxe_utils._build_pxe_config(
|
||||
self.ipxe_options, CONF.pxe.pxe_config_template,
|
||||
'{{ ROOT }}', '{{ DISK_IDENTIFIER }}')
|
||||
rendered_template = utils.render_template(
|
||||
CONF.pxe.pxe_config_template,
|
||||
{'pxe_options': self.ipxe_options,
|
||||
'ROOT': '{{ ROOT }}',
|
||||
'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'})
|
||||
|
||||
expected_template = open(
|
||||
'ironic/tests/unit/drivers/ipxe_config.template').read().rstrip()
|
||||
|
||||
self.assertEqual(six.text_type(expected_template), rendered_template)
|
||||
|
||||
def test__build_ipxe_timeout_config(self):
|
||||
def test_default_ipxe_timeout_config(self):
|
||||
# NOTE(lucasagomes): iPXE is just an extension of the PXE driver,
|
||||
# it doesn't have it's own configuration option for template.
|
||||
# More info:
|
||||
@ -105,16 +120,18 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
group='pxe'
|
||||
)
|
||||
self.config(http_url='http://1.2.3.4:1234', group='deploy')
|
||||
rendered_template = pxe_utils._build_pxe_config(
|
||||
self.ipxe_options_timeout, CONF.pxe.pxe_config_template,
|
||||
'{{ ROOT }}', '{{ DISK_IDENTIFIER }}')
|
||||
rendered_template = utils.render_template(
|
||||
CONF.pxe.pxe_config_template,
|
||||
{'pxe_options': self.ipxe_options_timeout,
|
||||
'ROOT': '{{ ROOT }}',
|
||||
'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'})
|
||||
|
||||
tpl_file = 'ironic/tests/unit/drivers/ipxe_config_timeout.template'
|
||||
expected_template = open(tpl_file).read().rstrip()
|
||||
|
||||
self.assertEqual(six.text_type(expected_template), rendered_template)
|
||||
|
||||
def test__build_elilo_config(self):
|
||||
def test_default_elilo_config(self):
|
||||
pxe_opts = self.pxe_options
|
||||
pxe_opts['boot_mode'] = 'uefi'
|
||||
self.config(
|
||||
@ -122,9 +139,11 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
'elilo_efi_pxe_config.template'),
|
||||
group='pxe'
|
||||
)
|
||||
rendered_template = pxe_utils._build_pxe_config(
|
||||
pxe_opts, CONF.pxe.uefi_pxe_config_template,
|
||||
'{{ ROOT }}', '{{ DISK_IDENTIFIER }}')
|
||||
rendered_template = utils.render_template(
|
||||
CONF.pxe.uefi_pxe_config_template,
|
||||
{'pxe_options': pxe_opts,
|
||||
'ROOT': '{{ ROOT }}',
|
||||
'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'})
|
||||
|
||||
expected_template = open(
|
||||
'ironic/tests/unit/drivers/elilo_efi_pxe_config.template'
|
||||
@ -132,13 +151,15 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
|
||||
self.assertEqual(six.text_type(expected_template), rendered_template)
|
||||
|
||||
def test__build_grub_config(self):
|
||||
def test_default_grub_config(self):
|
||||
pxe_opts = self.pxe_options
|
||||
pxe_opts['boot_mode'] = 'uefi'
|
||||
pxe_opts['tftp_server'] = '192.0.2.1'
|
||||
rendered_template = pxe_utils._build_pxe_config(
|
||||
pxe_opts, CONF.pxe.uefi_pxe_config_template,
|
||||
'(( ROOT ))', '(( DISK_IDENTIFIER ))')
|
||||
rendered_template = utils.render_template(
|
||||
CONF.pxe.uefi_pxe_config_template,
|
||||
{'pxe_options': pxe_opts,
|
||||
'ROOT': '(( ROOT ))',
|
||||
'DISK_IDENTIFIER': '(( DISK_IDENTIFIER ))'})
|
||||
|
||||
template_file = 'ironic/tests/unit/drivers/pxe_grub_config.template'
|
||||
expected_template = open(template_file).read().rstrip()
|
||||
@ -254,18 +275,19 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
create_link_mock.assert_has_calls(create_link_calls)
|
||||
|
||||
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||
@mock.patch.object(pxe_utils, '_build_pxe_config', autospec=True)
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
@mock.patch('oslo_utils.fileutils.ensure_tree', autospec=True)
|
||||
def test_create_pxe_config(self, ensure_tree_mock, build_mock,
|
||||
def test_create_pxe_config(self, ensure_tree_mock, render_mock,
|
||||
write_mock):
|
||||
build_mock.return_value = self.pxe_options
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
pxe_utils.create_pxe_config(task, self.pxe_options,
|
||||
CONF.pxe.pxe_config_template)
|
||||
build_mock.assert_called_with(self.pxe_options,
|
||||
CONF.pxe.pxe_config_template,
|
||||
'{{ ROOT }}',
|
||||
'{{ DISK_IDENTIFIER }}')
|
||||
render_mock.assert_called_with(
|
||||
CONF.pxe.pxe_config_template,
|
||||
{'pxe_options': self.pxe_options,
|
||||
'ROOT': '{{ ROOT }}',
|
||||
'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'}
|
||||
)
|
||||
ensure_calls = [
|
||||
mock.call(os.path.join(CONF.pxe.tftp_root, self.node.uuid)),
|
||||
mock.call(os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg'))
|
||||
@ -273,21 +295,21 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
ensure_tree_mock.assert_has_calls(ensure_calls)
|
||||
|
||||
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
||||
write_mock.assert_called_with(pxe_cfg_file_path, self.pxe_options)
|
||||
write_mock.assert_called_with(pxe_cfg_file_path,
|
||||
render_mock.return_value)
|
||||
|
||||
@mock.patch('ironic.common.pxe_utils._link_ip_address_pxe_configs',
|
||||
autospec=True)
|
||||
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||
@mock.patch('ironic.common.pxe_utils._build_pxe_config', autospec=True)
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
@mock.patch('oslo_utils.fileutils.ensure_tree', autospec=True)
|
||||
def test_create_pxe_config_uefi_elilo(self, ensure_tree_mock, build_mock,
|
||||
def test_create_pxe_config_uefi_elilo(self, ensure_tree_mock, render_mock,
|
||||
write_mock, link_ip_configs_mock):
|
||||
self.config(
|
||||
uefi_pxe_config_template=('ironic/drivers/modules/'
|
||||
'elilo_efi_pxe_config.template'),
|
||||
group='pxe'
|
||||
)
|
||||
build_mock.return_value = self.pxe_options
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
task.node.properties['capabilities'] = 'boot_mode:uefi'
|
||||
pxe_utils.create_pxe_config(task, self.pxe_options,
|
||||
@ -298,23 +320,24 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
mock.call(os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg'))
|
||||
]
|
||||
ensure_tree_mock.assert_has_calls(ensure_calls)
|
||||
build_mock.assert_called_with(self.pxe_options,
|
||||
CONF.pxe.uefi_pxe_config_template,
|
||||
'{{ ROOT }}',
|
||||
'{{ DISK_IDENTIFIER }}')
|
||||
render_mock.assert_called_with(
|
||||
CONF.pxe.uefi_pxe_config_template,
|
||||
{'pxe_options': self.pxe_options,
|
||||
'ROOT': '{{ ROOT }}',
|
||||
'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'})
|
||||
link_ip_configs_mock.assert_called_once_with(task, True)
|
||||
|
||||
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
||||
write_mock.assert_called_with(pxe_cfg_file_path, self.pxe_options)
|
||||
write_mock.assert_called_with(pxe_cfg_file_path,
|
||||
render_mock.return_value)
|
||||
|
||||
@mock.patch('ironic.common.pxe_utils._link_ip_address_pxe_configs',
|
||||
autospec=True)
|
||||
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||
@mock.patch('ironic.common.pxe_utils._build_pxe_config', autospec=True)
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
@mock.patch('oslo_utils.fileutils.ensure_tree', autospec=True)
|
||||
def test_create_pxe_config_uefi_grub(self, ensure_tree_mock, build_mock,
|
||||
def test_create_pxe_config_uefi_grub(self, ensure_tree_mock, render_mock,
|
||||
write_mock, link_ip_configs_mock):
|
||||
build_mock.return_value = self.pxe_options
|
||||
grub_tmplte = "ironic/drivers/modules/pxe_grub_config.template"
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
task.node.properties['capabilities'] = 'boot_mode:uefi'
|
||||
@ -326,24 +349,25 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
mock.call(os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg'))
|
||||
]
|
||||
ensure_tree_mock.assert_has_calls(ensure_calls)
|
||||
build_mock.assert_called_with(self.pxe_options,
|
||||
grub_tmplte,
|
||||
'(( ROOT ))',
|
||||
'(( DISK_IDENTIFIER ))')
|
||||
render_mock.assert_called_with(
|
||||
grub_tmplte,
|
||||
{'pxe_options': self.pxe_options,
|
||||
'ROOT': '(( ROOT ))',
|
||||
'DISK_IDENTIFIER': '(( DISK_IDENTIFIER ))'})
|
||||
link_ip_configs_mock.assert_called_once_with(task, False)
|
||||
|
||||
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
||||
write_mock.assert_called_with(pxe_cfg_file_path, self.pxe_options)
|
||||
write_mock.assert_called_with(pxe_cfg_file_path,
|
||||
render_mock.return_value)
|
||||
|
||||
@mock.patch('ironic.common.pxe_utils._link_mac_pxe_configs',
|
||||
autospec=True)
|
||||
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||
@mock.patch('ironic.common.pxe_utils._build_pxe_config', autospec=True)
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
@mock.patch('oslo_utils.fileutils.ensure_tree', autospec=True)
|
||||
def test_create_pxe_config_uefi_ipxe(self, ensure_tree_mock, build_mock,
|
||||
def test_create_pxe_config_uefi_ipxe(self, ensure_tree_mock, render_mock,
|
||||
write_mock, link_mac_pxe_mock):
|
||||
self.config(ipxe_enabled=True, group='pxe')
|
||||
build_mock.return_value = self.ipxe_options
|
||||
ipxe_template = "ironic/drivers/modules/ipxe_config.template"
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
task.node.properties['capabilities'] = 'boot_mode:uefi'
|
||||
@ -355,15 +379,16 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
mock.call(os.path.join(CONF.deploy.http_root, 'pxelinux.cfg'))
|
||||
]
|
||||
ensure_tree_mock.assert_has_calls(ensure_calls)
|
||||
build_mock.assert_called_with(self.ipxe_options,
|
||||
ipxe_template,
|
||||
'{{ ROOT }}',
|
||||
'{{ DISK_IDENTIFIER }}')
|
||||
render_mock.assert_called_with(
|
||||
ipxe_template,
|
||||
{'pxe_options': self.ipxe_options,
|
||||
'ROOT': '{{ ROOT }}',
|
||||
'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'})
|
||||
link_mac_pxe_mock.assert_called_once_with(task)
|
||||
|
||||
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
||||
write_mock.assert_called_with(pxe_cfg_file_path,
|
||||
self.ipxe_options)
|
||||
render_mock.return_value)
|
||||
|
||||
@mock.patch('ironic.common.utils.rmtree_without_raise', autospec=True)
|
||||
@mock.patch('ironic_lib.utils.unlink_without_raise', autospec=True)
|
||||
|
@ -21,6 +21,7 @@ import os.path
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import jinja2
|
||||
import mock
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
@ -248,6 +249,20 @@ class GenericUtilsTestCase(base.TestCase):
|
||||
self.assertRaises(exception.InvalidParameterValue, utils.hash_file,
|
||||
file_like_object, 'hickory-dickory-dock')
|
||||
|
||||
def test_file_has_content_equal(self):
|
||||
data = b'Mary had a little lamb, its fleece as white as snow'
|
||||
ref = data
|
||||
with mock.patch('ironic.common.utils.open',
|
||||
mock.mock_open(read_data=data)):
|
||||
self.assertTrue(utils.file_has_content('foo', ref))
|
||||
|
||||
def test_file_has_content_differ(self):
|
||||
data = b'Mary had a little lamb, its fleece as white as snow'
|
||||
ref = data + b'!'
|
||||
with mock.patch('ironic.common.utils.open',
|
||||
mock.mock_open(read_data=data)):
|
||||
self.assertFalse(utils.file_has_content('foo', ref))
|
||||
|
||||
def test_is_valid_datapath_id(self):
|
||||
self.assertTrue(utils.is_valid_datapath_id("525400cf2d319fdf"))
|
||||
self.assertTrue(utils.is_valid_datapath_id("525400CF2D319FDF"))
|
||||
@ -613,3 +628,28 @@ class GetUpdatedCapabilitiesTestCase(base.TestCase):
|
||||
'Port "invalid" is not a valid integer.',
|
||||
utils.validate_network_port,
|
||||
'invalid')
|
||||
|
||||
|
||||
class JinjaTemplatingTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(JinjaTemplatingTestCase, self).setUp()
|
||||
self.template = '{{ foo }} {{ bar }}'
|
||||
self.params = {'foo': 'spam', 'bar': 'ham'}
|
||||
self.expected = 'spam ham'
|
||||
|
||||
def test_render_string(self):
|
||||
self.assertEqual(self.expected,
|
||||
utils.render_template(self.template,
|
||||
self.params,
|
||||
is_file=False))
|
||||
|
||||
@mock.patch('ironic.common.utils.jinja2.FileSystemLoader')
|
||||
def test_render_file(self, jinja_fsl_mock):
|
||||
path = '/path/to/template.j2'
|
||||
jinja_fsl_mock.return_value = jinja2.DictLoader(
|
||||
{'template.j2': self.template})
|
||||
self.assertEqual(self.expected,
|
||||
utils.render_template(path,
|
||||
self.params))
|
||||
jinja_fsl_mock.assert_called_once_with('/path/to')
|
||||
|
24
ironic/tests/unit/drivers/boot.ipxe
Normal file
24
ironic/tests/unit/drivers/boot.ipxe
Normal file
@ -0,0 +1,24 @@
|
||||
#!ipxe
|
||||
|
||||
# NOTE(lucasagomes): Loop over all network devices and boot from
|
||||
# the first one capable of booting. For more information see:
|
||||
# https://bugs.launchpad.net/ironic/+bug/1504482
|
||||
set netid:int32 -1
|
||||
:loop
|
||||
inc netid || chain pxelinux.cfg/${mac:hexhyp} || goto old_rom
|
||||
isset ${net${netid}/mac} || goto loop_done
|
||||
echo Attempting to boot from MAC ${net${netid}/mac:hexhyp}
|
||||
chain pxelinux.cfg/${net${netid}/mac:hexhyp} || goto loop
|
||||
|
||||
:loop_done
|
||||
echo PXE boot failed! No configuration found for any of the present NICs.
|
||||
echo Press any key to reboot...
|
||||
prompt --timeout 180
|
||||
reboot
|
||||
|
||||
:old_rom
|
||||
echo PXE boot failed! No configuration found for NIC ${mac:hexhyp}.
|
||||
echo Please update your iPXE ROM and retry.
|
||||
echo Press any key to reboot...
|
||||
prompt --timeout 180
|
||||
reboot
|
@ -15,9 +15,7 @@
|
||||
|
||||
"""Test class for PXE driver."""
|
||||
|
||||
import filecmp
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from ironic_lib import utils as ironic_utils
|
||||
@ -164,8 +162,8 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
image_info = pxe._get_instance_image_info(self.node, self.context)
|
||||
self.assertEqual({}, image_info)
|
||||
|
||||
@mock.patch.object(pxe_utils, '_build_pxe_config', autospec=True)
|
||||
def _test_build_pxe_config_options_pxe(self, build_pxe_mock,
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
def _test_build_pxe_config_options_pxe(self, render_mock,
|
||||
whle_dsk_img=False):
|
||||
self.config(pxe_append_params='test_param', group='pxe')
|
||||
# NOTE: right '/' should be removed from url string
|
||||
@ -272,8 +270,8 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
|
||||
@mock.patch('ironic.common.image_service.GlanceImageService',
|
||||
autospec=True)
|
||||
@mock.patch.object(pxe_utils, '_build_pxe_config', autospec=True)
|
||||
def _test_build_pxe_config_options_ipxe(self, build_pxe_mock, glance_mock,
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
def _test_build_pxe_config_options_ipxe(self, render_mock, glance_mock,
|
||||
whle_dsk_img=False,
|
||||
ipxe_timeout=0,
|
||||
ipxe_use_swift=False):
|
||||
@ -770,46 +768,57 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
self._test_prepare_ramdisk(uefi=True)
|
||||
|
||||
@mock.patch.object(os.path, 'isfile', autospec=True)
|
||||
@mock.patch.object(filecmp, 'cmp', autospec=True)
|
||||
@mock.patch.object(shutil, 'copyfile', autospec=True)
|
||||
@mock.patch('ironic.common.utils.file_has_content', autospec=True)
|
||||
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
def test_prepare_ramdisk_ipxe_with_copy_file_different(
|
||||
self, copyfile_mock, cmp_mock, isfile_mock):
|
||||
self, render_mock, write_mock, cmp_mock, isfile_mock):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.save()
|
||||
self.config(group='pxe', ipxe_enabled=True)
|
||||
self.config(group='deploy', http_url='http://myserver')
|
||||
isfile_mock.return_value = True
|
||||
cmp_mock.return_value = False
|
||||
render_mock.return_value = 'foo'
|
||||
self._test_prepare_ramdisk()
|
||||
copyfile_mock.assert_called_once_with(
|
||||
CONF.pxe.ipxe_boot_script,
|
||||
write_mock.assert_called_once_with(
|
||||
os.path.join(
|
||||
CONF.deploy.http_root,
|
||||
os.path.basename(CONF.pxe.ipxe_boot_script)))
|
||||
os.path.basename(CONF.pxe.ipxe_boot_script)),
|
||||
'foo')
|
||||
render_mock.assert_called_once_with(
|
||||
CONF.pxe.ipxe_boot_script,
|
||||
{'ipxe_for_mac_uri': 'pxelinux.cfg/'})
|
||||
|
||||
@mock.patch.object(os.path, 'isfile', autospec=True)
|
||||
@mock.patch.object(filecmp, 'cmp', autospec=True)
|
||||
@mock.patch.object(shutil, 'copyfile', autospec=True)
|
||||
@mock.patch('ironic.common.utils.file_has_content', autospec=True)
|
||||
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
def test_prepare_ramdisk_ipxe_with_copy_no_file(
|
||||
self, copyfile_mock, cmp_mock, isfile_mock):
|
||||
self, render_mock, write_mock, cmp_mock, isfile_mock):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.save()
|
||||
self.config(group='pxe', ipxe_enabled=True)
|
||||
self.config(group='deploy', http_url='http://myserver')
|
||||
isfile_mock.return_value = False
|
||||
render_mock.return_value = 'foo'
|
||||
self._test_prepare_ramdisk()
|
||||
self.assertFalse(cmp_mock.called)
|
||||
copyfile_mock.assert_called_once_with(
|
||||
CONF.pxe.ipxe_boot_script,
|
||||
write_mock.assert_called_once_with(
|
||||
os.path.join(
|
||||
CONF.deploy.http_root,
|
||||
os.path.basename(CONF.pxe.ipxe_boot_script)))
|
||||
os.path.basename(CONF.pxe.ipxe_boot_script)),
|
||||
'foo')
|
||||
render_mock.assert_called_once_with(
|
||||
CONF.pxe.ipxe_boot_script,
|
||||
{'ipxe_for_mac_uri': 'pxelinux.cfg/'})
|
||||
|
||||
@mock.patch.object(os.path, 'isfile', autospec=True)
|
||||
@mock.patch.object(filecmp, 'cmp', autospec=True)
|
||||
@mock.patch.object(shutil, 'copyfile', autospec=True)
|
||||
@mock.patch('ironic.common.utils.file_has_content', autospec=True)
|
||||
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
def test_prepare_ramdisk_ipxe_without_copy(
|
||||
self, copyfile_mock, cmp_mock, isfile_mock):
|
||||
self, render_mock, write_mock, cmp_mock, isfile_mock):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.save()
|
||||
self.config(group='pxe', ipxe_enabled=True)
|
||||
@ -817,35 +826,40 @@ class PXEBootTestCase(db_base.DbTestCase):
|
||||
isfile_mock.return_value = True
|
||||
cmp_mock.return_value = True
|
||||
self._test_prepare_ramdisk()
|
||||
self.assertFalse(copyfile_mock.called)
|
||||
self.assertFalse(write_mock.called)
|
||||
|
||||
@mock.patch.object(shutil, 'copyfile', autospec=True)
|
||||
def test_prepare_ramdisk_ipxe_swift(self, copyfile_mock):
|
||||
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
def test_prepare_ramdisk_ipxe_swift(self, render_mock, write_mock):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.save()
|
||||
self.config(group='pxe', ipxe_enabled=True)
|
||||
self.config(group='pxe', ipxe_use_swift=True)
|
||||
self.config(group='deploy', http_url='http://myserver')
|
||||
render_mock.return_value = 'foo'
|
||||
self._test_prepare_ramdisk(ipxe_use_swift=True)
|
||||
copyfile_mock.assert_called_once_with(
|
||||
CONF.pxe.ipxe_boot_script,
|
||||
write_mock.assert_called_once_with(
|
||||
os.path.join(
|
||||
CONF.deploy.http_root,
|
||||
os.path.basename(CONF.pxe.ipxe_boot_script)))
|
||||
os.path.basename(CONF.pxe.ipxe_boot_script)),
|
||||
'foo')
|
||||
|
||||
@mock.patch.object(shutil, 'copyfile', autospec=True)
|
||||
def test_prepare_ramdisk_ipxe_swift_whole_disk_image(self, copyfile_mock):
|
||||
@mock.patch('ironic.common.utils.write_to_file', autospec=True)
|
||||
@mock.patch('ironic.common.utils.render_template', autospec=True)
|
||||
def test_prepare_ramdisk_ipxe_swift_whole_disk_image(
|
||||
self, render_mock, write_mock):
|
||||
self.node.provision_state = states.DEPLOYING
|
||||
self.node.save()
|
||||
self.config(group='pxe', ipxe_enabled=True)
|
||||
self.config(group='pxe', ipxe_use_swift=True)
|
||||
self.config(group='deploy', http_url='http://myserver')
|
||||
render_mock.return_value = 'foo'
|
||||
self._test_prepare_ramdisk(ipxe_use_swift=True, whole_disk_image=True)
|
||||
copyfile_mock.assert_called_once_with(
|
||||
CONF.pxe.ipxe_boot_script,
|
||||
write_mock.assert_called_once_with(
|
||||
os.path.join(
|
||||
CONF.deploy.http_root,
|
||||
os.path.basename(CONF.pxe.ipxe_boot_script)))
|
||||
os.path.basename(CONF.pxe.ipxe_boot_script)),
|
||||
'foo')
|
||||
|
||||
def test_prepare_ramdisk_cleaning(self):
|
||||
self.node.provision_state = states.CLEANING
|
||||
|
Loading…
x
Reference in New Issue
Block a user