Add iPXE support for Ironic
As the size of our deploy ramdisk would continue to increase (Ironic Python Agent) we need a more reliable way to transfer such data via the network without relying on TFTP. The problem with TFTP is that it's unreliable and any transmission error will result consequently in boot problems. This patch adds support for iPXE so that we have the ability to transfer data through HTTP which is a reliable protocol. New configuration options added to the 'pxe' group: - ipxe_enabled: Whether iPXE is enabled or not - ipxe_boot_script: The path to the main iPXE script file - http_url: The HTTP server URL - http_root: The HTTP root path Two functions from pxe.py were renamed because they are not related only to tftp anymore: - _get_tftp_image_info renamed to _get_image_info - _cache_tftp_images renamed to _cache_ramdisk_kernel (because that's what this function is about, it fetchs the kernels and ramdisks associated with the image, not the image itself) Implements: blueprint ipxe-boot Change-Id: I8dc7640a19374a9c4d687877ea6c0ff1ebc13979
This commit is contained in:
parent
435c20bd01
commit
d8a0cf5815
@ -1100,6 +1100,19 @@
|
||||
# (integer value)
|
||||
#image_cache_ttl=10080
|
||||
|
||||
# Ironic compute node's HTTP server URL. Example:
|
||||
# http://192.1.2.3:8080 (string value)
|
||||
#http_url=<None>
|
||||
|
||||
# Ironic compute node's HTTP root path. (string value)
|
||||
#http_root=/httpboot
|
||||
|
||||
# Enable iPXE boot. (boolean value)
|
||||
#ipxe_enabled=false
|
||||
|
||||
# The path to the main iPXE script file. (string value)
|
||||
#ipxe_boot_script=$pybasedir/drivers/modules/boot.ipxe
|
||||
|
||||
|
||||
[seamicro]
|
||||
|
||||
|
@ -31,15 +31,23 @@ LOG = logging.getLogger(__name__)
|
||||
PXE_CFG_DIR_NAME = 'pxelinux.cfg'
|
||||
|
||||
|
||||
def get_root_dir():
|
||||
"""Returns the directory where the config files and images will live."""
|
||||
if CONF.pxe.ipxe_enabled:
|
||||
return CONF.pxe.http_root
|
||||
else:
|
||||
return CONF.pxe.tftp_root
|
||||
|
||||
|
||||
def _ensure_config_dirs_exist(node_uuid):
|
||||
"""Ensure that the node's and PXE configuration directories exist.
|
||||
|
||||
:param node_uuid: the UUID of the node.
|
||||
|
||||
"""
|
||||
tftp_root = CONF.pxe.tftp_root
|
||||
fileutils.ensure_tree(os.path.join(tftp_root, node_uuid))
|
||||
fileutils.ensure_tree(os.path.join(tftp_root, PXE_CFG_DIR_NAME))
|
||||
root_dir = get_root_dir()
|
||||
fileutils.ensure_tree(os.path.join(root_dir, node_uuid))
|
||||
fileutils.ensure_tree(os.path.join(root_dir, PXE_CFG_DIR_NAME))
|
||||
|
||||
|
||||
def _build_pxe_config(pxe_options, template):
|
||||
@ -80,11 +88,12 @@ def _get_pxe_mac_path(mac):
|
||||
:returns: the path to the config file.
|
||||
|
||||
"""
|
||||
return os.path.join(
|
||||
CONF.pxe.tftp_root,
|
||||
PXE_CFG_DIR_NAME,
|
||||
"01-" + mac.replace(":", "-").lower()
|
||||
)
|
||||
if CONF.pxe.ipxe_enabled:
|
||||
mac_file_name = mac.replace(':', '').lower()
|
||||
else:
|
||||
mac_file_name = "01-" + mac.replace(":", "-").lower()
|
||||
|
||||
return os.path.join(get_root_dir(), PXE_CFG_DIR_NAME, mac_file_name)
|
||||
|
||||
|
||||
def get_deploy_kr_info(node_uuid, driver_info):
|
||||
@ -92,12 +101,13 @@ def get_deploy_kr_info(node_uuid, driver_info):
|
||||
|
||||
Note: driver_info should be validated outside of this method.
|
||||
"""
|
||||
root_dir = get_root_dir()
|
||||
image_info = {}
|
||||
for label in ('deploy_kernel', 'deploy_ramdisk'):
|
||||
# the values for these keys will look like "glance://image-uuid"
|
||||
image_info[label] = (
|
||||
str(driver_info[label]).split('/')[-1],
|
||||
os.path.join(CONF.pxe.tftp_root, node_uuid, label)
|
||||
os.path.join(root_dir, node_uuid, label)
|
||||
)
|
||||
return image_info
|
||||
|
||||
@ -109,7 +119,7 @@ def get_pxe_config_file_path(node_uuid):
|
||||
:returns: The path to the node's PXE configuration file.
|
||||
|
||||
"""
|
||||
return os.path.join(CONF.pxe.tftp_root, node_uuid, 'config')
|
||||
return os.path.join(get_root_dir(), node_uuid, 'config')
|
||||
|
||||
|
||||
def create_pxe_config(task, pxe_options, template=None):
|
||||
@ -152,16 +162,31 @@ def clean_up_pxe_config(task):
|
||||
for mac in driver_utils.get_node_mac_addresses(task):
|
||||
utils.unlink_without_raise(_get_pxe_mac_path(mac))
|
||||
|
||||
utils.rmtree_without_raise(os.path.join(CONF.pxe.tftp_root,
|
||||
utils.rmtree_without_raise(os.path.join(get_root_dir(),
|
||||
task.node.uuid))
|
||||
|
||||
|
||||
def dhcp_options_for_instance():
|
||||
"""Retrieves the DHCP PXE boot options."""
|
||||
return [{'opt_name': 'bootfile-name',
|
||||
'opt_value': CONF.pxe.pxe_bootfile_name},
|
||||
{'opt_name': 'server-ip-address',
|
||||
'opt_value': CONF.pxe.tftp_server},
|
||||
{'opt_name': 'tftp-server',
|
||||
'opt_value': CONF.pxe.tftp_server}
|
||||
]
|
||||
dhcp_opts = []
|
||||
if CONF.pxe.ipxe_enabled:
|
||||
script_name = os.path.basename(CONF.pxe.ipxe_boot_script)
|
||||
ipxe_script_url = '/'.join([CONF.pxe.http_url, script_name])
|
||||
# if the request comes from dumb firmware send them the iPXE
|
||||
# boot image. !175 == non-iPXE.
|
||||
# http://ipxe.org/howto/dhcpd#ipxe-specific_options
|
||||
dhcp_opts.append({'opt_name': '!175,bootfile-name',
|
||||
'opt_value': CONF.pxe.pxe_bootfile_name})
|
||||
# If the request comes from iPXE, direct it to boot from the
|
||||
# iPXE script
|
||||
dhcp_opts.append({'opt_name': 'bootfile-name',
|
||||
'opt_value': ipxe_script_url})
|
||||
else:
|
||||
dhcp_opts.append({'opt_name': 'bootfile-name',
|
||||
'opt_value': CONF.pxe.pxe_bootfile_name})
|
||||
|
||||
dhcp_opts.append({'opt_name': 'server-ip-address',
|
||||
'opt_value': CONF.pxe.tftp_server})
|
||||
dhcp_opts.append({'opt_name': 'tftp-server',
|
||||
'opt_value': CONF.pxe.tftp_server})
|
||||
return dhcp_opts
|
||||
|
10
ironic/drivers/modules/boot.ipxe
Normal file
10
ironic/drivers/modules/boot.ipxe
Normal file
@ -0,0 +1,10 @@
|
||||
#!ipxe
|
||||
|
||||
# load the MAC-specific file or fail if it's not found
|
||||
chain --autofree pxelinux.cfg/${mac:hexraw} || goto error_no_config
|
||||
|
||||
:error_no_config
|
||||
echo PXE boot failed. No configuration found for MAC ${mac}
|
||||
echo Press any key to reboot...
|
||||
prompt --timeout 180
|
||||
reboot
|
@ -20,6 +20,7 @@ import socket
|
||||
import stat
|
||||
import time
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.utils import excutils
|
||||
|
||||
from ironic.common import disk_partitioner
|
||||
@ -31,6 +32,8 @@ from ironic.openstack.common import processutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
# All functions are called from deploy() directly or indirectly.
|
||||
# They are split for stub-out.
|
||||
@ -166,12 +169,14 @@ def switch_pxe_config(path, root_uuid):
|
||||
with open(path) as f:
|
||||
lines = f.readlines()
|
||||
root = 'UUID=%s' % root_uuid
|
||||
pxe_cmd = 'goto' if CONF.pxe.ipxe_enabled else 'default'
|
||||
rre = re.compile(r'\{\{ ROOT \}\}')
|
||||
dre = re.compile('^default .*$')
|
||||
dre = re.compile('^%s .*$' % pxe_cmd)
|
||||
boot_line = '%s boot' % pxe_cmd
|
||||
with open(path, 'w') as f:
|
||||
for line in lines:
|
||||
line = rre.sub(root, line)
|
||||
line = dre.sub('default boot', line)
|
||||
line = dre.sub(boot_line, line)
|
||||
f.write(line)
|
||||
|
||||
|
||||
|
15
ironic/drivers/modules/ipxe_config.template
Normal file
15
ironic/drivers/modules/ipxe_config.template
Normal file
@ -0,0 +1,15 @@
|
||||
#!ipxe
|
||||
|
||||
dhcp
|
||||
|
||||
goto deploy
|
||||
|
||||
:deploy
|
||||
kernel {{ pxe_options.deployment_aki_path }} selinux=0 disk=cciss/c0d0,sda,hda,vda iscsi_target_iqn={{ pxe_options.deployment_iscsi_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 {{ pxe_options.pxe_append_params|default("", true) }} ip=${ip}:${next-server}:${gateway}:${netmask} BOOTIF=${mac}
|
||||
initrd {{ pxe_options.deployment_ari_path }}
|
||||
boot
|
||||
|
||||
:boot
|
||||
kernel {{ pxe_options.aki_path }} root={{ ROOT }} ro {{ pxe_options.pxe_append_params|default("", true) }}
|
||||
initrd {{ pxe_options.ari_path }}
|
||||
boot
|
@ -18,6 +18,7 @@ PXE Driver and supporting meta-classes.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.utils import strutils
|
||||
@ -84,6 +85,19 @@ pxe_opts = [
|
||||
default=10080,
|
||||
help='Maximum TTL (in minutes) for old master images in '
|
||||
'cache.'),
|
||||
cfg.StrOpt('http_url',
|
||||
help='Ironic compute node\'s HTTP server URL. '
|
||||
'Example: http://192.1.2.3:8080'),
|
||||
cfg.StrOpt('http_root',
|
||||
default='/httpboot',
|
||||
help='Ironic compute node\'s HTTP root path.'),
|
||||
cfg.BoolOpt('ipxe_enabled',
|
||||
default=False,
|
||||
help='Enable iPXE boot.'),
|
||||
cfg.StrOpt('ipxe_boot_script',
|
||||
default=paths.basedir_def(
|
||||
'drivers/modules/boot.ipxe'),
|
||||
help='The path to the main iPXE script file.'),
|
||||
]
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -223,14 +237,27 @@ def _build_pxe_config_options(node, pxe_info, ctx):
|
||||
node.instance_info = i_info
|
||||
node.save(ctx)
|
||||
|
||||
if CONF.pxe.ipxe_enabled:
|
||||
deploy_kernel = '/'.join([CONF.pxe.http_url, node.uuid,
|
||||
'deploy_kernel'])
|
||||
deploy_ramdisk = '/'.join([CONF.pxe.http_url, node.uuid,
|
||||
'deploy_ramdisk'])
|
||||
kernel = '/'.join([CONF.pxe.http_url, node.uuid, 'kernel'])
|
||||
ramdisk = '/'.join([CONF.pxe.http_url, node.uuid, 'ramdisk'])
|
||||
else:
|
||||
deploy_kernel = pxe_info['deploy_kernel'][1]
|
||||
deploy_ramdisk = pxe_info['deploy_ramdisk'][1]
|
||||
kernel = pxe_info['kernel'][1]
|
||||
ramdisk = pxe_info['ramdisk'][1]
|
||||
|
||||
pxe_options = {
|
||||
'deployment_id': node['uuid'],
|
||||
'deployment_key': deploy_key,
|
||||
'deployment_iscsi_iqn': "iqn-%s" % node.uuid,
|
||||
'deployment_aki_path': pxe_info['deploy_kernel'][1],
|
||||
'deployment_ari_path': pxe_info['deploy_ramdisk'][1],
|
||||
'aki_path': pxe_info['kernel'][1],
|
||||
'ari_path': pxe_info['ramdisk'][1],
|
||||
'deployment_aki_path': deploy_kernel,
|
||||
'deployment_ari_path': deploy_ramdisk,
|
||||
'aki_path': kernel,
|
||||
'ari_path': ramdisk,
|
||||
'ironic_api_url': ironic_api,
|
||||
'pxe_append_params': CONF.pxe.pxe_append_params,
|
||||
}
|
||||
@ -328,10 +355,10 @@ def _fetch_images(ctx, cache, images_info):
|
||||
cache.fetch_image(uuid, path, ctx=ctx)
|
||||
|
||||
|
||||
def _cache_tftp_images(ctx, node, pxe_info):
|
||||
def _cache_ramdisk_kernel(ctx, node, pxe_info):
|
||||
"""Fetch the necessary kernels and ramdisks for the instance."""
|
||||
fileutils.ensure_tree(
|
||||
os.path.join(CONF.pxe.tftp_root, node.uuid))
|
||||
os.path.join(pxe_utils.get_root_dir(), node.uuid))
|
||||
LOG.debug("Fetching kernel and ramdisk for node %s",
|
||||
node.uuid)
|
||||
_fetch_images(ctx, TFTPImageCache(), pxe_info.values())
|
||||
@ -367,7 +394,7 @@ def _cache_instance_image(ctx, node):
|
||||
return (uuid, image_path)
|
||||
|
||||
|
||||
def _get_tftp_image_info(node, ctx):
|
||||
def _get_image_info(node, ctx):
|
||||
"""Generate the paths for tftp files for this instance
|
||||
|
||||
Raises IronicException if
|
||||
@ -378,6 +405,7 @@ def _get_tftp_image_info(node, ctx):
|
||||
"""
|
||||
d_info = _parse_deploy_info(node)
|
||||
image_info = {}
|
||||
root_dir = pxe_utils.get_root_dir()
|
||||
|
||||
image_info.update(pxe_utils.get_deploy_kr_info(node.uuid, d_info))
|
||||
|
||||
@ -394,7 +422,7 @@ def _get_tftp_image_info(node, ctx):
|
||||
for label in labels:
|
||||
image_info[label] = (
|
||||
i_info[label],
|
||||
os.path.join(CONF.pxe.tftp_root, node.uuid, label)
|
||||
os.path.join(root_dir, node.uuid, label)
|
||||
)
|
||||
|
||||
return image_info
|
||||
@ -489,6 +517,12 @@ class PXEDeploy(base.DeployInterface):
|
||||
|
||||
d_info = _parse_deploy_info(node)
|
||||
|
||||
if CONF.pxe.ipxe_enabled:
|
||||
if not CONF.pxe.http_url or not CONF.pxe.http_root:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"iPXE boot is enabled but no HTTP URL or HTTP "
|
||||
"root was specified."))
|
||||
|
||||
# Try to get the URL of the Ironic API
|
||||
try:
|
||||
# TODO(lucasagomes): Validate the format of the URL
|
||||
@ -552,12 +586,17 @@ class PXEDeploy(base.DeployInterface):
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
"""
|
||||
# TODO(deva): optimize this if rerun on existing files
|
||||
pxe_info = _get_tftp_image_info(task.node, task.context)
|
||||
if CONF.pxe.ipxe_enabled:
|
||||
# Copy the iPXE boot script to HTTP root directory
|
||||
bootfile_path = os.path.join(CONF.pxe.http_root,
|
||||
os.path.basename(CONF.pxe.ipxe_boot_script))
|
||||
shutil.copyfile(CONF.pxe.ipxe_boot_script, bootfile_path)
|
||||
pxe_info = _get_image_info(task.node, task.context)
|
||||
pxe_options = _build_pxe_config_options(task.node, pxe_info,
|
||||
task.context)
|
||||
pxe_utils.create_pxe_config(task, pxe_options,
|
||||
CONF.pxe.pxe_config_template)
|
||||
_cache_tftp_images(task.context, task.node, pxe_info)
|
||||
_cache_ramdisk_kernel(task.context, task.node, pxe_info)
|
||||
|
||||
def clean_up(self, task):
|
||||
"""Clean up the deployment environment for the task's node.
|
||||
@ -569,7 +608,7 @@ class PXEDeploy(base.DeployInterface):
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
"""
|
||||
node = task.node
|
||||
pxe_info = _get_tftp_image_info(node, task.context)
|
||||
pxe_info = _get_image_info(node, task.context)
|
||||
d_info = _parse_deploy_info(node)
|
||||
for label in pxe_info:
|
||||
path = pxe_info[label][1]
|
||||
|
@ -20,6 +20,8 @@ import mock
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from ironic.common import disk_partitioner
|
||||
from ironic.common import exception
|
||||
from ironic.common import utils as common_utils
|
||||
@ -53,6 +55,42 @@ kernel kernel
|
||||
append initrd=ramdisk root=UUID=12345678-1234-1234-1234-1234567890abcdef
|
||||
"""
|
||||
|
||||
_IPXECONF_DEPLOY = """
|
||||
#!ipxe
|
||||
|
||||
dhcp
|
||||
|
||||
goto deploy
|
||||
|
||||
:deploy
|
||||
kernel deploy_kernel
|
||||
initrd deploy_ramdisk
|
||||
boot
|
||||
|
||||
:boot
|
||||
kernel kernel
|
||||
append initrd=ramdisk root={{ ROOT }}
|
||||
boot
|
||||
"""
|
||||
|
||||
_IPXECONF_BOOT = """
|
||||
#!ipxe
|
||||
|
||||
dhcp
|
||||
|
||||
goto boot
|
||||
|
||||
:deploy
|
||||
kernel deploy_kernel
|
||||
initrd deploy_ramdisk
|
||||
boot
|
||||
|
||||
:boot
|
||||
kernel kernel
|
||||
append initrd=ramdisk root=UUID=12345678-1234-1234-1234-1234567890abcdef
|
||||
boot
|
||||
"""
|
||||
|
||||
|
||||
class PhysicalWorkTestCase(tests_base.TestCase):
|
||||
def setUp(self):
|
||||
@ -363,20 +401,32 @@ class PhysicalWorkTestCase(tests_base.TestCase):
|
||||
|
||||
|
||||
class SwitchPxeConfigTestCase(tests_base.TestCase):
|
||||
def setUp(self):
|
||||
super(SwitchPxeConfigTestCase, self).setUp()
|
||||
(fd, self.fname) = tempfile.mkstemp()
|
||||
os.write(fd, _PXECONF_DEPLOY)
|
||||
|
||||
def _create_config(self, ipxe=False):
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
pxe_cfg = _IPXECONF_DEPLOY if ipxe else _PXECONF_DEPLOY
|
||||
os.write(fd, pxe_cfg)
|
||||
os.close(fd)
|
||||
self.addCleanup(os.unlink, self.fname)
|
||||
self.addCleanup(os.unlink, fname)
|
||||
return fname
|
||||
|
||||
def test_switch_pxe_config(self):
|
||||
utils.switch_pxe_config(self.fname,
|
||||
fname = self._create_config()
|
||||
utils.switch_pxe_config(fname,
|
||||
'12345678-1234-1234-1234-1234567890abcdef')
|
||||
with open(self.fname, 'r') as f:
|
||||
with open(fname, 'r') as f:
|
||||
pxeconf = f.read()
|
||||
self.assertEqual(_PXECONF_BOOT, pxeconf)
|
||||
|
||||
def test_switch_ipxe_config(self):
|
||||
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
|
||||
fname = self._create_config(ipxe=True)
|
||||
utils.switch_pxe_config(fname,
|
||||
'12345678-1234-1234-1234-1234567890abcdef')
|
||||
with open(fname, 'r') as f:
|
||||
pxeconf = f.read()
|
||||
self.assertEqual(_IPXECONF_BOOT, pxeconf)
|
||||
|
||||
|
||||
class OtherFunctionTestCase(tests_base.TestCase):
|
||||
def test_get_dev(self):
|
||||
|
@ -217,7 +217,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
return self.dbapi.create_port(p)
|
||||
|
||||
@mock.patch.object(base_image_service.BaseImageService, '_show')
|
||||
def test__get_tftp_image_info(self, show_mock):
|
||||
def test__get_image_info(self, show_mock):
|
||||
properties = {'properties': {u'kernel_id': u'instance_kernel_uuid',
|
||||
u'ramdisk_id': u'instance_ramdisk_uuid'}}
|
||||
|
||||
@ -242,14 +242,14 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
self.node.uuid,
|
||||
'deploy_kernel'))}
|
||||
show_mock.return_value = properties
|
||||
image_info = pxe._get_tftp_image_info(self.node, self.context)
|
||||
image_info = pxe._get_image_info(self.node, self.context)
|
||||
show_mock.assert_called_once_with('glance://image_uuid',
|
||||
method='get')
|
||||
self.assertEqual(expected_info, image_info)
|
||||
|
||||
# test with saved info
|
||||
show_mock.reset_mock()
|
||||
image_info = pxe._get_tftp_image_info(self.node, self.context)
|
||||
image_info = pxe._get_image_info(self.node, self.context)
|
||||
self.assertEqual(expected_info, image_info)
|
||||
self.assertFalse(show_mock.called)
|
||||
self.assertEqual('instance_kernel_uuid',
|
||||
@ -259,7 +259,8 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
|
||||
@mock.patch.object(utils, 'random_alnum')
|
||||
@mock.patch.object(pxe_utils, '_build_pxe_config')
|
||||
def test_build_pxe_config_options(self, build_pxe_mock, random_alnum_mock):
|
||||
def _test_build_pxe_config_options(self, build_pxe_mock, random_alnum_mock,
|
||||
ipxe_enabled=False):
|
||||
self.config(pxe_append_params='test_param', group='pxe')
|
||||
# NOTE: right '/' should be removed from url string
|
||||
self.config(api_url='http://192.168.122.184:6385/', group='conductor')
|
||||
@ -269,36 +270,56 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
fake_key = '0123456789ABCDEFGHIJKLMNOPQRSTUV'
|
||||
random_alnum_mock.return_value = fake_key
|
||||
|
||||
if ipxe_enabled:
|
||||
http_url = 'http://192.1.2.3:1234'
|
||||
self.config(ipxe_enabled=True, group='pxe')
|
||||
self.config(http_url=http_url, group='pxe')
|
||||
|
||||
deploy_kernel = os.path.join(http_url, self.node.uuid,
|
||||
'deploy_kernel')
|
||||
deploy_ramdisk = os.path.join(http_url, self.node.uuid,
|
||||
'deploy_ramdisk')
|
||||
kernel = os.path.join(http_url, self.node.uuid, 'kernel')
|
||||
ramdisk = os.path.join(http_url, self.node.uuid, 'ramdisk')
|
||||
root_dir = CONF.pxe.http_root
|
||||
else:
|
||||
deploy_kernel = os.path.join(CONF.pxe.tftp_root, self.node.uuid,
|
||||
'deploy_kernel')
|
||||
deploy_ramdisk = os.path.join(CONF.pxe.tftp_root, self.node.uuid,
|
||||
'deploy_ramdisk')
|
||||
kernel = os.path.join(CONF.pxe.tftp_root, self.node.uuid,
|
||||
'kernel')
|
||||
ramdisk = os.path.join(CONF.pxe.tftp_root, self.node.uuid,
|
||||
'ramdisk')
|
||||
root_dir = CONF.pxe.tftp_root
|
||||
|
||||
expected_options = {
|
||||
'deployment_key': '0123456789ABCDEFGHIJKLMNOPQRSTUV',
|
||||
'ari_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/'
|
||||
u'ramdisk',
|
||||
'ari_path': ramdisk,
|
||||
'deployment_iscsi_iqn': u'iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33'
|
||||
u'c123',
|
||||
'deployment_ari_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7'
|
||||
u'f33c123/deploy_ramdisk',
|
||||
'deployment_ari_path': deploy_ramdisk,
|
||||
'pxe_append_params': 'test_param',
|
||||
'aki_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/'
|
||||
u'kernel',
|
||||
'aki_path': kernel,
|
||||
'deployment_id': u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
|
||||
'ironic_api_url': 'http://192.168.122.184:6385',
|
||||
'deployment_aki_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-'
|
||||
u'c02d7f33c123/deploy_kernel'
|
||||
'deployment_aki_path': deploy_kernel,
|
||||
}
|
||||
|
||||
image_info = {'deploy_kernel': ('deploy_kernel',
|
||||
os.path.join(CONF.pxe.tftp_root,
|
||||
os.path.join(root_dir,
|
||||
self.node.uuid,
|
||||
'deploy_kernel')),
|
||||
'deploy_ramdisk': ('deploy_ramdisk',
|
||||
os.path.join(CONF.pxe.tftp_root,
|
||||
os.path.join(root_dir,
|
||||
self.node.uuid,
|
||||
'deploy_ramdisk')),
|
||||
'kernel': ('kernel_id',
|
||||
os.path.join(CONF.pxe.tftp_root,
|
||||
os.path.join(root_dir,
|
||||
self.node.uuid,
|
||||
'kernel')),
|
||||
'ramdisk': ('ramdisk_id',
|
||||
os.path.join(CONF.pxe.tftp_root,
|
||||
os.path.join(root_dir,
|
||||
self.node.uuid,
|
||||
'ramdisk'))
|
||||
}
|
||||
@ -314,6 +335,12 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
db_key = db_node.instance_info.get('deploy_key')
|
||||
self.assertEqual(fake_key, db_key)
|
||||
|
||||
def test__build_pxe_config_options(self):
|
||||
self._test_build_pxe_config_options(ipxe_enabled=False)
|
||||
|
||||
def test__build_pxe_config_options_ipxe(self):
|
||||
self._test_build_pxe_config_options(ipxe_enabled=True)
|
||||
|
||||
def test__get_image_dir_path(self):
|
||||
self.assertEqual(os.path.join(CONF.pxe.images_path, self.node.uuid),
|
||||
pxe._get_image_dir_path(self.node.uuid))
|
||||
@ -341,7 +368,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
image_info = {'deploy_kernel': ('deploy_kernel', image_path)}
|
||||
fileutils.ensure_tree(CONF.pxe.tftp_master_path)
|
||||
|
||||
pxe._cache_tftp_images(None, self.node, image_info)
|
||||
pxe._cache_ramdisk_kernel(None, self.node, image_info)
|
||||
|
||||
mock_fetch_image.assert_called_once_with(None,
|
||||
mock.ANY,
|
||||
@ -368,6 +395,33 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
'disk'),
|
||||
image_path)
|
||||
|
||||
@mock.patch.object(pxe, 'TFTPImageCache', lambda: None)
|
||||
@mock.patch.object(fileutils, 'ensure_tree')
|
||||
@mock.patch.object(pxe, '_fetch_images')
|
||||
def test__cache_ramdisk_kernel(self, mock_fetch_image, mock_ensure_tree):
|
||||
self.config(ipxe_enabled=False, group='pxe')
|
||||
fake_pxe_info = {'foo': 'bar'}
|
||||
expected_path = os.path.join(CONF.pxe.tftp_root, self.node.uuid)
|
||||
|
||||
pxe._cache_ramdisk_kernel(self.context, self.node, fake_pxe_info)
|
||||
mock_ensure_tree.assert_called_with(expected_path)
|
||||
mock_fetch_image.assert_called_once_with(self.context, mock.ANY,
|
||||
fake_pxe_info.values())
|
||||
|
||||
@mock.patch.object(pxe, 'TFTPImageCache', lambda: None)
|
||||
@mock.patch.object(fileutils, 'ensure_tree')
|
||||
@mock.patch.object(pxe, '_fetch_images')
|
||||
def test__cache_ramdisk_kernel_ipxe(self, mock_fetch_image,
|
||||
mock_ensure_tree):
|
||||
self.config(ipxe_enabled=True, group='pxe')
|
||||
fake_pxe_info = {'foo': 'bar'}
|
||||
expected_path = os.path.join(CONF.pxe.http_root, self.node.uuid)
|
||||
|
||||
pxe._cache_ramdisk_kernel(self.context, self.node, fake_pxe_info)
|
||||
mock_ensure_tree.assert_called_with(expected_path)
|
||||
mock_fetch_image.assert_called_once_with(self.context, mock.ANY,
|
||||
fake_pxe_info.values())
|
||||
|
||||
|
||||
@mock.patch.object(pxe, 'TFTPImageCache')
|
||||
@mock.patch.object(pxe, 'InstanceImageCache')
|
||||
@ -649,25 +703,25 @@ class PXEDriverTestCase(db_base.DbTestCase):
|
||||
address='123456', iqn='aaa-bbb',
|
||||
key='fake-12345')
|
||||
|
||||
@mock.patch.object(pxe, '_get_tftp_image_info')
|
||||
@mock.patch.object(pxe, '_cache_tftp_images')
|
||||
@mock.patch.object(pxe, '_get_image_info')
|
||||
@mock.patch.object(pxe, '_cache_ramdisk_kernel')
|
||||
@mock.patch.object(pxe, '_build_pxe_config_options')
|
||||
@mock.patch.object(pxe_utils, 'create_pxe_config')
|
||||
def test_prepare(self, mock_pxe_config,
|
||||
mock_build_pxe, mock_cache_tftp_images,
|
||||
mock_tftp_img_info):
|
||||
mock_build_pxe, mock_cache_r_k,
|
||||
mock_img_info):
|
||||
mock_build_pxe.return_value = None
|
||||
mock_tftp_img_info.return_value = None
|
||||
mock_img_info.return_value = None
|
||||
mock_pxe_config.return_value = None
|
||||
mock_cache_tftp_images.return_value = None
|
||||
mock_cache_r_k.return_value = None
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
task.driver.deploy.prepare(task)
|
||||
mock_tftp_img_info.assert_called_once_with(task.node,
|
||||
self.context)
|
||||
mock_img_info.assert_called_once_with(task.node,
|
||||
self.context)
|
||||
mock_pxe_config.assert_called_once_with(
|
||||
task, None, CONF.pxe.pxe_config_template)
|
||||
mock_cache_tftp_images.assert_called_once_with(self.context,
|
||||
task.node, None)
|
||||
mock_cache_r_k.assert_called_once_with(self.context,
|
||||
task.node, None)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'get_image_mb')
|
||||
@mock.patch.object(pxe, '_get_image_file_path')
|
||||
@ -842,8 +896,8 @@ class PXEDriverTestCase(db_base.DbTestCase):
|
||||
self.assertEqual(1, _continue_deploy_mock.call_count,
|
||||
"_continue_deploy was not called once.")
|
||||
|
||||
@mock.patch.object(pxe, '_get_tftp_image_info')
|
||||
def clean_up_config(self, get_tftp_image_info_mock, master=None):
|
||||
@mock.patch.object(pxe, '_get_image_info')
|
||||
def clean_up_config(self, get_image_info_mock, master=None):
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
self.config(tftp_root=temp_dir, group='pxe')
|
||||
tftp_master_dir = os.path.join(CONF.pxe.tftp_root,
|
||||
@ -870,7 +924,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
|
||||
self.node.uuid, 'deploy_kernel')
|
||||
image_info = {'deploy_kernel': ('deploy_kernel_uuid', d_kernel_path)}
|
||||
|
||||
get_tftp_image_info_mock.return_value = image_info
|
||||
get_image_info_mock.return_value = image_info
|
||||
|
||||
pxecfg_dir = os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg')
|
||||
os.makedirs(pxecfg_dir)
|
||||
@ -913,7 +967,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.driver.deploy.clean_up(task)
|
||||
get_tftp_image_info_mock.called_once_with(task.node)
|
||||
get_image_info_mock.called_once_with(task.node)
|
||||
assert_false_path = [config_path, deploy_kernel_path, image_path,
|
||||
pxe_mac_path, image_dir, instance_dir,
|
||||
token_path]
|
||||
|
@ -133,6 +133,25 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
self.assertEqual('/tftpboot/pxelinux.cfg/01-00-11-22-33-44-55-66',
|
||||
pxe_utils._get_pxe_mac_path(mac))
|
||||
|
||||
def test__get_pxe_mac_path_ipxe(self):
|
||||
self.config(ipxe_enabled=True, group='pxe')
|
||||
self.config(http_root='/httpboot', group='pxe')
|
||||
mac = '00:11:22:33:AA:BB:CC'
|
||||
self.assertEqual('/httpboot/pxelinux.cfg/00112233aabbcc',
|
||||
pxe_utils._get_pxe_mac_path(mac))
|
||||
|
||||
def test_get_root_dir(self):
|
||||
expected_dir = '/tftproot'
|
||||
self.config(ipxe_enabled=False, group='pxe')
|
||||
self.config(tftp_root=expected_dir, group='pxe')
|
||||
self.assertEqual(expected_dir, pxe_utils.get_root_dir())
|
||||
|
||||
def test_get_root_dir_ipxe(self):
|
||||
expected_dir = '/httpboot'
|
||||
self.config(ipxe_enabled=True, group='pxe')
|
||||
self.config(http_root=expected_dir, group='pxe')
|
||||
self.assertEqual(expected_dir, pxe_utils.get_root_dir())
|
||||
|
||||
def test_get_pxe_config_file_path(self):
|
||||
self.assertEqual(os.path.join(CONF.pxe.tftp_root,
|
||||
self.node.uuid,
|
||||
@ -151,8 +170,7 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
]
|
||||
self.assertEqual(expected_info, pxe_utils.dhcp_options_for_instance())
|
||||
|
||||
def test_get_deploy_kr_info(self):
|
||||
self.config(tftp_root='/tftp', group='pxe')
|
||||
def _test_get_deploy_kr_info(self, expected_dir):
|
||||
node_uuid = 'fake-node'
|
||||
driver_info = {
|
||||
'deploy_kernel': 'glance://deploy-kernel',
|
||||
@ -161,14 +179,25 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
|
||||
expected = {
|
||||
'deploy_kernel': ('deploy-kernel',
|
||||
'/tftp/fake-node/deploy_kernel'),
|
||||
expected_dir + '/fake-node/deploy_kernel'),
|
||||
'deploy_ramdisk': ('deploy-ramdisk',
|
||||
'/tftp/fake-node/deploy_ramdisk'),
|
||||
expected_dir + '/fake-node/deploy_ramdisk'),
|
||||
}
|
||||
|
||||
kr_info = pxe_utils.get_deploy_kr_info(node_uuid, driver_info)
|
||||
self.assertEqual(expected, kr_info)
|
||||
|
||||
def test_get_deploy_kr_info(self):
|
||||
expected_dir = '/tftp'
|
||||
self.config(tftp_root=expected_dir, group='pxe')
|
||||
self._test_get_deploy_kr_info(expected_dir)
|
||||
|
||||
def test_get_deploy_kr_info_ipxe(self):
|
||||
expected_dir = '/http'
|
||||
self.config(ipxe_enabled=True, group='pxe')
|
||||
self.config(http_root=expected_dir, group='pxe')
|
||||
self._test_get_deploy_kr_info(expected_dir)
|
||||
|
||||
def test_get_deploy_kr_info_bad_driver_info(self):
|
||||
self.config(tftp_root='/tftp', group='pxe')
|
||||
node_uuid = 'fake-node'
|
||||
@ -177,3 +206,22 @@ class TestPXEUtils(db_base.DbTestCase):
|
||||
pxe_utils.get_deploy_kr_info,
|
||||
node_uuid,
|
||||
driver_info)
|
||||
|
||||
def test_dhcp_options_for_instance_ipxe(self):
|
||||
self.config(tftp_server='192.0.2.1', group='pxe')
|
||||
self.config(pxe_bootfile_name='fake-bootfile', group='pxe')
|
||||
self.config(ipxe_enabled=True, group='pxe')
|
||||
self.config(http_url='http://192.0.3.2:1234', group='pxe')
|
||||
self.config(ipxe_boot_script='/test/boot.ipxe', group='pxe')
|
||||
|
||||
expected_boot_script_url = 'http://192.0.3.2:1234/boot.ipxe'
|
||||
expected_info = [{'opt_name': '!175,bootfile-name',
|
||||
'opt_value': 'fake-bootfile'},
|
||||
{'opt_name': 'server-ip-address',
|
||||
'opt_value': '192.0.2.1'},
|
||||
{'opt_name': 'tftp-server',
|
||||
'opt_value': '192.0.2.1'},
|
||||
{'opt_name': 'bootfile-name',
|
||||
'opt_value': expected_boot_script_url}]
|
||||
self.assertEqual(sorted(expected_info),
|
||||
sorted(pxe_utils.dhcp_options_for_instance()))
|
||||
|
Loading…
Reference in New Issue
Block a user