summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/deploy/install-guide.rst49
-rw-r--r--doc/source/deploy/upgrade-guide.rst36
-rw-r--r--doc/source/index.rst1
-rw-r--r--etc/ironic/ironic.conf.sample5
-rw-r--r--ironic/common/grub_conf.template10
-rw-r--r--ironic/common/pxe_utils.py33
-rw-r--r--ironic/drivers/modules/agent.py45
-rw-r--r--ironic/drivers/modules/boot.ipxe2
-rw-r--r--ironic/drivers/modules/deploy_utils.py19
-rw-r--r--ironic/drivers/modules/ilo/common.py2
-rw-r--r--ironic/drivers/modules/ilo/deploy.py4
-rw-r--r--ironic/drivers/modules/ipmitool.py29
-rw-r--r--ironic/drivers/modules/iscsi_deploy.py11
-rw-r--r--ironic/tests/drivers/test_agent.py69
-rw-r--r--ironic/tests/drivers/test_ipmitool.py10
-rw-r--r--ironic/tests/drivers/test_iscsi_deploy.py19
-rw-r--r--ironic/tests/test_images.py11
-rw-r--r--ironic/tests/test_pxe_utils.py37
18 files changed, 309 insertions, 83 deletions
diff --git a/doc/source/deploy/install-guide.rst b/doc/source/deploy/install-guide.rst
index 2907ad54b..871e75313 100644
--- a/doc/source/deploy/install-guide.rst
+++ b/doc/source/deploy/install-guide.rst
@@ -4,8 +4,9 @@
Bare Metal Service Installation Guide
=====================================
-This document pertains to the Juno (2014.2) release of OpenStack. Users of
-earlier releases may encounter some differences in configuration of services.
+This document pertains to the Kilo (2015.1) release of OpenStack Ironic. Users
+of earlier releases may encounter differences, and are encouraged to look at
+earlier versions of this document for guidance.
Service Overview
@@ -14,23 +15,33 @@ Service Overview
The Bare Metal Service is a collection of components that provides support to
manage and provision physical machines.
-Also known as the ``ironic`` project, the Bare Metal Service interacts with
-several other OpenStack services such as:
+Also known as the ``Ironic`` project, the Bare Metal Service may, depending
+upon configuration, interact with several other OpenStack services. This
+includes:
-- the Identity Service (keystone) for request authentication and to
+- the Telemetry (Ceilometer) for consuming the IPMI metrics
+- the Identity Service (Keystone) for request authentication and to
locate other OpenStack services
-- the Image Service (glance) from which to retrieve images
-- the Networking Service (neutron) for DHCP and network configuration
-- the Compute Service (nova), which leverages the Bare Metal Service to
- manage compute instances on bare metal.
+- the Image Service (Glance) from which to retrieve images and image meta-data
+- the Networking Service (Neutron) for DHCP and network configuration
+- the Compute Service (Nova) works with Ironic and acts as a user-facing API
+ for instance management, while Ironic provides the admin/operator API for
+ hardware management. Nova also provides scheduling facilities (matching
+ flavors <-> images <-> hardware), tenant quotas, IP assignment, and other
+ services which Ironic does not, in and of itself, provide.
+
+- the Block Storage (Cinder) will provide volumes, but the aspect is not yet available.
The Bare Metal Service includes the following components:
-- ironic-api. A RESTful API that processes application requests by sending
+- ironic-api: A RESTful API that processes application requests by sending
them to the ironic-conductor over RPC.
-- ironic-conductor. Adds/edits/deletes nodes; powers on/off nodes with
+- ironic-conductor: Adds/edits/deletes nodes; powers on/off nodes with
ipmi or ssh; provisions/deploys/decommissions bare metal nodes.
-- Ironic client. A command-line interface (CLI) for interacting with
+- ironic-python-agent: A python service which is run in a temporary ramdisk to
+ provide ironic-conductor service(s) with remote access and in-band hardware
+ control.
+- python-ironicclient: A command-line interface (CLI) for interacting with
the Bare Metal Service.
Additionally, the Bare Metal Service has certain external dependencies, which are
@@ -44,6 +55,20 @@ very similar to other OpenStack Services:
- A queue. A central hub for passing messages. It should use the same
implementation as that of the Compute Service (typically RabbitMQ).
+Optionally, one may wish to utilize the following associated projects for
+additional functionality:
+
+- ironic-discoverd_; An associated service which performs in-band hardware
+ introspection by PXE booting unregistered hardware into a "discovery ramdisk".
+- diskimage-builder_; May be used to customize machine images, create and
+ discovery deploy ramdisks, if necessary.
+.. _ironic-discoverd: https://github.com/stackforge/ironic-discoverd
+.. _diskimage-builder: https://github.com/openstack/diskimage-builder
+
+
+.. todo: include coreos-image-builder reference here, once the split is done
+
+
Install and Configure Prerequisites
===================================
diff --git a/doc/source/deploy/upgrade-guide.rst b/doc/source/deploy/upgrade-guide.rst
new file mode 100644
index 000000000..2ce9e0952
--- /dev/null
+++ b/doc/source/deploy/upgrade-guide.rst
@@ -0,0 +1,36 @@
+.. _upgrade-guide:
+
+=====================================
+Bare Metal Service Upgrade Guide
+=====================================
+
+This document outlines various steps and notes for operators to consider when
+upgrading their Ironic-driven clouds from previous versions of OpenStack.
+
+The Ironic service is tightly coupled with the Ironic driver that is shipped
+with Nova. Currently, some special considerations must be taken into account
+when upgrading your cloud from previous versions of OpenStack.
+
+Upgrading from Juno to Kilo
+===========================
+
+When upgrading a cloud from Juno to Kilo, users must ensure the Nova
+service is upgraded prior to upgrading the Ironic service. Additionally,
+users need to set a special config flag in Nova prior to upgrading to ensure
+the newer version of Nova is not attempting to take advantage of new Ironic
+features until the Ironic service has been upgraded. The steps for upgrading
+your Nova and Ironic services are as follows:
+
+- Edit nova.conf and ensure force_config_drive=False is set in the [DEFAULT]
+ group. Restart nova-compute if necessary.
+- Install new Nova code, run database migrations
+- Install new python-ironicclient code.
+- Restart Nova services.
+- Install new Ironic code, run database migrations, restart Ironic services.
+- Edit nova.conf and set force_config_drive to your liking, restaring
+ nova-compute if necessary.
+
+Note that during the period between Nova's upgrade and Ironic's upgrades,
+instances can still be provisioned to nodes, however, any attempt by users
+to specify a config drive for an instance will cause error until Ironic's
+upgrade has completed.
diff --git a/doc/source/index.rst b/doc/source/index.rst
index a3339207d..7ea533fdb 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -57,6 +57,7 @@ Overview
deploy/user-guide
deploy/install-guide
+ deploy/upgrade-guide
deploy/drivers
deploy/cleaning
diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample
index a8a1501a6..ccf368f0d 100644
--- a/etc/ironic/ironic.conf.sample
+++ b/etc/ironic/ironic.conf.sample
@@ -360,6 +360,11 @@
# set to 0, will not run during cleaning. (integer value)
#agent_erase_devices_priority=<None>
+# Whether Ironic will manage TFTP files for the deploy
+# ramdisks. If set to False, you will need to configure your
+# own TFTP server that allows booting the deploy ramdisks.
+# (boolean value)
+#manage_tftp=true
#
# Options defined in ironic.drivers.modules.agent_base_vendor
diff --git a/ironic/common/grub_conf.template b/ironic/common/grub_conf.template
index 746a43d97..2a979d2d6 100644
--- a/ironic/common/grub_conf.template
+++ b/ironic/common/grub_conf.template
@@ -1,4 +1,8 @@
-menuentry "install" {
-linux {{ linux }} {{ kernel_params }} --
-initrd {{ initrd }}
+set default=0
+set timeout=5
+set hidden_timeout_quiet=false
+
+menuentry "boot_partition" {
+linuxefi {{ linux }} {{ kernel_params }} --
+initrdefi {{ initrd }}
}
diff --git a/ironic/common/pxe_utils.py b/ironic/common/pxe_utils.py
index a125da034..09528a218 100644
--- a/ironic/common/pxe_utils.py
+++ b/ironic/common/pxe_utils.py
@@ -80,12 +80,20 @@ def _link_mac_pxe_configs(task):
:param task: A TaskManager instance.
"""
- pxe_config_file_path = get_pxe_config_file_path(task.node.uuid)
- for mac in driver_utils.get_node_mac_addresses(task):
- mac_path = _get_pxe_mac_path(mac)
+
+ def create_link(mac_path):
utils.unlink_without_raise(mac_path)
utils.create_link_without_raise(pxe_config_file_path, mac_path)
+ pxe_config_file_path = get_pxe_config_file_path(task.node.uuid)
+ for mac in driver_utils.get_node_mac_addresses(task):
+ create_link(_get_pxe_mac_path(mac))
+ # TODO(lucasagomes): Backward compatibility with :hexraw,
+ # to be removed in M.
+ # see: https://bugs.launchpad.net/ironic/+bug/1441710
+ if CONF.pxe.ipxe_enabled:
+ create_link(_get_pxe_mac_path(mac, delimiter=''))
+
def _link_ip_address_pxe_configs(task):
"""Link each IP address with the PXE configuration file.
@@ -110,17 +118,20 @@ def _link_ip_address_pxe_configs(task):
ip_address_path)
-def _get_pxe_mac_path(mac):
+def _get_pxe_mac_path(mac, delimiter=None):
"""Convert a MAC address into a PXE config file name.
:param mac: A MAC address string in the format xx:xx:xx:xx:xx:xx.
+ :param delimiter: The MAC address delimiter. Defaults to dash ('-').
:returns: the path to the config file.
"""
- if CONF.pxe.ipxe_enabled:
- mac_file_name = mac.replace(':', '').lower()
- else:
- mac_file_name = "01-" + mac.replace(":", "-").lower()
+ if delimiter is None:
+ delimiter = '-'
+
+ mac_file_name = mac.replace(':', delimiter).lower()
+ if not CONF.pxe.ipxe_enabled:
+ mac_file_name = '01-' + mac_file_name
return os.path.join(get_root_dir(), PXE_CFG_DIR_NAME, mac_file_name)
@@ -221,6 +232,12 @@ def clean_up_pxe_config(task):
else:
for mac in driver_utils.get_node_mac_addresses(task):
utils.unlink_without_raise(_get_pxe_mac_path(mac))
+ # TODO(lucasagomes): Backward compatibility with :hexraw,
+ # to be removed in M.
+ # see: https://bugs.launchpad.net/ironic/+bug/1441710
+ if CONF.pxe.ipxe_enabled:
+ utils.unlink_without_raise(_get_pxe_mac_path(mac,
+ delimiter=''))
utils.rmtree_without_raise(os.path.join(get_root_dir(),
task.node.uuid))
diff --git a/ironic/drivers/modules/agent.py b/ironic/drivers/modules/agent.py
index c257d2c77..6a590c8c4 100644
--- a/ironic/drivers/modules/agent.py
+++ b/ironic/drivers/modules/agent.py
@@ -57,7 +57,14 @@ agent_opts = [
'Python Agent ramdisk. If unset, will use the priority '
'set in the ramdisk (defaults to 10 for the '
'GenericHardwareManager). If set to 0, will not run '
- 'during cleaning.')
+ 'during cleaning.'),
+ cfg.BoolOpt('manage_tftp',
+ default=True,
+ help='Whether Ironic will manage TFTP files for the deploy '
+ 'ramdisks. If set to False, you will need to configure '
+ 'your own TFTP server that allows booting the deploy '
+ 'ramdisks.'
+ ),
]
CONF = cfg.CONF
@@ -196,12 +203,13 @@ def build_instance_info_for_deploy(task):
def _prepare_pxe_boot(task):
"""Prepare the files required for PXE booting the agent."""
- pxe_info = _get_tftp_image_info(task.node)
- pxe_options = _build_pxe_config_options(task.node, pxe_info)
- pxe_utils.create_pxe_config(task,
- pxe_options,
- CONF.agent.agent_pxe_config_template)
- _cache_tftp_images(task.context, task.node, pxe_info)
+ if CONF.agent.manage_tftp:
+ pxe_info = _get_tftp_image_info(task.node)
+ pxe_options = _build_pxe_config_options(task.node, pxe_info)
+ pxe_utils.create_pxe_config(task,
+ pxe_options,
+ CONF.agent.agent_pxe_config_template)
+ _cache_tftp_images(task.context, task.node, pxe_info)
def _do_pxe_boot(task, ports=None):
@@ -220,13 +228,13 @@ def _do_pxe_boot(task, ports=None):
def _clean_up_pxe(task):
"""Clean up left over PXE and DHCP files."""
- pxe_info = _get_tftp_image_info(task.node)
- for label in pxe_info:
- path = pxe_info[label][1]
- utils.unlink_without_raise(path)
- AgentTFTPImageCache().clean_up()
-
- pxe_utils.clean_up_pxe_config(task)
+ if CONF.agent.manage_tftp:
+ pxe_info = _get_tftp_image_info(task.node)
+ for label in pxe_info:
+ path = pxe_info[label][1]
+ utils.unlink_without_raise(path)
+ AgentTFTPImageCache().clean_up()
+ pxe_utils.clean_up_pxe_config(task)
class AgentDeploy(base.DeployInterface):
@@ -251,10 +259,11 @@ class AgentDeploy(base.DeployInterface):
"""
node = task.node
params = {}
- params['driver_info.deploy_kernel'] = node.driver_info.get(
- 'deploy_kernel')
- params['driver_info.deploy_ramdisk'] = node.driver_info.get(
- 'deploy_ramdisk')
+ if CONF.agent.manage_tftp:
+ params['driver_info.deploy_kernel'] = node.driver_info.get(
+ 'deploy_kernel')
+ params['driver_info.deploy_ramdisk'] = node.driver_info.get(
+ 'deploy_ramdisk')
image_source = node.instance_info.get('image_source')
params['instance_info.image_source'] = image_source
error_msg = _('Node %s failed to validate deploy image info. Some '
diff --git a/ironic/drivers/modules/boot.ipxe b/ironic/drivers/modules/boot.ipxe
index 25a0ea8dc..3567dc029 100644
--- a/ironic/drivers/modules/boot.ipxe
+++ b/ironic/drivers/modules/boot.ipxe
@@ -1,7 +1,7 @@
#!ipxe
# load the MAC-specific file or fail if it's not found
-chain --autofree pxelinux.cfg/${mac:hexraw} || goto error_no_config
+chain --autofree pxelinux.cfg/${mac:hexhyp} || goto error_no_config
:error_no_config
echo PXE boot failed. No configuration found for MAC ${mac}
diff --git a/ironic/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py
index 79fcaecd3..e6c2c48ef 100644
--- a/ironic/drivers/modules/deploy_utils.py
+++ b/ironic/drivers/modules/deploy_utils.py
@@ -1046,24 +1046,22 @@ def is_secure_boot_requested(node):
def get_boot_mode_for_deploy(node):
"""Returns the boot mode that would be used for deploy.
- This method returns boot mode to used for deploy using following order:
+ This method returns boot mode to be used for deploy.
It returns 'uefi' if 'secure_boot' is set to 'true' in
'instance_info/capabilities' of node.
- It returns value of 'boot_mode' in 'properties/capabilities' of node.
- It returns boot mode specified in 'instance_info/deploy_boot_mode' of
- node.
+ Otherwise it returns value of 'boot_mode' in 'properties/capabilities'
+ of node if set. If that is not set, it returns boot mode in
+ 'instance_info/deploy_boot_mode' for the node.
It would return None if boot mode is present neither in 'capabilities' of
- node 'properties' nor in node's 'instance_info'.
+ node 'properties' nor in node's 'instance_info' (which could also be None).
:param node: an ironic node object.
:returns: 'bios', 'uefi' or None
"""
if is_secure_boot_requested(node):
- boot_mode = 'uefi'
- LOG.debug('Deploy boot mode is %(boot_mode)s for %(node)s.',
- {'boot_mode': boot_mode, 'node': node.uuid})
- return boot_mode
+ LOG.debug('Deploy boot mode is uefi for %s.', node.uuid)
+ return 'uefi'
boot_mode = driver_utils.get_node_capability(node, 'boot_mode')
if boot_mode is None:
@@ -1072,4 +1070,5 @@ def get_boot_mode_for_deploy(node):
LOG.debug('Deploy boot mode is %(boot_mode)s for %(node)s.',
{'boot_mode': boot_mode, 'node': node.uuid})
- return boot_mode
+
+ return boot_mode.lower() if boot_mode else boot_mode
diff --git a/ironic/drivers/modules/ilo/common.py b/ironic/drivers/modules/ilo/common.py
index 02085ea5d..1d1363d0d 100644
--- a/ironic/drivers/modules/ilo/common.py
+++ b/ironic/drivers/modules/ilo/common.py
@@ -348,7 +348,7 @@ def update_boot_mode(task):
if boot_mode is not None:
LOG.debug("Node %(uuid)s boot mode is being set to %(boot_mode)s",
{'uuid': node.uuid, 'boot_mode': boot_mode})
- set_boot_mode(node, boot_mode.lower())
+ set_boot_mode(node, boot_mode)
return
LOG.debug("Check pending boot mode for node %s.", node.uuid)
diff --git a/ironic/drivers/modules/ilo/deploy.py b/ironic/drivers/modules/ilo/deploy.py
index dbd9d7340..86dc0c729 100644
--- a/ironic/drivers/modules/ilo/deploy.py
+++ b/ironic/drivers/modules/ilo/deploy.py
@@ -335,8 +335,8 @@ def _prepare_node_for_deploy(task):
if change_boot_mode:
ilo_common.update_boot_mode(task)
else:
- # Need to update boot mode that would used during deploy, if one is not
- # provided.
+ # Need to update boot mode that will be used during deploy, if one is
+ # not provided.
# Since secure boot was disabled, we are in 'uefi' boot mode.
if deploy_utils.get_boot_mode_for_deploy(task.node) is None:
instance_info = task.node.instance_info
diff --git a/ironic/drivers/modules/ipmitool.py b/ironic/drivers/modules/ipmitool.py
index 55fc1fa2d..ddebd68ef 100644
--- a/ironic/drivers/modules/ipmitool.py
+++ b/ironic/drivers/modules/ipmitool.py
@@ -111,7 +111,11 @@ ipmitool_command_options = {
'dual_bridge': ['ipmitool', '-m', '0', '-b', '0', '-t', '0',
'-B', '0', '-T', '0', '-h']}
-# Note(TheJulia): This string is hardcoded in ipmitool's lanplus driver.
+# Note(TheJulia): This string is hardcoded in ipmitool's lanplus driver
+# and is substituted in return for the error code received from the IPMI
+# controller. As of 1.8.15, no internationalization support appears to
+# be in ipmitool which means the string should always be returned in this
+# form regardless of locale.
IPMITOOL_RETRYABLE_FAILURES = ['insufficient resources for session']
@@ -348,14 +352,14 @@ def _exec_ipmitool(driver_info, command):
args.append('-N')
args.append(str(CONF.ipmi.min_command_interval))
- end_time = (_time() + CONF.ipmi.retry_timeout)
+ end_time = (time.time() + CONF.ipmi.retry_timeout)
while True:
num_tries = num_tries - 1
# NOTE(deva): ensure that no communications are sent to a BMC more
# often than once every min_command_interval seconds.
time_till_next_poll = CONF.ipmi.min_command_interval - (
- _time() - LAST_CMD_TIME.get(driver_info['address'], 0))
+ time.time() - LAST_CMD_TIME.get(driver_info['address'], 0))
if time_till_next_poll > 0:
time.sleep(time_till_next_poll)
# Resetting the list that will be utilized so the password arguments
@@ -377,21 +381,21 @@ def _exec_ipmitool(driver_info, command):
with excutils.save_and_reraise_exception() as ctxt:
err_list = [x for x in IPMITOOL_RETRYABLE_FAILURES
if x in e.message]
- if ((_time() > end_time) or
+ if ((time.time() > end_time) or
(num_tries == 0) or
not err_list):
- LOG.error(_LE('IPMI Error attempting to execute '
+ LOG.error(_LE('IPMI Error while attempting '
'"%(cmd)s" for node %(node)s. '
'Error: %(error)s'),
{
- 'node': driver_info['uuid'],
- 'cmd': e.cmd,
- 'error': e
+ 'node': driver_info['uuid'],
+ 'cmd': e.cmd,
+ 'error': e
})
else:
ctxt.reraise = False
LOG.warning(_LW('IPMI Error encountered, retrying '
- '"%(cmd)s" for node %(node)s '
+ '"%(cmd)s" for node %(node)s. '
'Error: %(error)s'),
{
'node': driver_info['uuid'],
@@ -399,7 +403,7 @@ def _exec_ipmitool(driver_info, command):
'error': e
})
finally:
- LAST_CMD_TIME[driver_info['address']] = _time()
+ LAST_CMD_TIME[driver_info['address']] = time.time()
def _sleep_time(iter):
@@ -626,11 +630,6 @@ def send_raw(task, raw_bytes):
raise exception.IPMIFailure(cmd=cmd)
-def _time():
- """Wrapper for time.time() enabling simplified unit testing."""
- return time.time()
-
-
class IPMIPower(base.PowerInterface):
def __init__(self):
diff --git a/ironic/drivers/modules/iscsi_deploy.py b/ironic/drivers/modules/iscsi_deploy.py
index 57df4a7d6..ac7315d33 100644
--- a/ironic/drivers/modules/iscsi_deploy.py
+++ b/ironic/drivers/modules/iscsi_deploy.py
@@ -422,7 +422,7 @@ def _get_boot_mode(node):
"""
boot_mode = deploy_utils.get_boot_mode_for_deploy(node)
if boot_mode:
- return boot_mode.lower()
+ return boot_mode
return "bios"
@@ -447,13 +447,20 @@ def build_deploy_ramdisk_options(node):
node.instance_info = i_info
node.save()
+ # XXX(jroll) DIB relies on boot_option=local to decide whether or not to
+ # lay down a bootloader. Hack this for now; fix it for real in Liberty.
+ # See also bug #1441556.
+ boot_option = get_boot_option(node)
+ if node.driver_internal_info.get('is_whole_disk_image'):
+ boot_option = 'netboot'
+
deploy_options = {
'deployment_id': node['uuid'],
'deployment_key': deploy_key,
'iscsi_target_iqn': "iqn-%s" % node.uuid,
'ironic_api_url': ironic_api,
'disk': CONF.pxe.disk_devices,
- 'boot_option': get_boot_option(node),
+ 'boot_option': boot_option,
'boot_mode': _get_boot_mode(node),
# NOTE: The below entry is a temporary workaround for bug/1433812
'coreos.configdrive': 0,
diff --git a/ironic/tests/drivers/test_agent.py b/ironic/tests/drivers/test_agent.py
index b9ea638e0..bb9675f3f 100644
--- a/ironic/tests/drivers/test_agent.py
+++ b/ironic/tests/drivers/test_agent.py
@@ -164,6 +164,14 @@ class TestAgentDeploy(db_base.DbTestCase):
self.assertIn('driver_info.deploy_ramdisk', str(e))
self.assertIn('driver_info.deploy_kernel', str(e))
+ def test_validate_driver_info_manage_tftp_false(self):
+ self.config(manage_tftp=False, group='agent')
+ self.node.driver_info = {}
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ self.driver.validate(task)
+
def test_validate_instance_info_missing_params(self):
self.node.instance_info = {}
self.node.save()
@@ -199,6 +207,37 @@ class TestAgentDeploy(db_base.DbTestCase):
self.assertRaises(exception.InvalidParameterValue,
task.driver.deploy.validate, task)
+ @mock.patch.object(agent, '_cache_tftp_images')
+ @mock.patch.object(pxe_utils, 'create_pxe_config')
+ @mock.patch.object(agent, '_build_pxe_config_options')
+ @mock.patch.object(agent, '_get_tftp_image_info')
+ def test__prepare_pxe_boot(self, pxe_info_mock, options_mock,
+ create_mock, cache_mock):
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ agent._prepare_pxe_boot(task)
+ pxe_info_mock.assert_called_once_with(task.node)
+ options_mock.assert_called_once_with(task.node, mock.ANY)
+ create_mock.assert_called_once_with(
+ task, mock.ANY, CONF.agent.agent_pxe_config_template)
+ cache_mock.assert_called_once_with(task.context, task.node,
+ mock.ANY)
+
+ @mock.patch.object(agent, '_cache_tftp_images')
+ @mock.patch.object(pxe_utils, 'create_pxe_config')
+ @mock.patch.object(agent, '_build_pxe_config_options')
+ @mock.patch.object(agent, '_get_tftp_image_info')
+ def test__prepare_pxe_boot_manage_tftp_false(
+ self, pxe_info_mock, options_mock, create_mock, cache_mock):
+ self.config(manage_tftp=False, group='agent')
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ agent._prepare_pxe_boot(task)
+ self.assertFalse(pxe_info_mock.called)
+ self.assertFalse(options_mock.called)
+ self.assertFalse(create_mock.called)
+ self.assertFalse(cache_mock.called)
+
@mock.patch.object(dhcp_factory.DHCPFactory, 'update_dhcp')
@mock.patch('ironic.conductor.utils.node_set_boot_device')
@mock.patch('ironic.conductor.utils.node_power_action')
@@ -221,6 +260,36 @@ class TestAgentDeploy(db_base.DbTestCase):
power_mock.assert_called_once_with(task, states.POWER_OFF)
self.assertEqual(driver_return, states.DELETED)
+ @mock.patch.object(pxe_utils, 'clean_up_pxe_config')
+ @mock.patch.object(agent, 'AgentTFTPImageCache')
+ @mock.patch('ironic.common.utils.unlink_without_raise')
+ @mock.patch.object(agent, '_get_tftp_image_info')
+ def test__clean_up_pxe(self, info_mock, unlink_mock, cache_mock,
+ clean_mock):
+ info_mock.return_value = {'label': ['fake1', 'fake2']}
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ agent._clean_up_pxe(task)
+ info_mock.assert_called_once_with(task.node)
+ unlink_mock.assert_called_once_with('fake2')
+ clean_mock.assert_called_once_with(task)
+
+ @mock.patch.object(pxe_utils, 'clean_up_pxe_config')
+ @mock.patch.object(agent.AgentTFTPImageCache, 'clean_up')
+ @mock.patch('ironic.common.utils.unlink_without_raise')
+ @mock.patch.object(agent, '_get_tftp_image_info')
+ def test__clean_up_pxe_manage_tftp_false(
+ self, info_mock, unlink_mock, cache_mock, clean_mock):
+ self.config(manage_tftp=False, group='agent')
+ info_mock.return_value = {'label': ['fake1', 'fake2']}
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ agent._clean_up_pxe(task)
+ self.assertFalse(info_mock.called)
+ self.assertFalse(unlink_mock.called)
+ self.assertFalse(cache_mock.called)
+ self.assertFalse(clean_mock.called)
+
@mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.delete_cleaning_ports')
@mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.create_cleaning_ports')
@mock.patch('ironic.drivers.modules.agent._do_pxe_boot')
diff --git a/ironic/tests/drivers/test_ipmitool.py b/ironic/tests/drivers/test_ipmitool.py
index 0828f2c60..2e0097fba 100644
--- a/ironic/tests/drivers/test_ipmitool.py
+++ b/ironic/tests/drivers/test_ipmitool.py
@@ -891,21 +891,21 @@ class IPMIToolPrivateMethodTestCase(db_base.DbTestCase):
@mock.patch.object(ipmi, '_is_option_supported', autospec=True)
@mock.patch.object(utils, 'execute', autospec=True)
- def test__exec_ipmitool_exception_retry_failure_unhandable(self,
+ def test__exec_ipmitool_exception_non_retryable_failure(self,
mock_exec, mock_support, mock_sleep):
ipmi.LAST_CMD_TIME = {}
mock_support.return_value = False
- # Return a retryable error, then a error that cannot
- # be retryable thus resulting in a single retry
- # attempt by _exec_ipmitool that is successful.
+ # Return a retryable error, then an error that cannot
+ # be retried thus resulting in a single retry
+ # attempt by _exec_ipmitool.
mock_exec.side_effect = iter([
processutils.ProcessExecutionError(
stderr="insufficient resources for session"
),
processutils.ProcessExecutionError(
- "Unknown"
+ stderr="Unknown"
),
])
diff --git a/ironic/tests/drivers/test_iscsi_deploy.py b/ironic/tests/drivers/test_iscsi_deploy.py
index a0b1f40ba..4d3803542 100644
--- a/ironic/tests/drivers/test_iscsi_deploy.py
+++ b/ironic/tests/drivers/test_iscsi_deploy.py
@@ -487,6 +487,25 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
self._test_build_deploy_ramdisk_options(mock_alnum, fake_api_url,
expected_boot_option=expected)
+ @mock.patch.object(keystone, 'get_service_url', autospec=True)
+ @mock.patch.object(utils, 'random_alnum', autospec=True)
+ def test_build_deploy_ramdisk_options_whole_disk_image(self, mock_alnum,
+ mock_get_url):
+ """Tests a hack to boot_option for whole disk images.
+
+ This hack is in place to fix bug #1441556.
+ """
+ self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
+ dii = self.node.driver_internal_info
+ dii['is_whole_disk_image'] = True
+ self.node.driver_internal_info = dii
+ self.node.save()
+ expected = 'netboot'
+ fake_api_url = 'http://127.0.0.1:6385'
+ self.config(api_url=fake_api_url, group='conductor')
+ self._test_build_deploy_ramdisk_options(mock_alnum, fake_api_url,
+ expected_boot_option=expected)
+
def test_get_boot_option(self):
self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
result = iscsi_deploy.get_boot_option(self.node)
diff --git a/ironic/tests/test_images.py b/ironic/tests/test_images.py
index fb1da9dbc..b610d4b0a 100644
--- a/ironic/tests/test_images.py
+++ b/ironic/tests/test_images.py
@@ -424,12 +424,15 @@ class FsImageTestCase(base.TestCase):
self.assertEqual(expected_cfg, cfg)
def test__generate_grub_cfg(self):
-
kernel_params = ['key1=value1', 'key2']
options = {'linux': '/vmlinuz', 'initrd': '/initrd'}
- expected_cfg = ("menuentry \"install\" {\n"
- "linux /vmlinuz key1=value1 key2 --\n"
- "initrd /initrd\n"
+ expected_cfg = ("set default=0\n"
+ "set timeout=5\n"
+ "set hidden_timeout_quiet=false\n"
+ "\n"
+ "menuentry \"boot_partition\" {\n"
+ "linuxefi /vmlinuz key1=value1 key2 --\n"
+ "initrdefi /initrd\n"
"}")
cfg = images._generate_cfg(kernel_params,
diff --git a/ironic/tests/test_pxe_utils.py b/ironic/tests/test_pxe_utils.py
index f16cd3e1b..1792d840a 100644
--- a/ironic/tests/test_pxe_utils.py
+++ b/ironic/tests/test_pxe_utils.py
@@ -144,7 +144,40 @@ class TestPXEUtils(db_base.DbTestCase):
]
unlink_calls = [
mock.call('/tftpboot/pxelinux.cfg/01-00-11-22-33-44-55-66'),
- mock.call('/tftpboot/pxelinux.cfg/01-00-11-22-33-44-55-67')
+ mock.call('/tftpboot/pxelinux.cfg/01-00-11-22-33-44-55-67'),
+ ]
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ pxe_utils._link_mac_pxe_configs(task)
+
+ unlink_mock.assert_has_calls(unlink_calls)
+ create_link_mock.assert_has_calls(create_link_calls)
+
+ @mock.patch('ironic.common.utils.create_link_without_raise', autospec=True)
+ @mock.patch('ironic.common.utils.unlink_without_raise', autospec=True)
+ @mock.patch('ironic.drivers.utils.get_node_mac_addresses', autospec=True)
+ def test__write_mac_ipxe_configs(self, get_macs_mock, unlink_mock,
+ create_link_mock):
+ self.config(ipxe_enabled=True, group='pxe')
+ macs = [
+ '00:11:22:33:44:55:66',
+ '00:11:22:33:44:55:67'
+ ]
+ get_macs_mock.return_value = macs
+ create_link_calls = [
+ mock.call(u'/httpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
+ '/httpboot/pxelinux.cfg/00-11-22-33-44-55-66'),
+ mock.call(u'/httpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
+ '/httpboot/pxelinux.cfg/00112233445566'),
+ mock.call(u'/httpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
+ '/httpboot/pxelinux.cfg/00-11-22-33-44-55-67'),
+ mock.call(u'/httpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config',
+ '/httpboot/pxelinux.cfg/00112233445567'),
+ ]
+ unlink_calls = [
+ mock.call('/httpboot/pxelinux.cfg/00-11-22-33-44-55-66'),
+ mock.call('/httpboot/pxelinux.cfg/00112233445566'),
+ mock.call('/httpboot/pxelinux.cfg/00-11-22-33-44-55-67'),
+ mock.call('/httpboot/pxelinux.cfg/00112233445567'),
]
with task_manager.acquire(self.context, self.node.uuid) as task:
pxe_utils._link_mac_pxe_configs(task)
@@ -218,7 +251,7 @@ class TestPXEUtils(db_base.DbTestCase):
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',
+ self.assertEqual('/httpboot/pxelinux.cfg/00-11-22-33-aa-bb-cc',
pxe_utils._get_pxe_mac_path(mac))
def test__get_pxe_ip_address_path(self):