summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--devstack/lib/ironic38
-rw-r--r--devstack/plugin.sh3
-rwxr-xr-xdevstack/tools/ironic/scripts/cleanup-node.sh2
-rw-r--r--doc/source/deploy/install-guide.rst106
-rw-r--r--doc/source/deploy/user-guide.rst8
-rw-r--r--doc/source/drivers/ilo.rst103
-rw-r--r--doc/source/drivers/ipa.rst20
-rw-r--r--etc/ironic/ironic.conf.sample4
-rw-r--r--ironic/api/controllers/v1/chassis.py3
-rw-r--r--ironic/api/controllers/v1/node.py2
-rw-r--r--ironic/api/hooks.py9
-rw-r--r--ironic/common/driver_factory.py21
-rw-r--r--ironic/conductor/task_manager.py2
-rw-r--r--ironic/db/api.py2
-rw-r--r--ironic/drivers/modules/agent.py4
-rw-r--r--ironic/drivers/modules/agent_base_vendor.py52
-rw-r--r--ironic/drivers/modules/agent_client.py7
-rw-r--r--ironic/drivers/modules/deploy_utils.py24
-rw-r--r--ironic/drivers/modules/ilo/vendor.py3
-rw-r--r--ironic/drivers/modules/iscsi_deploy.py3
-rw-r--r--ironic/tests/unit/api/test_hooks.py20
-rw-r--r--ironic/tests/unit/common/test_driver_factory.py8
-rw-r--r--ironic/tests/unit/common/test_hash_ring.py2
-rw-r--r--ironic/tests/unit/conductor/test_manager.py43
-rw-r--r--ironic/tests/unit/drivers/modules/cimc/test_common.py3
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_power.py3
-rw-r--r--ironic/tests/unit/drivers/modules/irmc/test_power.py3
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent.py7
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent_base_vendor.py58
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent_client.py6
-rw-r--r--ironic/tests/unit/drivers/modules/test_deploy_utils.py26
-rw-r--r--ironic/tests/unit/drivers/modules/test_pxe.py2
-rw-r--r--releasenotes/notes/add-support-for-no-poweroff-on-failure-86e43b3e39043990.yaml12
-rw-r--r--releasenotes/notes/disk-label-fix-7580de913835ff44.yaml5
-rw-r--r--releasenotes/notes/duplicated-driver-entry-775370ad84736206.yaml5
-rw-r--r--releasenotes/notes/oob-power-off-7bbdf5947ed24bf8.yaml7
-rw-r--r--releasenotes/notes/opentack-baremetal-request-id-daa72b785eaaaa8d.yaml4
-rw-r--r--releasenotes/source/index.rst5
-rw-r--r--releasenotes/source/mitaka.rst6
-rw-r--r--releasenotes/source/newton.rst6
40 files changed, 529 insertions, 118 deletions
diff --git a/devstack/lib/ironic b/devstack/lib/ironic
index a578ad74b..d9ceeabf1 100644
--- a/devstack/lib/ironic
+++ b/devstack/lib/ironic
@@ -69,6 +69,7 @@ IRONIC_BAREMETAL_BASIC_OPS=$(trueorfalse False IRONIC_BAREMETAL_BASIC_OPS)
IRONIC_ENABLED_DRIVERS=${IRONIC_ENABLED_DRIVERS:-fake,pxe_ssh,pxe_ipmitool}
IRONIC_SSH_USERNAME=${IRONIC_SSH_USERNAME:-`whoami`}
IRONIC_SSH_TIMEOUT=${IRONIC_SSH_TIMEOUT:-15}
+IRONIC_SSH_ATTEMPTS=${IRONIC_SSH_ATTEMPTS:-5}
IRONIC_SSH_KEY_DIR=${IRONIC_SSH_KEY_DIR:-$IRONIC_DATA_DIR/ssh_keys}
IRONIC_SSH_KEY_FILENAME=${IRONIC_SSH_KEY_FILENAME:-ironic_key}
IRONIC_KEY_FILE=${IRONIC_KEY_FILE:-$IRONIC_SSH_KEY_DIR/$IRONIC_SSH_KEY_FILENAME}
@@ -89,7 +90,7 @@ IRONIC_VM_MACS_CSV_FILE=${IRONIC_VM_MACS_CSV_FILE:-$IRONIC_DATA_DIR/ironic_macs.
IRONIC_AUTHORIZED_KEYS_FILE=${IRONIC_AUTHORIZED_KEYS_FILE:-$HOME/.ssh/authorized_keys}
# By default, baremetal VMs will console output to file.
-IRONIC_VM_LOG_CONSOLE=${IRONIC_VM_LOG_CONSOLE:-True}
+IRONIC_VM_LOG_CONSOLE=$(trueorfalse True IRONIC_VM_LOG_CONSOLE)
IRONIC_VM_LOG_DIR=${IRONIC_VM_LOG_DIR:-$IRONIC_DATA_DIR/logs/}
IRONIC_VM_LOG_ROTATE=$(trueorfalse True IRONIC_VM_LOG_ROTATE)
@@ -689,6 +690,7 @@ function _clean_ncpu_failure {
function enroll_nodes {
local chassis_id
chassis_id=$(ironic chassis-create -d "ironic test chassis" | grep " uuid " | get_field 2)
+ die_if_not_set $LINENO chassis_id "Failed to create chassis"
if [[ "$IRONIC_IS_HARDWARE" == "False" ]]; then
local ironic_node_cpu=$IRONIC_VM_SPECS_CPU
@@ -843,13 +845,16 @@ function configure_ironic_ssh_keypair {
mkdir -p $HOME/.ssh
chmod 700 $HOME/.ssh
fi
- if [[ ! -e $IRONIC_KEY_FILE ]]; then
+ # recreate ssh if any part is missing
+ if [[ ! -e $IRONIC_KEY_FILE ]] || [[ ! -e $IRONIC_KEY_FILE.pub ]]; then
if [[ ! -d $(dirname $IRONIC_KEY_FILE) ]]; then
mkdir -p $(dirname $IRONIC_KEY_FILE)
fi
echo -e 'n\n' | ssh-keygen -q -t rsa -P '' -f $IRONIC_KEY_FILE
fi
cat $IRONIC_KEY_FILE.pub | tee -a $IRONIC_AUTHORIZED_KEYS_FILE
+ # remove duplicate keys.
+ sort -u -o $IRONIC_AUTHORIZED_KEYS_FILE $IRONIC_AUTHORIZED_KEYS_FILE
}
function ironic_ssh_check {
@@ -857,15 +862,26 @@ function ironic_ssh_check {
local floating_ip=$2
local port=$3
local default_instance_user=$4
- local active_timeout=$5
- if ! timeout $active_timeout sh -c "while ! ssh -p $port -o StrictHostKeyChecking=no -i $key_file ${default_instance_user}@$floating_ip echo success; do sleep 1; done"; then
+ local attempt=$5
+ local status=false
+ local ssh_options="-o BatchMode=yes -o ConnectTimeout=$IRONIC_SSH_TIMEOUT -o StrictHostKeyChecking=no"
+ while [[ $attempt -gt 0 ]]; do
+ ssh -p $port $ssh_options -i $key_file ${default_instance_user}@$floating_ip exit
+ if [[ "$?" == "0" ]]; then
+ status=true
+ break
+ fi
+ attempt=$((attempt - 1))
+ echo "SSH connection failed. $attempt attempts left."
+ done
+ if ! $status; then
die $LINENO "server didn't become ssh-able!"
fi
}
function configure_ironic_auxiliary {
configure_ironic_ssh_keypair
- ironic_ssh_check $IRONIC_KEY_FILE $IRONIC_VM_SSH_ADDRESS $IRONIC_VM_SSH_PORT $IRONIC_SSH_USERNAME $IRONIC_SSH_TIMEOUT
+ ironic_ssh_check $IRONIC_KEY_FILE $IRONIC_VM_SSH_ADDRESS $IRONIC_VM_SSH_PORT $IRONIC_SSH_USERNAME $IRONIC_SSH_ATTEMPTS
}
function build_ipa_coreos_ramdisk {
@@ -1008,9 +1024,11 @@ function cleanup_baremetal_basic_ops {
local vm_name
for vm_name in $(_ironic_bm_vm_names); do
- sudo su $STACK_USER -c "$IRONIC_SCRIPTS_DIR/cleanup-node.sh $vm_name $IRONIC_VM_NETWORK_BRIDGE"
+ sudo su $STACK_USER -c "$IRONIC_SCRIPTS_DIR/cleanup-node.sh $vm_name"
done
+ sudo ovs-vsctl --if-exists del-br $IRONIC_VM_NETWORK_BRIDGE
+
sudo rm -rf /etc/xinetd.d/tftp /etc/init/tftpd-hpa.override
restart_service xinetd
sudo iptables -D INPUT -d $HOST_IP -p udp --dport 69 -j ACCEPT || true
@@ -1023,6 +1041,14 @@ function cleanup_baremetal_basic_ops {
sudo rmmod nf_nat_tftp || true
}
+function ironic_configure_tempest {
+ local bm_flavor_id
+ bm_flavor_id=$(openstack flavor show baremetal -f value -c id)
+ die_if_not_set $LINENO bm_flavor_id "Failed to get id of baremetal flavor"
+ iniset $TEMPEST_CONFIG compute flavor_ref $bm_flavor_id
+ iniset $TEMPEST_CONFIG compute flavor_ref_alt $bm_flavor_id
+}
+
# Restore xtrace + pipefail
$_XTRACE_IRONIC
$_PIPEFAIL_IRONIC
diff --git a/devstack/plugin.sh b/devstack/plugin.sh
index de584e6d9..06f83c8a4 100644
--- a/devstack/plugin.sh
+++ b/devstack/plugin.sh
@@ -26,6 +26,9 @@ if is_service_enabled ir-api ir-cond; then
echo_summary "Starting Ironic"
start_ironic
prepare_baremetal_basic_ops
+ if is_service_enabled tempest; then
+ ironic_configure_tempest
+ fi
fi
if [[ "$1" == "unstack" ]]; then
diff --git a/devstack/tools/ironic/scripts/cleanup-node.sh b/devstack/tools/ironic/scripts/cleanup-node.sh
index 60cd39a3c..0fa9e25bb 100755
--- a/devstack/tools/ironic/scripts/cleanup-node.sh
+++ b/devstack/tools/ironic/scripts/cleanup-node.sh
@@ -11,7 +11,6 @@ LIBVIRT_STORAGE_POOL=${LIBVIRT_STORAGE_POOL:-"default"}
LIBVIRT_CONNECT_URI=${LIBVIRT_CONNECT_URI:-"qemu:///system"}
NAME=$1
-NETWORK_BRIDGE=$2
export VIRSH_DEFAULT_CONNECT_URI=$LIBVIRT_CONNECT_URI
@@ -30,6 +29,5 @@ if virsh pool-list | grep -q $LIBVIRT_STORAGE_POOL ; then
fi
sudo brctl delif br-$NAME ovs-$NAME || true
-sudo ovs-vsctl del-port $NETWORK_BRIDGE ovs-$NAME || true
sudo ip link set dev br-$NAME down || true
sudo brctl delbr br-$NAME || true
diff --git a/doc/source/deploy/install-guide.rst b/doc/source/deploy/install-guide.rst
index a05fe332b..b85262585 100644
--- a/doc/source/deploy/install-guide.rst
+++ b/doc/source/deploy/install-guide.rst
@@ -828,10 +828,10 @@ node(s) where ``ironic-conductor`` is running.
#. Install tftp server and the syslinux package with the PXE boot images::
Ubuntu: (Up to and including 14.04)
- sudo apt-get install tftpd-hpa syslinux-common syslinux
+ sudo apt-get install xinetd tftpd-hpa syslinux-common syslinux
Ubuntu: (14.10 and after)
- sudo apt-get install tftpd-hpa syslinux-common pxelinux
+ sudo apt-get install xinetd tftpd-hpa syslinux-common pxelinux
Fedora 21/RHEL7/CentOS7:
sudo yum install tftp-server syslinux-tftpboot
@@ -839,7 +839,31 @@ node(s) where ``ironic-conductor`` is running.
Fedora 22 or higher:
sudo dnf install tftp-server syslinux-tftpboot
-#. Setup tftp server to serve ``/tftpboot``.
+#. Using xinetd to provide a tftp server setup to serve ``/tftpboot``.
+ Create or edit ``/etc/xinetd.d/tftp`` as below::
+
+ service tftp
+ {
+ protocol = udp
+ port = 69
+ socket_type = dgram
+ wait = yes
+ user = root
+ server = /usr/sbin/in.tftpd
+ server_args = -v -v -v -v -v --map-file /tftpboot/map-file /tftpboot
+ disable = no
+ # This is a workaround for Fedora, where TFTP will listen only on
+ # IPv6 endpoint, if IPv4 flag is not used.
+ flags = IPv4
+ }
+
+ and restart xinetd service::
+
+ Ubuntu:
+ sudo service xinetd restart
+
+ Fedora:
+ sudo systemctl restart xinetd
#. Copy the PXE image to ``/tftpboot``. The PXE image might be found at [1]_::
@@ -876,11 +900,6 @@ node(s) where ``ironic-conductor`` is running.
echo 're ^(^/) /tftpboot/\1' >> /tftpboot/map-file
echo 're ^([^/]) /tftpboot/\1' >> /tftpboot/map-file
-#. Enable tftp map file, modify ``/etc/xinetd.d/tftp`` as below and restart xinetd
- service::
-
- server_args = -v -v -v -v -v --map-file /tftpboot/map-file /tftpboot
-
.. [1] On **Fedora/RHEL** the ``syslinux-tftpboot`` package already install
the library modules and PXE image at ``/tftpboot``. If the TFTP server
is configured to listen to a different directory you should copy the
@@ -1386,11 +1405,6 @@ bare metal node to be deployed with when Ironic is responsible for
partitioning the disk; therefore choosing the disk label does not apply
when the image being deployed is a ``whole disk image``.
-.. note::
- At present the agent_* drivers do not support deploying partition
- images, therefore forcing the disk label for those drivers is not
- possible.
-
There are some edge cases where someone may want to choose a specific
disk label for the images being deployed, including but not limited to:
@@ -2019,6 +2033,72 @@ of the following ways:
<http://docs.openstack.org/developer/swift/deployment_guide.html>`_
(recommended only for testing purpose by swift).
+.. _EnableHTTPSinGlance:
+
+Enabling HTTPS in Image service
+===============================
+
+Ironic drivers usually use Image service during node provisioning. By default,
+image service does not use HTTPS, but it is required for secure communication.
+It can be enabled by making the following changes to ``/etc/glance/glance-api.conf``:
+
+#. `Configuring SSL support
+ <http://docs.openstack.org/developer/glance/configuring.html#configuring-ssl-support>`_
+
+#. Restart the glance-api service::
+
+ Fedora/RHEL7/CentOS7:
+ sudo systemctl restart openstack-glance-api
+
+ Debian/Ubuntu:
+ sudo service glance-api restart
+
+See the `Glance <http://docs.openstack.org/developer/glance/>`_ documentation,
+for more details on the Image service.
+
+Enabling HTTPS communication between Image service and Object storage
+=====================================================================
+
+This section describes the steps needed to enable secure HTTPS communication between
+Image service and Object storage when Object storage is used as the Backend.
+
+To enable secure HTTPS communication between Image service and Object storage follow these steps:
+
+#. :ref:`EnableHTTPSinSwift`.
+
+#. `Configure Swift Storage Backend
+ <http://docs.openstack.org/developer/glance/configuring.html#configuring-the-swift-storage-backend>`_
+
+#. :ref:`EnableHTTPSinGlance`
+
+Enabling HTTPS communication between Image service and Bare Metal service
+=========================================================================
+
+This section describes the steps needed to enable secure HTTPS communication between
+Image service and Bare Metal service.
+
+To enable secure HTTPS communication between Bare Metal service and Image service follow these steps:
+
+#. Edit ``/etc/ironic/ironic.conf``::
+
+ [glance]
+ ...
+ glance_cafile=/path/to/certfile
+ glance_protocol=https
+ glance_api_insecure=False
+
+ .. note::
+ 'glance_cafile' is a optional path to a CA certificate bundle to be used to validate the SSL certificate
+ served by Image service.
+
+#. Restart ironic-conductor service::
+
+ Fedora/RHEL7/CentOS7:
+ sudo systemctl restart openstack-ironic-conductor
+
+ Debian/Ubuntu:
+ sudo service ironic-conductor restart
+
Using Bare Metal service as a standalone service
================================================
diff --git a/doc/source/deploy/user-guide.rst b/doc/source/deploy/user-guide.rst
index b586379ea..9c0f503ba 100644
--- a/doc/source/deploy/user-guide.rst
+++ b/doc/source/deploy/user-guide.rst
@@ -173,8 +173,12 @@ through the steps involved during the provisioning of a bare metal instance.
These pre-requisites must be met before the deployment process:
-- Dependent packages to be configured on the compute node like tftp-server,
- ipmi, syslinux etc for bare metal provisioning.
+- Dependent packages to be configured on the Bare Metal service node(s)
+ where ironic-conductor is running like tftp-server, ipmi, syslinux etc for
+ bare metal provisioning.
+- Nova must be configured to make use of the bare metal service endpoint
+ and compute driver should be configured to use ironic driver on the Nova
+ compute node(s).
- Flavors to be created for the available hardware. Nova must know the flavor
to boot from.
- Images to be made available in Glance. Listed below are some image types
diff --git a/doc/source/drivers/ilo.rst b/doc/source/drivers/ilo.rst
index 541d27af4..348b4119f 100644
--- a/doc/source/drivers/ilo.rst
+++ b/doc/source/drivers/ilo.rst
@@ -28,7 +28,8 @@ it from data channel which is used for deployment.
``iscsi_ilo`` and ``agent_ilo`` drivers use deployment ramdisk
built from ``diskimage-builder``. The ``iscsi_ilo`` driver deploys from
ironic conductor and supports both net-boot and local-boot of instance.
-``agent_ilo`` deploys from bare metal node and always does local-boot.
+``agent_ilo`` deploys from bare metal node and supports both net-boot
+and local-boot of instance.
``pxe_ilo`` driver uses PXE/iSCSI for deployment (just like normal PXE driver)
and deploys from ironic conductor. Additionally it supports automatic setting of
@@ -206,12 +207,12 @@ Target Users
security enhanced PXE-less deployment mechanism.
The PXE driver passes management information in clear-text to the
- bare metal node. However, if swift proxy server has an HTTPS
- endpoint (See :ref:`EnableHTTPSinSwift` for more information), the
- ``iscsi_ilo`` driver provides enhanced security by passing
- management information to and from swift endpoint over HTTPS. The
- management information, deploy ramdisk and boot images for the instance will
- be retrieved over encrypted management network via iLO virtual media.
+ bare metal node. However, if swift proxy server and glance have HTTPS
+ endpoints (See :ref:`EnableHTTPSinSwift`, :ref:`EnableHTTPSinGlance` for more
+ information), the ``iscsi_ilo`` driver provides enhanced security by
+ exchanging management information with swift and glance endpoints over HTTPS.
+ The management information, deploy ramdisk and boot images for the instance
+ will be retrieved over encrypted management network via iLO virtual media.
Tested Platforms
~~~~~~~~~~~~~~~~
@@ -239,11 +240,11 @@ Features
* UEFI Boot Support
* UEFI Secure Boot Support
* Passing management information via secure, encrypted management network
- (virtual media) if swift proxy server has an HTTPS endpoint. See
- :ref:`EnableHTTPSinSwift` for more info. User image provisioning is done
- using iSCSI over data network, so this driver has the benefit
- of security enhancement with the same performance. It segregates management
- info from data channel.
+ (virtual media) if swift proxy server and glance have HTTPS endpoints. See
+ :ref:`EnableHTTPSinSwift`, :ref:`EnableHTTPSinGlance` for more info. User
+ image provisioning is done using iSCSI over data network, so this driver has
+ the benefit of security enhancement with the same performance. It segregates
+ management info from data channel.
* Support for out-of-band cleaning operations.
* Remote Console
* HW Sensors
@@ -267,8 +268,10 @@ Requirements
Deploy Process
~~~~~~~~~~~~~~
-Refer to `Netboot with glance and swift`_ for the deploy process of partition image
-and `Localboot with glance and swift`_ for the deploy process of whole disk image.
+Refer to `Netboot with glance and swift`_ and
+`Localboot with glance and swift for partition images`_ for the deploy process
+of partition image and `Localboot with glance and swift`_ for the deploy
+process of whole disk image.
Configuring and Enabling the driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -348,12 +351,12 @@ Target Users
want to have a security enhanced PXE-less deployment mechanism.
The PXE based agent drivers pass management information in clear-text to
- the bare metal node. However, if swift proxy server has an HTTPS
- endpoint (See :ref:`EnableHTTPSinSwift` for more information),
- the ``agent_ilo`` driver provides enhanced security by passing authtoken
- and management information to and from swift endpoint over HTTPS. The
- management information and deploy ramdisk will be retrieved over encrypted
- management network via iLO.
+ the bare metal node. However, if swift proxy server and glance have HTTPS
+ endpoints (See :ref:`EnableHTTPSinSwift`, :ref:`EnableHTTPSinGlance` for more
+ information), the ``agent_ilo`` driver provides enhanced security by
+ exchanging authtoken and management information with swift and glance
+ endpoints over HTTPS. The management information and deploy ramdisk will be
+ retrieved over encrypted management network via iLO.
Tested Platforms
~~~~~~~~~~~~~~~~
@@ -376,7 +379,9 @@ Features
* Remote Console
* HW Sensors
* IPA runs on the bare metal node and pulls the image directly from swift.
-* IPA deployed instances always boots from local disk.
+* Supports booting the instance from virtual media (netboot) as well as booting
+ locally from disk. By default, the instance will always boot from virtual
+ media for partition images.
* Segregates management info from data channel.
* UEFI Boot Support
* UEFI Secure Boot Support
@@ -400,7 +405,10 @@ Requirements
Deploy Process
~~~~~~~~~~~~~~
-Refer to `Localboot with glance and swift`_ for details.
+Refer to `Netboot with glance and swift`_ and
+`Localboot with glance and swift for partition images`_ for the deploy process
+of partition image and `Localboot with glance and swift`_ for the deploy
+process of whole disk image.
Configuring and Enabling the driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -943,11 +951,10 @@ intermediate images on conductor as described in
Deploy Process
~~~~~~~~~~~~~~
-``iscsi_ilo`` supports both netboot and localboot, while ``agent_ilo`` supports
-only localboot. Refer to `Netboot in standalone ironic`_ and
-`Localboot in standalone ironic`_ for details of deploy process
-for netboot and localboot respectively. For ``pxe_ilo``, the deploy process
-is same as native ``pxe_ipmitool`` driver.
+``iscsi_ilo`` and ``agent_ilo`` supports both netboot and localboot. Refer
+to `Netboot in standalone ironic`_ and `Localboot in standalone ironic`_
+for details of deploy process for netboot and localboot respectively.
+For ``pxe_ilo``, the deploy process is same as native ``pxe_ipmitool`` driver.
Deploy Process
==============
@@ -996,6 +1003,48 @@ Netboot with glance and swift
Baremetal -> Baremetal [label = "Instance kernel finds root partition and continues booting from disk"];
}
+Localboot with glance and swift for partition images
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. seqdiag::
+ :scale: 80
+
+ diagram {
+ Glance; Conductor; Baremetal; Swift; IPA; iLO;
+ activation = none;
+ span_height = 1;
+ edge_length = 250;
+ default_note_color = white;
+ default_fontsize = 14;
+
+ Conductor -> iLO [label = "Powers off the node"];
+ Conductor -> Glance [label = "Get the metadata for deploy ISO"];
+ Glance -> Conductor [label = "Returns the metadata for deploy ISO"];
+ Conductor -> Conductor [label = "Generates swift tempURL for deploy ISO"];
+ Conductor -> Conductor [label = "Creates the FAT32 image containing ironic API URL and driver name"];
+ Conductor -> Swift [label = "Uploads the FAT32 image"];
+ Conductor -> Conductor [label = "Generates swift tempURL for FAT32 image"];
+ Conductor -> iLO [label = "Attaches the FAT32 image swift tempURL as virtual media floppy"];
+ Conductor -> iLO [label = "Attaches the deploy ISO swift tempURL as virtual media CDROM"];
+ Conductor -> iLO [label = "Sets one time boot to CDROM"];
+ Conductor -> iLO [label = "Reboot the node"];
+ iLO -> Swift [label = "Downloads deploy ISO"];
+ Baremetal -> iLO [label = "Boots deploy kernel/ramdisk from iLO virtual media CDROM"];
+ IPA -> Conductor [label = "Lookup node"];
+ Conductor -> IPA [label = "Provides node UUID"];
+ IPA -> Conductor [label = "Heartbeat"];
+ Conductor -> IPA [label = "Sends the user image HTTP(S) URL"];
+ IPA -> Swift [label = "Retrieves the user image on bare metal"];
+ IPA -> IPA [label = "Writes user image to root partition"];
+ IPA -> IPA [label = "Installs boot loader"];
+ IPA -> Conductor [label = "Heartbeat"];
+ Conductor -> Baremetal [label = "Sets boot device to disk"];
+ Conductor -> IPA [label = "Power off the node"];
+ Conductor -> iLO [label = "Power on the node"];
+ Baremetal -> Baremetal [label = "Boot user image from disk"];
+ }
+
+
Localboot with glance and swift
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/doc/source/drivers/ipa.rst b/doc/source/drivers/ipa.rst
index aa5ecf7a0..bebbafba5 100644
--- a/doc/source/drivers/ipa.rst
+++ b/doc/source/drivers/ipa.rst
@@ -104,3 +104,23 @@ Steps to enable proxies
``image_no_proxy`` to driver_info properties in each node that will use the
proxy. Please refer to ``ironic driver-properties`` output of the
``agent_*`` driver you're using for descriptions of these properties.
+
+Advanced configuration
+======================
+
+Out-of-band Vs. in-band power off on deploy
+-------------------------------------------
+
+After deploying an image onto the node's hard disk Ironic will reboot
+the machine into the new image. By default this power action happens
+``in-band``, meaning that the ironic-conductor will instruct the IPA
+ramdisk to power itself off.
+
+Some hardware may have a problem with the default approach and
+would require Ironic to talk directly to the management controller
+to switch the power off and on again. In order to tell Ironic to do
+that you have to update the node's ``driver_info`` field and set the
+``deploy_forces_oob_reboot`` parameter with the value of **True**. For
+example, the below command sets this configuration in a specific node::
+
+ ironic node-update <UUID or name> add driver_info/deploy_forces_oob_reboot=True
diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample
index cc651034d..7a53e1af5 100644
--- a/etc/ironic/ironic.conf.sample
+++ b/etc/ironic/ironic.conf.sample
@@ -919,6 +919,10 @@
# Deprecated group/name - [agent]/agent_erase_devices_iterations
#erase_devices_iterations=1
+# Whether to power off a node after deploy failure. Defaults
+# to True. (boolean value)
+#power_off_after_deploy_failure=true
+
[dhcp]
diff --git a/ironic/api/controllers/v1/chassis.py b/ironic/api/controllers/v1/chassis.py
index 819278fae..df5f9df78 100644
--- a/ironic/api/controllers/v1/chassis.py
+++ b/ironic/api/controllers/v1/chassis.py
@@ -143,6 +143,9 @@ class ChassisCollection(collection.Collection):
@classmethod
def sample(cls):
+ # FIXME(jroll) hack for docs build, bug #1560508
+ if not hasattr(objects, 'Chassis'):
+ objects.register_all()
sample = cls()
sample.chassis = [Chassis.sample(expand=False)]
return sample
diff --git a/ironic/api/controllers/v1/node.py b/ironic/api/controllers/v1/node.py
index 30d52e868..381eb3ce6 100644
--- a/ironic/api/controllers/v1/node.py
+++ b/ironic/api/controllers/v1/node.py
@@ -1051,7 +1051,7 @@ class NodesController(rest.RestController):
If it does, is necessary to prevent updating it because the new driver
will not be able to stop a console started by the previous one.
- :param rpc_node: RPC Node object to be veryfied.
+ :param rpc_node: RPC Node object to be verified.
:param node_ident: the UUID or logical name of a node.
:raises: wsme.exc.ClientSideError
"""
diff --git a/ironic/api/hooks.py b/ironic/api/hooks.py
index 3c406e554..764b0a434 100644
--- a/ironic/api/hooks.py
+++ b/ironic/api/hooks.py
@@ -89,6 +89,15 @@ class ContextHook(hooks.PecanHook):
show_password=show_password,
**creds)
+ def after(self, state):
+ if state.request.context == {}:
+ # An incorrect url path will not create RequestContext
+ return
+ # NOTE(lintan): RequestContext will generate a request_id if no one
+ # passing outside, so it always contain a request_id.
+ request_id = state.request.context.request_id
+ state.response.headers['Openstack-Request-Id'] = request_id
+
class RPCHook(hooks.PecanHook):
"""Attach the rpcapi object to the request so controllers can get to it."""
diff --git a/ironic/common/driver_factory.py b/ironic/common/driver_factory.py
index 191ced70a..6e834d4f7 100644
--- a/ironic/common/driver_factory.py
+++ b/ironic/common/driver_factory.py
@@ -23,6 +23,7 @@ from stevedore import dispatch
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common.i18n import _LI
+from ironic.common.i18n import _LW
from ironic.drivers import base as driver_base
@@ -136,6 +137,18 @@ class DriverFactory(object):
if cls._extension_manager:
return
+ # Check for duplicated driver entries and warn the operator
+ # about them
+ counter = collections.Counter(CONF.enabled_drivers).items()
+ duplicated_drivers = list(dup for (dup, i) in counter if i > 1)
+ if duplicated_drivers:
+ LOG.warning(_LW('The driver(s) "%s" is/are duplicated in the '
+ 'list of enabled_drivers. Please check your '
+ 'configuration file.'),
+ ', '.join(duplicated_drivers))
+
+ enabled_drivers = set(CONF.enabled_drivers)
+
# NOTE(deva): Drivers raise "DriverLoadError" if they are unable to be
# loaded, eg. due to missing external dependencies.
# We capture that exception, and, only if it is for an
@@ -147,13 +160,13 @@ class DriverFactory(object):
def _catch_driver_not_found(mgr, ep, exc):
# NOTE(deva): stevedore loads plugins *before* evaluating
# _check_func, so we need to check here, too.
- if ep.name in CONF.enabled_drivers:
+ if ep.name in enabled_drivers:
if not isinstance(exc, exception.DriverLoadError):
raise exception.DriverLoadError(driver=ep.name, reason=exc)
raise exc
def _check_func(ext):
- return ext.name in CONF.enabled_drivers
+ return ext.name in enabled_drivers
cls._extension_manager = (
dispatch.NameDispatchExtensionManager(
@@ -164,10 +177,10 @@ class DriverFactory(object):
# NOTE(deva): if we were unable to load any configured driver, perhaps
# because it is not present on the system, raise an error.
- if (sorted(CONF.enabled_drivers) !=
+ if (sorted(enabled_drivers) !=
sorted(cls._extension_manager.names())):
found = cls._extension_manager.names()
- names = [n for n in CONF.enabled_drivers if n not in found]
+ names = [n for n in enabled_drivers if n not in found]
# just in case more than one could not be found ...
names = ', '.join(names)
raise exception.DriverNotFound(driver_name=names)
diff --git a/ironic/conductor/task_manager.py b/ironic/conductor/task_manager.py
index 3aed594ae..caca8a17b 100644
--- a/ironic/conductor/task_manager.py
+++ b/ironic/conductor/task_manager.py
@@ -449,7 +449,7 @@ class TaskManager(object):
# also makes it easier to test.
fut.add_done_callback(self._thread_release_resources)
# Don't unlock! The unlock will occur when the
- # thread finshes.
+ # thread finishes.
return
except Exception as e:
with excutils.save_and_reraise_exception():
diff --git a/ironic/db/api.py b/ironic/db/api.py
index ca2715345..f8ab5a686 100644
--- a/ironic/db/api.py
+++ b/ironic/db/api.py
@@ -369,7 +369,7 @@ class Connection(object):
def create_portgroup(self, values):
"""Create a new portgroup.
- :param values: Dict of values with the the following keys:
+ :param values: Dict of values with the following keys:
'id'
'uuid'
'name'
diff --git a/ironic/drivers/modules/agent.py b/ironic/drivers/modules/agent.py
index 65aba00c5..0c9814bd8 100644
--- a/ironic/drivers/modules/agent.py
+++ b/ironic/drivers/modules/agent.py
@@ -486,6 +486,10 @@ class AgentVendorInterface(agent_base_vendor.BaseAgentVendor):
else:
image_info['deploy_boot_mode'] = 'bios'
image_info['boot_option'] = boot_option
+ disk_label = deploy_utils.get_disk_label(node)
+ if disk_label is not None:
+ image_info['disk_label'] = disk_label
+ image_info['node_uuid'] = node.uuid
# Tell the client to download and write the image with the given args
self._client.prepare_image(node, image_info)
diff --git a/ironic/drivers/modules/agent_base_vendor.py b/ironic/drivers/modules/agent_base_vendor.py
index bf10d1a35..9df0cd392 100644
--- a/ironic/drivers/modules/agent_base_vendor.py
+++ b/ironic/drivers/modules/agent_base_vendor.py
@@ -22,6 +22,7 @@ import time
from oslo_config import cfg
from oslo_log import log
from oslo_utils import excutils
+from oslo_utils import strutils
from oslo_utils import timeutils
import retrying
@@ -82,6 +83,13 @@ LOG = log.getLogger(__name__)
# completing 'delete_configuration' of raid interface.
POST_CLEAN_STEP_HOOKS = {}
+VENDOR_PROPERTIES = {
+ 'deploy_forces_oob_reboot': _(
+ 'Whether Ironic should force a reboot of the Node via the out-of-band '
+ 'channel after deployment is complete. Provides compatiblity with '
+ 'older deploy ramdisks. Defaults to False. Optional.')
+}
+
def _get_client():
client = agent_client.AgentClient()
@@ -178,9 +186,7 @@ class BaseAgentVendor(base.VendorInterface):
:returns: dictionary of <property name>:<property description> entries.
"""
- # NOTE(jroll) all properties are set by the driver,
- # not by the operator.
- return {}
+ return VENDOR_PROPERTIES
def validate(self, task, method, **kwargs):
"""Validate the driver-specific Node deployment info.
@@ -688,18 +694,38 @@ class BaseAgentVendor(base.VendorInterface):
return task.driver.power.get_power_state(task)
node = task.node
+ # Whether ironic should power off the node via out-of-band or
+ # in-band methods
+ oob_power_off = strutils.bool_from_string(
+ node.driver_info.get('deploy_forces_oob_reboot', False))
try:
- try:
- self._client.power_off(node)
- _wait_until_powered_off(task)
- except Exception as e:
- LOG.warning(
- _LW('Failed to soft power off node %(node_uuid)s '
- 'in at least %(timeout)d seconds. Error: %(error)s'),
- {'node_uuid': node.uuid,
- 'timeout': (wait * (attempts - 1)) / 1000,
- 'error': e})
+ if not oob_power_off:
+ try:
+ self._client.power_off(node)
+ _wait_until_powered_off(task)
+ except Exception as e:
+ LOG.warning(
+ _LW('Failed to soft power off node %(node_uuid)s '
+ 'in at least %(timeout)d seconds. '
+ 'Error: %(error)s'),
+ {'node_uuid': node.uuid,
+ 'timeout': (wait * (attempts - 1)) / 1000,
+ 'error': e})
+ else:
+ # Flush the file system prior to hard rebooting the node
+ result = self._client.sync(node)
+ error = result.get('faultstring')
+ if error:
+ if 'Unknown command' in error:
+ error = _('The version of the IPA ramdisk used in '
+ 'the deployment do not support the '
+ 'command "sync"')
+ LOG.warning(_LW(
+ 'Failed to flush the file system prior to hard '
+ 'rebooting the node %(node)s. Error: %(error)s'),
+ {'node': node.uuid, 'error': error})
+
manager_utils.node_power_action(task, states.REBOOT)
except Exception as e:
msg = (_('Error rebooting node %(node)s after deploy. '
diff --git a/ironic/drivers/modules/agent_client.py b/ironic/drivers/modules/agent_client.py
index 86c985e59..d27c8dbd1 100644
--- a/ironic/drivers/modules/agent_client.py
+++ b/ironic/drivers/modules/agent_client.py
@@ -174,3 +174,10 @@ class AgentClient(object):
return self._command(node=node,
method='standby.power_off',
params={})
+
+ def sync(self, node):
+ """Flush file system buffers forcing changed blocks to disk."""
+ return self._command(node=node,
+ method='standby.sync',
+ params={},
+ wait=True)
diff --git a/ironic/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py
index 7b615a9b8..7baa13cb6 100644
--- a/ironic/drivers/modules/deploy_utils.py
+++ b/ironic/drivers/modules/deploy_utils.py
@@ -71,6 +71,10 @@ deploy_opts = [
deprecated_group='agent',
default=1,
help=_('Number of iterations to be run for erasing devices.')),
+ cfg.BoolOpt('power_off_after_deploy_failure',
+ default=True,
+ help=_('Whether to power off a node after deploy failure. '
+ 'Defaults to True.')),
]
CONF = cfg.CONF
CONF.register_opts(deploy_opts, group='deploy')
@@ -492,15 +496,15 @@ def set_failed_state(task, msg):
% {'node': node.uuid, 'state': node.provision_state})
LOG.exception(msg2)
- try:
- manager_utils.node_power_action(task, states.POWER_OFF)
- except Exception:
- msg2 = (_LE('Node %s failed to power off while handling deploy '
- 'failure. This may be a serious condition. Node '
- 'should be removed from Ironic or put in maintenance '
- 'mode until the problem is resolved.') % node.uuid)
- LOG.exception(msg2)
-
+ if CONF.deploy.power_off_after_deploy_failure:
+ try:
+ manager_utils.node_power_action(task, states.POWER_OFF)
+ except Exception:
+ msg2 = (_LE('Node %s failed to power off while handling deploy '
+ 'failure. This may be a serious condition. Node '
+ 'should be removed from Ironic or put in maintenance '
+ 'mode until the problem is resolved.') % node.uuid)
+ LOG.exception(msg2)
# NOTE(deva): node_power_action() erases node.last_error
# so we need to set it here.
node.last_error = msg
@@ -623,7 +627,7 @@ def agent_execute_clean_step(task, step):
def agent_add_clean_params(task):
- """Add required config parameters to node's driver_interal_info.
+ """Add required config parameters to node's driver_internal_info.
Adds the required conf options to node's driver_internal_info.
It is Required to pass the information to IPA.
diff --git a/ironic/drivers/modules/ilo/vendor.py b/ironic/drivers/modules/ilo/vendor.py
index d991a145d..981afa599 100644
--- a/ironic/drivers/modules/ilo/vendor.py
+++ b/ironic/drivers/modules/ilo/vendor.py
@@ -57,9 +57,6 @@ class IloVirtualMediaAgentVendorInterface(agent.AgentVendorInterface):
class VendorPassthru(iscsi_deploy.VendorPassthru):
"""Vendor-specific interfaces for iLO deploy drivers."""
- def get_properties(self):
- return {}
-
def validate(self, task, method, **kwargs):
"""Validate vendor-specific actions.
diff --git a/ironic/drivers/modules/iscsi_deploy.py b/ironic/drivers/modules/iscsi_deploy.py
index 0206b738d..bc111643e 100644
--- a/ironic/drivers/modules/iscsi_deploy.py
+++ b/ironic/drivers/modules/iscsi_deploy.py
@@ -701,9 +701,6 @@ class ISCSIDeploy(base.DeployInterface):
class VendorPassthru(agent_base_vendor.BaseAgentVendor):
"""Interface to mix IPMI and PXE vendor-specific interfaces."""
- def get_properties(self):
- return {}
-
def validate(self, task, method, **kwargs):
"""Validates the inputs for a vendor passthru.
diff --git a/ironic/tests/unit/api/test_hooks.py b/ironic/tests/unit/api/test_hooks.py
index c4372a3c4..e29ea298f 100644
--- a/ironic/tests/unit/api/test_hooks.py
+++ b/ironic/tests/unit/api/test_hooks.py
@@ -285,6 +285,26 @@ class TestContextHook(base.BaseApiTest):
is_admin=False,
roles=headers['X-Roles'].split(','))
+ @mock.patch.object(context, 'RequestContext')
+ def test_context_hook_after_add_request_id(self, mock_ctx):
+ headers = fake_headers(admin=True)
+ reqstate = FakeRequestState(headers=headers)
+ reqstate.set_context()
+ reqstate.request.context.request_id = 'fake-id'
+ context_hook = hooks.ContextHook(None)
+ context_hook.after(reqstate)
+ self.assertIn('Openstack-Request-Id',
+ reqstate.response.headers)
+ self.assertEqual(
+ 'fake-id',
+ reqstate.response.headers['Openstack-Request-Id'])
+
+ def test_context_hook_after_miss_context(self):
+ response = self.get_json('/bad/path',
+ expect_errors=True)
+ self.assertNotIn('Openstack-Request-Id',
+ response.headers)
+
class TestTrustedCallHook(base.BaseApiTest):
def test_trusted_call_hook_not_admin(self):
diff --git a/ironic/tests/unit/common/test_driver_factory.py b/ironic/tests/unit/common/test_driver_factory.py
index fae28f46a..e42fe1541 100644
--- a/ironic/tests/unit/common/test_driver_factory.py
+++ b/ironic/tests/unit/common/test_driver_factory.py
@@ -64,6 +64,14 @@ class DriverLoadTestCase(base.TestCase):
driver_factory.DriverFactory._init_extension_manager()
self.assertEqual(2, mock_em.call_count)
+ @mock.patch.object(driver_factory.LOG, 'warning', autospec=True)
+ def test_driver_duplicated_entry(self, mock_log):
+ self.config(enabled_drivers=['fake', 'fake'])
+ driver_factory.DriverFactory._init_extension_manager()
+ self.assertEqual(
+ ['fake'], driver_factory.DriverFactory._extension_manager.names())
+ self.assertTrue(mock_log.called)
+
class GetDriverTestCase(base.TestCase):
def setUp(self):
diff --git a/ironic/tests/unit/common/test_hash_ring.py b/ironic/tests/unit/common/test_hash_ring.py
index ae38250b8..1966b9e8e 100644
--- a/ironic/tests/unit/common/test_hash_ring.py
+++ b/ironic/tests/unit/common/test_hash_ring.py
@@ -259,5 +259,5 @@ class HashRingManagerTestCase(db_base.DbTestCase):
self.ring_manager.__getitem__,
'driver1')
self.register_conductors()
- self.ring_manager.updated_at = time.time() - 30
+ self.ring_manager.updated_at = time.time() - 31
self.ring_manager.__getitem__('driver1')
diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py
index 48cf4c038..98a54635f 100644
--- a/ironic/tests/unit/conductor/test_manager.py
+++ b/ironic/tests/unit/conductor/test_manager.py
@@ -249,13 +249,15 @@ class UpdateNodeTestCase(mgr_utils.ServiceSetUpMixin,
node = obj_utils.create_test_node(self.context, driver='fake',
instance_uuid=None,
power_state=states.NOSTATE)
- node.instance_uuid = 'fake-uuid'
+ uuid1 = uuidutils.generate_uuid()
+ uuid2 = uuidutils.generate_uuid()
+ node.instance_uuid = uuid1
self.service.update_node(self.context, node)
# Check if the change was applied
- node.instance_uuid = 'meow'
+ node.instance_uuid = uuid2
node.refresh()
- self.assertEqual('fake-uuid', node.instance_uuid)
+ self.assertEqual(uuid1, node.instance_uuid)
def test_associate_node_powered_off(self):
self._test_associate_node(states.POWER_OFF)
@@ -2548,8 +2550,8 @@ class DestroyNodeTestCase(mgr_utils.ServiceSetUpMixin,
def test_destroy_node_associated(self):
self._start_service()
- node = obj_utils.create_test_node(self.context,
- instance_uuid='fake-uuid')
+ node = obj_utils.create_test_node(
+ self.context, instance_uuid=uuidutils.generate_uuid())
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.destroy_node,
@@ -2577,10 +2579,9 @@ class DestroyNodeTestCase(mgr_utils.ServiceSetUpMixin,
def test_destroy_node_allowed_in_maintenance(self):
self._start_service()
- node = obj_utils.create_test_node(self.context,
- instance_uuid='fake-uuid',
- provision_state=states.ACTIVE,
- maintenance=True)
+ node = obj_utils.create_test_node(
+ self.context, instance_uuid=uuidutils.generate_uuid(),
+ provision_state=states.ACTIVE, maintenance=True)
self.service.destroy_node(self.context, node.uuid)
self.assertRaises(exception.NodeNotFound,
self.dbapi.get_node_by_uuid,
@@ -3803,7 +3804,8 @@ class ManagerTestProperties(tests_db_base.DbTestCase):
self._check_driver_properties("fake_ssh", expected)
def test_driver_properties_fake_pxe(self):
- expected = ['deploy_kernel', 'deploy_ramdisk']
+ expected = ['deploy_kernel', 'deploy_ramdisk',
+ 'deploy_forces_oob_reboot']
self._check_driver_properties("fake_pxe", expected)
def test_driver_properties_fake_seamicro(self):
@@ -3824,34 +3826,37 @@ class ManagerTestProperties(tests_db_base.DbTestCase):
'ipmi_transit_address', 'ipmi_target_channel',
'ipmi_target_address', 'ipmi_local_address',
'deploy_kernel', 'deploy_ramdisk', 'ipmi_protocol_version',
- 'ipmi_force_boot_device'
- ]
+ 'ipmi_force_boot_device', 'deploy_forces_oob_reboot']
self._check_driver_properties("pxe_ipmitool", expected)
def test_driver_properties_pxe_ipminative(self):
expected = ['ipmi_address', 'ipmi_password', 'ipmi_username',
'deploy_kernel', 'deploy_ramdisk',
- 'ipmi_terminal_port', 'ipmi_force_boot_device']
+ 'ipmi_terminal_port', 'ipmi_force_boot_device',
+ 'deploy_forces_oob_reboot']
self._check_driver_properties("pxe_ipminative", expected)
def test_driver_properties_pxe_ssh(self):
expected = ['deploy_kernel', 'deploy_ramdisk',
'ssh_address', 'ssh_username', 'ssh_virt_type',
'ssh_key_contents', 'ssh_key_filename',
- 'ssh_password', 'ssh_port', 'ssh_terminal_port']
+ 'ssh_password', 'ssh_port', 'ssh_terminal_port',
+ 'deploy_forces_oob_reboot']
self._check_driver_properties("pxe_ssh", expected)
def test_driver_properties_pxe_seamicro(self):
expected = ['deploy_kernel', 'deploy_ramdisk',
'seamicro_api_endpoint', 'seamicro_password',
'seamicro_server_id', 'seamicro_username',
- 'seamicro_api_version', 'seamicro_terminal_port']
+ 'seamicro_api_version', 'seamicro_terminal_port',
+ 'deploy_forces_oob_reboot']
self._check_driver_properties("pxe_seamicro", expected)
def test_driver_properties_pxe_snmp(self):
expected = ['deploy_kernel', 'deploy_ramdisk',
'snmp_driver', 'snmp_address', 'snmp_port', 'snmp_version',
- 'snmp_community', 'snmp_security', 'snmp_outlet']
+ 'snmp_community', 'snmp_security', 'snmp_outlet',
+ 'deploy_forces_oob_reboot']
self._check_driver_properties("pxe_snmp", expected)
def test_driver_properties_fake_ilo(self):
@@ -3862,13 +3867,15 @@ class ManagerTestProperties(tests_db_base.DbTestCase):
def test_driver_properties_ilo_iscsi(self):
expected = ['ilo_address', 'ilo_username', 'ilo_password',
'client_port', 'client_timeout', 'ilo_deploy_iso',
- 'console_port', 'ilo_change_password']
+ 'console_port', 'ilo_change_password',
+ 'deploy_forces_oob_reboot']
self._check_driver_properties("iscsi_ilo", expected)
def test_driver_properties_agent_ilo(self):
expected = ['ilo_address', 'ilo_username', 'ilo_password',
'client_port', 'client_timeout', 'ilo_deploy_iso',
- 'console_port', 'ilo_change_password']
+ 'console_port', 'ilo_change_password',
+ 'deploy_forces_oob_reboot']
self._check_driver_properties("agent_ilo", expected)
def test_driver_properties_fail(self):
diff --git a/ironic/tests/unit/drivers/modules/cimc/test_common.py b/ironic/tests/unit/drivers/modules/cimc/test_common.py
index a32c9f2ab..03aa45d7e 100644
--- a/ironic/tests/unit/drivers/modules/cimc/test_common.py
+++ b/ironic/tests/unit/drivers/modules/cimc/test_common.py
@@ -16,6 +16,7 @@ import mock
from oslo_config import cfg
from oslo_utils import importutils
+from oslo_utils import uuidutils
from ironic.common import exception
from ironic.conductor import task_manager
@@ -39,7 +40,7 @@ class CIMCBaseTestCase(db_base.DbTestCase):
self.context,
driver='fake_cimc',
driver_info=db_utils.get_test_cimc_info(),
- instance_uuid="fake_uuid")
+ instance_uuid=uuidutils.generate_uuid())
CONF.set_override('max_retry', 2, 'cimc')
CONF.set_override('action_interval', 0, 'cimc')
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_power.py b/ironic/tests/unit/drivers/modules/ilo/test_power.py
index 41b2f4dcc..4df4a8ab8 100644
--- a/ironic/tests/unit/drivers/modules/ilo/test_power.py
+++ b/ironic/tests/unit/drivers/modules/ilo/test_power.py
@@ -18,6 +18,7 @@
import mock
from oslo_config import cfg
from oslo_utils import importutils
+from oslo_utils import uuidutils
from ironic.common import boot_devices
from ironic.common import exception
@@ -47,7 +48,7 @@ class IloPowerInternalMethodsTestCase(db_base.DbTestCase):
self.node = db_utils.create_test_node(
driver='fake_ilo',
driver_info=driver_info,
- instance_uuid='instance_uuid_123')
+ instance_uuid=uuidutils.generate_uuid())
CONF.set_override('power_retry', 2, 'ilo')
CONF.set_override('power_wait', 0, 'ilo')
diff --git a/ironic/tests/unit/drivers/modules/irmc/test_power.py b/ironic/tests/unit/drivers/modules/irmc/test_power.py
index 4d3af5e2b..7f01d2993 100644
--- a/ironic/tests/unit/drivers/modules/irmc/test_power.py
+++ b/ironic/tests/unit/drivers/modules/irmc/test_power.py
@@ -18,6 +18,7 @@ Test class for iRMC Power Driver
import mock
from oslo_config import cfg
+from oslo_utils import uuidutils
from ironic.common import exception
from ironic.common import states
@@ -45,7 +46,7 @@ class IRMCPowerInternalMethodsTestCase(db_base.DbTestCase):
self.node = db_utils.create_test_node(
driver='fake_irmc',
driver_info=driver_info,
- instance_uuid='instance_uuid_123')
+ instance_uuid=uuidutils.generate_uuid())
@mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed')
def test__set_power_state_power_on_ok(
diff --git a/ironic/tests/unit/drivers/modules/test_agent.py b/ironic/tests/unit/drivers/modules/test_agent.py
index 147612e3e..c22a4c1e4 100644
--- a/ironic/tests/unit/drivers/modules/test_agent.py
+++ b/ironic/tests/unit/drivers/modules/test_agent.py
@@ -690,7 +690,8 @@ class TestAgentVendor(db_base.DbTestCase):
i_info['image_type'] = 'partition'
i_info['root_mb'] = 10240
i_info['deploy_boot_mode'] = 'bios'
- i_info['capabilities'] = '{"boot_option": "local"}'
+ i_info['capabilities'] = {"boot_option": "local",
+ "disk_label": "msdos"}
self.node.instance_info = i_info
driver_internal_info = self.node.driver_internal_info
driver_internal_info['is_whole_disk_image'] = False
@@ -700,6 +701,7 @@ class TestAgentVendor(db_base.DbTestCase):
expected_image_info = {
'urls': [test_temp_url],
'id': 'fake-image',
+ 'node_uuid': self.node.uuid,
'checksum': 'checksum',
'disk_format': 'qcow2',
'container_format': 'bare',
@@ -715,7 +717,8 @@ class TestAgentVendor(db_base.DbTestCase):
'image_type': 'partition',
'root_mb': 10240,
'boot_option': 'local',
- 'deploy_boot_mode': 'bios'
+ 'deploy_boot_mode': 'bios',
+ 'disk_label': 'msdos'
}
client_mock = mock.MagicMock(spec_set=['prepare_image'])
diff --git a/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py b/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py
index b4c1ded17..29f4ee94b 100644
--- a/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py
+++ b/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py
@@ -684,6 +684,60 @@ class TestBaseAgentVendor(db_base.DbTestCase):
self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'sync',
+ spec=types.FunctionType)
+ def test_reboot_and_finish_deploy_power_action_oob_power_off(
+ self, sync_mock, node_power_action_mock):
+ # Enable force power off
+ driver_info = self.node.driver_info
+ driver_info['deploy_forces_oob_reboot'] = True
+ self.node.driver_info = driver_info
+
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.passthru.reboot_and_finish_deploy(task)
+
+ sync_mock.assert_called_once_with(task.node)
+ node_power_action_mock.assert_called_once_with(
+ task, states.REBOOT)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+
+ @mock.patch.object(agent_base_vendor.LOG, 'warning', autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'sync',
+ spec=types.FunctionType)
+ def test_reboot_and_finish_deploy_power_action_oob_power_off_failed(
+ self, sync_mock, node_power_action_mock, log_mock):
+ # Enable force power off
+ driver_info = self.node.driver_info
+ driver_info['deploy_forces_oob_reboot'] = True
+ self.node.driver_info = driver_info
+
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ sync_mock.return_value = {'faultstring': 'Unknown command: blah'}
+ self.passthru.reboot_and_finish_deploy(task)
+
+ sync_mock.assert_called_once_with(task.node)
+ node_power_action_mock.assert_called_once_with(
+ task, states.REBOOT)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ log_error = ('The version of the IPA ramdisk used in the '
+ 'deployment do not support the command "sync"')
+ log_mock.assert_called_once_with(
+ 'Failed to flush the file system prior to hard rebooting the '
+ 'node %(node)s. Error: %(error)s',
+ {'node': task.node.uuid, 'error': log_error})
+
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
autospec=True)
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
@@ -1276,3 +1330,7 @@ class TestRefreshCleanSteps(TestBaseAgentVendor):
task)
client_mock.assert_called_once_with(mock.ANY, task.node,
task.ports)
+
+ def test_get_properties(self):
+ expected = agent_base_vendor.VENDOR_PROPERTIES
+ self.assertEqual(expected, self.passthru.get_properties())
diff --git a/ironic/tests/unit/drivers/modules/test_agent_client.py b/ironic/tests/unit/drivers/modules/test_agent_client.py
index fe5be2dba..aead8915a 100644
--- a/ironic/tests/unit/drivers/modules/test_agent_client.py
+++ b/ironic/tests/unit/drivers/modules/test_agent_client.py
@@ -236,3 +236,9 @@ class TestAgentClient(base.TestCase):
self.client.power_off(self.node)
self.client._command.assert_called_once_with(
node=self.node, method='standby.power_off', params={})
+
+ def test_sync(self):
+ self.client._command = mock.MagicMock(spec_set=[])
+ self.client.sync(self.node)
+ self.client._command.assert_called_once_with(
+ node=self.node, method='standby.sync', params={}, wait=True)
diff --git a/ironic/tests/unit/drivers/modules/test_deploy_utils.py b/ironic/tests/unit/drivers/modules/test_deploy_utils.py
index 8a916b11a..8362c07bb 100644
--- a/ironic/tests/unit/drivers/modules/test_deploy_utils.py
+++ b/ironic/tests/unit/drivers/modules/test_deploy_utils.py
@@ -1252,7 +1252,7 @@ class OtherFunctionTestCase(db_base.DbTestCase):
autospec=True)
def _test_set_failed_state(self, mock_event, mock_power, mock_log,
event_value=None, power_value=None,
- log_calls=None):
+ log_calls=None, poweroff=True):
err_msg = 'some failure'
mock_event.side_effect = event_value
mock_power.side_effect = power_value
@@ -1260,9 +1260,12 @@ class OtherFunctionTestCase(db_base.DbTestCase):
shared=False) as task:
utils.set_failed_state(task, err_msg)
mock_event.assert_called_once_with(task, 'fail')
- mock_power.assert_called_once_with(task, states.POWER_OFF)
+ if poweroff:
+ mock_power.assert_called_once_with(task, states.POWER_OFF)
+ else:
+ self.assertFalse(mock_power.called)
self.assertEqual(err_msg, task.node.last_error)
- if log_calls:
+ if (log_calls and poweroff):
mock_log.exception.assert_has_calls(log_calls)
else:
self.assertFalse(mock_log.called)
@@ -1283,6 +1286,23 @@ class OtherFunctionTestCase(db_base.DbTestCase):
power_value=iter([exc_param] * len(calls)),
log_calls=calls)
+ def test_set_failed_state_no_poweroff(self):
+ cfg.CONF.deploy.power_off_after_deploy_failure = False
+ exc_state = exception.InvalidState('invalid state')
+ exc_param = exception.InvalidParameterValue('invalid parameter')
+ mock_call = mock.call(mock.ANY)
+ self._test_set_failed_state(poweroff=False)
+ calls = [mock_call]
+ self._test_set_failed_state(event_value=iter([exc_state] * len(calls)),
+ log_calls=calls, poweroff=False)
+ calls = [mock_call]
+ self._test_set_failed_state(power_value=iter([exc_param] * len(calls)),
+ log_calls=calls, poweroff=False)
+ calls = [mock_call, mock_call]
+ self._test_set_failed_state(event_value=iter([exc_state] * len(calls)),
+ power_value=iter([exc_param] * len(calls)),
+ log_calls=calls, poweroff=False)
+
def test_get_boot_option(self):
self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
result = utils.get_boot_option(self.node)
diff --git a/ironic/tests/unit/drivers/modules/test_pxe.py b/ironic/tests/unit/drivers/modules/test_pxe.py
index 745626240..9e023ea18 100644
--- a/ironic/tests/unit/drivers/modules/test_pxe.py
+++ b/ironic/tests/unit/drivers/modules/test_pxe.py
@@ -32,6 +32,7 @@ from ironic.common.glance_service import base_image_service
from ironic.common import pxe_utils
from ironic.common import states
from ironic.conductor import task_manager
+from ironic.drivers.modules import agent_base_vendor
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules import pxe
from ironic.tests.unit.conductor import mgr_utils
@@ -549,6 +550,7 @@ class PXEBootTestCase(db_base.DbTestCase):
def test_get_properties(self):
expected = pxe.COMMON_PROPERTIES
+ expected.update(agent_base_vendor.VENDOR_PROPERTIES)
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertEqual(expected, task.driver.get_properties())
diff --git a/releasenotes/notes/add-support-for-no-poweroff-on-failure-86e43b3e39043990.yaml b/releasenotes/notes/add-support-for-no-poweroff-on-failure-86e43b3e39043990.yaml
new file mode 100644
index 000000000..4808ad400
--- /dev/null
+++ b/releasenotes/notes/add-support-for-no-poweroff-on-failure-86e43b3e39043990.yaml
@@ -0,0 +1,12 @@
+---
+features:
+ - Operators can now set
+ deploy.power_off_after_deploy_failure to leave nodes
+ powered on when a deployment fails. This is useful
+ for troubleshooting deployment issues. As a note,
+ Nova will still attempt to delete a node after a failed
+ deployment, so deploy.power_off_after_deploy_failure
+ may not be very effective in non-standalone
+ deployments until a similar patch to ironic's driver in
+ nova is proposed.
+
diff --git a/releasenotes/notes/disk-label-fix-7580de913835ff44.yaml b/releasenotes/notes/disk-label-fix-7580de913835ff44.yaml
new file mode 100644
index 000000000..b7a8d5314
--- /dev/null
+++ b/releasenotes/notes/disk-label-fix-7580de913835ff44.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - Fixes the bug where the user specified
+ disk_label is ignored for the agent
+ drivers for partition images.
diff --git a/releasenotes/notes/duplicated-driver-entry-775370ad84736206.yaml b/releasenotes/notes/duplicated-driver-entry-775370ad84736206.yaml
new file mode 100644
index 000000000..bb7be7e55
--- /dev/null
+++ b/releasenotes/notes/duplicated-driver-entry-775370ad84736206.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - Fixes a problem which causes the conductor to error out on startup
+ in case there's a duplicated entry in the enabled_drivers configuration
+ option.
diff --git a/releasenotes/notes/oob-power-off-7bbdf5947ed24bf8.yaml b/releasenotes/notes/oob-power-off-7bbdf5947ed24bf8.yaml
new file mode 100644
index 000000000..ddbbf152a
--- /dev/null
+++ b/releasenotes/notes/oob-power-off-7bbdf5947ed24bf8.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - Fixes a problem where some hardware/firmware (specially faulty ones)
+ won't come back online after an in-band ACPI soft power off by adding
+ a new driver property called "deploy_forces_oob_reboot" that can be set
+ to the nodes being deployed by the IPA ramdisk. If the value of this
+ property is True, Ironic will power cycle the node via out-of-band.
diff --git a/releasenotes/notes/opentack-baremetal-request-id-daa72b785eaaaa8d.yaml b/releasenotes/notes/opentack-baremetal-request-id-daa72b785eaaaa8d.yaml
new file mode 100644
index 000000000..fcc50f077
--- /dev/null
+++ b/releasenotes/notes/opentack-baremetal-request-id-daa72b785eaaaa8d.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - Append request_id as ``Openstack-Request-Id`` header to the response.
+
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index 02f2a659e..09be841aa 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -5,8 +5,9 @@
.. toctree::
:maxdepth: 1
- Current (4.3.0 - unreleased) <current-series>
- Mitaka (4.3.0 - unreleased) <mitaka>
+ Current (unreleased) <current-series>
+ Newton (unreleased) <newton>
+ Mitaka (4.3.0 - 5.1.x) <mitaka>
Liberty (4.0.0 - 4.2.x) <liberty>
Kilo (2015.1) <https://wiki.openstack.org/wiki/Ironic/ReleaseNotes/Kilo>
Juno (2014.2) <https://wiki.openstack.org/wiki/Ironic/ReleaseNotes/Juno>
diff --git a/releasenotes/source/mitaka.rst b/releasenotes/source/mitaka.rst
index b66086b8b..05ed3e48e 100644
--- a/releasenotes/source/mitaka.rst
+++ b/releasenotes/source/mitaka.rst
@@ -1,6 +1,6 @@
-============================
+===========================
Mitaka Series Release Notes
-============================
+===========================
.. release-notes::
- :branch: origin/master
+ :branch: origin/stable/mitaka
diff --git a/releasenotes/source/newton.rst b/releasenotes/source/newton.rst
new file mode 100644
index 000000000..55006ebac
--- /dev/null
+++ b/releasenotes/source/newton.rst
@@ -0,0 +1,6 @@
+===========================
+Newton Series Release Notes
+===========================
+
+.. release-notes::
+ :branch: origin/master