summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/deploy/cleaning.rst37
-rw-r--r--doc/source/deploy/install-guide.rst13
-rw-r--r--doc/source/dev/dev-quickstart.rst6
-rw-r--r--doc/source/drivers/ilo.rst4
-rw-r--r--driver-requirements.txt2
-rw-r--r--etc/ironic/ironic.conf.sample53
-rw-r--r--ironic/api/controllers/root.py4
-rw-r--r--ironic/api/controllers/v1/__init__.py4
-rw-r--r--ironic/api/controllers/v1/chassis.py14
-rw-r--r--ironic/api/controllers/v1/driver.py12
-rw-r--r--ironic/api/controllers/v1/node.py45
-rw-r--r--ironic/api/controllers/v1/port.py14
-rw-r--r--ironic/api/expose.py24
-rw-r--r--ironic/api/middleware/auth_token.py2
-rw-r--r--ironic/common/glance_service/base_image_service.py31
-rw-r--r--ironic/common/image_service.py5
-rw-r--r--ironic/common/pxe_utils.py40
-rw-r--r--ironic/drivers/modules/boot.ipxe2
-rw-r--r--ironic/drivers/modules/deploy_utils.py44
-rw-r--r--ironic/drivers/modules/ilo/common.py6
-rw-r--r--ironic/drivers/modules/ilo/deploy.py82
-rw-r--r--ironic/drivers/modules/ipmitool.py69
-rw-r--r--ironic/drivers/modules/iscsi_deploy.py99
-rw-r--r--ironic/drivers/modules/pxe.py53
-rw-r--r--ironic/drivers/utils.py52
-rw-r--r--ironic/locale/ironic-log-error.pot236
-rw-r--r--ironic/locale/ironic-log-info.pot205
-rw-r--r--ironic/locale/ironic-log-warning.pot174
-rw-r--r--ironic/locale/ironic.pot1289
-rw-r--r--ironic/locale/pt_BR/LC_MESSAGES/ironic-log-critical.po25
-rw-r--r--ironic/openstack/common/service.py32
-rw-r--r--ironic/openstack/common/versionutils.py11
-rw-r--r--ironic/tests/api/test_acl.py9
-rw-r--r--ironic/tests/api/utils.py10
-rw-r--r--ironic/tests/api/v1/test_nodes.py47
-rw-r--r--ironic/tests/api/v1/test_utils.py13
-rw-r--r--ironic/tests/db/sqlalchemy/test_migrations.py12
-rw-r--r--ironic/tests/db/test_conductor.py14
-rw-r--r--ironic/tests/db/test_nodes.py10
-rw-r--r--ironic/tests/dhcp/test_neutron.py4
-rw-r--r--ironic/tests/drivers/amt/test_vendor.py3
-rw-r--r--ironic/tests/drivers/ilo/test_deploy.py184
-rw-r--r--ironic/tests/drivers/test_deploy_utils.py46
-rw-r--r--ironic/tests/drivers/test_ipmitool.py80
-rw-r--r--ironic/tests/drivers/test_iscsi_deploy.py134
-rw-r--r--ironic/tests/drivers/test_pxe.py46
-rw-r--r--ironic/tests/drivers/test_utils.py35
-rw-r--r--ironic/tests/stubs.py12
-rw-r--r--ironic/tests/test_glance_service.py22
-rw-r--r--ironic/tests/test_pxe_utils.py37
-rw-r--r--tools/config/oslo.config.generator.rc2
-rw-r--r--tox.ini1
52 files changed, 2298 insertions, 1112 deletions
diff --git a/doc/source/deploy/cleaning.rst b/doc/source/deploy/cleaning.rst
index b19b88a1b..8897bf46b 100644
--- a/doc/source/deploy/cleaning.rst
+++ b/doc/source/deploy/cleaning.rst
@@ -13,9 +13,14 @@ the tenant will get a consistent baremetal node deployed every time.
Ironic implements cleaning by collecting a list of steps to perform on a node
from each Power, Deploy, and Management driver assigned to the node. These
steps are then arranged by priority and executed on the node when it is moved
-to CLEANING state, if cleaning is enabled.
+to cleaning state, if cleaning is enabled.
-Ironic added support for cleaning used nodes in the Kilo release.
+Typically, nodes move to cleaning state when moving from active -> available.
+Nodes also traverse cleaning when going from manageable -> available. For a
+full understanding of all state transitions into cleaning, please see
+:ref:`states`.
+
+Ironic added support for cleaning nodes in the Kilo release.
Enabling Cleaning
@@ -41,8 +46,8 @@ In-band steps are performed by Ironic making API calls to a ramdisk running
on the node using a Deploy driver. Currently, only the ironic-python-agent
ramdisk used with an agent_* driver supports in-band cleaning. By default,
ironic-python-agent ships with a minimal cleaning configuration, only erasing
-disks. However, with this ramdisk, you can add your own clean_steps and/or
-override default clean_steps with a custom Hardware Manager.
+disks. However, with this ramdisk, you can add your own cleaning steps and/or
+override default cleaning steps with a custom Hardware Manager.
There is currently no support for in-band cleaning using the Ironic pxe
ramdisk.
@@ -81,12 +86,14 @@ to disable erase_devices, you'd use the following config::
agent_erase_devices_priority=0
-What clean_step is running?
----------------------------
-To check what clean_step the node is performing or attempted to perform and
+What cleaning step is running?
+------------------------------
+To check what cleaning step the node is performing or attempted to perform and
failed, either query the node endpoint for the node or run ``ironic node-show
-$node_ident`` and look at the 'clean_step' field. This will tell you which
-step for which driver is or was (if in CLEANFAIL state) being executed.
+$node_ident`` and look in the `internal_driver_info` field. The `clean_steps`
+field will contain a list of all remaining steps with their priority, and the
+first one listed is the step currently in progress or that the node failed
+before going into cleanfail state.
Should I disable cleaning?
--------------------------
@@ -107,17 +114,17 @@ cleaning.
Troubleshooting
===============
-If cleaning fails on a node, the node will be put into CLEANFAIL state and
+If cleaning fails on a node, the node will be put into cleanfail state and
placed in maintenance mode, to prevent Ironic from taking actions on the
node.
-Nodes in CLEANFAIL will not be powered off, as the node might be in a state
+Nodes in cleanfail will not be powered off, as the node might be in a state
such that powering it off could damage the node or remove useful information
about the nature of the cleaning failure.
-A CLEANFAIL node can be moved to MANAGEABLE state, where they cannot be
+A cleanfail node can be moved to manageable state, where they cannot be
scheduled by Nova and you can safely attempt to fix the node. To move a node
-from CLEANFAIL to MANAGEABLE: ``ironic node-set-provision-state manage``.
+from cleanfail to manageable: ``ironic node-set-provision-state manage``.
You can now take actions on the node, such as replacing a bad disk drive.
Strategies for determining why a cleaning step failed include checking the
@@ -125,7 +132,7 @@ Ironic conductor logs, viewing logs on the still-running ironic-python-agent
(if an in-band step failed), or performing general hardware troubleshooting on
the node.
-When the node is repaired, you can move the node back to AVAILABLE state, to
+When the node is repaired, you can move the node back to available state, to
allow it to be scheduled by Nova.
::
@@ -136,5 +143,5 @@ allow it to be scheduled by Nova.
# Now, make the node available for scheduling by Nova
ironic node-set-provision-state $node_ident provide
-The node will begin cleaning from the start, and move to AVAILABLE state
+The node will begin cleaning from the start, and move to available state
when complete.
diff --git a/doc/source/deploy/install-guide.rst b/doc/source/deploy/install-guide.rst
index 4d5737343..2907ad54b 100644
--- a/doc/source/deploy/install-guide.rst
+++ b/doc/source/deploy/install-guide.rst
@@ -1099,6 +1099,19 @@ by an operator. There are two kinds of inspection supported by Ironic:
being run on a separate host from the ironic-conductor service, or is using
non-standard port.
+ In order to ensure that ports in Ironic are synchronized with NIC ports on
+ the node, the following settings in the ironic-discoverd configuration file
+ must be set::
+
+ [discoverd]
+ add_ports = all
+ keep_ports = present
+
+ (requires ironic-discoverd of version 1.1.0 or higher). Note that in this
+ case an operator is responsible for deleting ports that can't be actually
+ used by Ironic, see `bug 1405131
+ <https://bugs.launchpad.net/ironic/+bug/1405131>`_ for explanation.
+
.. _ironic-discoverd: https://github.com/stackforge/ironic-discoverd
diff --git a/doc/source/dev/dev-quickstart.rst b/doc/source/dev/dev-quickstart.rst
index 01b27d644..223052651 100644
--- a/doc/source/dev/dev-quickstart.rst
+++ b/doc/source/dev/dev-quickstart.rst
@@ -366,11 +366,11 @@ Source credentials, create a key, and spawn an instance::
.. note::
Because devstack create multiple networks, we need to pass an additional parameter
- `--nic net-id` to the nova boot command when using the admin account, for example:
+ ``--nic net-id`` to the nova boot command when using the admin account, for example::
- net_id=$(neutron net-list | egrep "$PRIVATE_NETWORK_NAME"'[^-]' | awk '{ print $2 }')
+ net_id=$(neutron net-list | egrep "$PRIVATE_NETWORK_NAME"'[^-]' | awk '{ print $2 }')
- nova boot --flavor baremetal --nic net-id=$net_id --image $image --key-name default testing
+ nova boot --flavor baremetal --nic net-id=$net_id --image $image --key-name default testing
As the demo tenant, you should now see a Nova instance building::
diff --git a/doc/source/drivers/ilo.rst b/doc/source/drivers/ilo.rst
index b7737a57a..0cfe092ed 100644
--- a/doc/source/drivers/ilo.rst
+++ b/doc/source/drivers/ilo.rst
@@ -40,9 +40,9 @@ Prerequisites
managing HP Proliant hardware.
Install ``proliantutils`` [2]_ module on the Ironic conductor node. Minimum
- version required is 2.0.1.::
+ version required is 2.1.0.::
- $ pip install "proliantutils>=2.0.1"
+ $ pip install "proliantutils>=2.1.0"
* ``ipmitool`` command must be present on the service node(s) where
``ironic-conductor`` is running. On most distros, this is provided as part
diff --git a/driver-requirements.txt b/driver-requirements.txt
index db32cebf7..842c51799 100644
--- a/driver-requirements.txt
+++ b/driver-requirements.txt
@@ -5,7 +5,7 @@
# These are available on pypi
ironic-discoverd>=1.0.0
-proliantutils>=2.0.1
+proliantutils>=2.1.0
pyghmi
pysnmp
python-scciclient
diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample
index cf75c9b1f..ccf368f0d 100644
--- a/etc/ironic/ironic.conf.sample
+++ b/etc/ironic/ironic.conf.sample
@@ -829,8 +829,9 @@
# (integer value)
#glance_num_retries=0
-# Default protocol to use when connecting to glance. Set to
-# https for SSL. (string value)
+# Authentication strategy to use when connecting to glance.
+# Only "keystone" and "noauth" are currently supported by
+# ironic. (string value)
#auth_strategy=keystone
@@ -1195,6 +1196,23 @@
#cleaning_network_uuid=<None>
+[oslo_concurrency]
+
+#
+# Options defined in oslo.concurrency
+#
+
+# Enables or disables inter-process locks. (boolean value)
+#disable_process_locking=false
+
+# Directory to use for lock files. For security, the
+# specified directory should only be writable by the user
+# running the processes that need locking. Defaults to
+# environment variable OSLO_LOCK_PATH. If external locks are
+# used, a lock path must be set. (string value)
+#lock_path=<None>
+
+
[oslo_messaging_amqp]
#
@@ -1379,11 +1397,42 @@
# value)
#rabbit_ha_queues=false
+# Number of seconds after which the Rabbit broker is
+# considered down if heartbeat's keep-alive fails (0 disable
+# the heartbeat). (integer value)
+#heartbeat_timeout_threshold=60
+
+# How often times during the heartbeat_timeout_threshold we
+# check the heartbeat. (integer value)
+#heartbeat_rate=2
+
# Deprecated, use rpc_backend=kombu+memory or rpc_backend=fake
# (boolean value)
#fake_rabbit=false
+[oslo_policy]
+
+#
+# Options defined in oslo.policy
+#
+
+# The JSON file that defines policies. (string value)
+#policy_file=policy.json
+
+# Default rule. Enforced when a requested rule is not found.
+# (string value)
+#policy_default_rule=default
+
+# Directories where policy configuration files are stored.
+# They can be relative to any directory in the search path
+# defined by the config_dir option, or absolute paths. The
+# file defined by policy_file must exist for these directories
+# to be searched. Missing or empty directories are ignored.
+# (multi valued)
+#policy_dirs=policy.d
+
+
[pxe]
#
diff --git a/ironic/api/controllers/root.py b/ironic/api/controllers/root.py
index 9ae3a6a0c..c3ba09775 100644
--- a/ironic/api/controllers/root.py
+++ b/ironic/api/controllers/root.py
@@ -19,11 +19,11 @@
import pecan
from pecan import rest
from wsme import types as wtypes
-import wsmeext.pecan as wsme_pecan
from ironic.api.controllers import base
from ironic.api.controllers import link
from ironic.api.controllers import v1
+from ironic.api import expose
class Version(base.APIBase):
@@ -79,7 +79,7 @@ class RootController(rest.RestController):
v1 = v1.Controller()
- @wsme_pecan.wsexpose(Root)
+ @expose.expose(Root)
def get(self):
# NOTE: The reason why convert() it's being called for every
# request is because we need to get the host url from
diff --git a/ironic/api/controllers/v1/__init__.py b/ironic/api/controllers/v1/__init__.py
index 3a9b77e36..b2f432fde 100644
--- a/ironic/api/controllers/v1/__init__.py
+++ b/ironic/api/controllers/v1/__init__.py
@@ -26,7 +26,6 @@ import pecan
from pecan import rest
from webob import exc
from wsme import types as wtypes
-import wsmeext.pecan as wsme_pecan
from ironic.api.controllers import base
from ironic.api.controllers import link
@@ -34,6 +33,7 @@ from ironic.api.controllers.v1 import chassis
from ironic.api.controllers.v1 import driver
from ironic.api.controllers.v1 import node
from ironic.api.controllers.v1 import port
+from ironic.api import expose
from ironic.common.i18n import _
BASE_VERSION = 1
@@ -158,7 +158,7 @@ class Controller(rest.RestController):
chassis = chassis.ChassisController()
drivers = driver.DriversController()
- @wsme_pecan.wsexpose(V1)
+ @expose.expose(V1)
def get(self):
# NOTE: The reason why convert() it's being called for every
# request is because we need to get the host url from
diff --git a/ironic/api/controllers/v1/chassis.py b/ironic/api/controllers/v1/chassis.py
index a5134ff64..c6f42a7b1 100644
--- a/ironic/api/controllers/v1/chassis.py
+++ b/ironic/api/controllers/v1/chassis.py
@@ -19,7 +19,6 @@ import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
-import wsmeext.pecan as wsme_pecan
from ironic.api.controllers import base
from ironic.api.controllers import link
@@ -27,6 +26,7 @@ from ironic.api.controllers.v1 import collection
from ironic.api.controllers.v1 import node
from ironic.api.controllers.v1 import types
from ironic.api.controllers.v1 import utils as api_utils
+from ironic.api import expose
from ironic.common import exception
from ironic import objects
@@ -164,7 +164,7 @@ class ChassisController(rest.RestController):
sort_key=sort_key,
sort_dir=sort_dir)
- @wsme_pecan.wsexpose(ChassisCollection, types.uuid,
+ @expose.expose(ChassisCollection, types.uuid,
int, wtypes.text, wtypes.text)
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
"""Retrieve a list of chassis.
@@ -176,7 +176,7 @@ class ChassisController(rest.RestController):
"""
return self._get_chassis_collection(marker, limit, sort_key, sort_dir)
- @wsme_pecan.wsexpose(ChassisCollection, types.uuid, int,
+ @expose.expose(ChassisCollection, types.uuid, int,
wtypes.text, wtypes.text)
def detail(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
"""Retrieve a list of chassis with detail.
@@ -196,7 +196,7 @@ class ChassisController(rest.RestController):
return self._get_chassis_collection(marker, limit, sort_key, sort_dir,
expand, resource_url)
- @wsme_pecan.wsexpose(Chassis, types.uuid)
+ @expose.expose(Chassis, types.uuid)
def get_one(self, chassis_uuid):
"""Retrieve information about the given chassis.
@@ -206,7 +206,7 @@ class ChassisController(rest.RestController):
chassis_uuid)
return Chassis.convert_with_links(rpc_chassis)
- @wsme_pecan.wsexpose(Chassis, body=Chassis, status_code=201)
+ @expose.expose(Chassis, body=Chassis, status_code=201)
def post(self, chassis):
"""Create a new chassis.
@@ -220,7 +220,7 @@ class ChassisController(rest.RestController):
return Chassis.convert_with_links(new_chassis)
@wsme.validate(types.uuid, [ChassisPatchType])
- @wsme_pecan.wsexpose(Chassis, types.uuid, body=[ChassisPatchType])
+ @expose.expose(Chassis, types.uuid, body=[ChassisPatchType])
def patch(self, chassis_uuid, patch):
"""Update an existing chassis.
@@ -250,7 +250,7 @@ class ChassisController(rest.RestController):
rpc_chassis.save()
return Chassis.convert_with_links(rpc_chassis)
- @wsme_pecan.wsexpose(None, types.uuid, status_code=204)
+ @expose.expose(None, types.uuid, status_code=204)
def delete(self, chassis_uuid):
"""Delete a chassis.
diff --git a/ironic/api/controllers/v1/driver.py b/ironic/api/controllers/v1/driver.py
index 49146d03b..89cbe40cf 100644
--- a/ironic/api/controllers/v1/driver.py
+++ b/ironic/api/controllers/v1/driver.py
@@ -17,10 +17,10 @@ import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
-import wsmeext.pecan as wsme_pecan
from ironic.api.controllers import base
from ironic.api.controllers import link
+from ironic.api import expose
from ironic.common import exception
from ironic.common.i18n import _
@@ -114,7 +114,7 @@ class DriverPassthruController(rest.RestController):
'methods': ['GET']
}
- @wsme_pecan.wsexpose(wtypes.text, wtypes.text)
+ @expose.expose(wtypes.text, wtypes.text)
def methods(self, driver_name):
"""Retrieve information about vendor methods of the given driver.
@@ -132,7 +132,7 @@ class DriverPassthruController(rest.RestController):
return _VENDOR_METHODS[driver_name]
- @wsme_pecan.wsexpose(wtypes.text, wtypes.text, wtypes.text,
+ @expose.expose(wtypes.text, wtypes.text, wtypes.text,
body=wtypes.text)
def _default(self, driver_name, method, data=None):
"""Call a driver API extension.
@@ -166,7 +166,7 @@ class DriversController(rest.RestController):
'properties': ['GET'],
}
- @wsme_pecan.wsexpose(DriverList)
+ @expose.expose(DriverList)
def get_all(self):
"""Retrieve a list of drivers."""
# FIXME(deva): formatting of the auto-generated REST API docs
@@ -176,7 +176,7 @@ class DriversController(rest.RestController):
driver_list = pecan.request.dbapi.get_active_driver_dict()
return DriverList.convert_with_links(driver_list)
- @wsme_pecan.wsexpose(Driver, wtypes.text)
+ @expose.expose(Driver, wtypes.text)
def get_one(self, driver_name):
"""Retrieve a single driver."""
# NOTE(russell_h): There is no way to make this more efficient than
@@ -191,7 +191,7 @@ class DriversController(rest.RestController):
raise exception.DriverNotFound(driver_name=driver_name)
- @wsme_pecan.wsexpose(wtypes.text, wtypes.text)
+ @expose.expose(wtypes.text, wtypes.text)
def properties(self, driver_name):
"""Retrieve property information of the given driver.
diff --git a/ironic/api/controllers/v1/node.py b/ironic/api/controllers/v1/node.py
index 93d87db00..ce48e0916 100644
--- a/ironic/api/controllers/v1/node.py
+++ b/ironic/api/controllers/v1/node.py
@@ -23,7 +23,6 @@ import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
-import wsmeext.pecan as wsme_pecan
from ironic.api.controllers import base
from ironic.api.controllers import link
@@ -31,6 +30,7 @@ from ironic.api.controllers.v1 import collection
from ironic.api.controllers.v1 import port
from ironic.api.controllers.v1 import types
from ironic.api.controllers.v1 import utils as api_utils
+from ironic.api import expose
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import states as ir_states
@@ -132,7 +132,7 @@ class BootDeviceController(rest.RestController):
return pecan.request.rpcapi.get_boot_device(pecan.request.context,
rpc_node.uuid, topic)
- @wsme_pecan.wsexpose(None, types.uuid_or_name, wtypes.text, types.boolean,
+ @expose.expose(None, types.uuid_or_name, wtypes.text, types.boolean,
status_code=204)
def put(self, node_ident, boot_device, persistent=False):
"""Set the boot device for a node.
@@ -155,7 +155,7 @@ class BootDeviceController(rest.RestController):
persistent=persistent,
topic=topic)
- @wsme_pecan.wsexpose(wtypes.text, types.uuid_or_name)
+ @expose.expose(wtypes.text, types.uuid_or_name)
def get(self, node_ident):
"""Get the current boot device for a node.
@@ -170,7 +170,7 @@ class BootDeviceController(rest.RestController):
"""
return self._get_boot_device(node_ident)
- @wsme_pecan.wsexpose(wtypes.text, types.uuid_or_name)
+ @expose.expose(wtypes.text, types.uuid_or_name)
def supported(self, node_ident):
"""Get a list of the supported boot devices.
@@ -207,7 +207,7 @@ class ConsoleInfo(base.APIBase):
class NodeConsoleController(rest.RestController):
- @wsme_pecan.wsexpose(ConsoleInfo, types.uuid_or_name)
+ @expose.expose(ConsoleInfo, types.uuid_or_name)
def get(self, node_ident):
"""Get connection information about the console.
@@ -225,7 +225,7 @@ class NodeConsoleController(rest.RestController):
return ConsoleInfo(console_enabled=console_state, console_info=console)
- @wsme_pecan.wsexpose(None, types.uuid_or_name, types.boolean,
+ @expose.expose(None, types.uuid_or_name, types.boolean,
status_code=202)
def put(self, node_ident, enabled):
"""Start and stop the node console.
@@ -302,7 +302,7 @@ class NodeStatesController(rest.RestController):
console = NodeConsoleController()
"""Expose console as a sub-element of states"""
- @wsme_pecan.wsexpose(NodeStates, types.uuid_or_name)
+ @expose.expose(NodeStates, types.uuid_or_name)
def get(self, node_ident):
"""List the states of the node.
@@ -314,7 +314,7 @@ class NodeStatesController(rest.RestController):
rpc_node = api_utils.get_rpc_node(node_ident)
return NodeStates.convert(rpc_node)
- @wsme_pecan.wsexpose(None, types.uuid_or_name, wtypes.text,
+ @expose.expose(None, types.uuid_or_name, wtypes.text,
status_code=202)
def power(self, node_ident, target):
"""Set the power state of the node.
@@ -352,7 +352,7 @@ class NodeStatesController(rest.RestController):
url_args = '/'.join([node_ident, 'states'])
pecan.response.location = link.build_url('nodes', url_args)
- @wsme_pecan.wsexpose(None, types.uuid_or_name, wtypes.text,
+ @expose.expose(None, types.uuid_or_name, wtypes.text,
wtypes.text, status_code=202)
def provision(self, node_ident, target, configdrive=None):
"""Asynchronous trigger the provisioning of the node.
@@ -391,6 +391,11 @@ class NodeStatesController(rest.RestController):
raise exception.NodeLocked(node=rpc_node.uuid,
host=rpc_node.reservation)
+ if (target in (ir_states.ACTIVE, ir_states.REBUILD)
+ and rpc_node.maintenance):
+ raise exception.NodeInMaintenance(op=_('provisioning'),
+ node=rpc_node.uuid)
+
m = ir_states.machine.copy()
m.initialize(rpc_node.provision_state)
if not m.is_valid_event(ir_states.VERBS.get(target, target)):
@@ -665,7 +670,7 @@ class NodeVendorPassthruController(rest.RestController):
'methods': ['GET']
}
- @wsme_pecan.wsexpose(wtypes.text, types.uuid_or_name)
+ @expose.expose(wtypes.text, types.uuid_or_name)
def methods(self, node_ident):
"""Retrieve information about vendor methods of the given node.
@@ -685,7 +690,7 @@ class NodeVendorPassthruController(rest.RestController):
return _VENDOR_METHODS[rpc_node.driver]
- @wsme_pecan.wsexpose(wtypes.text, types.uuid_or_name, wtypes.text,
+ @expose.expose(wtypes.text, types.uuid_or_name, wtypes.text,
body=wtypes.text)
def _default(self, node_ident, method, data=None):
"""Call a vendor extension.
@@ -728,7 +733,7 @@ class NodeMaintenanceController(rest.RestController):
pecan.request.rpcapi.update_node(pecan.request.context,
rpc_node, topic=topic)
- @wsme_pecan.wsexpose(None, types.uuid_or_name, wtypes.text,
+ @expose.expose(None, types.uuid_or_name, wtypes.text,
status_code=202)
def put(self, node_ident, reason=None):
"""Put the node in maintenance mode.
@@ -739,7 +744,7 @@ class NodeMaintenanceController(rest.RestController):
"""
self._set_maintenance(node_ident, True, reason=reason)
- @wsme_pecan.wsexpose(None, types.uuid_or_name, status_code=202)
+ @expose.expose(None, types.uuid_or_name, status_code=202)
def delete(self, node_ident):
"""Remove the node from maintenance mode.
@@ -832,7 +837,7 @@ class NodesController(rest.RestController):
except exception.InstanceNotFound:
return []
- @wsme_pecan.wsexpose(NodeCollection, types.uuid, types.uuid,
+ @expose.expose(NodeCollection, types.uuid, types.uuid,
types.boolean, types.boolean, types.uuid, int, wtypes.text,
wtypes.text)
def get_all(self, chassis_uuid=None, instance_uuid=None, associated=None,
@@ -859,7 +864,7 @@ class NodesController(rest.RestController):
associated, maintenance, marker,
limit, sort_key, sort_dir)
- @wsme_pecan.wsexpose(NodeCollection, types.uuid, types.uuid,
+ @expose.expose(NodeCollection, types.uuid, types.uuid,
types.boolean, types.boolean, types.uuid, int, wtypes.text,
wtypes.text)
def detail(self, chassis_uuid=None, instance_uuid=None, associated=None,
@@ -894,7 +899,7 @@ class NodesController(rest.RestController):
limit, sort_key, sort_dir, expand,
resource_url)
- @wsme_pecan.wsexpose(wtypes.text, types.uuid_or_name, types.uuid)
+ @expose.expose(wtypes.text, types.uuid_or_name, types.uuid)
def validate(self, node=None, node_uuid=None):
"""Validate the driver interfaces, using the node's UUID or name.
@@ -917,7 +922,7 @@ class NodesController(rest.RestController):
return pecan.request.rpcapi.validate_driver_interfaces(
pecan.request.context, rpc_node.uuid, topic)
- @wsme_pecan.wsexpose(Node, types.uuid_or_name)
+ @expose.expose(Node, types.uuid_or_name)
def get_one(self, node_ident):
"""Retrieve information about the given node.
@@ -929,7 +934,7 @@ class NodesController(rest.RestController):
rpc_node = api_utils.get_rpc_node(node_ident)
return Node.convert_with_links(rpc_node)
- @wsme_pecan.wsexpose(Node, body=Node, status_code=201)
+ @expose.expose(Node, body=Node, status_code=201)
def post(self, node):
"""Create a new node.
@@ -972,7 +977,7 @@ class NodesController(rest.RestController):
return Node.convert_with_links(new_node)
@wsme.validate(types.uuid, [NodePatchType])
- @wsme_pecan.wsexpose(Node, types.uuid_or_name, body=[NodePatchType])
+ @expose.expose(Node, types.uuid_or_name, body=[NodePatchType])
def patch(self, node_ident, patch):
"""Update an existing node.
@@ -1066,7 +1071,7 @@ class NodesController(rest.RestController):
return Node.convert_with_links(new_node)
- @wsme_pecan.wsexpose(None, types.uuid_or_name, status_code=204)
+ @expose.expose(None, types.uuid_or_name, status_code=204)
def delete(self, node_ident):
"""Delete a node.
diff --git a/ironic/api/controllers/v1/port.py b/ironic/api/controllers/v1/port.py
index c841a337d..9ceaafb1f 100644
--- a/ironic/api/controllers/v1/port.py
+++ b/ironic/api/controllers/v1/port.py
@@ -20,13 +20,13 @@ import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
-import wsmeext.pecan as wsme_pecan
from ironic.api.controllers import base
from ironic.api.controllers import link
from ironic.api.controllers.v1 import collection
from ironic.api.controllers.v1 import types
from ironic.api.controllers.v1 import utils as api_utils
+from ironic.api import expose
from ironic.common import exception
from ironic.common.i18n import _
from ironic import objects
@@ -228,7 +228,7 @@ class PortsController(rest.RestController):
except exception.PortNotFound:
return []
- @wsme_pecan.wsexpose(PortCollection, types.uuid_or_name, types.uuid,
+ @expose.expose(PortCollection, types.uuid_or_name, types.uuid,
types.macaddress, types.uuid, int, wtypes.text,
wtypes.text)
def get_all(self, node=None, node_uuid=None, address=None, marker=None,
@@ -260,7 +260,7 @@ class PortsController(rest.RestController):
return self._get_ports_collection(node_uuid or node, address, marker,
limit, sort_key, sort_dir)
- @wsme_pecan.wsexpose(PortCollection, types.uuid_or_name, types.uuid,
+ @expose.expose(PortCollection, types.uuid_or_name, types.uuid,
types.macaddress, types.uuid, int, wtypes.text,
wtypes.text)
def detail(self, node=None, node_uuid=None, address=None, marker=None,
@@ -300,7 +300,7 @@ class PortsController(rest.RestController):
limit, sort_key, sort_dir, expand,
resource_url)
- @wsme_pecan.wsexpose(Port, types.uuid)
+ @expose.expose(Port, types.uuid)
def get_one(self, port_uuid):
"""Retrieve information about the given port.
@@ -312,7 +312,7 @@ class PortsController(rest.RestController):
rpc_port = objects.Port.get_by_uuid(pecan.request.context, port_uuid)
return Port.convert_with_links(rpc_port)
- @wsme_pecan.wsexpose(Port, body=Port, status_code=201)
+ @expose.expose(Port, body=Port, status_code=201)
def post(self, port):
"""Create a new port.
@@ -329,7 +329,7 @@ class PortsController(rest.RestController):
return Port.convert_with_links(new_port)
@wsme.validate(types.uuid, [PortPatchType])
- @wsme_pecan.wsexpose(Port, types.uuid, body=[PortPatchType])
+ @expose.expose(Port, types.uuid, body=[PortPatchType])
def patch(self, port_uuid, patch):
"""Update an existing port.
@@ -372,7 +372,7 @@ class PortsController(rest.RestController):
return Port.convert_with_links(new_port)
- @wsme_pecan.wsexpose(None, types.uuid, status_code=204)
+ @expose.expose(None, types.uuid, status_code=204)
def delete(self, port_uuid):
"""Delete a port.
diff --git a/ironic/api/expose.py b/ironic/api/expose.py
new file mode 100644
index 000000000..46d4649a6
--- /dev/null
+++ b/ironic/api/expose.py
@@ -0,0 +1,24 @@
+#
+# Copyright 2015 Rackspace, Inc
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import wsmeext.pecan as wsme_pecan
+
+
+def expose(*args, **kwargs):
+ """Ensure that only JSON, and not XML, is supported."""
+ if 'rest_content_types' not in kwargs:
+ kwargs['rest_content_types'] = ('json',)
+ return wsme_pecan.wsexpose(*args, **kwargs)
diff --git a/ironic/api/middleware/auth_token.py b/ironic/api/middleware/auth_token.py
index 539b0359d..053862ce6 100644
--- a/ironic/api/middleware/auth_token.py
+++ b/ironic/api/middleware/auth_token.py
@@ -32,6 +32,8 @@ class AuthTokenMiddleware(auth_token.AuthProtocol):
"""
def __init__(self, app, conf, public_api_routes=[]):
+ # TODO(mrda): Remove .xml and ensure that doesn't result in a
+ # 401 Authentication Required instead of 404 Not Found
route_pattern_tpl = '%s(\.json|\.xml)?$'
try:
diff --git a/ironic/common/glance_service/base_image_service.py b/ironic/common/glance_service/base_image_service.py
index 1b646d587..9a97cbbf0 100644
--- a/ironic/common/glance_service/base_image_service.py
+++ b/ironic/common/glance_service/base_image_service.py
@@ -22,6 +22,7 @@ import sys
import time
from glanceclient import client
+from glanceclient import exc as glance_exc
from oslo_config import cfg
import sendfile
import six.moves.urllib.parse as urlparse
@@ -36,23 +37,23 @@ CONF = cfg.CONF
def _translate_image_exception(image_id, exc_value):
- if isinstance(exc_value, (exception.Forbidden,
- exception.Unauthorized)):
+ if isinstance(exc_value, (glance_exc.Forbidden,
+ glance_exc.Unauthorized)):
return exception.ImageNotAuthorized(image_id=image_id)
- if isinstance(exc_value, exception.NotFound):
+ if isinstance(exc_value, glance_exc.NotFound):
return exception.ImageNotFound(image_id=image_id)
- if isinstance(exc_value, exception.BadRequest):
+ if isinstance(exc_value, glance_exc.BadRequest):
return exception.Invalid(exc_value)
return exc_value
def _translate_plain_exception(exc_value):
- if isinstance(exc_value, (exception.Forbidden,
- exception.Unauthorized)):
+ if isinstance(exc_value, (glance_exc.Forbidden,
+ glance_exc.Unauthorized)):
return exception.NotAuthorized(exc_value)
- if isinstance(exc_value, exception.NotFound):
+ if isinstance(exc_value, glance_exc.NotFound):
return exception.NotFound(exc_value)
- if isinstance(exc_value, exception.BadRequest):
+ if isinstance(exc_value, glance_exc.BadRequest):
return exception.Invalid(exc_value)
return exc_value
@@ -109,13 +110,13 @@ class BaseImageService(object):
:raises: GlanceConnectionFailed
"""
- retry_excs = (exception.ServiceUnavailable,
- exception.InvalidEndpoint,
- exception.CommunicationError)
- image_excs = (exception.Forbidden,
- exception.Unauthorized,
- exception.NotFound,
- exception.BadRequest)
+ retry_excs = (glance_exc.ServiceUnavailable,
+ glance_exc.InvalidEndpoint,
+ glance_exc.CommunicationError)
+ image_excs = (glance_exc.Forbidden,
+ glance_exc.Unauthorized,
+ glance_exc.NotFound,
+ glance_exc.BadRequest)
num_attempts = 1 + CONF.glance.glance_num_retries
for attempt in range(1, num_attempts + 1):
diff --git a/ironic/common/image_service.py b/ironic/common/image_service.py
index 57ab194d7..e4bef4ae3 100644
--- a/ironic/common/image_service.py
+++ b/ironic/common/image_service.py
@@ -65,8 +65,9 @@ glance_opts = [
'glance.'),
cfg.StrOpt('auth_strategy',
default='keystone',
- help='Default protocol to use when connecting to glance. '
- 'Set to https for SSL.'),
+ help='Authentication strategy to use when connecting to '
+ 'glance. Only "keystone" and "noauth" are currently '
+ 'supported by ironic.'),
]
CONF.register_opts(glance_opts, group='glance')
diff --git a/ironic/common/pxe_utils.py b/ironic/common/pxe_utils.py
index 8174eccc4..09528a218 100644
--- a/ironic/common/pxe_utils.py
+++ b/ironic/common/pxe_utils.py
@@ -23,6 +23,7 @@ from ironic.common import dhcp_factory
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import utils
+from ironic.drivers.modules import deploy_utils
from ironic.drivers import utils as driver_utils
from ironic.openstack.common import fileutils
from ironic.openstack.common import log as logging
@@ -79,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.
@@ -109,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)
@@ -191,7 +203,7 @@ def create_pxe_config(task, pxe_options, template=None):
pxe_config = _build_pxe_config(pxe_options, template)
utils.write_to_file(pxe_config_file_path, pxe_config)
- if driver_utils.get_boot_mode_for_deploy(task.node) == 'uefi':
+ if deploy_utils.get_boot_mode_for_deploy(task.node) == 'uefi':
_link_ip_address_pxe_configs(task)
else:
_link_mac_pxe_configs(task)
@@ -205,7 +217,7 @@ def clean_up_pxe_config(task):
"""
LOG.debug("Cleaning up PXE config for node %s", task.node.uuid)
- if driver_utils.get_boot_mode_for_deploy(task.node) == 'uefi':
+ if deploy_utils.get_boot_mode_for_deploy(task.node) == 'uefi':
api = dhcp_factory.DHCPFactory().provider
ip_addresses = api.get_ip_addresses(task)
if not ip_addresses:
@@ -220,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))
@@ -252,7 +270,7 @@ def dhcp_options_for_instance(task):
dhcp_opts.append({'opt_name': 'bootfile-name',
'opt_value': ipxe_script_url})
else:
- if driver_utils.get_boot_mode_for_deploy(task.node) == 'uefi':
+ if deploy_utils.get_boot_mode_for_deploy(task.node) == 'uefi':
boot_file = CONF.pxe.uefi_pxe_bootfile_name
else:
boot_file = CONF.pxe.pxe_bootfile_name
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 7a8aff5f9..79fcaecd3 100644
--- a/ironic/drivers/modules/deploy_utils.py
+++ b/ironic/drivers/modules/deploy_utils.py
@@ -751,8 +751,14 @@ def _iscsi_setup_and_handle_errors(address, port, iqn, lun,
delete_iscsi(address, port, iqn)
-def notify_deploy_complete(address):
- """Notifies the completion of deployment to the baremetal node.
+def notify_ramdisk_to_proceed(address):
+ """Notifies the ramdisk waiting for instructions from Ironic.
+
+ DIB ramdisk (from init script) makes vendor passhthrus and listens
+ on port 10000 for Ironic to notify back the completion of the task.
+ This method connects to port 10000 of the bare metal running the
+ ramdisk and then sends some data to notify the ramdisk to proceed
+ with it's next task.
:param address: The IP address of the node.
"""
@@ -964,7 +970,7 @@ def try_set_boot_device(task, device, persistent=True):
manager_utils.node_set_boot_device(task, device,
persistent=persistent)
except exception.IPMIFailure:
- if driver_utils.get_boot_mode_for_deploy(task.node) == 'uefi':
+ if get_boot_mode_for_deploy(task.node) == 'uefi':
LOG.warning(_LW("ipmitool is unable to set boot device while "
"the node %s is in UEFI boot mode. Please set "
"the boot device manually.") % task.node.uuid)
@@ -1035,3 +1041,35 @@ def is_secure_boot_requested(node):
sec_boot = capabilities.get('secure_boot', 'false').lower()
return sec_boot == 'true'
+
+
+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:
+ 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.
+ It would return None if boot mode is present neither in 'capabilities' of
+ node 'properties' nor in node's 'instance_info'.
+
+ :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
+
+ boot_mode = driver_utils.get_node_capability(node, 'boot_mode')
+ if boot_mode is None:
+ instance_info = node.instance_info
+ boot_mode = instance_info.get('deploy_boot_mode')
+
+ LOG.debug('Deploy boot mode is %(boot_mode)s for %(node)s.',
+ {'boot_mode': boot_mode, 'node': node.uuid})
+ return boot_mode
diff --git a/ironic/drivers/modules/ilo/common.py b/ironic/drivers/modules/ilo/common.py
index f2281c671..02085ea5d 100644
--- a/ironic/drivers/modules/ilo/common.py
+++ b/ironic/drivers/modules/ilo/common.py
@@ -30,7 +30,7 @@ from ironic.common.i18n import _LI
from ironic.common import images
from ironic.common import swift
from ironic.common import utils
-from ironic.drivers import utils as driver_utils
+from ironic.drivers.modules import deploy_utils
from ironic.openstack.common import log as logging
ilo_client = importutils.try_import('proliantutils.ilo.client')
@@ -343,7 +343,7 @@ def update_boot_mode(task):
"""
node = task.node
- boot_mode = driver_utils.get_boot_mode_for_deploy(node)
+ boot_mode = deploy_utils.get_boot_mode_for_deploy(node)
if boot_mode is not None:
LOG.debug("Node %(uuid)s boot mode is being set to %(boot_mode)s",
@@ -360,7 +360,7 @@ def update_boot_mode(task):
boot_mode = 'legacy'
if boot_mode != 'UNKNOWN':
- boot_mode = BOOT_MODE_ILO_TO_GENERIC[boot_mode.lower()]
+ boot_mode = BOOT_MODE_ILO_TO_GENERIC[boot_mode.lower()]
if boot_mode == 'UNKNOWN':
# NOTE(faizan) ILO will return this in remote cases and mostly on
diff --git a/ironic/drivers/modules/ilo/deploy.py b/ironic/drivers/modules/ilo/deploy.py
index af79ca93f..dbd9d7340 100644
--- a/ironic/drivers/modules/ilo/deploy.py
+++ b/ironic/drivers/modules/ilo/deploy.py
@@ -160,7 +160,7 @@ def _get_boot_iso(task, root_uuid):
# Option 3 - Create boot_iso from kernel/ramdisk, upload to Swift
# and provide its name.
deploy_iso_uuid = deploy_info['ilo_deploy_iso']
- boot_mode = driver_utils.get_boot_mode_for_deploy(task.node)
+ boot_mode = deploy_utils.get_boot_mode_for_deploy(task.node)
boot_iso_object_name = _get_boot_iso_object_name(task.node)
kernel_params = CONF.pxe.pxe_append_params
container = CONF.ilo.swift_ilo_container
@@ -332,13 +332,17 @@ def _prepare_node_for_deploy(task):
if _disable_secure_boot(task):
change_boot_mode = False
- # Set boot_mode capability to uefi for secure boot
- if deploy_utils.is_secure_boot_requested(task.node):
- LOG.debug('Secure boot deploy requested for node %s', task.node.uuid)
- _enable_uefi_capability(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.
+ # 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
+ instance_info['deploy_boot_mode'] = 'uefi'
+ task.node.instance_info = instance_info
+ task.node.save()
def _update_secure_boot_mode(task, mode):
@@ -363,15 +367,6 @@ def _update_secure_boot_mode(task, mode):
{'mode': mode, 'node': task.node.uuid})
-def _enable_uefi_capability(task):
- """Adds capability boot_mode='uefi' into Node property.
-
- :param task: a TaskManager instance containing the node to act on.
- """
- driver_utils.rm_node_capability(task, 'boot_mode')
- driver_utils.add_node_capability(task, 'boot_mode', 'uefi')
-
-
class IloVirtualMediaIscsiDeploy(base.DeployInterface):
def get_properties(self):
@@ -624,6 +619,10 @@ class IloVirtualMediaAgentVendorInterface(agent.AgentVendorInterface):
error = self.check_deploy_success(node)
if error is None:
+ # Set boot mode
+ ilo_common.update_boot_mode(task)
+
+ # Need to enable secure boot, if being requested
_update_secure_boot_mode(task, True)
super(IloVirtualMediaAgentVendorInterface,
@@ -730,6 +729,8 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
"""
if method == 'pass_deploy_info':
iscsi_deploy.get_deploy_info(task.node, **kwargs)
+ elif method == 'pass_bootloader_install_info':
+ iscsi_deploy.validate_pass_bootloader_info_input(task, kwargs)
def _configure_vmedia_boot(self, task, root_uuid):
"""Configure vmedia boot for the node."""
@@ -754,6 +755,29 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
@base.passthru(['POST'])
@task_manager.require_exclusive_lock
+ def pass_bootloader_install_info(self, task, **kwargs):
+ """Accepts the results of bootloader installation.
+
+ This method acts as a vendor passthru and accepts the result of
+ bootloader installation. If the bootloader installation was
+ successful, then it notifies the baremetal to proceed to reboot
+ and makes the instance active. If bootloader installation failed,
+ then it sets provisioning as failed and powers off the node.
+
+ :param task: A TaskManager object.
+ :param kwargs: The arguments sent with vendor passthru. The expected
+ kwargs are::
+ 'key': The deploy key for authorization
+ 'status': 'SUCCEEDED' or 'FAILED'
+ 'error': The error message if status == 'FAILED'
+ 'address': The IP address of the ramdisk
+ """
+ task.process_event('resume')
+ iscsi_deploy.validate_bootloader_install_status(task, kwargs)
+ iscsi_deploy.finish_deploy(task, kwargs['address'])
+
+ @base.passthru(['POST'])
+ @task_manager.require_exclusive_lock
def pass_deploy_info(self, task, **kwargs):
"""Continues the iSCSI deployment from where ramdisk left off.
@@ -782,30 +806,36 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
'root uuid', uuid_dict.get('disk identifier'))
try:
- # For iscsi_ilo driver, we boot from disk every time if the image
- # deployed is a whole disk image.
- if iscsi_deploy.get_boot_option(node) == "local" or iwdi:
- manager_utils.node_set_boot_device(task, boot_devices.DISK,
- persistent=True)
- else:
- self._configure_vmedia_boot(task, root_uuid_or_disk_id)
-
# Set boot mode
ilo_common.update_boot_mode(task)
# Need to enable secure boot, if being requested
_update_secure_boot_mode(task, True)
- deploy_utils.notify_deploy_complete(kwargs.get('address'))
+ # For iscsi_ilo driver, we boot from disk every time if the image
+ # deployed is a whole disk image.
+ if iscsi_deploy.get_boot_option(node) == "local" or iwdi:
+ manager_utils.node_set_boot_device(task, boot_devices.DISK,
+ persistent=True)
- LOG.info(_LI('Deployment to node %s done'), node.uuid)
- task.process_event('done')
+ # Ask the ramdisk to install bootloader and
+ # wait for the call-back through the vendor passthru
+ # 'pass_bootloader_install_info', if it's not a whole
+ # disk image.
+ if not iwdi:
+ deploy_utils.notify_ramdisk_to_proceed(kwargs['address'])
+ task.process_event('wait')
+ return
+ else:
+ self._configure_vmedia_boot(task, root_uuid_or_disk_id)
except Exception as e:
LOG.error(_LE('Deploy failed for instance %(instance)s. '
'Error: %(error)s'),
{'instance': node.instance_uuid, 'error': e})
msg = _('Failed to continue iSCSI deployment.')
deploy_utils.set_failed_state(task, msg)
+ else:
+ iscsi_deploy.finish_deploy(task, kwargs.get('address'))
@task_manager.require_exclusive_lock
def continue_deploy(self, task, **kwargs):
diff --git a/ironic/drivers/modules/ipmitool.py b/ironic/drivers/modules/ipmitool.py
index 2826dd1e7..ddebd68ef 100644
--- a/ironic/drivers/modules/ipmitool.py
+++ b/ironic/drivers/modules/ipmitool.py
@@ -111,6 +111,13 @@ 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
+# 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']
+
def _check_option_support(options):
"""Checks if the specific ipmitool options are supported on host.
@@ -335,32 +342,68 @@ def _exec_ipmitool(driver_info, command):
args.append(driver_info[name])
# specify retry timing more precisely, if supported
+ num_tries = max(
+ (CONF.ipmi.retry_timeout // CONF.ipmi.min_command_interval), 1)
+
if _is_option_supported('timing'):
- num_tries = max(
- (CONF.ipmi.retry_timeout // CONF.ipmi.min_command_interval), 1)
args.append('-R')
args.append(str(num_tries))
args.append('-N')
args.append(str(CONF.ipmi.min_command_interval))
- # 'ipmitool' command will prompt password if there is no '-f' option,
- # we set it to '\0' to write a password file to support empty password
- with _make_password_file(driver_info['password'] or '\0') as pw_file:
- args.append('-f')
- args.append(pw_file)
- args.extend(command.split(" "))
+ 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.time() - LAST_CMD_TIME.get(driver_info['address'], 0))
if time_till_next_poll > 0:
time.sleep(time_till_next_poll)
- try:
- out, err = utils.execute(*args)
- finally:
- LAST_CMD_TIME[driver_info['address']] = time.time()
- return out, err
+ # Resetting the list that will be utilized so the password arguments
+ # from any previous execution are preserved.
+ cmd_args = args[:]
+ # 'ipmitool' command will prompt password if there is no '-f'
+ # option, we set it to '\0' to write a password file to support
+ # empty password
+ with _make_password_file(
+ driver_info['password'] or '\0'
+ ) as pw_file:
+ cmd_args.append('-f')
+ cmd_args.append(pw_file)
+ cmd_args.extend(command.split(" "))
+ try:
+ out, err = utils.execute(*cmd_args)
+ return out, err
+ except processutils.ProcessExecutionError as e:
+ with excutils.save_and_reraise_exception() as ctxt:
+ err_list = [x for x in IPMITOOL_RETRYABLE_FAILURES
+ if x in e.message]
+ if ((time.time() > end_time) or
+ (num_tries == 0) or
+ not err_list):
+ 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
+ })
+ else:
+ ctxt.reraise = False
+ LOG.warning(_LW('IPMI Error encountered, retrying '
+ '"%(cmd)s" for node %(node)s. '
+ 'Error: %(error)s'),
+ {
+ 'node': driver_info['uuid'],
+ 'cmd': e.cmd,
+ 'error': e
+ })
+ finally:
+ LAST_CMD_TIME[driver_info['address']] = time.time()
def _sleep_time(iter):
diff --git a/ironic/drivers/modules/iscsi_deploy.py b/ironic/drivers/modules/iscsi_deploy.py
index 858412944..220e204a3 100644
--- a/ironic/drivers/modules/iscsi_deploy.py
+++ b/ironic/drivers/modules/iscsi_deploy.py
@@ -22,9 +22,13 @@ from six.moves.urllib import parse
from ironic.common import exception
from ironic.common.glance_service import service_utils as glance_service_utils
from ironic.common.i18n import _
+from ironic.common.i18n import _LE
+from ironic.common.i18n import _LI
from ironic.common import image_service as service
from ironic.common import keystone
+from ironic.common import states
from ironic.common import utils
+from ironic.conductor import utils as manager_utils
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules import image_cache
from ironic.drivers import utils as driver_utils
@@ -416,7 +420,7 @@ def _get_boot_mode(node):
:param node: A single Node.
:returns: A string representing the boot mode type. Defaults to 'bios'.
"""
- boot_mode = driver_utils.get_boot_mode_for_deploy(node)
+ boot_mode = deploy_utils.get_boot_mode_for_deploy(node)
if boot_mode:
return boot_mode.lower()
return "bios"
@@ -443,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,
@@ -539,3 +550,87 @@ def validate(task):
# Validate the root device hints
deploy_utils.parse_root_device_hints(node)
+
+
+def validate_pass_bootloader_info_input(task, input_params):
+ """Validates the input sent with bootloader install info passthru.
+
+ This method validates the input sent with bootloader install info
+ passthru.
+
+ :param task: A TaskManager object.
+ :param input_params: A dictionary of params sent as input to passthru.
+ :raises: InvalidParameterValue, if deploy key passed doesn't match the
+ one stored in instance_info.
+ :raises: MissingParameterValue, if some input is missing.
+ """
+ params = {'address': input_params.get('address'),
+ 'key': input_params.get('key'),
+ 'status': input_params.get('status')}
+ msg = _("Some mandatory input missing in 'pass_bootloader_info' "
+ "vendor passthru from ramdisk.")
+ deploy_utils.check_for_missing_params(params, msg)
+
+ deploy_key = task.node.instance_info['deploy_key']
+ if deploy_key != input_params.get('key'):
+ raise exception.InvalidParameterValue(
+ _("Deploy key %(key_sent)s does not match "
+ "with %(expected_key)s") %
+ {'key_sent': input_params.get('key'), 'expected_key': deploy_key})
+
+
+def validate_bootloader_install_status(task, input_params):
+ """Validate if bootloader was installed.
+
+ This method first validates if deploy key sent in vendor passthru
+ was correct one, and then validates whether bootloader installation
+ was successful or not.
+
+ :param task: A TaskManager object.
+ :param input_params: A dictionary of params sent as input to passthru.
+ :raises: InstanceDeployFailure, if bootloader installation was
+ reported from ramdisk as failure.
+ """
+ if input_params['status'] != 'SUCCEEDED':
+ msg = (_('Failed to install bootloader on node %(node)s. '
+ 'Error: %(error)s.') %
+ {'node': task.node.uuid, 'error': input_params.get('error')})
+ LOG.error(msg)
+ deploy_utils.set_failed_state(task, msg)
+ raise exception.InstanceDeployFailure(msg)
+
+
+def finish_deploy(task, address):
+ """Notifies the ramdisk to reboot the node and makes the instance active.
+
+ This method notifies the ramdisk to proceed to reboot and then
+ makes the instance active.
+
+ :param task: a TaskManager object.
+ :param address: The IP address of the bare metal node.
+ :raises: InstanceDeployFailure, if notifying ramdisk failed.
+ """
+ node = task.node
+ try:
+ deploy_utils.notify_ramdisk_to_proceed(address)
+ except Exception as e:
+ LOG.error(_LE('Deploy failed for instance %(instance)s. '
+ 'Error: %(error)s'),
+ {'instance': node.instance_uuid, 'error': e})
+ msg = (_('Failed to notify ramdisk to reboot after bootloader '
+ 'installation. Error: %s') % e)
+ deploy_utils.set_failed_state(task, msg)
+ raise exception.InstanceDeployFailure(msg)
+
+ # TODO(lucasagomes): When deploying a node with the DIB ramdisk
+ # Ironic will not power control the node at the end of the deployment,
+ # it's the DIB ramdisk that reboots the node. But, for the SSH driver
+ # some changes like setting the boot device only gets applied when the
+ # machine is powered off and on again. So the code below is enforcing
+ # it. For Liberty we need to change the DIB ramdisk so that Ironic
+ # always controls the power state of the node for all drivers.
+ if get_boot_option(node) == "local" and 'ssh' in node.driver:
+ manager_utils.node_power_action(task, states.REBOOT)
+
+ LOG.info(_LI('Deployment to node %s done'), node.uuid)
+ task.process_event('done')
diff --git a/ironic/drivers/modules/pxe.py b/ironic/drivers/modules/pxe.py
index 9a63c7af5..9ab3fc32c 100644
--- a/ironic/drivers/modules/pxe.py
+++ b/ironic/drivers/modules/pxe.py
@@ -28,7 +28,6 @@ from ironic.common import exception
from ironic.common.glance_service import service_utils
from ironic.common.i18n import _
from ironic.common.i18n import _LE
-from ironic.common.i18n import _LI
from ironic.common.i18n import _LW
from ironic.common import image_service as service
from ironic.common import keystone
@@ -243,7 +242,7 @@ def validate_boot_option_for_uefi(node):
:raises: InvalidParameterValue
"""
- boot_mode = driver_utils.get_boot_mode_for_deploy(node)
+ boot_mode = deploy_utils.get_boot_mode_for_deploy(node)
boot_option = iscsi_deploy.get_boot_option(node)
if (boot_mode == 'uefi' and
node.driver_internal_info.get('is_whole_disk_image') and
@@ -354,7 +353,7 @@ class PXEDeploy(base.DeployInterface):
driver_utils.validate_boot_mode_capability(node)
driver_utils.validate_boot_option_capability(node)
- boot_mode = driver_utils.get_boot_mode_for_deploy(task.node)
+ boot_mode = deploy_utils.get_boot_mode_for_deploy(task.node)
if CONF.pxe.ipxe_enabled:
if not CONF.pxe.http_url or not CONF.pxe.http_root:
@@ -448,7 +447,7 @@ class PXEDeploy(base.DeployInterface):
pxe_options = _build_pxe_config_options(task.node, pxe_info,
task.context)
- if driver_utils.get_boot_mode_for_deploy(task.node) == 'uefi':
+ if deploy_utils.get_boot_mode_for_deploy(task.node) == 'uefi':
pxe_config_template = CONF.pxe.uefi_pxe_config_template
else:
pxe_config_template = CONF.pxe.pxe_config_template
@@ -486,7 +485,7 @@ class PXEDeploy(base.DeployInterface):
task.node.uuid)
deploy_utils.switch_pxe_config(
pxe_config_path, root_uuid_or_disk_id,
- driver_utils.get_boot_mode_for_deploy(task.node),
+ deploy_utils.get_boot_mode_for_deploy(task.node),
iwdi)
def clean_up(self, task):
@@ -544,6 +543,7 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
Valid methods:
* pass_deploy_info
+ * pass_bootloader_install_info
:param task: a TaskManager instance containing the node to act on.
:param method: method to be validated.
@@ -553,6 +553,30 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
if method == 'pass_deploy_info':
driver_utils.validate_boot_option_capability(task.node)
iscsi_deploy.get_deploy_info(task.node, **kwargs)
+ elif method == 'pass_bootloader_install_info':
+ iscsi_deploy.validate_pass_bootloader_info_input(task, kwargs)
+
+ @base.passthru(['POST'])
+ @task_manager.require_exclusive_lock
+ def pass_bootloader_install_info(self, task, **kwargs):
+ """Accepts the results of bootloader installation.
+
+ This method acts as a vendor passthru and accepts the result of
+ the bootloader installation. If bootloader installation was
+ successful, then it notifies the bare metal to proceed to reboot
+ and makes the instance active. If the bootloader installation failed,
+ then it sets provisioning as failed and powers off the node.
+ :param task: A TaskManager object.
+ :param kwargs: The arguments sent with vendor passthru. The expected
+ kwargs are::
+ 'key': The deploy key for authorization
+ 'status': 'SUCCEEDED' or 'FAILED'
+ 'error': The error message if status == 'FAILED'
+ 'address': The IP address of the ramdisk
+ """
+ task.process_event('resume')
+ iscsi_deploy.validate_bootloader_install_status(task, kwargs)
+ iscsi_deploy.finish_deploy(task, kwargs['address'])
@base.passthru(['POST'])
@task_manager.require_exclusive_lock
@@ -587,25 +611,34 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
try:
if iscsi_deploy.get_boot_option(node) == "local":
deploy_utils.try_set_boot_device(task, boot_devices.DISK)
+
# If it's going to boot from the local disk, get rid of
# the PXE configuration files used for the deployment
pxe_utils.clean_up_pxe_config(task)
+
+ # Ask the ramdisk to install bootloader and
+ # wait for the call-back through the vendor passthru
+ # 'pass_bootloader_install_info', if it's not a
+ # whole disk image.
+ if not is_whole_disk_image:
+ deploy_utils.notify_ramdisk_to_proceed(kwargs['address'])
+ task.process_event('wait')
+ return
else:
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
- boot_mode = driver_utils.get_boot_mode_for_deploy(node)
+ boot_mode = deploy_utils.get_boot_mode_for_deploy(node)
deploy_utils.switch_pxe_config(pxe_config_path,
root_uuid_or_disk_id,
boot_mode, is_whole_disk_image)
- deploy_utils.notify_deploy_complete(kwargs['address'])
- LOG.info(_LI('Deployment to node %s done'), node.uuid)
- task.process_event('done')
except Exception as e:
LOG.error(_LE('Deploy failed for instance %(instance)s. '
'Error: %(error)s'),
{'instance': node.instance_uuid, 'error': e})
msg = _('Failed to continue iSCSI deployment.')
deploy_utils.set_failed_state(task, msg)
+ else:
+ iscsi_deploy.finish_deploy(task, kwargs.get('address'))
@task_manager.require_exclusive_lock
def continue_deploy(self, task, **kwargs):
@@ -648,7 +681,7 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
root_uuid_or_disk_id = uuid_dict.get(
'root uuid', uuid_dict.get('disk identifier'))
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
- boot_mode = driver_utils.get_boot_mode_for_deploy(node)
+ boot_mode = deploy_utils.get_boot_mode_for_deploy(node)
deploy_utils.switch_pxe_config(pxe_config_path,
root_uuid_or_disk_id,
boot_mode, is_whole_disk_image)
diff --git a/ironic/drivers/utils.py b/ironic/drivers/utils.py
index 9c34a49ac..7caa6cabe 100644
--- a/ironic/drivers/utils.py
+++ b/ironic/drivers/utils.py
@@ -144,33 +144,6 @@ def get_node_capability(node, capability):
"Format should be 'key:val'."), node_capability)
-def rm_node_capability(task, capability):
- """Remove 'capability' from node's 'capabilities' property.
-
- :param task: Task object.
- :param capability: Capability key.
-
- """
- node = task.node
- properties = node.properties
- capabilities = properties.get('capabilities')
-
- if not capabilities:
- return
-
- caps = []
- for cap in capabilities.split(','):
- parts = cap.split(':')
- if len(parts) == 2 and parts[0] and parts[1]:
- if parts[0] == capability:
- continue
- caps.append(cap)
- new_cap_str = ",".join(caps)
- properties['capabilities'] = new_cap_str if new_cap_str else None
- node.properties = properties
- node.save()
-
-
def add_node_capability(task, capability, value):
"""Add 'capability' to node's 'capabilities' property.
@@ -250,28 +223,3 @@ def validate_secure_boot_capability(node):
"""
validate_capability(node, 'secure_boot', ('true', 'false'))
-
-
-def get_boot_mode_for_deploy(node):
- """Returns the boot mode that would be used for deploy.
-
- This method returns deploy_boot_mode available in node field
- boot_mode from 'capabilities' of node 'properties'.
- Otherwise returns boot mode specified in node's 'instance_info'.
-
- :param node: an ironic node object.
- :returns: Value of boot mode that would be used for deploy.
- Possible values are 'bios', 'uefi'.
- It would return None if boot mode is present neither
- in 'capabilities' of node 'properties' nor in node's
- 'instance_info'.
-
- """
- boot_mode = get_node_capability(node, 'boot_mode')
- if boot_mode is None:
- instance_info = node.instance_info
- boot_mode = instance_info.get('deploy_boot_mode', None)
-
- LOG.debug('Deploy boot mode is %(boot_mode)s for %(node)s.',
- {'boot_mode': boot_mode, 'node': node.uuid})
- return boot_mode
diff --git a/ironic/locale/ironic-log-error.pot b/ironic/locale/ironic-log-error.pot
index ec19ab67e..1e63a60d5 100644
--- a/ironic/locale/ironic-log-error.pot
+++ b/ironic/locale/ironic-log-error.pot
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: ironic 2015.1.dev31\n"
+"Project-Id-Version: ironic 2015.1.dev139\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2015-02-18 06:15+0000\n"
+"POT-Creation-Date: 2015-04-08 06:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -26,40 +26,48 @@ msgstr ""
msgid "Exception in string format operation"
msgstr ""
-#: ironic/common/images.py:132
+#: ironic/common/images.py:148
#, python-format
msgid "vfat image creation failed. Error: %s"
msgstr ""
-#: ironic/common/images.py:200
+#: ironic/common/images.py:218 ironic/common/images.py:284
msgid "Creating the filesystem root failed."
msgstr ""
-#: ironic/common/images.py:214
+#: ironic/common/images.py:233 ironic/common/images.py:310
msgid "Creating ISO image failed."
msgstr ""
-#: ironic/common/service.py:89
+#: ironic/common/images.py:540
+msgid "mounting the deploy iso failed."
+msgstr ""
+
+#: ironic/common/images.py:554
+msgid "examining the deploy iso failed."
+msgstr ""
+
+#: ironic/common/service.py:92
#, python-format
msgid "Service error occurred when stopping the RPC server. Error: %s"
msgstr ""
-#: ironic/common/service.py:94
+#: ironic/common/service.py:97
#, python-format
msgid "Service error occurred when cleaning up the RPC manager. Error: %s"
msgstr ""
-#: ironic/common/utils.py:398
+#: ironic/common/utils.py:401
#, python-format
msgid "Could not remove tmpdir: %s"
msgstr ""
-#: ironic/common/utils.py:429
+#: ironic/common/utils.py:432
#, python-format
msgid "Failed to make file system. File system %s is not supported."
msgstr ""
-#: ironic/common/utils.py:433
+#: ironic/common/utils.py:436
#, python-format
msgid "Failed to create a file system in %(path)s. Error: %(error)s"
msgstr ""
@@ -71,7 +79,7 @@ msgid ""
"attempt %(attempt)s of %(num_attempts)s failed."
msgstr ""
-#: ironic/conductor/manager.py:224
+#: ironic/conductor/manager.py:256
#, python-format
msgid ""
"Conductor %s cannot be started because no drivers were loaded. This "
@@ -79,110 +87,128 @@ msgid ""
"option."
msgstr ""
-#: ironic/conductor/manager.py:1029
+#: ironic/conductor/manager.py:799
+#, python-format
+msgid "Error in tear_down of node %(node)s: %(err)s"
+msgstr ""
+
+#: ironic/conductor/manager.py:1284
#, python-format
msgid "Failed to stop console while deleting the node %(node)s: %(err)s."
msgstr ""
-#: ironic/conductor/manager.py:1510
+#: ironic/conductor/manager.py:1911
#, python-format
msgid "Unexpected state %(state)s returned while deploying node %(node)s."
msgstr ""
-#: ironic/conductor/manager.py:1637
+#: ironic/conductor/manager.py:2011
#, python-format
msgid ""
"Failed to change power state of node %(node)s to '%(state)s'. Attempts "
"left: %(left)s."
msgstr ""
-#: ironic/dhcp/neutron.py:124
+#: ironic/conductor/manager.py:2043
+#, python-format
+msgid "Failed to inspect node %(node)s: %(err)s"
+msgstr ""
+
+#: ironic/dhcp/neutron.py:128
#, python-format
msgid "Failed to update Neutron port %s."
msgstr ""
-#: ironic/dhcp/neutron.py:139
+#: ironic/dhcp/neutron.py:143
#, python-format
msgid "Failed to update MAC address on Neutron port %s."
msgstr ""
-#: ironic/dhcp/neutron.py:206
+#: ironic/dhcp/neutron.py:216
#, python-format
msgid "Failed to Get IP address on Neutron port %s."
msgstr ""
-#: ironic/dhcp/neutron.py:222
+#: ironic/dhcp/neutron.py:232
#, python-format
msgid "Neutron returned invalid IPv4 address %s."
msgstr ""
-#: ironic/dhcp/neutron.py:226
+#: ironic/dhcp/neutron.py:236
#, python-format
msgid "No IP address assigned to Neutron port %s."
msgstr ""
-#: ironic/drivers/base.py:407
+#: ironic/dhcp/neutron.py:379
+#, python-format
+msgid "Failed to rollback cleaning port changes for node %s"
+msgstr ""
+
+#: ironic/drivers/base.py:511
#, python-format
msgid "vendor_passthru failed with method %s"
msgstr ""
-#: ironic/drivers/modules/agent_base_vendor.py:168
+#: ironic/drivers/modules/agent.py:188
#, python-format
-msgid "Async exception for %(node)s: %(msg)s"
+msgid ""
+"Agent deploy supports only HTTP(S) URLs as instance_info['image_source']."
+" Either %s is not a valid HTTP(S) URL or is not reachable."
msgstr ""
-#: ironic/drivers/modules/agent_base_vendor.py:282
+#: ironic/drivers/modules/agent_base_vendor.py:374
#, python-format
msgid "Could not find matching node for the provided MACs %s."
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:354
+#: ironic/drivers/modules/deploy_utils.py:449
#, python-format
msgid ""
"Failed to erase beginning of disk for node %(node)s. Command: "
"%(command)s. Error: %(error)s."
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:366
+#: ironic/drivers/modules/deploy_utils.py:461
#, python-format
msgid ""
"Failed to get disk block count for node %(node)s. Command: %(command)s. "
"Error: %(error)s."
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:379
+#: ironic/drivers/modules/deploy_utils.py:474
#, python-format
msgid ""
"Failed to erase the end of the disk on node %(node)s. Command: "
"%(command)s. Error: %(error)s."
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:530
-msgid "Failed to detect root device UUID."
+#: ironic/drivers/modules/deploy_utils.py:646
+#, python-format
+msgid "Failed to detect %s"
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:572
-#: ironic/drivers/modules/deploy_utils.py:578
+#: ironic/drivers/modules/deploy_utils.py:741
+#: ironic/drivers/modules/deploy_utils.py:747
#, python-format
msgid "Deploy to address %s failed."
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:573
+#: ironic/drivers/modules/deploy_utils.py:742
#, python-format
msgid "Command: %s"
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:574
+#: ironic/drivers/modules/deploy_utils.py:743
#, python-format
msgid "StdOut: %r"
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:575
+#: ironic/drivers/modules/deploy_utils.py:744
#, python-format
msgid "StdErr: %r"
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:659
+#: ironic/drivers/modules/deploy_utils.py:832
#, python-format
msgid ""
"Node %s failed to power off while handling deploy failure. This may be a "
@@ -190,56 +216,74 @@ msgid ""
"maintenance mode until the problem is resolved."
msgstr ""
-#: ironic/drivers/modules/ipminative.py:263
+#: ironic/drivers/modules/discoverd.py:160
+#, python-format
+msgid ""
+"Exception during contacting ironic-discoverd for inspection of node "
+"%(node)s: %(err)s"
+msgstr ""
+
+#: ironic/drivers/modules/discoverd.py:191
+#, python-format
+msgid ""
+"Unexpected exception while getting inspection status for node %s, will "
+"retry later"
+msgstr ""
+
+#: ironic/drivers/modules/discoverd.py:197
+#, python-format
+msgid "Inspection failed for node %(uuid)s with error: %(err)s"
+msgstr ""
+
+#: ironic/drivers/modules/ipminative.py:268
#, python-format
msgid ""
"IPMI get sensor data failed for node %(node_id)s with the following "
"error: %(error)s"
msgstr ""
-#: ironic/drivers/modules/ipminative.py:414
+#: ironic/drivers/modules/ipminative.py:419
#, python-format
msgid ""
"IPMI set boot device failed for node %(node_id)s with the following "
"error: %(error)s"
msgstr ""
-#: ironic/drivers/modules/ipminative.py:449
+#: ironic/drivers/modules/ipminative.py:454
#, python-format
msgid ""
"IPMI get boot device failed for node %(node_id)s with the following "
"error: %(error)s"
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:413
+#: ironic/drivers/modules/ipmitool.py:424
#, python-format
msgid ""
"IPMI power %(state)s timed out after %(tries)s retries on node "
"%(node_id)s."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:573
+#: ironic/drivers/modules/ipmitool.py:584
#, python-format
msgid "IPMI \"raw bytes\" failed for node %(node_id)s with error: %(error)s."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:879
+#: ironic/drivers/modules/ipmitool.py:890
#, python-format
msgid "IPMI \"bmc reset\" failed for node %(node_id)s with error: %(error)s."
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:266
+#: ironic/drivers/modules/iscsi_deploy.py:610 ironic/drivers/modules/pxe.py:635
+#: ironic/drivers/modules/ilo/deploy.py:833
#, python-format
-msgid "Error returned from deploy ramdisk: %s"
+msgid "Deploy failed for instance %(instance)s. Error: %(error)s"
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:279 ironic/drivers/modules/pxe.py:515
-#: ironic/drivers/modules/ilo/deploy.py:525
-#, python-format
-msgid "Deploy failed for instance %(instance)s. Error: %(error)s"
+#: ironic/drivers/modules/pxe.py:250
+msgid "Whole disk image with netboot is not supported in UEFI boot mode."
msgstr ""
-#: ironic/drivers/modules/pxe.py:328
+#: ironic/drivers/modules/pxe.py:365
msgid "UEFI boot mode is not supported with iPXE boot enabled."
msgstr ""
@@ -281,8 +325,8 @@ msgstr ""
#: ironic/drivers/modules/virtualbox.py:160
#, python-format
msgid ""
-"Failed while creating a VirtualMachine object for node %(node)s. Error: "
-"%(error)s."
+"Failed while creating a VirtualMachine object for node %(node_id)s. "
+"Error: %(error)s."
msgstr ""
#: ironic/drivers/modules/virtualbox.py:176
@@ -305,6 +349,42 @@ msgstr ""
msgid "'set_boot_device' failed for node %(node_id)s with error: %(error)s"
msgstr ""
+#: ironic/drivers/modules/amt/common.py:105
+#, python-format
+msgid "Call to AMT with URI %(uri)s failed: got Fault %(fault)s"
+msgstr ""
+
+#: ironic/drivers/modules/amt/common.py:129
+#, python-format
+msgid ""
+"Call to AMT with URI %(uri)s and method %(method)s failed: return value "
+"was %(value)s"
+msgstr ""
+
+#: ironic/drivers/modules/amt/management.py:62
+#, python-format
+msgid ""
+"Failed to set boot device %(boot_device)s for node %(node_id)s with "
+"error: %(error)s."
+msgstr ""
+
+#: ironic/drivers/modules/amt/management.py:95
+#, python-format
+msgid "Failed to enable boot config for node %(node_id)s with error: %(error)s."
+msgstr ""
+
+#: ironic/drivers/modules/amt/power.py:112
+#, python-format
+msgid ""
+"Failed to set power state %(state)s for node %(node_id)s with error: "
+"%(error)s."
+msgstr ""
+
+#: ironic/drivers/modules/amt/power.py:136
+#, python-format
+msgid "Failed to get power state for node %(node_id)s with error: %(error)s."
+msgstr ""
+
#: ironic/drivers/modules/drac/management.py:82
#, python-format
msgid ""
@@ -361,59 +441,67 @@ msgid ""
"%(target_power_state)s. Reason: %(error)s."
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:412
+#: ironic/drivers/modules/ilo/common.py:450
#, python-format
msgid "Error while deleting %(object_name)s from %(container)s. Error: %(error)s"
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:422
+#: ironic/drivers/modules/ilo/common.py:460
#, python-format
msgid ""
"Error while ejecting virtual media %(device)s from node %(uuid)s. Error: "
"%(error)s"
msgstr ""
-#: ironic/drivers/modules/ilo/deploy.py:109
+#: ironic/drivers/modules/ilo/deploy.py:122
#, python-format
msgid ""
-"Unable to find boot_iso in Glance, required to deploy node %(node)s in "
-"UEFI boot mode."
+"Virtual media deploy accepts only Glance images or HTTP(S) URLs as "
+"instance_info['ilo_boot_iso']. Either %s is not a valid HTTP(S) URL or is"
+" not reachable."
msgstr ""
-#: ironic/drivers/modules/ilo/deploy.py:115
+#: ironic/drivers/modules/ilo/deploy.py:149
#, python-format
msgid ""
-"Unable to find 'kernel_id' and 'ramdisk_id' in Glance image %(image)s for"
-" generating boot ISO for %(node)s"
+"Unable to find kernel or ramdisk for image %(image)s to generate boot ISO"
+" for %(node)s"
msgstr ""
-#: ironic/drivers/modules/ilo/deploy.py:156
+#: ironic/drivers/modules/ilo/deploy.py:197
#, python-format
msgid "Failed to clean up boot ISO for %(node)s.Error: %(error)s."
msgstr ""
-#: ironic/drivers/modules/ilo/deploy.py:509
+#: ironic/drivers/modules/ilo/deploy.py:741
#, python-format
msgid "Cannot get boot ISO for node %s"
msgstr ""
-#: ironic/drivers/modules/ilo/power.py:85
+#: ironic/drivers/modules/ilo/power.py:92
#, python-format
msgid "iLO get_power_state failed for node %(node_id)s with error: %(error)s."
msgstr ""
-#: ironic/drivers/modules/ilo/power.py:157
+#: ironic/drivers/modules/ilo/power.py:164
#, python-format
msgid ""
"iLO set_power_state failed to set state to %(tstate)s for node "
"%(node_id)s with error: %(error)s"
msgstr ""
-#: ironic/drivers/modules/ilo/power.py:170
+#: ironic/drivers/modules/ilo/power.py:177
#, python-format
msgid "iLO failed to change state to %(tstate)s within %(timeout)s sec"
msgstr ""
+#: ironic/drivers/modules/irmc/management.py:60
+#, python-format
+msgid ""
+"SCCI get sensor data failed for node %(node_id)s with the following "
+"error: %(error)s"
+msgstr ""
+
#: ironic/drivers/modules/irmc/power.py:65
#, python-format
msgid ""
@@ -431,16 +519,6 @@ msgstr ""
msgid "Unable to instantiate unregistered object type %(objtype)s"
msgstr ""
-#: ironic/openstack/common/excutils.py:76
-#, python-format
-msgid "Original exception being dropped: %s"
-msgstr ""
-
-#: ironic/openstack/common/excutils.py:105
-#, python-format
-msgid "Unexpected exception occurred %d time(s)... retrying."
-msgstr ""
-
#: ironic/openstack/common/loopingcall.py:95
msgid "in fixed duration looping call"
msgstr ""
@@ -454,21 +532,11 @@ msgstr ""
msgid "Error during %(full_task_name)s: %(e)s"
msgstr ""
-#: ironic/openstack/common/policy.py:563 ironic/openstack/common/policy.py:843
-#, python-format
-msgid "Failed to understand rule %s"
-msgstr ""
-
-#: ironic/openstack/common/policy.py:573
-#, python-format
-msgid "No handler for matches of kind %s"
-msgstr ""
-
-#: ironic/openstack/common/service.py:269
+#: ironic/openstack/common/service.py:276
msgid "Unhandled exception"
msgstr ""
-#: ironic/tests/db/sqlalchemy/test_migrations.py:174
+#: ironic/tests/db/sqlalchemy/test_migrations.py:168
#, python-format
msgid "Failed to migrate to version %(version)s on engine %(engine)s"
msgstr ""
diff --git a/ironic/locale/ironic-log-info.pot b/ironic/locale/ironic-log-info.pot
index 1316e7925..7472171e5 100644
--- a/ironic/locale/ironic-log-info.pot
+++ b/ironic/locale/ironic-log-info.pot
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: ironic 2015.1.dev15\n"
+"Project-Id-Version: ironic 2015.1.dev139\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2015-02-13 06:14+0000\n"
+"POT-Creation-Date: 2015-04-08 06:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -31,115 +31,235 @@ msgstr ""
msgid "Loaded the following drivers: %s"
msgstr ""
-#: ironic/common/service.py:80
+#: ironic/common/service.py:83
#, python-format
msgid "Created RPC server for service %(service)s on host %(host)s."
msgstr ""
-#: ironic/common/service.py:98
+#: ironic/common/service.py:101
#, python-format
msgid "Stopped RPC server for service %(service)s on host %(host)s."
msgstr ""
-#: ironic/conductor/manager.py:260
+#: ironic/common/service.py:106
#, python-format
-msgid "Successfuly started conductor with hostname %(hostname)s."
+msgid ""
+"Got signal SIGUSR1. Not deregistering on next shutdown of service "
+"%(service)s on host %(host)s."
+msgstr ""
+
+#: ironic/conductor/manager.py:292
+#, python-format
+msgid "Successfully started conductor with hostname %(hostname)s."
msgstr ""
-#: ironic/conductor/manager.py:279
+#: ironic/conductor/manager.py:313
#, python-format
msgid "Successfully stopped conductor with hostname %(hostname)s."
msgstr ""
-#: ironic/conductor/manager.py:827
+#: ironic/conductor/manager.py:319
+#, python-format
+msgid "Not deregistering conductor with hostname %(hostname)s."
+msgstr ""
+
+#: ironic/conductor/manager.py:807
+#, python-format
+msgid "Successfully unprovisioned node %(node)s with instance %(instance)s."
+msgstr ""
+
+#: ironic/conductor/manager.py:877
+#, python-format
+msgid ""
+"Cleaning is disabled, node %s has been successfully moved to AVAILABLE "
+"state."
+msgstr ""
+
+#: ironic/conductor/manager.py:936
+#, python-format
+msgid "Executing %(state)s on node %(node)s, remaining steps: %(steps)s"
+msgstr ""
+
+#: ironic/conductor/manager.py:946
+#, python-format
+msgid "Executing %(step)s on node %(node)s"
+msgstr ""
+
+#: ironic/conductor/manager.py:965
+#, python-format
+msgid ""
+"Clean step %(step)s on node %(node)s being executed asynchronously, "
+"waiting for driver."
+msgstr ""
+
+#: ironic/conductor/manager.py:975
+#, python-format
+msgid "Node %(node)s finished clean step %(step)s"
+msgstr ""
+
+#: ironic/conductor/manager.py:990
+#, python-format
+msgid "Node %s cleaning complete"
+msgstr ""
+
+#: ironic/conductor/manager.py:1085
#, python-format
msgid ""
"During sync_power_state, node %(node)s was not found and presumed deleted"
" by another process."
msgstr ""
-#: ironic/conductor/manager.py:831
+#: ironic/conductor/manager.py:1089
#, python-format
msgid ""
"During sync_power_state, node %(node)s was already locked by another "
"process. Skip."
msgstr ""
-#: ironic/conductor/manager.py:1026
+#: ironic/conductor/manager.py:1288
#, python-format
msgid "Successfully deleted node %(node)s."
msgstr ""
-#: ironic/conductor/manager.py:1095
+#: ironic/conductor/manager.py:1307
#, python-format
-msgid "No console action was triggered because the console is already %s"
+msgid ""
+"Successfully deleted port %(port)s. The node associated with the port was"
+" %(node)s"
msgstr ""
-#: ironic/conductor/manager.py:1497
+#: ironic/conductor/manager.py:1378
#, python-format
-msgid "Successfully deployed node %(node)s with instance %(instance)s."
+msgid "No console action was triggered because the console is already %s"
msgstr ""
-#: ironic/conductor/manager.py:1526
+#: ironic/conductor/manager.py:1905
#, python-format
-msgid "Successfully unprovisioned node %(node)s with instance %(instance)s."
+msgid "Successfully deployed node %(node)s with instance %(instance)s."
msgstr ""
-#: ironic/conductor/manager.py:1601
+#: ironic/conductor/manager.py:1981
#, python-format
msgid ""
"During sync_power_state, node %(node)s has no previous known state. "
"Recording current state '%(state)s'."
msgstr ""
+#: ironic/conductor/manager.py:2056
+#, python-format
+msgid "Successfully inspected node %(node)s"
+msgstr ""
+
#: ironic/conductor/utils.py:124
#, python-format
-msgid "Succesfully set node %(node)s power state to %(state)s."
+msgid "Successfully set node %(node)s power state to %(state)s."
msgstr ""
-#: ironic/drivers/modules/image_cache.py:128
+#: ironic/drivers/modules/agent_base_vendor.py:448
+#: ironic/drivers/modules/iscsi_deploy.py:628
+#, python-format
+msgid "Deployment to node %s done"
+msgstr ""
+
+#: ironic/drivers/modules/discoverd.py:71
+#, python-format
+msgid ""
+"Inspection via ironic-discoverd is disabled in configuration for driver "
+"%s. To enable, change [discoverd] enabled = True."
+msgstr ""
+
+#: ironic/drivers/modules/discoverd.py:169
+#, python-format
+msgid "Node %s was sent to inspection to ironic-discoverd"
+msgstr ""
+
+#: ironic/drivers/modules/discoverd.py:204
+#, python-format
+msgid "Inspection finished successfully for node %s"
+msgstr ""
+
+#: ironic/drivers/modules/image_cache.py:138
#, python-format
msgid "Master cache miss for image %(uuid)s, starting download"
msgstr ""
-#: ironic/drivers/modules/image_cache.py:269
+#: ironic/drivers/modules/image_cache.py:279
#, python-format
msgid ""
"After cleaning up cache dir %(dir)s cache size %(actual)d is still larger"
" than threshold %(expected)d"
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:272
+#: ironic/drivers/modules/amt/management.py:67
#, python-format
-msgid "Continuing deployment for node %(node)s, params %(params)s"
+msgid "Successfully set boot device %(boot_device)s for node %(node_id)s"
msgstr ""
-#: ironic/drivers/modules/pxe.py:478 ironic/drivers/modules/ilo/deploy.py:518
+#: ironic/drivers/modules/amt/management.py:99
#, python-format
-msgid "Deployment to node %s done"
+msgid "Successfully enabled boot config for node %(node_id)s."
+msgstr ""
+
+#: ironic/drivers/modules/amt/power.py:117
+#, python-format
+msgid "Power state set to %(state)s for node %(node_id)s"
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:293
+#: ironic/drivers/modules/ilo/common.py:300
#, python-format
msgid "Attached virtual media %s successfully."
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:311
+#: ironic/drivers/modules/ilo/common.py:318
#, python-format
msgid "Node %(uuid)s pending boot mode is %(boot_mode)s."
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:323
+#: ironic/drivers/modules/ilo/common.py:330
#, python-format
msgid "Node %(uuid)s boot mode is set to %(boot_mode)s."
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:371
+#: ironic/drivers/modules/ilo/common.py:410
#, python-format
msgid "Setting up node %s to boot from virtual media"
msgstr ""
-#: ironic/openstack/common/eventlet_backdoor.py:140
+#: ironic/drivers/modules/ilo/deploy.py:362
+#, python-format
+msgid "Changed secure boot to %(mode)s for node %(node)s"
+msgstr ""
+
+#: ironic/drivers/modules/ilo/inspect.py:57
+#, python-format
+msgid "Port created for MAC address %(address)s for node %(node)s"
+msgstr ""
+
+#: ironic/drivers/modules/ilo/inspect.py:254
+#, python-format
+msgid "The node %s is not powered on. Powering on the node for inspection."
+msgstr ""
+
+#: ironic/drivers/modules/ilo/inspect.py:293
+#, python-format
+msgid "Node %s inspected."
+msgstr ""
+
+#: ironic/drivers/modules/ilo/inspect.py:296
+#, python-format
+msgid ""
+"The node %s was powered on for inspection. Powered off the node as "
+"inspection completed."
+msgstr ""
+
+#: ironic/drivers/modules/ilo/management.py:252
+#, python-format
+msgid ""
+"Missing 'ilo_change_password' parameter in driver_info. Clean step "
+"'reset_ilo_credential' is not performed on node %s."
+msgstr ""
+
+#: ironic/openstack/common/eventlet_backdoor.py:146
#, python-format
msgid "Eventlet backdoor listening on %(port)s for process %(pid)d"
msgstr ""
@@ -154,59 +274,54 @@ msgstr ""
msgid "Skipping periodic task %(task)s because it is disabled"
msgstr ""
-#: ironic/openstack/common/policy.py:275
-#, python-format
-msgid "Can not find policy directory: %s"
-msgstr ""
-
-#: ironic/openstack/common/service.py:174
+#: ironic/openstack/common/service.py:173
#, python-format
msgid "Caught %s, exiting"
msgstr ""
-#: ironic/openstack/common/service.py:232
+#: ironic/openstack/common/service.py:239
msgid "Parent process has died unexpectedly, exiting"
msgstr ""
-#: ironic/openstack/common/service.py:263
+#: ironic/openstack/common/service.py:270
#, python-format
msgid "Child caught %s, exiting"
msgstr ""
-#: ironic/openstack/common/service.py:302
+#: ironic/openstack/common/service.py:309
msgid "Forking too fast, sleeping"
msgstr ""
-#: ironic/openstack/common/service.py:321
+#: ironic/openstack/common/service.py:328
#, python-format
msgid "Started child %d"
msgstr ""
-#: ironic/openstack/common/service.py:331
+#: ironic/openstack/common/service.py:338
#, python-format
msgid "Starting %d workers"
msgstr ""
-#: ironic/openstack/common/service.py:348
+#: ironic/openstack/common/service.py:355
#, python-format
msgid "Child %(pid)d killed by signal %(sig)d"
msgstr ""
-#: ironic/openstack/common/service.py:352
+#: ironic/openstack/common/service.py:359
#, python-format
msgid "Child %(pid)s exited with status %(code)d"
msgstr ""
-#: ironic/openstack/common/service.py:391
+#: ironic/openstack/common/service.py:398
#, python-format
msgid "Caught %s, stopping children"
msgstr ""
-#: ironic/openstack/common/service.py:400
+#: ironic/openstack/common/service.py:413
msgid "Wait called after thread killed. Cleaning up."
msgstr ""
-#: ironic/openstack/common/service.py:416
+#: ironic/openstack/common/service.py:429
#, python-format
msgid "Waiting on %d children to exit"
msgstr ""
diff --git a/ironic/locale/ironic-log-warning.pot b/ironic/locale/ironic-log-warning.pot
index 5c1982c39..c1c0126b8 100644
--- a/ironic/locale/ironic-log-warning.pot
+++ b/ironic/locale/ironic-log-warning.pot
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: ironic 2015.1.dev31\n"
+"Project-Id-Version: ironic 2015.1.dev139\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2015-02-18 06:15+0000\n"
+"POT-Creation-Date: 2015-04-08 06:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -29,100 +29,100 @@ msgid ""
"expected format: %(line)s"
msgstr ""
-#: ironic/common/utils.py:445
+#: ironic/common/utils.py:448
#, python-format
msgid "Failed to unlink %(path)s, error: %(e)s"
msgstr ""
-#: ironic/common/utils.py:454
+#: ironic/common/utils.py:457
#, python-format
msgid "Failed to remove dir %(path)s, error: %(e)s"
msgstr ""
-#: ironic/common/utils.py:470
+#: ironic/common/utils.py:473
#, python-format
msgid "Failed to create symlink from %(source)s to %(link)s, error: %(e)s"
msgstr ""
-#: ironic/common/utils.py:484
+#: ironic/common/utils.py:487
#, python-format
msgid ""
"Failed to remove trailing character. Returning original object. Supplied "
"object is not a string: %s,"
msgstr ""
-#: ironic/conductor/manager.py:249
+#: ironic/conductor/manager.py:281
#, python-format
msgid ""
"A conductor with hostname %(hostname)s was previously registered. "
"Updating registration"
msgstr ""
-#: ironic/conductor/manager.py:315
+#: ironic/conductor/manager.py:353
msgid "Conductor could not connect to database while heartbeating."
msgstr ""
-#: ironic/conductor/manager.py:446
+#: ironic/conductor/manager.py:484
msgid ""
"Drivers implementing their own version of vendor_passthru() has been "
"deprecated. Please update the code to use the @passthru decorator."
msgstr ""
-#: ironic/conductor/manager.py:539
+#: ironic/conductor/manager.py:577
msgid ""
"Drivers implementing their own version of driver_vendor_passthru() has "
"been deprecated. Please update the code to use the @driver_passthru "
"decorator."
msgstr ""
-#: ironic/conductor/manager.py:1164
+#: ironic/conductor/manager.py:1440
#, python-format
msgid ""
"No VIF found for instance %(instance)s port %(port)s when attempting to "
"update port MAC address."
msgstr ""
-#: ironic/conductor/manager.py:1221
+#: ironic/conductor/manager.py:1492
#, python-format
msgid ""
"get_sensors_data is not implemented for driver %(driver)s, node_uuid is "
"%(node)s"
msgstr ""
-#: ironic/conductor/manager.py:1225
+#: ironic/conductor/manager.py:1496
#, python-format
msgid ""
"During get_sensors_data, could not parse sensor data for node %(node)s. "
"Error: %(err)s."
msgstr ""
-#: ironic/conductor/manager.py:1229
+#: ironic/conductor/manager.py:1500
#, python-format
msgid ""
"During get_sensors_data, could not get sensor data for node %(node)s. "
"Error: %(err)s."
msgstr ""
-#: ironic/conductor/manager.py:1233
+#: ironic/conductor/manager.py:1504
#, python-format
msgid ""
"During send_sensor_data, node %(node)s was not found and presumed deleted"
" by another process."
msgstr ""
-#: ironic/conductor/manager.py:1237
+#: ironic/conductor/manager.py:1508
#, python-format
msgid "Failed to get sensor data for node %(node)s. Error: %(error)s"
msgstr ""
-#: ironic/conductor/manager.py:1378
+#: ironic/conductor/manager.py:1779
#, python-format
msgid ""
"No free conductor workers available to perform an action on node "
"%(node)s, setting node's power state back to %(power_state)s."
msgstr ""
-#: ironic/conductor/manager.py:1406
+#: ironic/conductor/manager.py:1807
#, python-format
msgid ""
"No free conductor workers available to perform an action on node "
@@ -130,41 +130,36 @@ msgid ""
"target_provision_state to %(tgt_prov_state)s."
msgstr ""
-#: ironic/conductor/manager.py:1474
+#: ironic/conductor/manager.py:1875
#, python-format
msgid "Error while uploading the configdrive for %(node)s to Swift"
msgstr ""
-#: ironic/conductor/manager.py:1484
+#: ironic/conductor/manager.py:1885
#, python-format
msgid "Error while preparing to deploy to node %(node)s: %(err)s"
msgstr ""
-#: ironic/conductor/manager.py:1493
+#: ironic/conductor/manager.py:1894
#, python-format
msgid "Error in deploy of node %(node)s: %(err)s"
msgstr ""
-#: ironic/conductor/manager.py:1525
-#, python-format
-msgid "Error in tear_down of node %(node)s: %(err)s"
-msgstr ""
-
-#: ironic/conductor/manager.py:1598
+#: ironic/conductor/manager.py:1971
#, python-format
msgid ""
"During sync_power_state, could not get power state for node %(node)s. "
"Error: %(err)s."
msgstr ""
-#: ironic/conductor/manager.py:1625
+#: ironic/conductor/manager.py:1999
#, python-format
msgid ""
"During sync_power_state, node %(node)s state '%(actual)s' does not match "
"expected state. Changing hardware state to '%(state)s'."
msgstr ""
-#: ironic/conductor/manager.py:1643
+#: ironic/conductor/manager.py:2017
#, python-format
msgid ""
"During sync_power_state, node %(node)s state does not match expected "
@@ -188,24 +183,24 @@ msgstr ""
msgid "Driver returns ERROR power state for node %s."
msgstr ""
-#: ironic/db/sqlalchemy/api.py:583
+#: ironic/db/sqlalchemy/api.py:584
#, python-format
msgid "Cleared reservations held by %(hostname)s: %(nodes)s"
msgstr ""
-#: ironic/dhcp/neutron.py:179
+#: ironic/dhcp/neutron.py:189
#, python-format
msgid ""
"Some errors were encountered when updating the DHCP BOOT options for node"
" %(node)s on the following ports: %(ports)s."
msgstr ""
-#: ironic/dhcp/neutron.py:243
+#: ironic/dhcp/neutron.py:253
#, python-format
msgid "No VIFs found for node %(node)s when attempting to get port IP address."
msgstr ""
-#: ironic/dhcp/neutron.py:272
+#: ironic/dhcp/neutron.py:282
#, python-format
msgid ""
"Some errors were encountered on node %(node)s while retrieving IP address"
@@ -217,12 +212,12 @@ msgstr ""
msgid "Ignoring malformed capability '%s'. Format should be 'key:val'."
msgstr ""
-#: ironic/drivers/modules/agent_base_vendor.py:259
+#: ironic/drivers/modules/agent_base_vendor.py:351
#, python-format
msgid "Malformed MAC: %s"
msgstr ""
-#: ironic/drivers/modules/agent_base_vendor.py:302
+#: ironic/drivers/modules/agent_base_vendor.py:394
#, python-format
msgid "MAC address %s not found in database"
msgstr ""
@@ -239,6 +234,13 @@ msgstr ""
msgid "No console pid found for node %s while trying to stop shellinabox console."
msgstr ""
+#: ironic/drivers/modules/deploy_utils.py:974
+#, python-format
+msgid ""
+"ipmitool is unable to set boot device while the node %s is in UEFI boot "
+"mode. Please set the boot device manually."
+msgstr ""
+
#: ironic/drivers/modules/iboot.py:113
#, python-format
msgid ""
@@ -246,86 +248,100 @@ msgid ""
"get_relays() failed."
msgstr ""
-#: ironic/drivers/modules/image_cache.py:194
+#: ironic/drivers/modules/image_cache.py:204
#, python-format
msgid ""
"Cache clean up was unable to reclaim %(required)d MiB of disk space, "
"still %(left)d MiB required"
msgstr ""
-#: ironic/drivers/modules/image_cache.py:221
-#: ironic/drivers/modules/image_cache.py:260
+#: ironic/drivers/modules/image_cache.py:231
+#: ironic/drivers/modules/image_cache.py:270
#, python-format
msgid "Unable to delete file %(name)s from master image cache: %(exc)s"
msgstr ""
-#: ironic/drivers/modules/ipminative.py:133
+#: ironic/drivers/modules/ipminative.py:138
#, python-format
msgid ""
"IPMI power on failed for node %(node_id)s with the following error: "
"%(error)s"
msgstr ""
-#: ironic/drivers/modules/ipminative.py:163
+#: ironic/drivers/modules/ipminative.py:168
#, python-format
msgid ""
"IPMI power off failed for node %(node_id)s with the following error: "
"%(error)s"
msgstr ""
-#: ironic/drivers/modules/ipminative.py:195
+#: ironic/drivers/modules/ipminative.py:200
#, python-format
msgid ""
"IPMI power reboot failed for node %(node_id)s with the following error: "
"%(error)s"
msgstr ""
-#: ironic/drivers/modules/ipminative.py:230
+#: ironic/drivers/modules/ipminative.py:235
#, python-format
msgid ""
"IPMI get power state failed for node %(node_id)s with the following "
"error: %(error)s"
msgstr ""
-#: ironic/drivers/modules/ipminative.py:244
+#: ironic/drivers/modules/ipminative.py:249
#, python-format
msgid ""
"IPMI get power state for node %(node_id)s returns the following details: "
"%(detail)s"
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:402
+#: ironic/drivers/modules/ipmitool.py:413
#, python-format
msgid "IPMI power %(state)s failed for node %(node)s."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:467
+#: ironic/drivers/modules/ipmitool.py:478
#, python-format
msgid "IPMI power status failed for node %(node_id)s with error: %(error)s."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:742
+#: ironic/drivers/modules/ipmitool.py:753
#, python-format
msgid ""
"IPMI set boot device failed for node %(node)s when executing \"ipmitool "
"%(cmd)s\". Error: %(error)s"
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:773
+#: ironic/drivers/modules/ipmitool.py:784
#, python-format
msgid ""
"IPMI get boot device failed for node %(node)s when executing \"ipmitool "
"%(cmd)s\". Error: %(error)s"
msgstr ""
-#: ironic/drivers/modules/pxe.py:282
+#: ironic/drivers/modules/pxe.py:132
#, python-format
msgid ""
-"ipmitool is unable to set boot device while the node %s is in UEFI boot "
-"mode. Please set the boot device manually."
+"The \"%(old_param)s\" parameter is deprecated. Please update the node "
+"%(node)s to use \"%(new_param)s\" instead."
+msgstr ""
+
+#: ironic/drivers/modules/pxe.py:474
+#, python-format
+msgid ""
+"The UUID for the root partition can't be found, unable to switch the pxe "
+"config from deployment mode to service (boot) mode for node %(node)s"
+msgstr ""
+
+#: ironic/drivers/modules/pxe.py:479
+#, python-format
+msgid ""
+"The disk id for the whole disk image can't be found, unable to switch the"
+" pxe config from deployment mode to service (boot) mode for node %(node)s"
msgstr ""
-#: ironic/drivers/modules/pxe.py:429
+#: ironic/drivers/modules/pxe.py:504
#, python-format
msgid "Could not get image info to clean up images for node %(node)s: %(err)s"
msgstr ""
@@ -364,6 +380,46 @@ msgid ""
" support this operation"
msgstr ""
+#: ironic/drivers/modules/amt/power.py:179
+#, python-format
+msgid ""
+"AMT failed to set power state %(state)s after %(tries)s retries on node "
+"%(node_id)s."
+msgstr ""
+
+#: ironic/drivers/modules/amt/power.py:189
+#, python-format
+msgid ""
+"AMT set power state %(state)s for node %(node)s - Attempt %(attempt)s "
+"times of %(max_attempt)s failed."
+msgstr ""
+
+#: ironic/drivers/modules/drac/client.py:73
+#, python-format
+msgid ""
+"Empty response on calling %(action)s on client. Last error (cURL error "
+"code): %(last_error)s, fault string: \"%(fault_string)s\" response_code: "
+"%(response_code)s. Retry attempt %(count)d"
+msgstr ""
+
+#: ironic/drivers/modules/ilo/deploy.py:456
+#: ironic/drivers/modules/ilo/deploy.py:536
+#, python-format
+msgid "Secure boot mode is not supported for node %s"
+msgstr ""
+
+#: ironic/drivers/modules/ilo/inspect.py:60
+#, python-format
+msgid "Port already exists for MAC address %(address)s for node %(node)s"
+msgstr ""
+
+#: ironic/drivers/modules/ilo/management.py:98
+#, python-format
+msgid ""
+"'%(step)s' clean step is not supported on node %(uuid)s. Skipping the "
+"clean step."
+msgstr ""
+
#: ironic/nova/scheduler/ironic_host_manager.py:35
msgid ""
"This class (ironic.nova.scheduler.ironic_host_manager.IronicHostManager) "
@@ -381,22 +437,10 @@ msgstr ""
#: ironic/openstack/common/loopingcall.py:87
#, python-format
-msgid "task %(func_name)s run outlasted interval by %(delay).2f sec"
-msgstr ""
-
-#: ironic/openstack/common/network_utils.py:149
-msgid "tcp_keepidle not available on your system"
-msgstr ""
-
-#: ironic/openstack/common/network_utils.py:156
-msgid "tcp_keepintvl not available on your system"
-msgstr ""
-
-#: ironic/openstack/common/network_utils.py:163
-msgid "tcp_keepknt not available on your system"
+msgid "task %(func_name)r run outlasted interval by %(delay).2f sec"
msgstr ""
-#: ironic/openstack/common/service.py:356
+#: ironic/openstack/common/service.py:363
#, python-format
msgid "pid %d not in child list"
msgstr ""
diff --git a/ironic/locale/ironic.pot b/ironic/locale/ironic.pot
index bfd7b3f3e..38f3bff0e 100644
--- a/ironic/locale/ironic.pot
+++ b/ironic/locale/ironic.pot
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: ironic 2015.1.dev31\n"
+"Project-Id-Version: ironic 2015.1.dev139\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2015-02-18 06:14+0000\n"
+"POT-Creation-Date: 2015-04-08 06:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,96 +17,105 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
-#: ironic/api/controllers/base.py:92
-msgid "Invalid value for X-OpenStack-Ironic-API-Version header."
+#: ironic/api/controllers/base.py:102
+#, python-format
+msgid "Invalid value for %s header"
msgstr ""
-#: ironic/api/controllers/v1/__init__.py:164
+#: ironic/api/controllers/v1/__init__.py:173
#, python-format
msgid ""
"Mutually exclusive versions requested. Version %(ver)s requested but not "
-"supported by this service."
+"supported by this service. The supported version range is: [%(min)s, "
+"%(max)s]."
msgstr ""
-#: ironic/api/controllers/v1/__init__.py:170
+#: ironic/api/controllers/v1/__init__.py:180
#, python-format
msgid ""
-"Unsupported minor version requested. This API service supports the "
-"following version range: [%(min)s, %(max)s]."
+"Version %(ver)s was requested but the minor version is not supported by "
+"this service. The supported version range is: [%(min)s, %(max)s]."
msgstr ""
#: ironic/api/controllers/v1/driver.py:146
-#: ironic/api/controllers/v1/node.py:716
+#: ironic/api/controllers/v1/node.py:703
msgid "Method not specified"
msgstr ""
-#: ironic/api/controllers/v1/node.py:423
+#: ironic/api/controllers/v1/node.py:402
#, python-format
msgid "Adding a config drive is only supported when setting provision state to %s"
msgstr ""
-#: ironic/api/controllers/v1/node.py:446
+#: ironic/api/controllers/v1/node.py:428
#, python-format
msgid "The requested action \"%(action)s\" could not be understood."
msgstr ""
-#: ironic/api/controllers/v1/node.py:801
+#: ironic/api/controllers/v1/node.py:788
msgid "Chassis id not specified."
msgstr ""
-#: ironic/api/controllers/v1/node.py:975
+#: ironic/api/controllers/v1/node.py:963
#, python-format
msgid "Cannot create node with invalid name %(name)s"
msgstr ""
-#: ironic/api/controllers/v1/node.py:1003
+#: ironic/api/controllers/v1/node.py:1002
#, python-format
msgid "Node %s can not be updated while a state transition is in progress."
msgstr ""
-#: ironic/api/controllers/v1/node.py:1013
+#: ironic/api/controllers/v1/node.py:1012
#, python-format
msgid "Node %(node)s: Cannot change name to invalid name '%(name)s'"
msgstr ""
-#: ironic/api/controllers/v1/port.py:182
-msgid "Node id not specified."
+#: ironic/api/controllers/v1/node.py:1060
+#, python-format
+msgid ""
+"Node %s can not update the driver while the console is enabled. Please "
+"stop the console first."
msgstr ""
-#: ironic/api/controllers/v1/types.py:171
+#: ironic/api/controllers/v1/port.py:183
+msgid "Node identifier not specified."
+msgstr ""
+
+#: ironic/api/controllers/v1/types.py:173
#, python-format
msgid "%s is not JSON serializable"
msgstr ""
-#: ironic/api/controllers/v1/types.py:222
+#: ironic/api/controllers/v1/types.py:224
#, python-format
msgid "'%s' is an internal attribute and can not be updated"
msgstr ""
-#: ironic/api/controllers/v1/types.py:226
+#: ironic/api/controllers/v1/types.py:228
#, python-format
msgid "'%s' is a mandatory attribute and can not be removed"
msgstr ""
-#: ironic/api/controllers/v1/types.py:231
+#: ironic/api/controllers/v1/types.py:233
msgid "'add' and 'replace' operations needs value"
msgstr ""
-#: ironic/api/controllers/v1/utils.py:32
+#: ironic/api/controllers/v1/utils.py:38
msgid "Limit must be positive"
msgstr ""
-#: ironic/api/controllers/v1/utils.py:39
+#: ironic/api/controllers/v1/utils.py:45
#, python-format
msgid "Invalid sort direction: %s. Acceptable values are 'asc' or 'desc'"
msgstr ""
-#: ironic/api/controllers/v1/utils.py:49
+#: ironic/api/controllers/v1/utils.py:55
#, python-format
msgid "Adding a new attribute (%s) to the root of the resource is not allowed"
msgstr ""
-#: ironic/api/middleware/auth_token.py:41
+#: ironic/api/middleware/auth_token.py:43
#, python-format
msgid "Cannot compile public API routes: %s"
msgstr ""
@@ -134,510 +143,551 @@ msgstr ""
msgid "An unknown exception occurred."
msgstr ""
-#: ironic/common/exception.py:99
+#: ironic/common/exception.py:106
msgid "Not authorized."
msgstr ""
-#: ironic/common/exception.py:104
+#: ironic/common/exception.py:111
msgid "Operation not permitted."
msgstr ""
-#: ironic/common/exception.py:108
+#: ironic/common/exception.py:115
msgid "Unacceptable parameters."
msgstr ""
-#: ironic/common/exception.py:113
+#: ironic/common/exception.py:120
msgid "Conflict."
msgstr ""
-#: ironic/common/exception.py:118
+#: ironic/common/exception.py:125
msgid "Resource temporarily unavailable, please retry."
msgstr ""
-#: ironic/common/exception.py:124
+#: ironic/common/exception.py:131
msgid "Request not acceptable."
msgstr ""
-#: ironic/common/exception.py:129
+#: ironic/common/exception.py:136
msgid "Invalid resource state."
msgstr ""
-#: ironic/common/exception.py:133
+#: ironic/common/exception.py:140
#, python-format
msgid "A node with UUID %(uuid)s already exists."
msgstr ""
-#: ironic/common/exception.py:137
+#: ironic/common/exception.py:144
#, python-format
msgid "A port with MAC address %(mac)s already exists."
msgstr ""
-#: ironic/common/exception.py:141
+#: ironic/common/exception.py:148
#, python-format
msgid "A chassis with UUID %(uuid)s already exists."
msgstr ""
-#: ironic/common/exception.py:145
+#: ironic/common/exception.py:152
#, python-format
msgid "A port with UUID %(uuid)s already exists."
msgstr ""
-#: ironic/common/exception.py:149
+#: ironic/common/exception.py:156
#, python-format
msgid ""
"Instance %(instance_uuid)s is already associated with a node, it cannot "
"be associated with this other node %(node)s"
msgstr ""
-#: ironic/common/exception.py:154
+#: ironic/common/exception.py:161
#, python-format
msgid "A node with name %(name)s already exists."
msgstr ""
-#: ironic/common/exception.py:158
+#: ironic/common/exception.py:165
#, python-format
msgid "Expected a uuid but received %(uuid)s."
msgstr ""
-#: ironic/common/exception.py:162
+#: ironic/common/exception.py:169
#, python-format
msgid "Expected a logical name or uuid but received %(name)s."
msgstr ""
-#: ironic/common/exception.py:166
+#: ironic/common/exception.py:173
#, python-format
msgid "Expected a logical name but received %(name)s."
msgstr ""
-#: ironic/common/exception.py:170
+#: ironic/common/exception.py:177
#, python-format
msgid "Expected an uuid or int but received %(identity)s."
msgstr ""
-#: ironic/common/exception.py:174
+#: ironic/common/exception.py:181
#, python-format
msgid "Expected a MAC address but received %(mac)s."
msgstr ""
-#: ironic/common/exception.py:178
+#: ironic/common/exception.py:185
#, python-format
msgid ""
"The requested action \"%(action)s\" can not be performed on node "
"\"%(node)s\" while it is in state \"%(state)s\"."
msgstr ""
-#: ironic/common/exception.py:183
+#: ironic/common/exception.py:190
#, python-format
msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s"
msgstr ""
-#: ironic/common/exception.py:187
+#: ironic/common/exception.py:194
#, python-format
msgid "Failed to deploy instance: %(reason)s"
msgstr ""
-#: ironic/common/exception.py:191 ironic/common/exception.py:195
+#: ironic/common/exception.py:198 ironic/common/exception.py:202
#, python-format
msgid "Image %(image_id)s is unacceptable: %(reason)s"
msgstr ""
-#: ironic/common/exception.py:201 ironic/common/exception.py:205
+#: ironic/common/exception.py:208 ironic/common/exception.py:212
#, python-format
msgid "%(err)s"
msgstr ""
-#: ironic/common/exception.py:209
+#: ironic/common/exception.py:216
msgid "Resource already exists."
msgstr ""
-#: ironic/common/exception.py:213
+#: ironic/common/exception.py:220
msgid "Resource could not be found."
msgstr ""
-#: ironic/common/exception.py:218
+#: ironic/common/exception.py:225
#, python-format
msgid "Failed to load DHCP provider %(dhcp_provider_name)s."
msgstr ""
-#: ironic/common/exception.py:222
+#: ironic/common/exception.py:229
#, python-format
msgid "Could not find the following driver(s): %(driver_name)s."
msgstr ""
-#: ironic/common/exception.py:226
+#: ironic/common/exception.py:233
#, python-format
msgid "Image %(image_id)s could not be found."
msgstr ""
-#: ironic/common/exception.py:230
+#: ironic/common/exception.py:237
#, python-format
msgid "No valid host was found. Reason: %(reason)s"
msgstr ""
-#: ironic/common/exception.py:234
+#: ironic/common/exception.py:241
#, python-format
msgid "Instance %(instance)s could not be found."
msgstr ""
-#: ironic/common/exception.py:238
+#: ironic/common/exception.py:245
#, python-format
msgid "Node %(node)s could not be found."
msgstr ""
-#: ironic/common/exception.py:242
+#: ironic/common/exception.py:249
#, python-format
msgid "Node %(node)s is associated with instance %(instance)s."
msgstr ""
-#: ironic/common/exception.py:246
+#: ironic/common/exception.py:253
#, python-format
msgid "Port %(port)s could not be found."
msgstr ""
-#: ironic/common/exception.py:250
+#: ironic/common/exception.py:257
#, python-format
msgid "Update DHCP options on port: %(port_id)s failed."
msgstr ""
-#: ironic/common/exception.py:254
+#: ironic/common/exception.py:261
#, python-format
msgid "Retrieve IP address on port: %(port_id)s failed."
msgstr ""
-#: ironic/common/exception.py:258
+#: ironic/common/exception.py:265
#, python-format
msgid "Invalid IPv4 address %(ip_address)s."
msgstr ""
-#: ironic/common/exception.py:262
+#: ironic/common/exception.py:269
#, python-format
msgid "Update MAC address on port: %(port_id)s failed."
msgstr ""
-#: ironic/common/exception.py:266
+#: ironic/common/exception.py:273
#, python-format
msgid "Chassis %(chassis)s could not be found."
msgstr ""
-#: ironic/common/exception.py:270
+#: ironic/common/exception.py:277
#, python-format
msgid "Conductor %(conductor)s cannot be started because no drivers were loaded."
msgstr ""
-#: ironic/common/exception.py:275
+#: ironic/common/exception.py:282
#, python-format
msgid "Conductor %(conductor)s could not be found."
msgstr ""
-#: ironic/common/exception.py:279
+#: ironic/common/exception.py:286
#, python-format
msgid "Conductor %(conductor)s already registered."
msgstr ""
-#: ironic/common/exception.py:283
+#: ironic/common/exception.py:290
#, python-format
msgid "Failed to set node power state to %(pstate)s."
msgstr ""
-#: ironic/common/exception.py:287
+#: ironic/common/exception.py:294
msgid "An exclusive lock is required, but the current context has a shared lock."
msgstr ""
-#: ironic/common/exception.py:292
+#: ironic/common/exception.py:299
#, python-format
msgid "Failed to toggle maintenance-mode flag for node %(node)s: %(reason)s"
msgstr ""
-#: ironic/common/exception.py:297
+#: ironic/common/exception.py:304
#, python-format
msgid "Console access is not enabled on node %(node)s"
msgstr ""
-#: ironic/common/exception.py:301
+#: ironic/common/exception.py:308
#, python-format
msgid ""
"The %(op)s operation can't be performed on node %(node)s because it's in "
"maintenance mode."
msgstr ""
-#: ironic/common/exception.py:306
+#: ironic/common/exception.py:313
#, python-format
msgid ""
"Can not change instance association while node %(node)s is in power state"
" %(pstate)s."
msgstr ""
-#: ironic/common/exception.py:311
+#: ironic/common/exception.py:318
#, python-format
msgid ""
"Cannot complete the requested action because chassis %(chassis)s contains"
" nodes."
msgstr ""
-#: ironic/common/exception.py:316
+#: ironic/common/exception.py:323
#, python-format
msgid "IPMI call failed: %(cmd)s."
msgstr ""
-#: ironic/common/exception.py:320
+#: ironic/common/exception.py:327
+msgid "Failed to connect to AMT service."
+msgstr ""
+
+#: ironic/common/exception.py:331
+#, python-format
+msgid "AMT call failed: %(cmd)s."
+msgstr ""
+
+#: ironic/common/exception.py:335
#, python-format
msgid "Failed to establish SSH connection to host %(host)s."
msgstr ""
-#: ironic/common/exception.py:324
+#: ironic/common/exception.py:339
#, python-format
msgid "Failed to execute command via SSH: %(cmd)s."
msgstr ""
-#: ironic/common/exception.py:328
+#: ironic/common/exception.py:343
#, python-format
msgid "Unsupported object type %(objtype)s"
msgstr ""
-#: ironic/common/exception.py:332
+#: ironic/common/exception.py:347
#, python-format
msgid "Cannot call %(method)s on orphaned %(objtype)s object"
msgstr ""
-#: ironic/common/exception.py:336
+#: ironic/common/exception.py:351
#, python-format
-msgid "Driver %(driver)s does not support %(extension)s."
+msgid ""
+"Driver %(driver)s does not support %(extension)s (disabled or not "
+"implemented)."
msgstr ""
-#: ironic/common/exception.py:340
+#: ironic/common/exception.py:356
#, python-format
msgid "Version %(objver)s of %(objname)s is not supported"
msgstr ""
-#: ironic/common/exception.py:344
+#: ironic/common/exception.py:360
#, python-format
msgid "Connection to glance host %(host)s:%(port)s failed: %(reason)s"
msgstr ""
-#: ironic/common/exception.py:349
+#: ironic/common/exception.py:365
#, python-format
msgid "Not authorized for image %(image_id)s."
msgstr ""
-#: ironic/common/exception.py:353
+#: ironic/common/exception.py:369
#, python-format
msgid "Invalid image href %(image_href)s."
msgstr ""
-#: ironic/common/exception.py:357
+#: ironic/common/exception.py:373
+#, python-format
+msgid "Validation of image href %(image_href)s failed, reason: %(reason)s"
+msgstr ""
+
+#: ironic/common/exception.py:378
+#, python-format
+msgid "Failed to download image %(image_href)s, reason: %(reason)s"
+msgstr ""
+
+#: ironic/common/exception.py:382
msgid "Not authorized in Keystone."
msgstr ""
-#: ironic/common/exception.py:370
+#: ironic/common/exception.py:395
#, python-format
msgid ""
"Service type %(service_type)s with endpoint type %(endpoint_type)s not "
"found in keystone service catalog."
msgstr ""
-#: ironic/common/exception.py:375
+#: ironic/common/exception.py:400
msgid "Connection failed"
msgstr ""
-#: ironic/common/exception.py:379
+#: ironic/common/exception.py:404
msgid "Requested OpenStack Images API is forbidden"
msgstr ""
-#: ironic/common/exception.py:387
+#: ironic/common/exception.py:412
msgid "The provided endpoint is invalid"
msgstr ""
-#: ironic/common/exception.py:391
+#: ironic/common/exception.py:416
msgid "Unable to communicate with the server."
msgstr ""
-#: ironic/common/exception.py:407
+#: ironic/common/exception.py:432
#, python-format
msgid "Could not find config at %(path)s"
msgstr ""
-#: ironic/common/exception.py:411
+#: ironic/common/exception.py:436
#, python-format
msgid ""
"Node %(node)s is locked by host %(host)s, please retry after the current "
"operation is completed."
msgstr ""
-#: ironic/common/exception.py:416
+#: ironic/common/exception.py:441
#, python-format
msgid "Node %(node)s found not to be locked on release"
msgstr ""
-#: ironic/common/exception.py:420
+#: ironic/common/exception.py:445
msgid ""
"Requested action cannot be performed due to lack of free conductor "
"workers."
msgstr ""
-#: ironic/common/exception.py:430
+#: ironic/common/exception.py:455
#, python-format
msgid "Invalid configuration file. %(error_msg)s"
msgstr ""
-#: ironic/common/exception.py:434
+#: ironic/common/exception.py:459
#, python-format
msgid "Driver %(driver)s could not be loaded. Reason: %(reason)s."
msgstr ""
-#: ironic/common/exception.py:442
+#: ironic/common/exception.py:467
#, python-format
msgid "Could not find pid in pid file %(pid_path)s"
msgstr ""
-#: ironic/common/exception.py:446
+#: ironic/common/exception.py:471
#, python-format
msgid "Console subprocess failed to start. %(error)s"
msgstr ""
-#: ironic/common/exception.py:450
+#: ironic/common/exception.py:475
#, python-format
msgid "Failed to create the password file. %(error)s"
msgstr ""
-#: ironic/common/exception.py:458
+#: ironic/common/exception.py:483
#, python-format
msgid "%(operation)s failed, error: %(error)s"
msgstr ""
-#: ironic/common/exception.py:466
+#: ironic/common/exception.py:487
+#, python-format
+msgid "%(operation)s not supported. error: %(error)s"
+msgstr ""
+
+#: ironic/common/exception.py:495
#, python-format
msgid ""
"DRAC client failed. Last error (cURL error code): %(last_error)s, fault "
"string: \"%(fault_string)s\" response_code: %(response_code)s"
msgstr ""
-#: ironic/common/exception.py:473
+#: ironic/common/exception.py:502
#, python-format
msgid "DRAC operation failed. Message: %(message)s"
msgstr ""
-#: ironic/common/exception.py:477
+#: ironic/common/exception.py:506
#, python-format
msgid ""
"DRAC operation yielded return value %(actual_return_value)s that is "
"neither error nor expected %(expected_return_value)s"
msgstr ""
-#: ironic/common/exception.py:482
+#: ironic/common/exception.py:511
#, python-format
msgid ""
"Another job with ID %(job_id)s is already created to configure "
"%(target)s. Wait until existing job is completed or is canceled"
msgstr ""
-#: ironic/common/exception.py:488
+#: ironic/common/exception.py:517
#, python-format
msgid ""
"Invalid filter dialect '%(invalid_filter)s'. Supported options are "
"%(supported)s"
msgstr ""
-#: ironic/common/exception.py:493
+#: ironic/common/exception.py:522
#, python-format
msgid "Failed to get sensor data for node %(node)s. Error: %(error)s"
msgstr ""
-#: ironic/common/exception.py:498
+#: ironic/common/exception.py:527
#, python-format
msgid "Failed to parse sensor data for node %(node)s. Error: %(error)s"
msgstr ""
-#: ironic/common/exception.py:503
+#: ironic/common/exception.py:532
#, python-format
msgid ""
"Disk volume where '%(path)s' is located doesn't have enough disk space. "
"Required %(required)d MiB, only %(actual)d MiB available space present."
msgstr ""
-#: ironic/common/exception.py:509
+#: ironic/common/exception.py:538
#, python-format
msgid "Creating %(image_type)s image failed: %(error)s"
msgstr ""
-#: ironic/common/exception.py:513
+#: ironic/common/exception.py:542
#, python-format
msgid "Swift operation '%(operation)s' failed: %(error)s"
msgstr ""
-#: ironic/common/exception.py:517
+#: ironic/common/exception.py:546
#, python-format
msgid "SNMP operation '%(operation)s' failed: %(error)s"
msgstr ""
-#: ironic/common/exception.py:521
+#: ironic/common/exception.py:550
#, python-format
msgid "Failed to create a file system. File system %(fs)s is not supported."
msgstr ""
-#: ironic/common/exception.py:526
+#: ironic/common/exception.py:555
#, python-format
msgid "iRMC %(operation)s failed. Reason: %(error)s"
msgstr ""
-#: ironic/common/exception.py:530
+#: ironic/common/exception.py:559
#, python-format
msgid "VirtualBox operation '%(operation)s' failed. Error: %(error)s"
msgstr ""
-#: ironic/common/fsm.py:84
+#: ironic/common/exception.py:564
+#, python-format
+msgid "Failed to inspect hardware. Reason: %(error)s"
+msgstr ""
+
+#: ironic/common/exception.py:568
+#, python-format
+msgid "Failed to clean node %(node)s: %(reason)s"
+msgstr ""
+
+#: ironic/common/fsm.py:94
#, python-format
msgid "State '%s' already defined"
msgstr ""
-#: ironic/common/fsm.py:87
+#: ironic/common/fsm.py:97
msgid "On enter callback must be callable"
msgstr ""
-#: ironic/common/fsm.py:90
+#: ironic/common/fsm.py:100
msgid "On exit callback must be callable"
msgstr ""
-#: ironic/common/fsm.py:92
+#: ironic/common/fsm.py:102
#, python-format
msgid "Target state '%s' does not exist"
msgstr ""
-#: ironic/common/fsm.py:108
+#: ironic/common/fsm.py:106
+#, python-format
+msgid "Target state '%s' is not a 'stable' state"
+msgstr ""
+
+#: ironic/common/fsm.py:122
#, python-format
msgid ""
"Can not add a transition on event '%(event)s' that starts in a undefined "
"state '%(state)s'"
msgstr ""
-#: ironic/common/fsm.py:113
+#: ironic/common/fsm.py:127
#, python-format
msgid ""
"Can not add a transition on event '%(event)s' that ends in a undefined "
"state '%(state)s'"
msgstr ""
-#: ironic/common/fsm.py:124
+#: ironic/common/fsm.py:138
msgid "Can only process events after being initialized (not before)"
msgstr ""
-#: ironic/common/fsm.py:128
+#: ironic/common/fsm.py:142
#, python-format
msgid "Can not transition from terminal state '%(state)s' on event '%(event)s'"
msgstr ""
-#: ironic/common/fsm.py:133
+#: ironic/common/fsm.py:147
#, python-format
msgid ""
"Can not transition from state '%(state)s' on event '%(event)s' (no "
"defined transition)"
msgstr ""
-#: ironic/common/fsm.py:171
+#: ironic/common/fsm.py:185
#, python-format
msgid "Can not start from an undefined state '%s'"
msgstr ""
-#: ironic/common/fsm.py:174
+#: ironic/common/fsm.py:188
#, python-format
msgid "Can not start from a terminal state '%s'"
msgstr ""
@@ -655,20 +705,49 @@ msgstr ""
msgid "The driver '%s' is unknown."
msgstr ""
-#: ironic/common/images.py:257
+#: ironic/common/image_service.py:137
+#, python-format
+msgid "Got HTTP code %s instead of 200 in response to HEAD request."
+msgstr ""
+
+#: ironic/common/image_service.py:159
+#, python-format
+msgid "Got HTTP code %s instead of 200 in response to GET request."
+msgstr ""
+
+#: ironic/common/image_service.py:181
+msgid ""
+"Cannot determine image size as there is no Content-Length header "
+"specified in response to HEAD request."
+msgstr ""
+
+#: ironic/common/image_service.py:204
+msgid "Specified image file not found."
+msgstr ""
+
+#: ironic/common/image_service.py:280
+#, python-format
+msgid "Image download protocol %s is not supported."
+msgstr ""
+
+#: ironic/common/images.py:357
msgid "'qemu-img info' parsing failed."
msgstr ""
-#: ironic/common/images.py:263
+#: ironic/common/images.py:363
#, python-format
msgid "fmt=%(fmt)s backed by: %(backing_file)s"
msgstr ""
-#: ironic/common/images.py:278
+#: ironic/common/images.py:378
#, python-format
msgid "Converted to raw, but format is now %s"
msgstr ""
+#: ironic/common/images.py:561
+msgid "Deploy iso didn't contain efiboot.img or grub.cfg"
+msgstr ""
+
#: ironic/common/keystone.py:52
msgid "Keystone API endpoint is missing"
msgstr ""
@@ -682,7 +761,7 @@ msgstr ""
msgid "No Keystone service catalog loaded"
msgstr ""
-#: ironic/common/pxe_utils.py:100
+#: ironic/common/pxe_utils.py:102
#, python-format
msgid "Failed to get IP address for any port on node %s."
msgstr ""
@@ -711,7 +790,7 @@ msgstr ""
msgid "post object"
msgstr ""
-#: ironic/common/utils.py:117
+#: ironic/common/utils.py:116
msgid "Invalid private key"
msgstr ""
@@ -748,92 +827,144 @@ msgid ""
"swift_store_multiple_containers_seed."
msgstr ""
-#: ironic/conductor/manager.py:338
+#: ironic/conductor/manager.py:376
msgid "Invalid method call: update_node can not change node state."
msgstr ""
-#: ironic/conductor/manager.py:463 ironic/conductor/manager.py:555
+#: ironic/conductor/manager.py:501 ironic/conductor/manager.py:593
#: ironic/drivers/utils.py:84
#, python-format
msgid "No handler for method %s"
msgstr ""
-#: ironic/conductor/manager.py:468 ironic/conductor/manager.py:560
+#: ironic/conductor/manager.py:506 ironic/conductor/manager.py:598
#, python-format
msgid "The method %(method)s does not support HTTP %(http)s"
msgstr ""
-#: ironic/conductor/manager.py:657
+#: ironic/conductor/manager.py:695
msgid "provisioning"
msgstr ""
-#: ironic/conductor/manager.py:664
+#: ironic/conductor/manager.py:730
#, python-format
msgid "RPC do_node_deploy failed to validate deploy or power info. Error: %(msg)s"
msgstr ""
-#: ironic/conductor/manager.py:724
+#: ironic/conductor/manager.py:777
#, python-format
msgid ""
"Failed to validate power driver interface. Can not delete instance. "
"Error: %(msg)s"
msgstr ""
-#: ironic/conductor/manager.py:994
+#: ironic/conductor/manager.py:802
+#, python-format
+msgid "Failed to tear down. Error: %s"
+msgstr ""
+
+#: ironic/conductor/manager.py:851
+#, python-format
+msgid ""
+"Cannot continue cleaning on %(node)s, node is in %(state)s state, should "
+"be %(clean_state)s"
+msgstr ""
+
+#: ironic/conductor/manager.py:887
+#, python-format
+msgid ""
+"Failed to validate power driver interface. Can not clean node %(node)s. "
+"Error: %(msg)s"
+msgstr ""
+
+#: ironic/conductor/manager.py:897
+#, python-format
+msgid "Failed to prepare node %(node)s for cleaning: %(e)s"
+msgstr ""
+
+#: ironic/conductor/manager.py:929
+#, python-format
+msgid "Node %(node)s got an invalid last step for %(state)s: %(step)s."
+msgstr ""
+
+#: ironic/conductor/manager.py:951
+#, python-format
+msgid "Node %(node)s failed step %(step)s: %(exc)s"
+msgstr ""
+
+#: ironic/conductor/manager.py:970
+#, python-format
+msgid ""
+"While executing step %(step)s on node %(node)s, step returned invalid "
+"value: %(val)s"
+msgstr ""
+
+#: ironic/conductor/manager.py:986
+#, python-format
+msgid "Failed to tear down from cleaning for node %s"
+msgstr ""
+
+#: ironic/conductor/manager.py:1249
msgid "not supported"
msgstr ""
-#: ironic/conductor/manager.py:1022
+#: ironic/conductor/manager.py:1277
#, python-format
msgid "Node %s can't be deleted because it's not powered off"
msgstr ""
-#: ironic/conductor/manager.py:1101
+#: ironic/conductor/manager.py:1377
msgid "enabled"
msgstr ""
-#: ironic/conductor/manager.py:1101
+#: ironic/conductor/manager.py:1377
msgid "disabled"
msgstr ""
-#: ironic/conductor/manager.py:1124
+#: ironic/conductor/manager.py:1400
msgid "enabling"
msgstr ""
-#: ironic/conductor/manager.py:1124
+#: ironic/conductor/manager.py:1400
msgid "disabling"
msgstr ""
-#: ironic/conductor/manager.py:1125
+#: ironic/conductor/manager.py:1401
#, python-format
msgid "Error %(op)s the console on node %(node)s. Reason: %(error)s"
msgstr ""
-#: ironic/conductor/manager.py:1376 ironic/conductor/manager.py:1404
+#: ironic/conductor/manager.py:1660
+#, python-format
+msgid ""
+"RPC inspect_hardware failed to validate inspection or power info. Error: "
+"%(msg)s"
+msgstr ""
+
+#: ironic/conductor/manager.py:1692
+msgid "timeout reached while inspecting the node"
+msgstr ""
+
+#: ironic/conductor/manager.py:1777 ironic/conductor/manager.py:1805
msgid "No free conductor workers available"
msgstr ""
-#: ironic/conductor/manager.py:1476
+#: ironic/conductor/manager.py:1877
#, python-format
msgid "Failed to upload the configdrive to Swift. Error: %s"
msgstr ""
-#: ironic/conductor/manager.py:1486
+#: ironic/conductor/manager.py:1887
#, python-format
msgid "Failed to prepare to deploy. Error: %s"
msgstr ""
-#: ironic/conductor/manager.py:1494
+#: ironic/conductor/manager.py:1895
#, python-format
msgid "Failed to deploy. Error: %s"
msgstr ""
-#: ironic/conductor/manager.py:1528
-#, python-format
-msgid "Failed to tear down. Error: %s"
-msgstr ""
-
-#: ironic/conductor/manager.py:1548
+#: ironic/conductor/manager.py:1921
#, python-format
msgid ""
"During sync_power_state, max retries exceeded for node %(node)s, node "
@@ -841,11 +972,16 @@ msgid ""
"state to '%(actual)s' Switching node to maintenance mode."
msgstr ""
-#: ironic/conductor/manager.py:1594
+#: ironic/conductor/manager.py:1967
msgid "Power driver returned ERROR state while trying to sync power state."
msgstr ""
-#: ironic/conductor/rpcapi.py:105
+#: ironic/conductor/manager.py:2059
+#, python-format
+msgid "During inspection, driver returned unexpected state %(state)s"
+msgstr ""
+
+#: ironic/conductor/rpcapi.py:108
#, python-format
msgid "No conductor service registered which supports driver %s."
msgstr ""
@@ -871,69 +1007,98 @@ msgid ""
"aborting. More info may be found in the log file."
msgstr ""
-#: ironic/db/sqlalchemy/api.py:338
+#: ironic/db/sqlalchemy/api.py:334
msgid "Cannot overwrite UUID for an existing Node."
msgstr ""
-#: ironic/db/sqlalchemy/api.py:425
+#: ironic/db/sqlalchemy/api.py:431
msgid "Cannot overwrite UUID for an existing Port."
msgstr ""
-#: ironic/db/sqlalchemy/api.py:488
+#: ironic/db/sqlalchemy/api.py:489
msgid "Cannot overwrite UUID for an existing Chassis."
msgstr ""
-#: ironic/dhcp/neutron.py:70
+#: ironic/dhcp/neutron.py:74
msgid "Neutron auth_strategy should be either \"noauth\" or \"keystone\"."
msgstr ""
-#: ironic/dhcp/neutron.py:161
+#: ironic/dhcp/neutron.py:171
#, python-format
msgid ""
"No VIFs found for node %(node)s when attempting to update DHCP BOOT "
"options."
msgstr ""
-#: ironic/dhcp/neutron.py:175
+#: ironic/dhcp/neutron.py:185
#, python-format
msgid "Failed to set DHCP BOOT options for any port on node %s."
msgstr ""
-#: ironic/drivers/agent.py:103 ironic/drivers/fake.py:197
-#: ironic/drivers/pxe.py:231
+#: ironic/dhcp/neutron.py:297
+msgid "Valid cleaning network UUID not provided"
+msgstr ""
+
+#: ironic/dhcp/neutron.py:313
+#, python-format
+msgid "Could not create cleaning port on network %(net)s from %(node)s. %(exc)s"
+msgstr ""
+
+#: ironic/dhcp/neutron.py:322
+#, python-format
+msgid "Failed to create cleaning ports for node %(node)s"
+msgstr ""
+
+#: ironic/dhcp/neutron.py:343
+#, python-format
+msgid ""
+"Could not get cleaning network vif for %(node)s from Neutron, possible "
+"network issue. %(exc)s"
+msgstr ""
+
+#: ironic/dhcp/neutron.py:357
+#, python-format
+msgid ""
+"Could not remove cleaning ports on network %(net)s from %(node)s, "
+"possible network issue. %(exc)s"
+msgstr ""
+
+#: ironic/drivers/agent.py:103 ironic/drivers/fake.py:205
+#: ironic/drivers/pxe.py:244
msgid "Unable to import pyremotevbox library"
msgstr ""
-#: ironic/drivers/drac.py:34 ironic/drivers/fake.py:159
+#: ironic/drivers/drac.py:35 ironic/drivers/fake.py:166
+#: ironic/drivers/fake.py:233 ironic/drivers/pxe.py:264
msgid "Unable to import pywsman library"
msgstr ""
-#: ironic/drivers/fake.py:96
+#: ironic/drivers/fake.py:102
msgid "Unable to import pyghmi IPMI library"
msgstr ""
-#: ironic/drivers/fake.py:110 ironic/drivers/pxe.py:114
+#: ironic/drivers/fake.py:116 ironic/drivers/pxe.py:126
msgid "Unable to import seamicroclient library"
msgstr ""
-#: ironic/drivers/fake.py:134 ironic/drivers/pxe.py:142
+#: ironic/drivers/fake.py:140 ironic/drivers/pxe.py:154
msgid "Unable to import iboot library"
msgstr ""
-#: ironic/drivers/fake.py:146 ironic/drivers/ilo.py:43 ironic/drivers/ilo.py:66
-#: ironic/drivers/pxe.py:162
+#: ironic/drivers/fake.py:152 ironic/drivers/ilo.py:43 ironic/drivers/ilo.py:67
+#: ironic/drivers/pxe.py:174
msgid "Unable to import proliantutils library"
msgstr ""
-#: ironic/drivers/fake.py:173 ironic/drivers/pxe.py:184
+#: ironic/drivers/fake.py:180 ironic/drivers/pxe.py:197
msgid "Unable to import pysnmp library"
msgstr ""
-#: ironic/drivers/fake.py:185 ironic/drivers/pxe.py:207
+#: ironic/drivers/fake.py:192 ironic/drivers/pxe.py:220
msgid "Unable to import python-scciclient library"
msgstr ""
-#: ironic/drivers/pxe.py:91
+#: ironic/drivers/pxe.py:101
msgid "Unable to import pyghmi library"
msgstr ""
@@ -948,62 +1113,136 @@ msgid ""
"%(valid_values)s."
msgstr ""
-#: ironic/drivers/modules/agent.py:182
+#: ironic/drivers/modules/agent.py:71 ironic/drivers/modules/pxe.py:102
+msgid "UUID (from Glance) of the deployment kernel. Required."
+msgstr ""
+
+#: ironic/drivers/modules/agent.py:73
+msgid ""
+"UUID (from Glance) of the ramdisk with agent that is used at deploy time."
+" Required."
+msgstr ""
+
+#: ironic/drivers/modules/agent.py:260
#, python-format
msgid "Node %s failed to validate deploy image info. Some parameters were missing"
msgstr ""
-#: ironic/drivers/modules/agent.py:336
+#: ironic/drivers/modules/agent.py:266
+#, python-format
+msgid ""
+"image_source's image_checksum must be provided in instance_info for node "
+"%s"
+msgstr ""
+
+#: ironic/drivers/modules/agent.py:274
+#, python-format
+msgid ""
+"Node %(node)s is configured to use the %(driver)s driver which currently "
+"does not support deploying partition images."
+msgstr ""
+
+#: ironic/drivers/modules/agent.py:483
#, python-format
msgid "node %(node)s command status errored: %(error)s"
msgstr ""
-#: ironic/drivers/modules/agent_base_vendor.py:119
+#: ironic/drivers/modules/agent_base_vendor.py:124
msgid "Missing parameter version"
msgstr ""
-#: ironic/drivers/modules/agent_base_vendor.py:122
+#: ironic/drivers/modules/agent_base_vendor.py:127
#, python-format
msgid "Unknown lookup payload version: %s"
msgstr ""
-#: ironic/drivers/modules/agent_base_vendor.py:149
+#: ironic/drivers/modules/agent_base_vendor.py:160
+#, python-format
+msgid "Agent returned error for clean step %(step)s on node %(node)s : %(err)s."
+msgstr ""
+
+#: ironic/drivers/modules/agent_base_vendor.py:172
+#, python-format
+msgid "Could not restart cleaning on node %(node)s: %(err)s."
+msgstr ""
+
+#: ironic/drivers/modules/agent_base_vendor.py:184
+#, python-format
+msgid ""
+"Agent returned unknown status for clean step %(step)s on node %(node)s : "
+"%(err)s."
+msgstr ""
+
+#: ironic/drivers/modules/agent_base_vendor.py:215
msgid "For heartbeat operation, \"agent_url\" must be specified."
msgstr ""
-#: ironic/drivers/modules/agent_base_vendor.py:158
+#: ironic/drivers/modules/agent_base_vendor.py:224
msgid "Failed checking if deploy is done."
msgstr ""
-#: ironic/drivers/modules/agent_base_vendor.py:161
+#: ironic/drivers/modules/agent_base_vendor.py:232
msgid "Node failed to get image for deploy."
msgstr ""
-#: ironic/drivers/modules/agent_base_vendor.py:165
+#: ironic/drivers/modules/agent_base_vendor.py:236
msgid "Node failed to move to active state."
msgstr ""
-#: ironic/drivers/modules/agent_base_vendor.py:245
+#: ironic/drivers/modules/agent_base_vendor.py:249
+#, python-format
+msgid "Asynchronous exception for node %(node)s: %(msg)s exception: %(e)s"
+msgstr ""
+
+#: ironic/drivers/modules/agent_base_vendor.py:337
#, python-format
msgid "Malformed network interfaces lookup: %s"
msgstr ""
-#: ironic/drivers/modules/agent_base_vendor.py:274
+#: ironic/drivers/modules/agent_base_vendor.py:366
#, python-format
msgid "No ports matching the given MAC addresses %sexist in the database."
msgstr ""
-#: ironic/drivers/modules/agent_base_vendor.py:322
+#: ironic/drivers/modules/agent_base_vendor.py:414
#, python-format
msgid ""
"Ports matching mac addresses match multiple nodes. MACs: %(macs)s. Port "
"ids: %(port_ids)s"
msgstr ""
+#: ironic/drivers/modules/agent_base_vendor.py:443
+#, python-format
+msgid "Error rebooting node %(node)s. Error: %(error)s"
+msgstr ""
+
+#: ironic/drivers/modules/agent_base_vendor.py:475
+#, python-format
+msgid ""
+"Failed to install a bootloader when deploying node %(node)s. Error: "
+"%(error)s"
+msgstr ""
+
+#: ironic/drivers/modules/agent_base_vendor.py:484
+#, python-format
+msgid ""
+"Failed to change the boot device to %(boot_dev)s when deploying node "
+"%(node)s. Error: %(error)s"
+msgstr ""
+
#: ironic/drivers/modules/agent_client.py:48
msgid "Agent driver requires agent_url in driver_internal_info"
msgstr ""
+#: ironic/drivers/modules/agent_client.py:79
+#, python-format
+msgid ""
+"Unable to decode response as JSON.\n"
+"Request URL: %(url)s\n"
+"Request body: \"%(body)s\"\n"
+"Response: \"%(response)s\""
+msgstr ""
+
#: ironic/drivers/modules/console_utils.py:89
#, python-format
msgid ""
@@ -1037,74 +1276,125 @@ msgstr ""
msgid "Could not stop the console for node '%(node)s'. Reason: %(err)s."
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:117
+#: ironic/drivers/modules/deploy_utils.py:129
#, python-format
msgid ""
"iSCSI connection was not seen by the file system after attempting to "
"verify %d times."
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:140
+#: ironic/drivers/modules/deploy_utils.py:152
#, python-format
msgid ""
"iSCSI connection did not become active after attempting to verify %d "
"times."
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:406
+#: ironic/drivers/modules/deploy_utils.py:303
+#, python-format
+msgid ""
+"Unable to stat device %(dev)s after attempting to verify %(attempts)d "
+"times."
+msgstr ""
+
+#: ironic/drivers/modules/deploy_utils.py:501
#, python-format
msgid ""
"Can't download the configdrive content for node %(node)s from '%(url)s'. "
"Reason: %(reason)s"
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:415
+#: ironic/drivers/modules/deploy_utils.py:510
#, python-format
msgid ""
"Config drive for node %s is not base64 encoded or the content is "
"malformed."
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:418
+#: ironic/drivers/modules/deploy_utils.py:513
#, python-format
msgid " Downloaded from \"%s\"."
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:431
+#: ironic/drivers/modules/deploy_utils.py:526
#, python-format
msgid ""
"Encountered error while decompressing and writing config drive for node "
"%(node)s. Error: %(exc)s"
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:468
+#: ironic/drivers/modules/deploy_utils.py:597
#, python-format
-msgid "Parent device '%s' not found"
+msgid "Root device '%s' not found"
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:496
+#: ironic/drivers/modules/deploy_utils.py:607
#, python-format
-msgid "Root device '%s' not found"
+msgid "'%(partition)s' device '%(part_device)s' not found"
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:505
+#: ironic/drivers/modules/deploy_utils.py:735
#, python-format
-msgid "'%(partition)s' device '%(part_device)s' not found"
+msgid "Parent device '%s' not found"
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:613
+#: ironic/drivers/modules/deploy_utils.py:786
#, python-format
msgid "%(error_msg)s. Missing are: %(missing_info)s"
msgstr ""
-#: ironic/drivers/modules/deploy_utils.py:701
+#: ironic/drivers/modules/deploy_utils.py:875
#, python-format
msgid ""
"Error parsing capabilities from Node %s instance_info field. A dictionary"
-" or a dictionary string is expected."
+" or a \"jsonified\" dictionary is expected."
+msgstr ""
+
+#: ironic/drivers/modules/deploy_utils.py:909
+#, python-format
+msgid "get_clean_steps for node %(node)s returned invalid result: %(result)s"
+msgstr ""
+
+#: ironic/drivers/modules/deploy_utils.py:945
+#, python-format
+msgid "Agent on node %(node)s returned bad command result: %(result)s"
+msgstr ""
+
+#: ironic/drivers/modules/deploy_utils.py:1002
+#, python-format
+msgid ""
+"The hints \"%(invalid_hints)s\" are invalid. Valid hints are: "
+"\"%(valid_hints)s\""
+msgstr ""
+
+#: ironic/drivers/modules/deploy_utils.py:1012
+msgid "Root device hint \"size\" is not an integer value."
+msgstr ""
+
+#: ironic/drivers/modules/discoverd.py:78
+msgid "ironic-discoverd support is disabled"
+msgstr ""
+
+#: ironic/drivers/modules/discoverd.py:82
+msgid "ironic-discoverd Python module not found"
+msgstr ""
+
+#: ironic/drivers/modules/discoverd.py:88
+#, python-format
+msgid "ironic-discoverd version is too old: required >= 1.0.0, got %s"
+msgstr ""
+
+#: ironic/drivers/modules/discoverd.py:166
+#, python-format
+msgid "Failed to start inspection: %s"
+msgstr ""
+
+#: ironic/drivers/modules/discoverd.py:200
+#, python-format
+msgid "ironic-discoverd inspection failed: %s"
msgstr ""
-#: ironic/drivers/modules/fake.py:48 ironic/drivers/modules/ipminative.py:338
+#: ironic/drivers/modules/fake.py:48 ironic/drivers/modules/ipminative.py:343
#, python-format
msgid "set_power_state called with an invalid power state: %s."
msgstr ""
@@ -1130,11 +1420,12 @@ msgstr ""
msgid "Test if the value of bar is meow"
msgstr ""
-#: ironic/drivers/modules/fake.py:163 ironic/drivers/modules/ipminative.py:404
-#: ironic/drivers/modules/ipmitool.py:723
+#: ironic/drivers/modules/fake.py:163 ironic/drivers/modules/ipminative.py:409
+#: ironic/drivers/modules/ipmitool.py:734
#: ironic/drivers/modules/seamicro.py:571 ironic/drivers/modules/ssh.py:644
#: ironic/drivers/modules/virtualbox.py:338
-#: ironic/drivers/modules/ilo/management.py:132
+#: ironic/drivers/modules/ilo/management.py:198
+#: ironic/drivers/modules/irmc/management.py:142
#, python-format
msgid "Invalid boot device %s specified."
msgstr ""
@@ -1177,100 +1468,100 @@ msgstr ""
msgid "Cannot get power status for node '%(node)s'. iBoot get_relays() failed."
msgstr ""
-#: ironic/drivers/modules/iboot.py:185 ironic/drivers/modules/ipmitool.py:642
+#: ironic/drivers/modules/iboot.py:185 ironic/drivers/modules/ipmitool.py:653
#: ironic/drivers/modules/snmp.py:672 ironic/drivers/modules/ssh.py:561
#, python-format
msgid "set_power_state called with invalid power state %s."
msgstr ""
-#: ironic/drivers/modules/ipminative.py:63
+#: ironic/drivers/modules/ipminative.py:68
msgid "IP of the node's BMC. Required."
msgstr ""
-#: ironic/drivers/modules/ipminative.py:64
+#: ironic/drivers/modules/ipminative.py:69
msgid "IPMI password. Required."
msgstr ""
-#: ironic/drivers/modules/ipminative.py:65
+#: ironic/drivers/modules/ipminative.py:70
msgid "IPMI username. Required."
msgstr ""
-#: ironic/drivers/modules/ipminative.py:68
-#: ironic/drivers/modules/ipmitool.py:96 ironic/drivers/modules/seamicro.py:80
-#: ironic/drivers/modules/ilo/common.py:72
+#: ironic/drivers/modules/ipminative.py:73
+#: ironic/drivers/modules/ipmitool.py:95 ironic/drivers/modules/seamicro.py:80
+#: ironic/drivers/modules/ilo/common.py:75
msgid "node's UDP port to connect to. Only required for console access."
msgstr ""
-#: ironic/drivers/modules/ipminative.py:92
-#: ironic/drivers/modules/ipmitool.py:212
+#: ironic/drivers/modules/ipminative.py:97
+#: ironic/drivers/modules/ipmitool.py:223
#, python-format
msgid "Missing the following IPMI credentials in node's driver_info: %s."
msgstr ""
-#: ironic/drivers/modules/ipminative.py:110
-#: ironic/drivers/modules/ipmitool.py:232
+#: ironic/drivers/modules/ipminative.py:115
+#: ironic/drivers/modules/ipmitool.py:243
msgid "IPMI terminal port is not an integer."
msgstr ""
-#: ironic/drivers/modules/ipminative.py:494
-#: ironic/drivers/modules/ipmitool.py:940
+#: ironic/drivers/modules/ipminative.py:499
+#: ironic/drivers/modules/ipmitool.py:951
msgid "Missing 'ipmi_terminal_port' parameter in node's driver_info."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:70
+#: ironic/drivers/modules/ipmitool.py:69
msgid "IP address or hostname of the node. Required."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:73
+#: ironic/drivers/modules/ipmitool.py:72
msgid "password. Optional."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:74
+#: ironic/drivers/modules/ipmitool.py:73
#, python-format
msgid "privilege level; default is ADMINISTRATOR. One of %s. Optional."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:76
+#: ironic/drivers/modules/ipmitool.py:75
msgid "username; default is NULL user. Optional."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:77
+#: ironic/drivers/modules/ipmitool.py:76
msgid ""
"bridging_type; default is \"no\". One of \"single\", \"dual\", \"no\". "
"Optional."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:79
+#: ironic/drivers/modules/ipmitool.py:78
msgid ""
"transit channel for bridged request. Required only if ipmi_bridging is "
"set to \"dual\"."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:81
+#: ironic/drivers/modules/ipmitool.py:80
msgid ""
"transit address for bridged request. Required only if ipmi_bridging is "
"set to \"dual\"."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:83
+#: ironic/drivers/modules/ipmitool.py:82
msgid ""
"destination channel for bridged request. Required only if ipmi_bridging "
"is set to \"single\" or \"dual\"."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:86
+#: ironic/drivers/modules/ipmitool.py:85
msgid ""
"destination address for bridged request. Required only if ipmi_bridging "
"is set to \"single\" or \"dual\"."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:89
+#: ironic/drivers/modules/ipmitool.py:88
msgid ""
"local IPMB address for bridged requests. Used only if ipmi_bridging is "
"set to \"single\" or \"dual\". Optional."
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:243
+#: ironic/drivers/modules/ipmitool.py:254
#, python-format
msgid ""
"Value for ipmi_bridging is provided as %s, but IPMI bridging is not "
@@ -1278,152 +1569,206 @@ msgid ""
"is > 1.8.11"
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:266
+#: ironic/drivers/modules/ipmitool.py:277
#, python-format
msgid "%(param)s not provided"
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:269
+#: ironic/drivers/modules/ipmitool.py:280
#, python-format
msgid ""
"Invalid value for ipmi_bridging: %(bridging_type)s, the valid value can "
"be one of: %(bridging_types)s"
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:277
+#: ironic/drivers/modules/ipmitool.py:288
#, python-format
msgid ""
"Invalid privilege level value:%(priv_level)s, the valid value can be one "
"of %(valid_levels)s"
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:507
+#: ironic/drivers/modules/ipmitool.py:518
#, python-format
msgid "parse ipmi sensor data failed, unknown sensor type data: %(sensors_data)s"
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:545
+#: ironic/drivers/modules/ipmitool.py:556
#, python-format
msgid ""
"parse ipmi sensor data failed, get nothing with input data: "
"%(sensors_data)s"
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:587
-#: ironic/drivers/modules/ipmitool.py:678
-#: ironic/drivers/modules/ipmitool.py:830
-#: ironic/drivers/modules/ipmitool.py:922
+#: ironic/drivers/modules/ipmitool.py:598
+#: ironic/drivers/modules/ipmitool.py:689
+#: ironic/drivers/modules/ipmitool.py:841
+#: ironic/drivers/modules/ipmitool.py:933
msgid ""
"Unable to locate usable ipmitool command in the system path when checking"
" ipmitool version"
msgstr ""
-#: ironic/drivers/modules/ipmitool.py:906
+#: ironic/drivers/modules/ipmitool.py:917
msgid "Parameter raw_bytes (string of bytes) was not specified."
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:119
+#: ironic/drivers/modules/iscsi_deploy.py:127
msgid ""
"Cannot validate iSCSI deploy. Some parameters were missing in node's "
"instance_info"
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:131
+#: ironic/drivers/modules/iscsi_deploy.py:136
#, python-format
msgid ""
"Cannot validate parameter for iSCSI deploy. Invalid parameter %(param)s. "
"Reason: %(reason)s"
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:137
+#: ironic/drivers/modules/iscsi_deploy.py:142
#, python-format
-msgid "'%s' is not an integer value."
+msgid "%s is not an integer value."
+msgstr ""
+
+#: ironic/drivers/modules/iscsi_deploy.py:149
+msgid "Cannot deploy whole disk image with swap or ephemeral size set"
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:166
+#: ironic/drivers/modules/iscsi_deploy.py:182
#, python-format
msgid ""
"Root partition is too small for requested image. Image size: %(image_mb)d"
" MB, Root size: %(root_mb)d MB"
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:221
+#: ironic/drivers/modules/iscsi_deploy.py:236
msgid "Deploy key does not match"
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:237
+#: ironic/drivers/modules/iscsi_deploy.py:257
#, python-format
msgid "Parameters %s were not passed to ironic for deploy."
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:268
-msgid "Failure in deploy ramdisk."
+#: ironic/drivers/modules/iscsi_deploy.py:304
+#, python-format
+msgid "Error returned from deploy ramdisk: %s"
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:282 ironic/drivers/modules/pxe.py:518
-#: ironic/drivers/modules/ilo/deploy.py:528
-msgid "Failed to continue iSCSI deployment."
+#: ironic/drivers/modules/iscsi_deploy.py:325
+#, python-format
+msgid "Deploy failed for instance %(instance)s. Error: %(error)s"
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:389
+#: ironic/drivers/modules/iscsi_deploy.py:333
+#, python-format
+msgid ""
+"Couldn't determine the UUID of the root partition or the disk identifier "
+"after deploying node %s"
+msgstr ""
+
+#: ironic/drivers/modules/iscsi_deploy.py:371
+#, python-format
+msgid ""
+"Failed to start the iSCSI target to deploy the node %(node)s. Error: "
+"%(error)s"
+msgstr ""
+
+#: ironic/drivers/modules/iscsi_deploy.py:495
#, python-format
msgid "Failed to connect to Glance to get the properties of the image %s"
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:393
+#: ironic/drivers/modules/iscsi_deploy.py:499
#, python-format
-msgid "Image %s not found in Glance"
+msgid "Image %s can not be found."
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:403
+#: ironic/drivers/modules/iscsi_deploy.py:511
#, python-format
msgid "Image %(image)s is missing the following properties: %(properties)s"
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:423 ironic/drivers/modules/ssh.py:508
+#: ironic/drivers/modules/iscsi_deploy.py:531 ironic/drivers/modules/ssh.py:508
#, python-format
msgid "Node %s does not have any port associated with it."
msgstr ""
-#: ironic/drivers/modules/iscsi_deploy.py:432
+#: ironic/drivers/modules/iscsi_deploy.py:540
#, python-format
msgid ""
"Couldn't get the URL of the Ironic API service from the configuration "
"file or keystone catalog. Keystone error: %s"
msgstr ""
-#: ironic/drivers/modules/pxe.py:100
-msgid "UUID (from Glance) of the deployment kernel. Required."
+#: ironic/drivers/modules/iscsi_deploy.py:563
+msgid ""
+"Some mandatory input missing in 'pass_bootloader_info' vendor passthru "
+"from ramdisk."
+msgstr ""
+
+#: ironic/drivers/modules/iscsi_deploy.py:570
+#, python-format
+msgid "Deploy key %(key_sent)s does not match with %(expected_key)s"
+msgstr ""
+
+#: ironic/drivers/modules/iscsi_deploy.py:588
+#, python-format
+msgid "Failed to install bootloader on node %(node)s. Error: %(error)s."
msgstr ""
-#: ironic/drivers/modules/pxe.py:102
+#: ironic/drivers/modules/iscsi_deploy.py:613
+#, python-format
+msgid ""
+"Failed to notify ramdisk to reboot after bootloader installation. Error: "
+"%s"
+msgstr ""
+
+#: ironic/drivers/modules/pxe.py:104
msgid "UUID (from Glance) of the ramdisk that is mounted at boot time. Required."
msgstr ""
-#: ironic/drivers/modules/pxe.py:124
+#: ironic/drivers/modules/pxe.py:106
+msgid ""
+"DEPRECATED: Use deploy_kernel instead. UUID (from Glance) of the "
+"deployment kernel. Required."
+msgstr ""
+
+#: ironic/drivers/modules/pxe.py:109
+msgid ""
+"DEPRECATED: Use deploy_ramdisk instead. UUID (from Glance) of the ramdisk"
+" that is mounted at boot time. Required."
+msgstr ""
+
+#: ironic/drivers/modules/pxe.py:147
msgid ""
"Cannot validate PXE bootloader. Some parameters were missing in node's "
"driver_info"
msgstr ""
-#: ironic/drivers/modules/pxe.py:315
+#: ironic/drivers/modules/pxe.py:252
#, python-format
msgid ""
-"Local boot is requested, but can't be used with node %s because it's "
-"configured to use UEFI boot"
+"Conflict: Whole disk image being used for deploy, but cannot be used with"
+" node %(node_uuid)s configured to use UEFI boot with netboot option"
msgstr ""
-#: ironic/drivers/modules/pxe.py:323
+#: ironic/drivers/modules/pxe.py:360
msgid "iPXE boot is enabled but no HTTP URL or HTTP root was specified."
msgstr ""
-#: ironic/drivers/modules/pxe.py:330
+#: ironic/drivers/modules/pxe.py:367
#, python-format
msgid ""
"Conflict: iPXE is enabled, but cannot be used with node%(node_uuid)s "
"configured to use UEFI boot"
msgstr ""
+#: ironic/drivers/modules/pxe.py:638 ironic/drivers/modules/ilo/deploy.py:836
+msgid "Failed to continue iSCSI deployment."
+msgstr ""
+
#: ironic/drivers/modules/seamicro.py:68
msgid "API endpoint. Required."
msgstr ""
@@ -1667,6 +2012,46 @@ msgstr ""
msgid "'set_power_state' called with invalid power state '%s'"
msgstr ""
+#: ironic/drivers/modules/amt/common.py:37
+msgid "IP address or host name of the node. Required."
+msgstr ""
+
+#: ironic/drivers/modules/amt/common.py:38
+msgid "Password. Required."
+msgstr ""
+
+#: ironic/drivers/modules/amt/common.py:39
+msgid "Username to log into AMT system. Required."
+msgstr ""
+
+#: ironic/drivers/modules/amt/common.py:42
+msgid ""
+"Protocol used for AMT endpoint. one of http, https; default is \"http\". "
+"Optional."
+msgstr ""
+
+#: ironic/drivers/modules/amt/common.py:159
+#, python-format
+msgid "AMT driver requires the following to be set in node's driver_info: %s."
+msgstr ""
+
+#: ironic/drivers/modules/amt/common.py:167
+#, python-format
+msgid "Invalid protocol %s."
+msgstr ""
+
+#: ironic/drivers/modules/amt/management.py:144
+#, python-format
+msgid ""
+"set_boot_device called with invalid device %(device)s for node "
+"%(node_id)s."
+msgstr ""
+
+#: ironic/drivers/modules/amt/power.py:165
+#, python-format
+msgid "Unsupported target_state: %s"
+msgstr ""
+
#: ironic/drivers/modules/drac/common.py:26
msgid "IP address or hostname of the DRAC card. Required."
msgstr ""
@@ -1722,138 +2107,222 @@ msgid ""
"%s"
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:62
+#: ironic/drivers/modules/ilo/common.py:65
msgid "IP address or hostname of the iLO. Required."
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:63
+#: ironic/drivers/modules/ilo/common.py:66
msgid "username for the iLO with administrator privileges. Required."
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:65
+#: ironic/drivers/modules/ilo/common.py:68
msgid "password for ilo_username. Required."
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:68
+#: ironic/drivers/modules/ilo/common.py:71
msgid "port to be used for iLO operations. Optional."
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:69
+#: ironic/drivers/modules/ilo/common.py:72
msgid "timeout (in seconds) for iLO operations. Optional."
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:108
+#: ironic/drivers/modules/ilo/common.py:79
+msgid ""
+"new password for iLO. Required if the clean step 'reset_ilo_credential' "
+"is enabled."
+msgstr ""
+
+#: ironic/drivers/modules/ilo/common.py:115
#, python-format
msgid ""
"The following required iLO parameters are missing from the node's "
"driver_info: %s"
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:129
+#: ironic/drivers/modules/ilo/common.py:136
#, python-format
msgid ""
"The following iLO parameters from the node's driver_info should be "
"integers: %s"
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:177
+#: ironic/drivers/modules/ilo/common.py:184
msgid "iLO license check"
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:289
+#: ironic/drivers/modules/ilo/common.py:296
#, python-format
msgid "Inserting virtual media %s"
msgstr ""
-#: ironic/drivers/modules/ilo/common.py:319
+#: ironic/drivers/modules/ilo/common.py:326
+#: ironic/drivers/modules/ilo/common.py:375
#, python-format
msgid "Setting %s as boot mode"
msgstr ""
-#: ironic/drivers/modules/ilo/deploy.py:47
+#: ironic/drivers/modules/ilo/common.py:479
+#, python-format
+msgid "Get secure boot mode for node %s."
+msgstr ""
+
+#: ironic/drivers/modules/ilo/common.py:512
+#, python-format
+msgid "Setting secure boot to %(flag)s for node %(node)s."
+msgstr ""
+
+#: ironic/drivers/modules/ilo/deploy.py:61
msgid "UUID (from Glance) of the deployment ISO. Required."
msgstr ""
-#: ironic/drivers/modules/ilo/deploy.py:177
+#: ironic/drivers/modules/ilo/deploy.py:218
msgid ""
"Error validating iLO virtual media deploy. Some parameters were missing "
"in node's driver_info"
msgstr ""
-#: ironic/drivers/modules/ilo/deploy.py:446
+#: ironic/drivers/modules/ilo/deploy.py:695
msgid "Missing 'console_port' parameter in node's driver_info."
msgstr ""
-#: ironic/drivers/modules/ilo/management.py:100
+#: ironic/drivers/modules/ilo/inspect.py:101
+#, python-format
+msgid "Server didn't return the key(s): %(key)s"
+msgstr ""
+
+#: ironic/drivers/modules/ilo/inspect.py:106
+#, python-format
+msgid ""
+"Essential properties are expected to be in dictionary format, received "
+"%(properties)s from node %(node)s."
+msgstr ""
+
+#: ironic/drivers/modules/ilo/inspect.py:112
+#, python-format
+msgid "The node %s didn't return 'properties' as the key with inspection."
+msgstr ""
+
+#: ironic/drivers/modules/ilo/inspect.py:118
+#, python-format
+msgid "Node %(node)s didn't return MACs %(macs)s in dictionary format."
+msgstr ""
+
+#: ironic/drivers/modules/ilo/inspect.py:123
+#, python-format
+msgid "The node %s didn't return 'macs' as the key with inspection."
+msgstr ""
+
+#: ironic/drivers/modules/ilo/inspect.py:172
+#, python-format
+msgid ""
+"Node %(node)s has invalid capabilities string %(capabilities)s, unable to"
+" modify the node properties['capabilities'] string"
+msgstr ""
+
+#: ironic/drivers/modules/ilo/inspect.py:180
+#, python-format
+msgid ""
+"The expected format of capabilities from inspection is dictionary while "
+"node %(node)s returned %(capabilities)s."
+msgstr ""
+
+#: ironic/drivers/modules/ilo/inspect.py:249
+#, python-format
+msgid "Inspecting hardware (get_power_state) on %s"
+msgstr ""
+
+#: ironic/drivers/modules/ilo/management.py:91
+#, python-format
+msgid "Clean step '%s' not found. 'proliantutils' package needs to be updated."
+msgstr ""
+
+#: ironic/drivers/modules/ilo/management.py:102
+#, python-format
+msgid "Clean step %(step)s failed on node %(node)s with error: %(err)s"
+msgstr ""
+
+#: ironic/drivers/modules/ilo/management.py:166
msgid "Get boot device"
msgstr ""
-#: ironic/drivers/modules/ilo/management.py:143
+#: ironic/drivers/modules/ilo/management.py:209
#, python-format
msgid "Setting %s as boot device"
msgstr ""
-#: ironic/drivers/modules/ilo/power.py:88
+#: ironic/drivers/modules/ilo/power.py:95
msgid "iLO get_power_status"
msgstr ""
-#: ironic/drivers/modules/ilo/power.py:152
+#: ironic/drivers/modules/ilo/power.py:159
#: ironic/drivers/modules/irmc/power.py:60
#, python-format
msgid "_set_power_state called with invalid power state '%s'"
msgstr ""
-#: ironic/drivers/modules/ilo/power.py:161
+#: ironic/drivers/modules/ilo/power.py:168
msgid "iLO set_power_state"
msgstr ""
-#: ironic/drivers/modules/irmc/common.py:46
+#: ironic/drivers/modules/irmc/common.py:50
msgid "IP address or hostname of the iRMC. Required."
msgstr ""
-#: ironic/drivers/modules/irmc/common.py:47
+#: ironic/drivers/modules/irmc/common.py:51
msgid "Username for the iRMC with administrator privileges. Required."
msgstr ""
-#: ironic/drivers/modules/irmc/common.py:49
+#: ironic/drivers/modules/irmc/common.py:53
msgid "Password for irmc_username. Required."
msgstr ""
-#: ironic/drivers/modules/irmc/common.py:52
+#: ironic/drivers/modules/irmc/common.py:56
msgid ""
"Port to be used for iRMC operations; either 80 or 443. The default value "
"is 443. Optional."
msgstr ""
-#: ironic/drivers/modules/irmc/common.py:54
+#: ironic/drivers/modules/irmc/common.py:58
msgid ""
"Authentication method for iRMC operations; either 'basic' or 'digest'. "
-"The default value is 'digest'. Optional."
+"The default value is 'basic'. Optional."
msgstr ""
-#: ironic/drivers/modules/irmc/common.py:57
+#: ironic/drivers/modules/irmc/common.py:61
msgid ""
"Timeout (in seconds) for iRMC operations. The default value is 60. "
"Optional."
msgstr ""
-#: ironic/drivers/modules/irmc/common.py:82
+#: ironic/drivers/modules/irmc/common.py:63
+msgid ""
+"Sensor data retrieval method; either 'ipmitool' or 'scci'. The default "
+"value is 'ipmitool'. Optional."
+msgstr ""
+
+#: ironic/drivers/modules/irmc/common.py:89
#, python-format
msgid "Missing the following iRMC parameters in node's driver_info: %s."
msgstr ""
-#: ironic/drivers/modules/irmc/common.py:96
-#: ironic/drivers/modules/irmc/common.py:99
-#, python-format
-msgid "'%s' has unsupported value."
+#: ironic/drivers/modules/irmc/common.py:103
+msgid "'irmc_auth_method' has unsupported value."
msgstr ""
-#: ironic/drivers/modules/irmc/common.py:102
-#, python-format
-msgid "'%s' is not integer type."
+#: ironic/drivers/modules/irmc/common.py:106
+msgid "'irmc_port' has unsupported value."
msgstr ""
-#: ironic/drivers/modules/irmc/common.py:104
+#: ironic/drivers/modules/irmc/common.py:109
+msgid "'irmc_client_timeout' is not integer type."
+msgstr ""
+
+#: ironic/drivers/modules/irmc/common.py:112
+msgid "'irmc_sensor_method' has unsupported value."
+msgstr ""
+
+#: ironic/drivers/modules/irmc/common.py:114
#, python-format
msgid ""
"The following type errors were encountered while parsing driver_info:\n"
@@ -1900,18 +2369,6 @@ msgstr ""
msgid "An object of class %s is required here"
msgstr ""
-#: ironic/openstack/common/cliutils.py:271
-#, python-format
-msgid "No %(name)s with a name or ID of '%(name_or_id)s' exists."
-msgstr ""
-
-#: ironic/openstack/common/cliutils.py:279
-#, python-format
-msgid ""
-"Multiple %(name)s matches found for '%(name_or_id)s', use an ID to be "
-"more specific."
-msgstr ""
-
#: ironic/openstack/common/gettextutils.py:301
msgid "Message objects do not support addition."
msgstr ""
@@ -1922,16 +2379,17 @@ msgid ""
"characters. Please use unicode() or translate() instead."
msgstr ""
-#: ironic/openstack/common/imageutils.py:75
+#: ironic/openstack/common/imageutils.py:76
#, python-format
msgid "Invalid input value \"%s\"."
msgstr ""
-#: ironic/openstack/common/imageutils.py:104
+#: ironic/openstack/common/imageutils.py:105
msgid "Snapshot list encountered but no header found!"
msgstr ""
#: ironic/openstack/common/log.py:298
+#: ironic/openstack/common/versionutils.py:241
#, python-format
msgid "Deprecated: %s"
msgstr ""
@@ -1947,6 +2405,7 @@ msgid "syslog facility must be one of: %s"
msgstr ""
#: ironic/openstack/common/log.py:715
+#: ironic/openstack/common/versionutils.py:259
#, python-format
msgid "Fatal call to deprecated config: %(msg)s"
msgstr ""
@@ -1956,225 +2415,27 @@ msgstr ""
msgid "Unexpected argument for periodic task creation: %(arg)s."
msgstr ""
-#: ironic/openstack/common/policy.py:111
-msgid "The JSON file that defines policies."
-msgstr ""
-
-#: ironic/openstack/common/policy.py:114
-msgid "Default rule. Enforced when a requested rule is not found."
-msgstr ""
-
-#: ironic/openstack/common/policy.py:118
-msgid ""
-"Directories where policy configuration files are stored. They can be "
-"relative to any directory in the search path defined by the config_dir "
-"option, or absolute paths. The file defined by policy_file must exist for"
-" these directories to be searched."
-msgstr ""
-
-#: ironic/openstack/common/policy.py:142
-#, python-format
-msgid "Policy doesn't allow %s to be performed."
-msgstr ""
-
-#: ironic/openstack/common/policy.py:239
-#, python-format
-msgid "Rules must be an instance of dict or Rules, got %s instead"
-msgstr ""
-
-#: ironic/openstack/common/strutils.py:114
-#, python-format
-msgid "Unrecognized value '%(val)s', acceptable values are: %(acceptable)s"
-msgstr ""
-
-#: ironic/openstack/common/strutils.py:219
-#, python-format
-msgid "Invalid unit system: \"%s\""
-msgstr ""
-
-#: ironic/openstack/common/strutils.py:228
-#, python-format
-msgid "Invalid string format: %s"
-msgstr ""
-
-#: ironic/openstack/common/versionutils.py:88
+#: ironic/openstack/common/versionutils.py:108
#, python-format
msgid ""
"%(what)s is deprecated as of %(as_of)s in favor of %(in_favor_of)s and "
"may be removed in %(remove_in)s."
msgstr ""
-#: ironic/openstack/common/versionutils.py:92
+#: ironic/openstack/common/versionutils.py:112
#, python-format
msgid ""
"%(what)s is deprecated as of %(as_of)s and may be removed in "
"%(remove_in)s. It will not be superseded."
msgstr ""
-#: ironic/openstack/common/versionutils.py:96
+#: ironic/openstack/common/versionutils.py:116
#, python-format
msgid "%(what)s is deprecated as of %(as_of)s in favor of %(in_favor_of)s."
msgstr ""
-#: ironic/openstack/common/versionutils.py:99
+#: ironic/openstack/common/versionutils.py:119
#, python-format
msgid "%(what)s is deprecated as of %(as_of)s. It will not be superseded."
msgstr ""
-#: ironic/openstack/common/apiclient/base.py:224
-#: ironic/openstack/common/apiclient/base.py:381
-#, python-format
-msgid "No %(name)s matching %(args)s."
-msgstr ""
-
-#: ironic/openstack/common/apiclient/client.py:233
-msgid "Cannot find endpoint or token for request"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/client.py:356
-#, python-format
-msgid ""
-"Invalid %(api_name)s client version '%(version)s'. Must be one of: "
-"%(version_map)s"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:41
-#, python-format
-msgid "Missing arguments: %s"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:74
-#, python-format
-msgid "Authentication failed. Missing options: %s"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:83
-#, python-format
-msgid "AuthSystemNotFound: %s"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:106
-#, python-format
-msgid "AmbiguousEndpoints: %s"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:114
-msgid "HTTP Error"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:134
-msgid "HTTP Redirection"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:142
-msgid "HTTP Client Error"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:151
-msgid "HTTP Server Error"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:161
-msgid "Multiple Choices"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:170
-msgid "Bad Request"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:180
-msgid "Unauthorized"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:189
-msgid "Payment Required"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:199
-msgid "Forbidden"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:209
-msgid "Not Found"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:219
-msgid "Method Not Allowed"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:229
-msgid "Not Acceptable"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:238
-msgid "Proxy Authentication Required"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:247
-msgid "Request Timeout"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:257
-msgid "Conflict"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:267
-msgid "Gone"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:277
-msgid "Length Required"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:287
-msgid "Precondition Failed"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:296
-msgid "Request Entity Too Large"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:313
-msgid "Request-URI Too Long"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:323
-msgid "Unsupported Media Type"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:333
-msgid "Requested Range Not Satisfiable"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:342
-msgid "Expectation Failed"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:352
-msgid "Unprocessable Entity"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:361
-msgid "Internal Server Error"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:372
-msgid "Not Implemented"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:382
-msgid "Bad Gateway"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:391
-msgid "Service Unavailable"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:401
-msgid "Gateway Timeout"
-msgstr ""
-
-#: ironic/openstack/common/apiclient/exceptions.py:410
-msgid "HTTP Version Not Supported"
-msgstr ""
-
diff --git a/ironic/locale/pt_BR/LC_MESSAGES/ironic-log-critical.po b/ironic/locale/pt_BR/LC_MESSAGES/ironic-log-critical.po
new file mode 100644
index 000000000..712adbb10
--- /dev/null
+++ b/ironic/locale/pt_BR/LC_MESSAGES/ironic-log-critical.po
@@ -0,0 +1,25 @@
+# Translations template for ironic.
+# Copyright (C) 2015 ORGANIZATION
+# This file is distributed under the same license as the ironic project.
+#
+# Translators:
+# Lucas Alvares Gomes <lucasagomes@gmail.com>, 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Ironic\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2015-04-08 06:27+0000\n"
+"PO-Revision-Date: 2015-03-30 09:01+0000\n"
+"Last-Translator: Lucas Alvares Gomes <lucasagomes@gmail.com>\n"
+"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/"
+"ironic/language/pt_BR/)\n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 1.3\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: ironic/conductor/manager.py:297
+msgid "Failed to start keepalive"
+msgstr "Falha ao inicar o keep alive"
diff --git a/ironic/openstack/common/service.py b/ironic/openstack/common/service.py
index e4eed8a2e..694edf271 100644
--- a/ironic/openstack/common/service.py
+++ b/ironic/openstack/common/service.py
@@ -199,18 +199,30 @@ class ServiceWrapper(object):
class ProcessLauncher(object):
- def __init__(self):
- """Constructor."""
+ _signal_handlers_set = set()
+
+ @classmethod
+ def _handle_class_signals(cls, *args, **kwargs):
+ for handler in cls._signal_handlers_set:
+ handler(*args, **kwargs)
+ def __init__(self, wait_interval=0.01):
+ """Constructor.
+
+ :param wait_interval: The interval to sleep for between checks
+ of child process exit.
+ """
self.children = {}
self.sigcaught = None
self.running = True
+ self.wait_interval = wait_interval
rfd, self.writepipe = os.pipe()
self.readpipe = eventlet.greenio.GreenPipe(rfd, 'r')
self.handle_signal()
def handle_signal(self):
- _set_signals_handler(self._handle_signal)
+ self._signal_handlers_set.add(self._handle_signal)
+ _set_signals_handler(self._handle_class_signals)
def _handle_signal(self, signo, frame):
self.sigcaught = signo
@@ -329,8 +341,8 @@ class ProcessLauncher(object):
def _wait_child(self):
try:
- # Block while any of child processes have exited
- pid, status = os.waitpid(0, 0)
+ # Don't block if no child processes have exited
+ pid, status = os.waitpid(0, os.WNOHANG)
if not pid:
return None
except OSError as exc:
@@ -359,6 +371,10 @@ class ProcessLauncher(object):
while self.running:
wrap = self._wait_child()
if not wrap:
+ # Yield to other threads if no children have exited
+ # Sleep for a short time to avoid excessive CPU usage
+ # (see bug #1095346)
+ eventlet.greenthread.sleep(self.wait_interval)
continue
while self.running and len(wrap.children) < wrap.workers:
self._start_child(wrap)
@@ -383,8 +399,14 @@ class ProcessLauncher(object):
if not _is_sighup_and_daemon(self.sigcaught):
break
+ cfg.CONF.reload_config_files()
+ for service in set(
+ [wrap.service for wrap in self.children.values()]):
+ service.reset()
+
for pid in self.children:
os.kill(pid, signal.SIGHUP)
+
self.running = True
self.sigcaught = None
except eventlet.greenlet.GreenletExit:
diff --git a/ironic/openstack/common/versionutils.py b/ironic/openstack/common/versionutils.py
index dce07a82e..83bc04eac 100644
--- a/ironic/openstack/common/versionutils.py
+++ b/ironic/openstack/common/versionutils.py
@@ -17,6 +17,7 @@
Helpers for comparing version strings.
"""
+import copy
import functools
import inspect
import logging
@@ -32,13 +33,19 @@ LOG = logging.getLogger(__name__)
CONF = cfg.CONF
-opts = [
+deprecated_opts = [
cfg.BoolOpt('fatal_deprecations',
default=False,
help='Enables or disables fatal status of deprecations.'),
]
+def list_opts():
+ """Entry point for oslo.config-generator.
+ """
+ return [(None, copy.deepcopy(deprecated_opts))]
+
+
class deprecated(object):
"""A decorator to mark callables as deprecated.
@@ -232,7 +239,7 @@ def report_deprecated_feature(logger, msg, *args, **kwargs):
fatal deprecations.
"""
stdmsg = _("Deprecated: %s") % msg
- CONF.register_opts(opts)
+ CONF.register_opts(deprecated_opts)
if CONF.fatal_deprecations:
logger.critical(stdmsg, *args, **kwargs)
raise DeprecatedConfig(msg=stdmsg)
diff --git a/ironic/tests/api/test_acl.py b/ironic/tests/api/test_acl.py
index 0b60387d8..8de194ffc 100644
--- a/ironic/tests/api/test_acl.py
+++ b/ironic/tests/api/test_acl.py
@@ -88,7 +88,10 @@ class TestACL(base.FunctionalTest):
self.assertEqual(200, response.status_int)
def test_public_api_with_path_extensions(self):
- for route in ('/v1/', '/v1.json', '/v1.xml'):
- response = self.get_json(route,
+ routes = {'/v1/': 200,
+ '/v1.json': 200,
+ '/v1.xml': 404}
+ for url in routes:
+ response = self.get_json(url,
path_prefix='', expect_errors=True)
- self.assertEqual(200, response.status_int)
+ self.assertEqual(routes[url], response.status_int)
diff --git a/ironic/tests/api/utils.py b/ironic/tests/api/utils.py
index ee029717f..76d229b0a 100644
--- a/ironic/tests/api/utils.py
+++ b/ironic/tests/api/utils.py
@@ -94,3 +94,13 @@ def chassis_post_data(**kw):
chassis = utils.get_test_chassis(**kw)
internal = chassis_controller.ChassisPatchType.internal_attrs()
return remove_internal(chassis, internal)
+
+
+def post_get_test_node(**kw):
+ # NOTE(lucasagomes): When creating a node via API (POST)
+ # we have to use chassis_uuid
+ node = node_post_data(**kw)
+ chassis = utils.get_test_chassis()
+ node['chassis_id'] = None
+ node['chassis_uuid'] = kw.get('chassis_uuid', chassis['uuid'])
+ return node
diff --git a/ironic/tests/api/v1/test_nodes.py b/ironic/tests/api/v1/test_nodes.py
index 8950ec224..dbb95daaf 100644
--- a/ironic/tests/api/v1/test_nodes.py
+++ b/ironic/tests/api/v1/test_nodes.py
@@ -38,20 +38,9 @@ from ironic import objects
from ironic.tests.api import base as test_api_base
from ironic.tests.api import utils as test_api_utils
from ironic.tests import base
-from ironic.tests.db import utils as dbutils
from ironic.tests.objects import utils as obj_utils
-# NOTE(lucasagomes): When creating a node via API (POST)
-# we have to use chassis_uuid
-def post_get_test_node(**kw):
- node = test_api_utils.node_post_data(**kw)
- chassis = dbutils.get_test_chassis()
- node['chassis_id'] = None
- node['chassis_uuid'] = kw.get('chassis_uuid', chassis['uuid'])
- return node
-
-
class TestNodeObject(base.TestCase):
def test_node_init(self):
@@ -1132,7 +1121,7 @@ class TestPost(test_api_base.FunctionalTest):
@mock.patch.object(timeutils, 'utcnow')
def test_create_node(self, mock_utcnow):
- ndict = post_get_test_node()
+ ndict = test_api_utils.post_get_test_node()
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
response = self.post_json('/nodes', ndict)
@@ -1157,7 +1146,7 @@ class TestPost(test_api_base.FunctionalTest):
# as Unset).
with mock.patch.object(self.dbapi, 'create_node',
wraps=self.dbapi.create_node) as cn_mock:
- ndict = post_get_test_node(extra={'foo': 123})
+ ndict = test_api_utils.post_get_test_node(extra={'foo': 123})
self.post_json('/nodes', ndict)
result = self.get_json('/nodes/%s' % ndict['uuid'])
self.assertEqual(ndict['extra'], result['extra'])
@@ -1169,7 +1158,7 @@ class TestPost(test_api_base.FunctionalTest):
kwargs = {attr_name: {'str': 'foo', 'int': 123, 'float': 0.1,
'bool': True, 'list': [1, 2], 'none': None,
'dict': {'cat': 'meow'}}}
- ndict = post_get_test_node(**kwargs)
+ ndict = test_api_utils.post_get_test_node(**kwargs)
self.post_json('/nodes', ndict)
result = self.get_json('/nodes/%s' % ndict['uuid'])
self.assertEqual(ndict[attr_name], result[attr_name])
@@ -1295,7 +1284,7 @@ class TestPost(test_api_base.FunctionalTest):
self.assertEqual(403, response.status_int)
def test_create_node_no_mandatory_field_driver(self):
- ndict = post_get_test_node()
+ ndict = test_api_utils.post_get_test_node()
del ndict['driver']
response = self.post_json('/nodes', ndict, expect_errors=True)
self.assertEqual(400, response.status_int)
@@ -1303,7 +1292,7 @@ class TestPost(test_api_base.FunctionalTest):
self.assertTrue(response.json['error_message'])
def test_create_node_invalid_driver(self):
- ndict = post_get_test_node()
+ ndict = test_api_utils.post_get_test_node()
self.mock_gtf.side_effect = exception.NoValidHost('Fake Error')
response = self.post_json('/nodes', ndict, expect_errors=True)
self.assertEqual(400, response.status_int)
@@ -1311,7 +1300,7 @@ class TestPost(test_api_base.FunctionalTest):
self.assertTrue(response.json['error_message'])
def test_create_node_no_chassis_uuid(self):
- ndict = post_get_test_node()
+ ndict = test_api_utils.post_get_test_node()
del ndict['chassis_uuid']
response = self.post_json('/nodes', ndict)
self.assertEqual('application/json', response.content_type)
@@ -1323,7 +1312,8 @@ class TestPost(test_api_base.FunctionalTest):
expected_location)
def test_create_node_with_chassis_uuid(self):
- ndict = post_get_test_node(chassis_uuid=self.chassis.uuid)
+ ndict = test_api_utils.post_get_test_node(
+ chassis_uuid=self.chassis.uuid)
response = self.post_json('/nodes', ndict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
@@ -1336,7 +1326,7 @@ class TestPost(test_api_base.FunctionalTest):
expected_location)
def test_create_node_chassis_uuid_not_found(self):
- ndict = post_get_test_node(
+ ndict = test_api_utils.post_get_test_node(
chassis_uuid='1a1a1a1a-2b2b-3c3c-4d4d-5e5e5e5e5e5e')
response = self.post_json('/nodes', ndict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
@@ -1344,7 +1334,7 @@ class TestPost(test_api_base.FunctionalTest):
self.assertTrue(response.json['error_message'])
def test_create_node_with_internal_field(self):
- ndict = post_get_test_node()
+ ndict = test_api_utils.post_get_test_node()
ndict['reservation'] = 'fake'
response = self.post_json('/nodes', ndict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
@@ -1823,17 +1813,14 @@ class TestPut(test_api_base.FunctionalTest):
True, 'test-topic')
def test_provision_node_in_maintenance_fail(self):
- with mock.patch.object(rpcapi.ConductorAPI, 'do_node_deploy') as dnd:
- self.node.maintenance = True
- self.node.save()
- dnd.side_effect = exception.NodeInMaintenance(op='provisioning',
- node=self.node.uuid)
+ self.node.maintenance = True
+ self.node.save()
- ret = self.put_json('/nodes/%s/states/provision' % self.node.uuid,
- {'target': states.ACTIVE},
- expect_errors=True)
- self.assertEqual(400, ret.status_code)
- self.assertTrue(ret.json['error_message'])
+ ret = self.put_json('/nodes/%s/states/provision' % self.node.uuid,
+ {'target': states.ACTIVE},
+ expect_errors=True)
+ self.assertEqual(400, ret.status_code)
+ self.assertTrue(ret.json['error_message'])
@mock.patch.object(rpcapi.ConductorAPI, 'set_boot_device')
def test_set_boot_device(self, mock_sbd):
diff --git a/ironic/tests/api/v1/test_utils.py b/ironic/tests/api/v1/test_utils.py
index c8794707b..f55435884 100644
--- a/ironic/tests/api/v1/test_utils.py
+++ b/ironic/tests/api/v1/test_utils.py
@@ -24,21 +24,10 @@ from ironic.common import exception
from ironic import objects
from ironic.tests.api import utils as test_api_utils
from ironic.tests import base
-from ironic.tests.db import utils as dbutils
CONF = cfg.CONF
-# NOTE(lucasagomes): When creating a node via API (POST)
-# we have to use chassis_uuid
-def post_get_test_node(**kw):
- node = test_api_utils.node_post_data(**kw)
- chassis = dbutils.get_test_chassis()
- node['chassis_id'] = None
- node['chassis_uuid'] = kw.get('chassis_uuid', chassis['uuid'])
- return node
-
-
class TestApiUtils(base.TestCase):
def test_validate_limit(self):
@@ -73,7 +62,7 @@ class TestNodeIdent(base.TestCase):
self.valid_uuid = uuidutils.generate_uuid()
self.invalid_name = 'Mr Plow'
self.invalid_uuid = '636-555-3226-'
- self.node = post_get_test_node()
+ self.node = test_api_utils.post_get_test_node()
@mock.patch.object(pecan, 'request')
def test_allow_node_logical_names_pre_name(self, mock_pecan_req):
diff --git a/ironic/tests/db/sqlalchemy/test_migrations.py b/ironic/tests/db/sqlalchemy/test_migrations.py
index 8ddd3382f..37b539e02 100644
--- a/ironic/tests/db/sqlalchemy/test_migrations.py
+++ b/ironic/tests/db/sqlalchemy/test_migrations.py
@@ -15,15 +15,9 @@
# under the License.
"""
-Tests for database migrations. This test case reads the configuration
-file test_migrations.conf for database connection settings
-to use in the tests. For each connection found in the config file,
-the test case runs a series of test cases to ensure that migrations work
-properly.
-
-There are also "opportunistic" tests for both mysql and postgresql in here,
-which allows testing against all 3 databases (sqlite in memory, mysql, pg) in
-a properly configured unit test environment.
+Tests for database migrations. There are "opportunistic" tests for both mysql
+and postgresql in here, which allows testing against these databases in a
+properly configured unit test environment.
For the opportunistic testing you need to set up a db named 'openstack_citest'
with user 'openstack_citest' and password 'openstack_citest' on localhost.
diff --git a/ironic/tests/db/test_conductor.py b/ironic/tests/db/test_conductor.py
index d93aad120..1ff182615 100644
--- a/ironic/tests/db/test_conductor.py
+++ b/ironic/tests/db/test_conductor.py
@@ -64,7 +64,7 @@ class DbConductorTestCase(base.DbTestCase):
self.dbapi.unregister_conductor,
c.hostname)
- @mock.patch.object(timeutils, 'utcnow')
+ @mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_touch_conductor(self, mock_utcnow):
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
@@ -110,7 +110,7 @@ class DbConductorTestCase(base.DbTestCase):
self.assertEqual('hostname2', node2.reservation)
self.assertIsNone(node3.reservation)
- @mock.patch.object(timeutils, 'utcnow')
+ @mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_active_driver_dict_one_host_no_driver(self, mock_utcnow):
h = 'fake-host'
expected = {}
@@ -120,7 +120,7 @@ class DbConductorTestCase(base.DbTestCase):
result = self.dbapi.get_active_driver_dict()
self.assertEqual(expected, result)
- @mock.patch.object(timeutils, 'utcnow')
+ @mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_active_driver_dict_one_host_one_driver(self, mock_utcnow):
h = 'fake-host'
d = 'fake-driver'
@@ -131,7 +131,7 @@ class DbConductorTestCase(base.DbTestCase):
result = self.dbapi.get_active_driver_dict()
self.assertEqual(expected, result)
- @mock.patch.object(timeutils, 'utcnow')
+ @mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_active_driver_dict_one_host_many_drivers(self, mock_utcnow):
h = 'fake-host'
d1 = 'driver-one'
@@ -143,7 +143,7 @@ class DbConductorTestCase(base.DbTestCase):
result = self.dbapi.get_active_driver_dict()
self.assertEqual(expected, result)
- @mock.patch.object(timeutils, 'utcnow')
+ @mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_active_driver_dict_many_hosts_one_driver(self, mock_utcnow):
h1 = 'host-one'
h2 = 'host-two'
@@ -156,7 +156,7 @@ class DbConductorTestCase(base.DbTestCase):
result = self.dbapi.get_active_driver_dict()
self.assertEqual(expected, result)
- @mock.patch.object(timeutils, 'utcnow')
+ @mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_active_driver_dict_many_hosts_and_drivers(self, mock_utcnow):
h1 = 'host-one'
h2 = 'host-two'
@@ -172,7 +172,7 @@ class DbConductorTestCase(base.DbTestCase):
result = self.dbapi.get_active_driver_dict()
self.assertEqual(expected, result)
- @mock.patch.object(timeutils, 'utcnow')
+ @mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_active_driver_dict_with_old_conductor(self, mock_utcnow):
past = datetime.datetime(2000, 1, 1, 0, 0)
present = past + datetime.timedelta(minutes=2)
diff --git a/ironic/tests/db/test_nodes.py b/ironic/tests/db/test_nodes.py
index 7e3450345..be44943b4 100644
--- a/ironic/tests/db/test_nodes.py
+++ b/ironic/tests/db/test_nodes.py
@@ -136,7 +136,7 @@ class DbNodeTestCase(base.DbTestCase):
res = self.dbapi.get_node_list(filters={'maintenance': False})
self.assertEqual([node1.id], [r.id for r in res])
- @mock.patch.object(timeutils, 'utcnow')
+ @mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_nodeinfo_list_provision(self, mock_utcnow):
past = datetime.datetime(2000, 1, 1, 0, 0)
next = past + datetime.timedelta(minutes=8)
@@ -161,7 +161,7 @@ class DbNodeTestCase(base.DbTestCase):
states.DEPLOYWAIT})
self.assertEqual([node2.id], [r[0] for r in res])
- @mock.patch.object(timeutils, 'utcnow')
+ @mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_nodeinfo_list_inspection(self, mock_utcnow):
past = datetime.datetime(2000, 1, 1, 0, 0)
next = past + datetime.timedelta(minutes=8)
@@ -354,7 +354,7 @@ class DbNodeTestCase(base.DbTestCase):
node2.id,
{'instance_uuid': new_i_uuid})
- @mock.patch.object(timeutils, 'utcnow')
+ @mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_update_node_provision(self, mock_utcnow):
mocked_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = mocked_time
@@ -378,7 +378,7 @@ class DbNodeTestCase(base.DbTestCase):
self.assertIsNone(res['provision_updated_at'])
self.assertIsNone(res['inspection_started_at'])
- @mock.patch.object(timeutils, 'utcnow')
+ @mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_update_node_inspection_started_at(self, mock_utcnow):
mocked_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = mocked_time
@@ -390,7 +390,7 @@ class DbNodeTestCase(base.DbTestCase):
timeutils.normalize_time(result))
self.assertIsNone(res['inspection_finished_at'])
- @mock.patch.object(timeutils, 'utcnow')
+ @mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_update_node_inspection_finished_at(self, mock_utcnow):
mocked_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = mocked_time
diff --git a/ironic/tests/dhcp/test_neutron.py b/ironic/tests/dhcp/test_neutron.py
index 680a41edc..11a716fa9 100644
--- a/ironic/tests/dhcp/test_neutron.py
+++ b/ironic/tests/dhcp/test_neutron.py
@@ -377,8 +377,9 @@ class TestNeutron(db_base.DbTestCase):
'network_id': '00000000-0000-0000-0000-000000000000',
'admin_state_up': True, 'mac_address': self.ports[0].address}})
+ @mock.patch.object(neutron.NeutronDHCPApi, '_rollback_cleaning_ports')
@mock.patch.object(client.Client, 'create_port')
- def test_create_cleaning_ports_fail(self, create_mock):
+ def test_create_cleaning_ports_fail(self, create_mock, rollback_mock):
# Check that if creating a port fails, the ports are cleaned up
create_mock.side_effect = neutron_client_exc.ConnectionFailed
api = dhcp_factory.DHCPFactory().provider
@@ -390,6 +391,7 @@ class TestNeutron(db_base.DbTestCase):
create_mock.assert_called_once_with({'port': {
'network_id': '00000000-0000-0000-0000-000000000000',
'admin_state_up': True, 'mac_address': self.ports[0].address}})
+ rollback_mock.assert_called_once_with(task)
@mock.patch.object(client.Client, 'create_port')
def test_create_cleaning_ports_bad_config(self, create_mock):
diff --git a/ironic/tests/drivers/amt/test_vendor.py b/ironic/tests/drivers/amt/test_vendor.py
index f16079349..a6ddfc769 100644
--- a/ironic/tests/drivers/amt/test_vendor.py
+++ b/ironic/tests/drivers/amt/test_vendor.py
@@ -37,7 +37,8 @@ class AMTPXEVendorPassthruTestCase(db_base.DbTestCase):
driver='pxe_amt', driver_info=INFO_DICT)
def test_vendor_routes(self):
- expected = ['heartbeat', 'pass_deploy_info']
+ expected = ['heartbeat', 'pass_deploy_info',
+ 'pass_bootloader_install_info']
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
vendor_routes = task.driver.vendor.vendor_routes
diff --git a/ironic/tests/drivers/ilo/test_deploy.py b/ironic/tests/drivers/ilo/test_deploy.py
index 300e582fd..f44e532b9 100644
--- a/ironic/tests/drivers/ilo/test_deploy.py
+++ b/ironic/tests/drivers/ilo/test_deploy.py
@@ -124,7 +124,7 @@ class IloDeployPrivateMethodsTestCase(db_base.DbTestCase):
boot_iso_expected = 'boot-iso-uuid'
self.assertEqual(boot_iso_expected, boot_iso_actual)
- @mock.patch.object(driver_utils, 'get_boot_mode_for_deploy')
+ @mock.patch.object(deploy_utils, 'get_boot_mode_for_deploy')
@mock.patch.object(images, 'get_image_properties')
@mock.patch.object(ilo_deploy, '_parse_deploy_info')
def test__get_boot_iso_uefi_no_glance_image(self,
@@ -293,19 +293,6 @@ class IloDeployPrivateMethodsTestCase(db_base.DbTestCase):
ilo_deploy._update_secure_boot_mode(task, False)
self.assertFalse(func_set_secure_boot_mode.called)
- @mock.patch.object(driver_utils, 'add_node_capability')
- @mock.patch.object(driver_utils, 'rm_node_capability')
- def test__enable_uefi_capability(self, func_rm_node_capability,
- func_add_node_capability):
- with task_manager.acquire(self.context, self.node.uuid,
- shared=False) as task:
- ilo_deploy._enable_uefi_capability(task)
- func_rm_node_capability.assert_called_once_with(task,
- 'boot_mode')
- func_add_node_capability.assert_called_once_with(task,
- 'boot_mode',
- 'uefi')
-
@mock.patch.object(ilo_common, 'set_secure_boot_mode')
@mock.patch.object(ilo_common, 'get_secure_boot_mode')
def test__disable_secure_boot_false(self,
@@ -349,67 +336,86 @@ class IloDeployPrivateMethodsTestCase(db_base.DbTestCase):
self.assertFalse(returned_state)
@mock.patch.object(ilo_common, 'update_boot_mode')
- @mock.patch.object(deploy_utils, 'is_secure_boot_requested')
@mock.patch.object(ilo_deploy, '_disable_secure_boot')
@mock.patch.object(manager_utils, 'node_power_action')
def test__prepare_node_for_deploy(self,
func_node_power_action,
func_disable_secure_boot,
- func_is_secure_boot_requested,
func_update_boot_mode):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
func_disable_secure_boot.return_value = False
- func_is_secure_boot_requested.return_value = False
ilo_deploy._prepare_node_for_deploy(task)
func_node_power_action.assert_called_once_with(task,
states.POWER_OFF)
func_disable_secure_boot.assert_called_once_with(task)
- func_is_secure_boot_requested.assert_called_once_with(task.node)
func_update_boot_mode.assert_called_once_with(task)
+ bootmode = driver_utils.get_node_capability(task.node, "boot_mode")
+ self.assertIsNone(bootmode)
@mock.patch.object(ilo_common, 'update_boot_mode')
- @mock.patch.object(deploy_utils, 'is_secure_boot_requested')
@mock.patch.object(ilo_deploy, '_disable_secure_boot')
@mock.patch.object(manager_utils, 'node_power_action')
def test__prepare_node_for_deploy_sec_boot_on(self,
func_node_power_action,
func_disable_secure_boot,
- func_is_secure_boot_req,
func_update_boot_mode):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
func_disable_secure_boot.return_value = True
- func_is_secure_boot_req.return_value = False
ilo_deploy._prepare_node_for_deploy(task)
func_node_power_action.assert_called_once_with(task,
states.POWER_OFF)
func_disable_secure_boot.assert_called_once_with(task)
- func_is_secure_boot_req.assert_called_once_with(task.node)
self.assertFalse(func_update_boot_mode.called)
+ ret_boot_mode = task.node.instance_info['deploy_boot_mode']
+ self.assertEqual('uefi', ret_boot_mode)
+ bootmode = driver_utils.get_node_capability(task.node, "boot_mode")
+ self.assertIsNone(bootmode)
+
+ @mock.patch.object(ilo_common, 'update_boot_mode')
+ @mock.patch.object(ilo_deploy, '_disable_secure_boot')
+ @mock.patch.object(manager_utils, 'node_power_action')
+ def test__prepare_node_for_deploy_inst_info(self,
+ func_node_power_action,
+ func_disable_secure_boot,
+ func_update_boot_mode):
+ instance_info = {'capabilities': '{"secure_boot": "true"}'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ func_disable_secure_boot.return_value = False
+ task.node.instance_info = instance_info
+ ilo_deploy._prepare_node_for_deploy(task)
+ func_node_power_action.assert_called_once_with(task,
+ states.POWER_OFF)
+ func_disable_secure_boot.assert_called_once_with(task)
+ func_update_boot_mode.assert_called_once_with(task)
+ bootmode = driver_utils.get_node_capability(task.node, "boot_mode")
+ self.assertIsNone(bootmode)
+ deploy_boot_mode = task.node.instance_info.get('deploy_boot_mode')
+ self.assertIsNone(deploy_boot_mode)
@mock.patch.object(ilo_common, 'update_boot_mode')
- @mock.patch.object(ilo_deploy, '_enable_uefi_capability')
- @mock.patch.object(deploy_utils, 'is_secure_boot_requested')
@mock.patch.object(ilo_deploy, '_disable_secure_boot')
@mock.patch.object(manager_utils, 'node_power_action')
- def test__prepare_node_for_deploy_sec_boot_req(self,
- func_node_power_action,
- func_disable_secure_boot,
- func_is_secure_boot_req,
- func_enable_uefi_cap,
- func_update_boot_mode):
+ def test__prepare_node_for_deploy_sec_boot_on_inst_info(self,
+ func_node_power_action,
+ func_disable_secure_boot,
+ func_update_boot_mode):
+ instance_info = {'capabilities': '{"secure_boot": "true"}'}
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
func_disable_secure_boot.return_value = True
- func_is_secure_boot_req.return_value = True
+ task.node.instance_info = instance_info
ilo_deploy._prepare_node_for_deploy(task)
func_node_power_action.assert_called_once_with(task,
states.POWER_OFF)
func_disable_secure_boot.assert_called_once_with(task)
- func_is_secure_boot_req.assert_called_once_with(task.node)
- func_enable_uefi_cap.assert_called_once_with(task)
self.assertFalse(func_update_boot_mode.called)
+ bootmode = driver_utils.get_node_capability(task.node, "boot_mode")
+ self.assertIsNone(bootmode)
+ deploy_boot_mode = task.node.instance_info.get('deploy_boot_mode')
+ self.assertIsNone(deploy_boot_mode)
class IloVirtualMediaIscsiDeployTestCase(db_base.DbTestCase):
@@ -744,6 +750,18 @@ class VendorPassthruTestCase(db_base.DbTestCase):
get_deploy_info_mock.assert_called_once_with(task.node,
foo='bar')
+ @mock.patch.object(iscsi_deploy, 'validate_pass_bootloader_info_input',
+ autospec=True)
+ def test_validate_pass_bootloader_install_info(self,
+ validate_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ kwargs = {'address': '1.2.3.4', 'key': 'fake-key',
+ 'status': 'SUCCEEDED', 'error': ''}
+ task.driver.vendor.validate(
+ task, method='pass_bootloader_install_info', **kwargs)
+ validate_mock.assert_called_once_with(task, kwargs)
+
@mock.patch.object(iscsi_deploy, 'get_deploy_info')
def test_validate_heartbeat(self, get_deploy_info_mock):
with task_manager.acquire(self.context, self.node.uuid,
@@ -752,7 +770,22 @@ class VendorPassthruTestCase(db_base.DbTestCase):
vendor.validate(task, method='heartbeat', foo='bar')
self.assertFalse(get_deploy_info_mock.called)
- @mock.patch.object(deploy_utils, 'notify_deploy_complete')
+ @mock.patch.object(iscsi_deploy, 'validate_bootloader_install_status',
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'finish_deploy', autospec=True)
+ def test_pass_bootloader_install_info(self, finish_deploy_mock,
+ validate_input_mock):
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.pass_bootloader_install_info(task, **kwargs)
+ finish_deploy_mock.assert_called_once_with(task, '123456')
+ validate_input_mock.assert_called_once_with(task, kwargs)
+
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed')
@mock.patch.object(ilo_deploy, '_update_secure_boot_mode')
@mock.patch.object(ilo_common, 'update_boot_mode')
@mock.patch.object(manager_utils, 'node_set_boot_device')
@@ -765,7 +798,7 @@ class VendorPassthruTestCase(db_base.DbTestCase):
setup_vmedia_mock, set_boot_device_mock,
func_update_boot_mode,
func_update_secure_boot_mode,
- notify_deploy_complete_mock):
+ notify_ramdisk_to_proceed_mock):
kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
continue_deploy_mock.return_value = {'root uuid': 'root-uuid'}
get_boot_iso_mock.return_value = 'boot-iso'
@@ -791,7 +824,7 @@ class VendorPassthruTestCase(db_base.DbTestCase):
self.assertEqual('boot-iso',
task.node.instance_info['ilo_boot_iso'])
- notify_deploy_complete_mock.assert_called_once_with('123456')
+ notify_ramdisk_to_proceed_mock.assert_called_once_with('123456')
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot')
def test_pass_deploy_info_bad(self, cleanup_vmedia_boot_mock):
@@ -810,16 +843,19 @@ class VendorPassthruTestCase(db_base.DbTestCase):
self.assertEqual(states.NOSTATE, task.node.target_provision_state)
self.assertFalse(cleanup_vmedia_boot_mock.called)
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', autospec=True)
@mock.patch.object(manager_utils, 'node_power_action')
@mock.patch.object(iscsi_deploy, 'continue_deploy')
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot')
@mock.patch.object(ilo_deploy, '_get_boot_iso')
def test_pass_deploy_info_create_boot_iso_fail(self, get_iso_mock,
- cleanup_vmedia_boot_mock, continue_deploy_mock, node_power_mock):
+ cleanup_vmedia_boot_mock, continue_deploy_mock, node_power_mock,
+ update_boot_mode_mock, update_secure_boot_mode_mock):
kwargs = {'address': '123456'}
continue_deploy_mock.return_value = {'root uuid': 'root-uuid'}
get_iso_mock.side_effect = exception.ImageCreationFailed(
- image_type='iso', error="error")
+ image_type='iso', error="error")
self.node.provision_state = states.DEPLOYWAIT
self.node.target_provision_state = states.ACTIVE
self.node.save()
@@ -829,6 +865,8 @@ class VendorPassthruTestCase(db_base.DbTestCase):
task.driver.vendor.pass_deploy_info(task, **kwargs)
cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ update_boot_mode_mock.assert_called_once_with(task)
+ update_secure_boot_mode_mock.assert_called_once_with(task, True)
continue_deploy_mock.assert_called_once_with(task, **kwargs)
get_iso_mock.assert_called_once_with(task, 'root-uuid')
node_power_mock.assert_called_once_with(task, states.POWER_OFF)
@@ -836,22 +874,57 @@ class VendorPassthruTestCase(db_base.DbTestCase):
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
self.assertIsNotNone(task.node.last_error)
- @mock.patch.object(deploy_utils, 'notify_deploy_complete')
+ @mock.patch.object(iscsi_deploy, 'finish_deploy', autospec=True)
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ autospec=True)
@mock.patch.object(manager_utils, 'node_set_boot_device')
@mock.patch.object(ilo_deploy, '_update_secure_boot_mode')
@mock.patch.object(ilo_common, 'update_boot_mode')
@mock.patch.object(iscsi_deploy, 'continue_deploy')
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot')
- def _test_pass_deploy_info_localboot(self, cleanup_vmedia_boot_mock,
- continue_deploy_mock,
- func_update_boot_mode,
- func_update_secure_boot_mode,
- set_boot_device_mock,
- notify_deploy_complete_mock):
+ def test_pass_deploy_info_boot_option_local(
+ self, cleanup_vmedia_boot_mock, continue_deploy_mock,
+ func_update_boot_mode, func_update_secure_boot_mode,
+ set_boot_device_mock, notify_ramdisk_to_proceed_mock,
+ finish_deploy_mock):
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ continue_deploy_mock.return_value = {'root uuid': '<some-uuid>'}
+ self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ vendor = task.driver.vendor
+ vendor.pass_deploy_info(task, **kwargs)
+
+ cleanup_vmedia_boot_mock.assert_called_once_with(task)
+ continue_deploy_mock.assert_called_once_with(task, **kwargs)
+ set_boot_device_mock.assert_called_once_with(task,
+ boot_devices.DISK,
+ persistent=True)
+ func_update_boot_mode.assert_called_once_with(task)
+ func_update_secure_boot_mode.assert_called_once_with(task, True)
+ notify_ramdisk_to_proceed_mock.assert_called_once_with('123456')
+ self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ self.assertFalse(finish_deploy_mock.called)
+
+ @mock.patch.object(iscsi_deploy, 'finish_deploy', autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
+ @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', autospec=True)
+ @mock.patch.object(iscsi_deploy, 'continue_deploy', autospec=True)
+ @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', autospec=True)
+ def _test_pass_deploy_info_whole_disk_image(
+ self, cleanup_vmedia_boot_mock, continue_deploy_mock,
+ func_update_boot_mode, func_update_secure_boot_mode,
+ set_boot_device_mock, notify_ramdisk_to_proceed_mock):
kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
continue_deploy_mock.return_value = {'root uuid': '<some-uuid>'}
+ self.node.driver_internal_info = {'is_whole_disk_image': True}
self.node.provision_state = states.DEPLOYWAIT
self.node.target_provision_state = states.ACTIVE
self.node.save()
@@ -867,19 +940,15 @@ class VendorPassthruTestCase(db_base.DbTestCase):
persistent=True)
func_update_boot_mode.assert_called_once_with(task)
func_update_secure_boot_mode.assert_called_once_with(task, True)
- notify_deploy_complete_mock.assert_called_once_with('123456')
- self.assertEqual(states.ACTIVE, task.node.provision_state)
- self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ iscsi_deploy.finish_deploy.assert_called_once_with(task, '123456')
- def test_pass_deploy_info_boot_option_local(self):
+ def test_pass_deploy_info_whole_disk_image_local(self):
self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
self.node.save()
- self._test_pass_deploy_info_localboot()
+ self._test_pass_deploy_info_whole_disk_image()
def test_pass_deploy_info_whole_disk_image(self):
- self.node.driver_internal_info = {'is_whole_disk_image': True}
- self.node.save()
- self._test_pass_deploy_info_localboot()
+ self._test_pass_deploy_info_whole_disk_image()
@mock.patch.object(ilo_deploy, '_update_secure_boot_mode')
@mock.patch.object(ilo_common, 'update_boot_mode')
@@ -1075,7 +1144,8 @@ class IloPXEVendorPassthruTestCase(db_base.DbTestCase):
driver='pxe_ilo', driver_info=INFO_DICT)
def test_vendor_routes(self):
- expected = ['heartbeat', 'pass_deploy_info']
+ expected = ['heartbeat', 'pass_deploy_info',
+ 'pass_bootloader_install_info']
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
vendor_routes = task.driver.vendor.vendor_routes
@@ -1115,8 +1185,10 @@ class IloVirtualMediaAgentVendorInterfaceTestCase(db_base.DbTestCase):
@mock.patch.object(agent.AgentVendorInterface, 'reboot_to_instance')
@mock.patch.object(agent.AgentVendorInterface, 'check_deploy_success')
+ @mock.patch.object(ilo_common, 'update_boot_mode')
@mock.patch.object(ilo_deploy, '_update_secure_boot_mode')
def test_reboot_to_instance(self, func_update_secure_boot_mode,
+ func_update_boot_mode,
check_deploy_success_mock,
agent_reboot_to_instance_mock):
kwargs = {'address': '123456'}
@@ -1125,14 +1197,17 @@ class IloVirtualMediaAgentVendorInterfaceTestCase(db_base.DbTestCase):
shared=False) as task:
task.driver.vendor.reboot_to_instance(task, **kwargs)
check_deploy_success_mock.called_once_with(task.node)
+ func_update_boot_mode.assert_called_once_with(task)
func_update_secure_boot_mode.assert_called_once_with(task, True)
agent_reboot_to_instance_mock.assert_called_once_with(task,
**kwargs)
@mock.patch.object(agent.AgentVendorInterface, 'reboot_to_instance')
@mock.patch.object(agent.AgentVendorInterface, 'check_deploy_success')
+ @mock.patch.object(ilo_common, 'update_boot_mode')
@mock.patch.object(ilo_deploy, '_update_secure_boot_mode')
def test_reboot_to_instance_deploy_fail(self, func_update_secure_boot_mode,
+ func_update_boot_mode,
check_deploy_success_mock,
agent_reboot_to_instance_mock):
kwargs = {'address': '123456'}
@@ -1141,6 +1216,7 @@ class IloVirtualMediaAgentVendorInterfaceTestCase(db_base.DbTestCase):
shared=False) as task:
task.driver.vendor.reboot_to_instance(task, **kwargs)
check_deploy_success_mock.called_once_with(task.node)
+ self.assertFalse(func_update_boot_mode.called)
self.assertFalse(func_update_secure_boot_mode.called)
agent_reboot_to_instance_mock.assert_called_once_with(task,
**kwargs)
diff --git a/ironic/tests/drivers/test_deploy_utils.py b/ironic/tests/drivers/test_deploy_utils.py
index 033bd89d5..fdb6653a3 100644
--- a/ironic/tests/drivers/test_deploy_utils.py
+++ b/ironic/tests/drivers/test_deploy_utils.py
@@ -1020,13 +1020,18 @@ class WorkOnDiskTestCase(tests_base.TestCase):
self.swap_part = '/dev/fake-part1'
self.root_part = '/dev/fake-part2'
- self.mock_ibd = mock.patch.object(utils, 'is_block_device').start()
- self.mock_mp = mock.patch.object(utils, 'make_partitions').start()
- self.addCleanup(self.mock_ibd.stop)
- self.addCleanup(self.mock_mp.stop)
- self.mock_remlbl = mock.patch.object(utils,
- 'destroy_disk_metadata').start()
- self.addCleanup(self.mock_remlbl.stop)
+ self.mock_ibd_obj = mock.patch.object(
+ utils, 'is_block_device', autospec=True)
+ self.mock_ibd = self.mock_ibd_obj.start()
+ self.addCleanup(self.mock_ibd_obj.stop)
+ self.mock_mp_obj = mock.patch.object(
+ utils, 'make_partitions', autospec=True)
+ self.mock_mp = self.mock_mp_obj.start()
+ self.addCleanup(self.mock_mp_obj.stop)
+ self.mock_remlbl_obj = mock.patch.object(
+ utils, 'destroy_disk_metadata', autospec=True)
+ self.mock_remlbl = self.mock_remlbl_obj.start()
+ self.addCleanup(self.mock_remlbl_obj.stop)
self.mock_mp.return_value = {'swap': self.swap_part,
'root': self.root_part}
@@ -1044,7 +1049,7 @@ class WorkOnDiskTestCase(tests_base.TestCase):
boot_mode="bios")
def test_no_swap_partition(self):
- self.mock_ibd.side_effect = [True, False]
+ self.mock_ibd.side_effect = iter([True, False])
calls = [mock.call(self.root_part),
mock.call(self.swap_part)]
self.assertRaises(exception.InstanceDeployFailure,
@@ -1068,7 +1073,7 @@ class WorkOnDiskTestCase(tests_base.TestCase):
self.mock_mp.return_value = {'ephemeral': ephemeral_part,
'swap': swap_part,
'root': root_part}
- self.mock_ibd.side_effect = [True, True, False]
+ self.mock_ibd.side_effect = iter([True, True, False])
calls = [mock.call(root_part),
mock.call(swap_part),
mock.call(ephemeral_part)]
@@ -1096,7 +1101,7 @@ class WorkOnDiskTestCase(tests_base.TestCase):
self.mock_mp.return_value = {'swap': swap_part,
'configdrive': configdrive_part,
'root': root_part}
- self.mock_ibd.side_effect = [True, True, False]
+ self.mock_ibd.side_effect = iter([True, True, False])
calls = [mock.call(root_part),
mock.call(swap_part),
mock.call(configdrive_part)]
@@ -1465,6 +1470,27 @@ class ParseInstanceInfoCapabilitiesTestCase(tests_base.TestCase):
self.node.instance_info = {'capabilities': {"secure_boot": "invalid"}}
self.assertFalse(utils.is_secure_boot_requested(self.node))
+ def test_get_boot_mode_for_deploy_using_capabilities(self):
+ properties = {'capabilities': 'boot_mode:uefi,cap2:value2'}
+ self.node.properties = properties
+
+ result = utils.get_boot_mode_for_deploy(self.node)
+ self.assertEqual('uefi', result)
+
+ def test_get_boot_mode_for_deploy_using_instance_info_cap(self):
+ instance_info = {'capabilities': {'secure_boot': 'True'}}
+ self.node.instance_info = instance_info
+
+ result = utils.get_boot_mode_for_deploy(self.node)
+ self.assertEqual('uefi', result)
+
+ def test_get_boot_mode_for_deploy_using_instance_info(self):
+ instance_info = {'deploy_boot_mode': 'bios'}
+ self.node.instance_info = instance_info
+
+ result = utils.get_boot_mode_for_deploy(self.node)
+ self.assertEqual('bios', result)
+
class TrySetBootDeviceTestCase(db_base.DbTestCase):
diff --git a/ironic/tests/drivers/test_ipmitool.py b/ironic/tests/drivers/test_ipmitool.py
index a27060d0f..2e0097fba 100644
--- a/ironic/tests/drivers/test_ipmitool.py
+++ b/ironic/tests/drivers/test_ipmitool.py
@@ -840,6 +840,86 @@ class IPMIToolPrivateMethodTestCase(db_base.DbTestCase):
mock_support.assert_called_once_with('timing')
mock_pwf.assert_called_once_with(self.info['password'])
mock_exec.assert_called_once_with(*args)
+ self.assertEqual(1, mock_exec.call_count)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_exception_retry(self,
+ mock_exec, mock_support, mock_sleep):
+
+ ipmi.LAST_CMD_TIME = {}
+ mock_support.return_value = False
+ mock_exec.side_effect = iter([
+ processutils.ProcessExecutionError(
+ stderr="insufficient resources for session"
+ ),
+ (None, None)
+ ])
+
+ # Directly set the configuration values such that
+ # the logic will cause _exec_ipmitool to retry twice.
+ self.config(min_command_interval=1, group='ipmi')
+ self.config(retry_timeout=2, group='ipmi')
+
+ ipmi._exec_ipmitool(self.info, 'A B C')
+
+ mock_support.assert_called_once_with('timing')
+ self.assertEqual(2, mock_exec.call_count)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test__exec_ipmitool_exception_retries_exceeded(self,
+ mock_exec, mock_support, mock_sleep):
+
+ ipmi.LAST_CMD_TIME = {}
+ mock_support.return_value = False
+
+ mock_exec.side_effect = processutils.ProcessExecutionError(
+ stderr="insufficient resources for session"
+ )
+
+ # Directly set the configuration values such that
+ # the logic will cause _exec_ipmitool to timeout.
+ self.config(min_command_interval=1, group='ipmi')
+ self.config(retry_timeout=1, group='ipmi')
+
+ self.assertRaises(processutils.ProcessExecutionError,
+ ipmi._exec_ipmitool,
+ self.info, 'A B C')
+ mock_support.assert_called_once_with('timing')
+ self.assertEqual(1, mock_exec.call_count)
+
+ @mock.patch.object(ipmi, '_is_option_supported', autospec=True)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ 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 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(
+ stderr="Unknown"
+ ),
+ ])
+
+ # Directly set the configuration values such that
+ # the logic will cause _exec_ipmitool to retry up
+ # to 3 times.
+ self.config(min_command_interval=1, group='ipmi')
+ self.config(retry_timeout=3, group='ipmi')
+
+ self.assertRaises(processutils.ProcessExecutionError,
+ ipmi._exec_ipmitool,
+ self.info, 'A B C')
+ mock_support.assert_called_once_with('timing')
+ self.assertEqual(2, mock_exec.call_count)
@mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
def test__power_status_on(self, mock_exec, mock_sleep):
diff --git a/ironic/tests/drivers/test_iscsi_deploy.py b/ironic/tests/drivers/test_iscsi_deploy.py
index 0899f1702..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)
@@ -778,3 +797,118 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
self.assertEqual(states.DEPLOYFAIL, self.node.provision_state)
self.assertEqual(states.ACTIVE, self.node.target_provision_state)
self.assertIsNotNone(self.node.last_error)
+
+ def test_validate_pass_bootloader_info_input(self):
+ params = {'key': 'some-random-key', 'address': '1.2.3.4',
+ 'error': '', 'status': 'SUCCEEDED'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.instance_info['deploy_key'] = 'some-random-key'
+ # Assert that the method doesn't raise
+ iscsi_deploy.validate_pass_bootloader_info_input(task, params)
+
+ def test_validate_pass_bootloader_info_missing_status(self):
+ params = {'key': 'some-random-key', 'address': '1.2.3.4'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ iscsi_deploy.validate_pass_bootloader_info_input,
+ task, params)
+
+ def test_validate_pass_bootloader_info_missing_key(self):
+ params = {'status': 'SUCCEEDED', 'address': '1.2.3.4'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ iscsi_deploy.validate_pass_bootloader_info_input,
+ task, params)
+
+ def test_validate_pass_bootloader_info_missing_address(self):
+ params = {'status': 'SUCCEEDED', 'key': 'some-random-key'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.MissingParameterValue,
+ iscsi_deploy.validate_pass_bootloader_info_input,
+ task, params)
+
+ def test_validate_pass_bootloader_info_input_invalid_key(self):
+ params = {'key': 'some-other-key', 'address': '1.2.3.4',
+ 'status': 'SUCCEEDED'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.instance_info['deploy_key'] = 'some-random-key'
+ self.assertRaises(exception.InvalidParameterValue,
+ iscsi_deploy.validate_pass_bootloader_info_input,
+ task, params)
+
+ def test_validate_bootloader_install_status(self):
+ kwargs = {'key': 'abcdef', 'status': 'SUCCEEDED', 'error': ''}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.instance_info['deploy_key'] = 'abcdef'
+ # Nothing much to assert except that it shouldn't raise.
+ iscsi_deploy.validate_bootloader_install_status(task, kwargs)
+
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ def test_validate_bootloader_install_status_install_failed(
+ self, set_fail_state_mock):
+ kwargs = {'key': 'abcdef', 'status': 'FAILED', 'error': 'some-error'}
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = states.DEPLOYING
+ task.node.target_provision_state = states.ACTIVE
+ task.node.instance_info['deploy_key'] = 'abcdef'
+ self.assertRaises(exception.InstanceDeployFailure,
+ iscsi_deploy.validate_bootloader_install_status,
+ task, kwargs)
+ set_fail_state_mock.assert_called_once_with(task, mock.ANY)
+
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ autospec=True)
+ def test_finish_deploy(self, notify_mock):
+ 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=False) as task:
+ iscsi_deploy.finish_deploy(task, '1.2.3.4')
+ notify_mock.assert_called_once_with('1.2.3.4')
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ autospec=True)
+ def test_finish_deploy_notify_fails(self, notify_mock,
+ set_fail_state_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ notify_mock.side_effect = RuntimeError()
+ self.assertRaises(exception.InstanceDeployFailure,
+ iscsi_deploy.finish_deploy, task, '1.2.3.4')
+ set_fail_state_mock.assert_called_once_with(task, mock.ANY)
+
+ @mock.patch.object(manager_utils, 'node_power_action')
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed',
+ autospec=True)
+ def test_finish_deploy_ssh_with_local_boot(self, notify_mock,
+ node_power_mock):
+ instance_info = dict(INST_INFO_DICT)
+ instance_info['capabilities'] = {'boot_option': 'local'}
+ n = {
+ 'uuid': uuidutils.generate_uuid(),
+ 'driver': 'fake_ssh',
+ 'instance_info': instance_info,
+ 'provision_state': states.DEPLOYING,
+ 'target_provision_state': states.ACTIVE,
+ }
+ mgr_utils.mock_the_extension_manager(driver="fake_ssh")
+ node = obj_utils.create_test_node(self.context, **n)
+
+ with task_manager.acquire(self.context, node.uuid,
+ shared=False) as task:
+ iscsi_deploy.finish_deploy(task, '1.2.3.4')
+ notify_mock.assert_called_once_with('1.2.3.4')
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ node_power_mock.assert_called_once_with(task, states.REBOOT)
diff --git a/ironic/tests/drivers/test_pxe.py b/ironic/tests/drivers/test_pxe.py
index a8ed19c86..0c6168c86 100644
--- a/ironic/tests/drivers/test_pxe.py
+++ b/ironic/tests/drivers/test_pxe.py
@@ -681,6 +681,33 @@ class PXEDriverTestCase(db_base.DbTestCase):
address='123456', iqn='aaa-bbb',
key='fake-12345')
+ @mock.patch.object(iscsi_deploy, 'validate_pass_bootloader_info_input',
+ autospec=True)
+ def test_vendor_passthru_pass_bootloader_install_info(self,
+ validate_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ kwargs = {'address': '1.2.3.4', 'key': 'fake-key',
+ 'status': 'SUCCEEDED', 'error': ''}
+ task.driver.vendor.validate(
+ task, method='pass_bootloader_install_info', **kwargs)
+ validate_mock.assert_called_once_with(task, kwargs)
+
+ @mock.patch.object(iscsi_deploy, 'validate_bootloader_install_status',
+ autospec=True)
+ @mock.patch.object(iscsi_deploy, 'finish_deploy', autospec=True)
+ def test_pass_bootloader_install_info(self, finish_deploy_mock,
+ validate_input_mock):
+ kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.vendor.pass_bootloader_install_info(task, **kwargs)
+ finish_deploy_mock.assert_called_once_with(task, '123456')
+ validate_input_mock.assert_called_once_with(task, kwargs)
+
@mock.patch.object(pxe, '_get_image_info')
@mock.patch.object(pxe, '_cache_ramdisk_kernel')
@mock.patch.object(pxe, '_build_pxe_config_options')
@@ -901,7 +928,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
@mock.patch.object(pxe_utils, 'clean_up_pxe_config')
@mock.patch.object(manager_utils, 'node_set_boot_device')
- @mock.patch.object(deploy_utils, 'notify_deploy_complete')
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed')
@mock.patch.object(deploy_utils, 'switch_pxe_config')
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
@mock.patch.object(deploy_utils, 'deploy_partition_image')
@@ -932,8 +959,6 @@ class PXEDriverTestCase(db_base.DbTestCase):
task, address='123456', iqn='aaa-bbb', key='fake-56789')
self.node.refresh()
- self.assertEqual(states.ACTIVE, self.node.provision_state)
- self.assertEqual(states.NOSTATE, self.node.target_provision_state)
self.assertEqual(states.POWER_ON, self.node.power_state)
self.assertIn('root_uuid_or_disk_id', self.node.driver_internal_info)
self.assertIsNone(self.node.last_error)
@@ -957,7 +982,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
@mock.patch.object(pxe_utils, 'clean_up_pxe_config')
@mock.patch.object(manager_utils, 'node_set_boot_device')
- @mock.patch.object(deploy_utils, 'notify_deploy_complete')
+ @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed')
@mock.patch.object(deploy_utils, 'switch_pxe_config')
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
@mock.patch.object(deploy_utils, 'deploy_disk_image')
@@ -993,8 +1018,6 @@ class PXEDriverTestCase(db_base.DbTestCase):
key='fake-56789')
self.node.refresh()
- self.assertEqual(states.ACTIVE, self.node.provision_state)
- self.assertEqual(states.NOSTATE, self.node.target_provision_state)
self.assertEqual(states.POWER_ON, self.node.power_state)
self.assertIsNone(self.node.last_error)
self.assertFalse(os.path.exists(token_path))
@@ -1017,15 +1040,23 @@ class PXEDriverTestCase(db_base.DbTestCase):
def test_pass_deploy_info_deploy(self):
self._test_pass_deploy_info_deploy(False)
+ self.assertEqual(states.ACTIVE, self.node.provision_state)
+ self.assertEqual(states.NOSTATE, self.node.target_provision_state)
def test_pass_deploy_info_localboot(self):
self._test_pass_deploy_info_deploy(True)
+ self.assertEqual(states.DEPLOYWAIT, self.node.provision_state)
+ self.assertEqual(states.ACTIVE, self.node.target_provision_state)
def test_pass_deploy_info_whole_disk_image(self):
self._test_pass_deploy_info_whole_disk_image(False)
+ self.assertEqual(states.ACTIVE, self.node.provision_state)
+ self.assertEqual(states.NOSTATE, self.node.target_provision_state)
def test_pass_deploy_info_whole_disk_image_localboot(self):
self._test_pass_deploy_info_whole_disk_image(True)
+ self.assertEqual(states.ACTIVE, self.node.provision_state)
+ self.assertEqual(states.NOSTATE, self.node.target_provision_state)
def test_pass_deploy_info_invalid(self):
self.node.power_state = states.POWER_ON
@@ -1056,7 +1087,8 @@ class PXEDriverTestCase(db_base.DbTestCase):
"pass_deploy_info was not called once.")
def test_vendor_routes(self):
- expected = ['heartbeat', 'pass_deploy_info']
+ expected = ['heartbeat', 'pass_deploy_info',
+ 'pass_bootloader_install_info']
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
vendor_routes = task.driver.vendor.vendor_routes
diff --git a/ironic/tests/drivers/test_utils.py b/ironic/tests/drivers/test_utils.py
index 5eb9b5ace..cd14b464f 100644
--- a/ironic/tests/drivers/test_utils.py
+++ b/ironic/tests/drivers/test_utils.py
@@ -112,27 +112,6 @@ class UtilsTestCase(db_base.DbTestCase):
self.assertEqual('a:b,c:d,a:b',
task.node.properties['capabilities'])
- def test_rm_node_capability(self):
- with task_manager.acquire(self.context, self.node.uuid,
- shared=False) as task:
- task.node.properties['capabilities'] = 'a:b'
- driver_utils.rm_node_capability(task, 'a')
- self.assertIsNone(task.node.properties['capabilities'])
-
- def test_rm_node_capability_exists(self):
- with task_manager.acquire(self.context, self.node.uuid,
- shared=False) as task:
- task.node.properties['capabilities'] = 'a:b,c:d,x:y'
- self.assertIsNone(driver_utils.rm_node_capability(task, 'c'))
- self.assertEqual('a:b,x:y', task.node.properties['capabilities'])
-
- def test_rm_node_capability_non_existent(self):
- with task_manager.acquire(self.context, self.node.uuid,
- shared=False) as task:
- task.node.properties['capabilities'] = 'a:b'
- self.assertIsNone(driver_utils.rm_node_capability(task, 'x'))
- self.assertEqual('a:b', task.node.properties['capabilities'])
-
def test_validate_capability(self):
properties = {'capabilities': 'cat:meow,cap2:value2'}
self.node.properties = properties
@@ -191,17 +170,3 @@ class UtilsTestCase(db_base.DbTestCase):
self.assertRaises(exception.InvalidParameterValue,
driver_utils.validate_secure_boot_capability,
self.node)
-
- def test_get_boot_mode_for_deploy_using_capabilities(self):
- properties = {'capabilities': 'boot_mode:uefi,cap2:value2'}
- self.node.properties = properties
-
- result = driver_utils.get_boot_mode_for_deploy(self.node)
- self.assertEqual('uefi', result)
-
- def test_get_boot_mode_for_deploy_using_instance_info(self):
- instance_info = {'deploy_boot_mode': 'uefi'}
- self.node.instance_info = instance_info
-
- result = driver_utils.get_boot_mode_for_deploy(self.node)
- self.assertEqual('uefi', result)
diff --git a/ironic/tests/stubs.py b/ironic/tests/stubs.py
index 7d43d2676..d20c1fd8a 100644
--- a/ironic/tests/stubs.py
+++ b/ironic/tests/stubs.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from ironic.common import exception
+from glanceclient import exc as glance_exc
NOW_GLANCE_FORMAT = "2010-10-11T10:30:22"
@@ -40,7 +40,7 @@ class StubGlanceClient(object):
index += 1
break
else:
- raise exception.BadRequest('Marker not found')
+ raise glance_exc.BadRequest('Marker not found')
return self._images[index:index + limit]
@@ -48,7 +48,7 @@ class StubGlanceClient(object):
for image in self._images:
if image.id == str(image_id):
return image
- raise exception.ImageNotFound(image_id)
+ raise glance_exc.NotFound(image_id)
def data(self, image_id):
self.get(image_id)
@@ -76,7 +76,7 @@ class StubGlanceClient(object):
for k, v in metadata.items():
setattr(self._images[i], k, v)
return self._images[i]
- raise exception.NotFound(image_id)
+ raise glance_exc.NotFound(image_id)
def delete(self, image_id):
for i, image in enumerate(self._images):
@@ -86,10 +86,10 @@ class StubGlanceClient(object):
# HTTPForbidden.
image_data = self._images[i]
if image_data.deleted:
- raise exception.Forbidden()
+ raise glance_exc.Forbidden()
image_data.deleted = True
return
- raise exception.NotFound(image_id)
+ raise glance_exc.NotFound(image_id)
class FakeImage(object):
diff --git a/ironic/tests/test_glance_service.py b/ironic/tests/test_glance_service.py
index 266b86f12..6c3276da6 100644
--- a/ironic/tests/test_glance_service.py
+++ b/ironic/tests/test_glance_service.py
@@ -18,9 +18,13 @@ import datetime
import filecmp
import os
import tempfile
+import time
+from glanceclient import exc as glance_exc
import mock
+from oslo_config import cfg
from oslo_context import context
+from oslo_serialization import jsonutils
import testtools
@@ -32,8 +36,6 @@ from ironic.tests import base
from ironic.tests import matchers
from ironic.tests import stubs
-from oslo_config import cfg
-from oslo_serialization import jsonutils
CONF = cfg.CONF
@@ -458,7 +460,8 @@ class TestGlanceImageService(base.TestCase):
self.assertEqual(self.NOW_DATETIME, image_meta['created_at'])
self.assertEqual(self.NOW_DATETIME, image_meta['updated_at'])
- def test_download_with_retries(self):
+ @mock.patch.object(time, 'sleep', autospec=True)
+ def test_download_with_retries(self, mock_sleep):
tries = [0]
class MyGlanceStubClient(stubs.StubGlanceClient):
@@ -466,7 +469,7 @@ class TestGlanceImageService(base.TestCase):
def get(self, image_id):
if tries[0] == 0:
tries[0] = 1
- raise exception.ServiceUnavailable('')
+ raise glance_exc.ServiceUnavailable('')
else:
return {}
@@ -487,6 +490,7 @@ class TestGlanceImageService(base.TestCase):
tries = [0]
self.config(glance_num_retries=1, group='glance')
stub_service.download(image_id, writer)
+ self.assertTrue(mock_sleep.called)
def test_download_file_url(self):
# NOTE: only in v2 API
@@ -533,7 +537,7 @@ class TestGlanceImageService(base.TestCase):
class MyGlanceStubClient(stubs.StubGlanceClient):
"""A client that raises a Forbidden exception."""
def get(self, image_id):
- raise exception.Forbidden(image_id)
+ raise glance_exc.Forbidden(image_id)
stub_client = MyGlanceStubClient()
stub_context = context.RequestContext(auth_token=True)
@@ -549,7 +553,7 @@ class TestGlanceImageService(base.TestCase):
class MyGlanceStubClient(stubs.StubGlanceClient):
"""A client that raises a HTTPForbidden exception."""
def get(self, image_id):
- raise exception.HTTPForbidden(image_id)
+ raise glance_exc.HTTPForbidden(image_id)
stub_client = MyGlanceStubClient()
stub_context = context.RequestContext(auth_token=True)
@@ -565,7 +569,7 @@ class TestGlanceImageService(base.TestCase):
class MyGlanceStubClient(stubs.StubGlanceClient):
"""A client that raises a NotFound exception."""
def get(self, image_id):
- raise exception.NotFound(image_id)
+ raise glance_exc.NotFound(image_id)
stub_client = MyGlanceStubClient()
stub_context = context.RequestContext(auth_token=True)
@@ -581,7 +585,7 @@ class TestGlanceImageService(base.TestCase):
class MyGlanceStubClient(stubs.StubGlanceClient):
"""A client that raises a HTTPNotFound exception."""
def get(self, image_id):
- raise exception.HTTPNotFound(image_id)
+ raise glance_exc.HTTPNotFound(image_id)
stub_client = MyGlanceStubClient()
stub_context = context.RequestContext(auth_token=True)
@@ -632,7 +636,7 @@ def _create_failing_glance_client(info):
def get(self, image_id):
info['num_calls'] += 1
if info['num_calls'] == 1:
- raise exception.ServiceUnavailable('')
+ raise glance_exc.ServiceUnavailable('')
return {}
return MyGlanceStubClient()
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):
diff --git a/tools/config/oslo.config.generator.rc b/tools/config/oslo.config.generator.rc
index e224cf753..84c865ab5 100644
--- a/tools/config/oslo.config.generator.rc
+++ b/tools/config/oslo.config.generator.rc
@@ -1,2 +1,2 @@
-export IRONIC_CONFIG_GENERATOR_EXTRA_LIBRARIES='oslo.db oslo.messaging keystonemiddleware.auth_token'
+export IRONIC_CONFIG_GENERATOR_EXTRA_LIBRARIES='oslo.db oslo.messaging keystonemiddleware.auth_token oslo.concurrency oslo.policy'
export IRONIC_CONFIG_GENERATOR_EXTRA_MODULES=
diff --git a/tox.ini b/tox.ini
index c4159105f..5b83d7f5a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -43,6 +43,7 @@ commands =
bash tools/config/generate_sample.sh -b . -p ironic -o etc/ironic
[testenv:gendocs]
+setenv = PYTHONHASHSEED=0
sitepackages = False
envdir = {toxworkdir}/venv
commands =