summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ironic/api/controllers/v1/__init__.py146
-rw-r--r--ironic/api/controllers/v1/node.py3
-rw-r--r--ironic/conductor/inspection.py108
-rw-r--r--ironic/conductor/manager.py83
-rw-r--r--ironic/conf/inventory.py15
-rw-r--r--ironic/drivers/modules/ilo/boot.py12
-rw-r--r--ironic/drivers/modules/inspect_utils.py116
-rw-r--r--ironic/drivers/modules/inspector/interface.py14
-rw-r--r--ironic/drivers/modules/pxe.py15
-rw-r--r--ironic/drivers/modules/pxe_base.py7
-rw-r--r--ironic/tests/unit/api/controllers/v1/test_node.py44
-rw-r--r--ironic/tests/unit/conductor/test_inspection.py118
-rw-r--r--ironic/tests/unit/conductor/test_manager.py116
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_boot.py78
-rw-r--r--ironic/tests/unit/drivers/modules/inspector/test_interface.py12
-rw-r--r--ironic/tests/unit/drivers/modules/test_inspect_utils.py133
-rw-r--r--ironic/tests/unit/drivers/modules/test_pxe.py10
-rw-r--r--ironic/tests/unit/objects/utils.py12
-rw-r--r--playbooks/metal3-ci/run.yaml8
-rw-r--r--releasenotes/notes/fix_boot_mode_switch_with_anaconda_deploy_with_ilo_drivers-16637adb62f0ed2f.yaml5
-rw-r--r--releasenotes/notes/fix_secure_boot_with_anaconda_deploy-84d7c1e3bbfa40f2.yaml4
-rw-r--r--tools/benchmark/generate-statistics.py2
22 files changed, 530 insertions, 531 deletions
diff --git a/ironic/api/controllers/v1/__init__.py b/ironic/api/controllers/v1/__init__.py
index 9bd9af985..c352761c7 100644
--- a/ironic/api/controllers/v1/__init__.py
+++ b/ironic/api/controllers/v1/__init__.py
@@ -59,6 +59,27 @@ def max_version():
versions.min_version_string(), versions.max_version_string())
+def make_controller_links(name):
+ return [
+ link.make_link('self', api.request.public_url, name, ''),
+ link.make_link('bookmark', api.request.public_url, name, '',
+ bookmark=True)
+ ]
+
+
+VERSIONED_CONTROLLERS = {
+ 'portgroups': utils.allow_portgroups,
+ 'volume': utils.allow_volume,
+ 'lookup': utils.allow_ramdisk_endpoints,
+ 'heartbeat': utils.allow_ramdisk_endpoints,
+ 'conductors': utils.allow_expose_conductors,
+ 'allocations': utils.allow_allocations,
+ 'events': utils.allow_expose_events,
+ 'deploy_templates': utils.allow_deploy_templates,
+ 'shards': utils.allow_shards_endpoint,
+}
+
+
def v1():
v1 = {
'id': "v1",
@@ -75,124 +96,15 @@ def v1():
'base': 'application/json',
'type': 'application/vnd.openstack.ironic.v1+json'
},
- 'chassis': [
- link.make_link('self', api.request.public_url,
- 'chassis', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'chassis', '',
- bookmark=True)
- ],
- 'nodes': [
- link.make_link('self', api.request.public_url,
- 'nodes', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'nodes', '',
- bookmark=True)
- ],
- 'ports': [
- link.make_link('self', api.request.public_url,
- 'ports', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'ports', '',
- bookmark=True)
- ],
- 'drivers': [
- link.make_link('self', api.request.public_url,
- 'drivers', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'drivers', '',
- bookmark=True)
- ],
- 'version': version.default_version()
+ 'chassis': make_controller_links('chassis'),
+ 'nodes': make_controller_links('nodes'),
+ 'ports': make_controller_links('ports'),
+ 'drivers': make_controller_links('drivers'),
+ 'version': version.default_version(),
}
- if utils.allow_portgroups():
- v1['portgroups'] = [
- link.make_link('self', api.request.public_url,
- 'portgroups', ''),
- link.make_link('bookmark', api.request.public_url,
- 'portgroups', '', bookmark=True)
- ]
- if utils.allow_volume():
- v1['volume'] = [
- link.make_link('self',
- api.request.public_url,
- 'volume', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'volume', '',
- bookmark=True)
- ]
- if utils.allow_ramdisk_endpoints():
- v1['lookup'] = [
- link.make_link('self', api.request.public_url,
- 'lookup', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'lookup', '',
- bookmark=True)
- ]
- v1['heartbeat'] = [
- link.make_link('self',
- api.request.public_url,
- 'heartbeat', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'heartbeat', '',
- bookmark=True)
- ]
- if utils.allow_expose_conductors():
- v1['conductors'] = [
- link.make_link('self',
- api.request.public_url,
- 'conductors', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'conductors', '',
- bookmark=True)
- ]
- if utils.allow_allocations():
- v1['allocations'] = [
- link.make_link('self',
- api.request.public_url,
- 'allocations', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'allocations', '',
- bookmark=True)
- ]
- if utils.allow_expose_events():
- v1['events'] = [
- link.make_link('self', api.request.public_url,
- 'events', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'events', '',
- bookmark=True)
- ]
- if utils.allow_deploy_templates():
- v1['deploy_templates'] = [
- link.make_link('self',
- api.request.public_url,
- 'deploy_templates', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'deploy_templates', '',
- bookmark=True)
- ]
- if utils.allow_shards_endpoint():
- v1['shards'] = [
- link.make_link('self',
- api.request.public_url,
- 'shards', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'shards', '',
- bookmark=True)
- ]
+ for link_name, check_func in VERSIONED_CONTROLLERS.items():
+ if check_func():
+ v1[link_name] = make_controller_links(link_name)
return v1
diff --git a/ironic/api/controllers/v1/node.py b/ironic/api/controllers/v1/node.py
index 5bf0a4981..65adee544 100644
--- a/ironic/api/controllers/v1/node.py
+++ b/ironic/api/controllers/v1/node.py
@@ -1974,8 +1974,7 @@ class NodeInventoryController(rest.RestController):
"""
node = api_utils.check_node_policy_and_retrieve(
'baremetal:node:inventory:get', self.node_ident)
- return inspect_utils.get_introspection_data(node,
- api.request.context)
+ return inspect_utils.get_inspection_data(node, api.request.context)
class NodesController(rest.RestController):
diff --git a/ironic/conductor/inspection.py b/ironic/conductor/inspection.py
new file mode 100644
index 000000000..53c76e99d
--- /dev/null
+++ b/ironic/conductor/inspection.py
@@ -0,0 +1,108 @@
+# 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.
+
+"""Inspection implementation for the conductor."""
+
+from oslo_log import log
+from oslo_utils import excutils
+
+from ironic.common import exception
+from ironic.common.i18n import _
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.conductor import utils
+
+LOG = log.getLogger(__name__)
+
+
+@task_manager.require_exclusive_lock
+def inspect_hardware(task):
+ """Initiates inspection.
+
+ :param task: a TaskManager instance with an exclusive lock
+ on its node.
+ :raises: HardwareInspectionFailure if driver doesn't
+ return the state as states.MANAGEABLE, states.INSPECTWAIT.
+
+ """
+ node = task.node
+
+ def handle_failure(e, log_func=LOG.error):
+ utils.node_history_record(task.node, event=e,
+ event_type=states.INTROSPECTION,
+ error=True, user=task.context.user_id)
+ task.process_event('fail')
+ log_func("Failed to inspect node %(node)s: %(err)s",
+ {'node': node.uuid, 'err': e})
+
+ # Inspection cannot start in fast-track mode, wipe token and URL.
+ utils.wipe_token_and_url(task)
+
+ try:
+ new_state = task.driver.inspect.inspect_hardware(task)
+ except exception.IronicException as e:
+ with excutils.save_and_reraise_exception():
+ error = str(e)
+ handle_failure(error)
+ except Exception as e:
+ error = (_('Unexpected exception of type %(type)s: %(msg)s') %
+ {'type': type(e).__name__, 'msg': e})
+ handle_failure(error, log_func=LOG.exception)
+ raise exception.HardwareInspectionFailure(error=error)
+
+ if new_state == states.MANAGEABLE:
+ task.process_event('done')
+ LOG.info('Successfully inspected node %(node)s',
+ {'node': node.uuid})
+ elif new_state == states.INSPECTWAIT:
+ task.process_event('wait')
+ LOG.info('Successfully started introspection on node %(node)s',
+ {'node': node.uuid})
+ else:
+ error = (_("During inspection, driver returned unexpected "
+ "state %(state)s") % {'state': new_state})
+ handle_failure(error)
+ raise exception.HardwareInspectionFailure(error=error)
+
+
+@task_manager.require_exclusive_lock
+def abort_inspection(task):
+ """Abort inspection for the node."""
+ node = task.node
+
+ try:
+ task.driver.inspect.abort(task)
+ except exception.UnsupportedDriverExtension:
+ with excutils.save_and_reraise_exception():
+ LOG.error('Inspect interface "%(intf)s" does not support abort '
+ 'operation for node %(node)s',
+ {'intf': node.inspect_interface, 'node': node.uuid})
+ except Exception as e:
+ with excutils.save_and_reraise_exception():
+ LOG.exception('Error when aborting inspection of node %(node)s',
+ {'node': node.uuid})
+ error = _('Failed to abort inspection: %s') % e
+ utils.node_history_record(task.node, event=error,
+ event_type=states.INTROSPECTION,
+ error=True,
+ user=task.context.user_id)
+ node.save()
+
+ error = _('Inspection was aborted by request.')
+ utils.node_history_record(task.node, event=error,
+ event_type=states.INTROSPECTION,
+ error=True,
+ user=task.context.user_id)
+ utils.wipe_token_and_url(task)
+ task.process_event('abort')
+ LOG.info('Successfully aborted inspection of node %(node)s',
+ {'node': node.uuid})
diff --git a/ironic/conductor/manager.py b/ironic/conductor/manager.py
index 74e3192cf..bbd2355bd 100644
--- a/ironic/conductor/manager.py
+++ b/ironic/conductor/manager.py
@@ -63,6 +63,7 @@ from ironic.conductor import allocations
from ironic.conductor import base_manager
from ironic.conductor import cleaning
from ironic.conductor import deployments
+from ironic.conductor import inspection
from ironic.conductor import notification_utils as notify_utils
from ironic.conductor import periodics
from ironic.conductor import steps as conductor_steps
@@ -1365,35 +1366,7 @@ class ConductorManager(base_manager.BaseConductorManager):
return
if node.provision_state == states.INSPECTWAIT:
- try:
- task.driver.inspect.abort(task)
- except exception.UnsupportedDriverExtension:
- with excutils.save_and_reraise_exception():
- intf_name = task.driver.inspect.__class__.__name__
- LOG.error('Inspect interface %(intf)s does not '
- 'support abort operation when aborting '
- 'inspection of node %(node)s',
- {'intf': intf_name, 'node': node.uuid})
- except Exception as e:
- with excutils.save_and_reraise_exception():
- LOG.exception('Error in aborting the inspection of '
- 'node %(node)s', {'node': node.uuid})
- error = _('Failed to abort inspection: %s') % e
- utils.node_history_record(task.node, event=error,
- event_type=states.INTROSPECTION,
- error=True,
- user=task.context.user_id)
- node.save()
- error = _('Inspection was aborted by request.')
- utils.node_history_record(task.node, event=error,
- event_type=states.INTROSPECTION,
- error=True,
- user=task.context.user_id)
- utils.wipe_token_and_url(task)
- task.process_event('abort')
- LOG.info('Successfully aborted inspection of node %(node)s',
- {'node': node.uuid})
- return
+ return inspection.abort_inspection(task)
@METRICS.timer('ConductorManager._sync_power_states')
@periodics.periodic(spacing=CONF.conductor.sync_power_state_interval,
@@ -3038,7 +3011,7 @@ class ConductorManager(base_manager.BaseConductorManager):
task.process_event(
'inspect',
callback=self._spawn_worker,
- call_args=(_do_inspect_hardware, task),
+ call_args=(inspection.inspect_hardware, task),
err_handler=utils.provisioning_error_handler)
except exception.InvalidState:
@@ -3858,53 +3831,3 @@ def do_sync_power_state(task, count):
task, old_power_state)
return count
-
-
-@task_manager.require_exclusive_lock
-def _do_inspect_hardware(task):
- """Initiates inspection.
-
- :param task: a TaskManager instance with an exclusive lock
- on its node.
- :raises: HardwareInspectionFailure if driver doesn't
- return the state as states.MANAGEABLE, states.INSPECTWAIT.
-
- """
- node = task.node
-
- def handle_failure(e, log_func=LOG.error):
- utils.node_history_record(task.node, event=e,
- event_type=states.INTROSPECTION,
- error=True, user=task.context.user_id)
- task.process_event('fail')
- log_func("Failed to inspect node %(node)s: %(err)s",
- {'node': node.uuid, 'err': e})
-
- # Inspection cannot start in fast-track mode, wipe token and URL.
- utils.wipe_token_and_url(task)
-
- try:
- new_state = task.driver.inspect.inspect_hardware(task)
- except exception.IronicException as e:
- with excutils.save_and_reraise_exception():
- error = str(e)
- handle_failure(error)
- except Exception as e:
- error = (_('Unexpected exception of type %(type)s: %(msg)s') %
- {'type': type(e).__name__, 'msg': e})
- handle_failure(error, log_func=LOG.exception)
- raise exception.HardwareInspectionFailure(error=error)
-
- if new_state == states.MANAGEABLE:
- task.process_event('done')
- LOG.info('Successfully inspected node %(node)s',
- {'node': node.uuid})
- elif new_state == states.INSPECTWAIT:
- task.process_event('wait')
- LOG.info('Successfully started introspection on node %(node)s',
- {'node': node.uuid})
- else:
- error = (_("During inspection, driver returned unexpected "
- "state %(state)s") % {'state': new_state})
- handle_failure(error)
- raise exception.HardwareInspectionFailure(error=error)
diff --git a/ironic/conf/inventory.py b/ironic/conf/inventory.py
index 52f31bf60..47e88c6f9 100644
--- a/ironic/conf/inventory.py
+++ b/ironic/conf/inventory.py
@@ -17,16 +17,17 @@ from ironic.common.i18n import _
opts = [
cfg.StrOpt('data_backend',
- help=_('The storage backend for storing introspection data.'),
- choices=[('none', _('introspection data will not be stored')),
- ('database', _('introspection data stored in an SQL '
- 'database')),
- ('swift', _('introspection data stored in Swift'))],
+ help=_('The storage backend for storing inspection data.'),
+ choices=[
+ ('none', _('do not store inspection data')),
+ ('database', _('store in the service database')),
+ ('swift', _('store in the Object Storage (swift)')),
+ ],
default='database'),
cfg.StrOpt('swift_data_container',
default='introspection_data_container',
- help=_('The Swift introspection data container to store '
- 'the inventory data.')),
+ help=_('The Swift container prefix to store the inspection '
+ 'data (separately inventory and plugin data).')),
]
diff --git a/ironic/drivers/modules/ilo/boot.py b/ironic/drivers/modules/ilo/boot.py
index e29852981..fe2cef02a 100644
--- a/ironic/drivers/modules/ilo/boot.py
+++ b/ironic/drivers/modules/ilo/boot.py
@@ -604,6 +604,12 @@ class IloPXEBoot(pxe.PXEBoot):
else:
# Volume boot in BIOS boot mode is handled using
# PXE boot interface
+ boot_option = deploy_utils.get_boot_option(task.node)
+ if boot_option == "kickstart":
+ if task.node.provision_state in (states.DEPLOYING,
+ states.RESCUING,
+ states.CLEANING):
+ prepare_node_for_deploy(task)
super(IloPXEBoot, self).prepare_instance(task)
@METRICS.timer('IloPXEBoot.clean_up_instance')
@@ -696,6 +702,12 @@ class IloiPXEBoot(ipxe.iPXEBoot):
else:
# Volume boot in BIOS boot mode is handled using
# PXE boot interface
+ boot_option = deploy_utils.get_boot_option(task.node)
+ if boot_option == "kickstart":
+ if task.node.provision_state in (states.DEPLOYING,
+ states.RESCUING,
+ states.CLEANING):
+ prepare_node_for_deploy(task)
super(IloiPXEBoot, self).prepare_instance(task)
@METRICS.timer('IloiPXEBoot.clean_up_instance')
diff --git a/ironic/drivers/modules/inspect_utils.py b/ironic/drivers/modules/inspect_utils.py
index 432345cf1..f87fcc82b 100644
--- a/ironic/drivers/modules/inspect_utils.py
+++ b/ironic/drivers/modules/inspect_utils.py
@@ -68,101 +68,88 @@ def create_ports_if_not_exist(task, macs=None):
def clean_up_swift_entries(task):
- """Delete swift entries containing introspection data.
+ """Delete swift entries containing inspection data.
Delete swift entries related to the node in task.node containing
- introspection data. The entries are
+ inspection data. The entries are
``inspector_data-<task.node.uuid>-inventory`` for hardware inventory and
- similar for ``-plugin`` containing the rest of the introspection data.
+ similar for ``-plugin`` containing the rest of the inspection data.
:param task: A TaskManager instance.
"""
if CONF.inventory.data_backend != 'swift':
return
swift_api = swift.SwiftAPI()
- swift_object_name = '%s-%s' % (_OBJECT_NAME_PREFIX, task.node.uuid)
container = CONF.inventory.swift_data_container
- inventory_obj_name = swift_object_name + '-inventory'
- plugin_obj_name = swift_object_name + '-plugin'
+ inventory_obj_name = f'{_OBJECT_NAME_PREFIX}-{task.node.uuid}-inventory'
+ plugin_obj_name = f'{_OBJECT_NAME_PREFIX}-{task.node.uuid}-plugin'
try:
swift_api.delete_object(inventory_obj_name, container)
except swiftclient.exceptions.ClientException as e:
- if e.http_status == 404:
- # 404 -> entry did not exist - acceptable.
- pass
- else:
- LOG.error("Object %(obj)s related to node %(node)s "
- "failed to be deleted with expection: %(e)s",
+ if e.http_status != 404:
+ LOG.error("Object %(obj)s in container %(cont)s with inventory "
+ "for node %(node)s failed to be deleted: %(e)s",
{'obj': inventory_obj_name, 'node': task.node.uuid,
- 'e': e})
+ 'e': e, 'cont': container})
raise exception.SwiftObjectStillExists(obj=inventory_obj_name,
node=task.node.uuid)
try:
swift_api.delete_object(plugin_obj_name, container)
except swiftclient.exceptions.ClientException as e:
- if e.http_status == 404:
- # 404 -> entry did not exist - acceptable.
- pass
- else:
- LOG.error("Object %(obj)s related to node %(node)s "
- "failed to be deleted with exception: %(e)s",
+ if e.http_status != 404:
+ LOG.error("Object %(obj)s in container %(cont)s with plugin data "
+ "for node %(node)s failed to be deleted: %(e)s",
{'obj': plugin_obj_name, 'node': task.node.uuid,
- 'e': e})
+ 'e': e, 'cont': container})
raise exception.SwiftObjectStillExists(obj=plugin_obj_name,
node=task.node.uuid)
-def store_introspection_data(node, introspection_data, context):
- """Store introspection data.
+def store_inspection_data(node, inventory, plugin_data, context):
+ """Store inspection data.
- Store the introspection data for a node. Either to database
- or swift as configured.
+ Store the inspection data for a node. The storage is either the database
+ or the Object Storage API (swift/radosgw) as configured.
- :param node: the Ironic node that the introspection data is about
- :param introspection_data: the data to store
+ :param node: the Ironic node that the inspection data is about
+ :param inventory: the inventory to store
+ :param plugin_data: the plugin data (if any) to store
:param context: an admin context
"""
# If store_data == 'none', do not store the data
store_data = CONF.inventory.data_backend
if store_data == 'none':
- LOG.debug('Introspection data storage is disabled, the data will '
- 'not be saved for node %(node)s', {'node': node.uuid})
+ LOG.debug('Inspection data storage is disabled, the data will '
+ 'not be saved for node %s', node.uuid)
return
- inventory_data = introspection_data.pop("inventory")
- plugin_data = introspection_data
if store_data == 'database':
node_inventory.NodeInventory(
context,
node_id=node.id,
- inventory_data=inventory_data,
+ inventory_data=inventory,
plugin_data=plugin_data).create()
- LOG.info('Introspection data was stored in database for node '
- '%(node)s', {'node': node.uuid})
+ LOG.info('Inspection data was stored in database for node %s',
+ node.uuid)
if store_data == 'swift':
- swift_object_name = _store_introspection_data_in_swift(
+ swift_object_name = _store_inspection_data_in_swift(
node_uuid=node.uuid,
- inventory_data=inventory_data,
+ inventory_data=inventory,
plugin_data=plugin_data)
- LOG.info('Introspection data was stored for node %(node)s in Swift'
- ' object %(obj_name)s-inventory and %(obj_name)s-plugin',
+ LOG.info('Inspection data was stored in Swift for node %(node)s: '
+ 'objects %(obj_name)s-inventory and %(obj_name)s-plugin',
{'node': node.uuid, 'obj_name': swift_object_name})
-def _node_inventory_convert(node_inventory):
- inventory_data = node_inventory['inventory_data']
- plugin_data = node_inventory['plugin_data']
- return {"inventory": inventory_data, "plugin_data": plugin_data}
-
-
-def get_introspection_data(node, context):
- """Get introspection data.
+def get_inspection_data(node, context):
+ """Get inspection data.
- Retrieve the introspection data for a node. Either from database
- or swift as configured.
+ Retrieve the inspection data for a node either from database
+ or the Object Storage API (swift/radosgw) as configured.
- :param node_id: the Ironic node that the required data is about
+ :param node: the Ironic node that the required data is about
:param context: an admin context
:returns: dictionary with ``inventory`` and ``plugin_data`` fields
+ :raises: NodeInventoryNotFound if no inventory has been saved
"""
store_data = CONF.inventory.data_backend
if store_data == 'none':
@@ -170,58 +157,57 @@ def get_introspection_data(node, context):
if store_data == 'database':
node_inventory = objects.NodeInventory.get_by_node_id(
context, node.id)
- return _node_inventory_convert(node_inventory)
+ return {"inventory": node_inventory.inventory_data,
+ "plugin_data": node_inventory.plugin_data}
if store_data == 'swift':
try:
- node_inventory = _get_introspection_data_from_swift(node.uuid)
+ return _get_inspection_data_from_swift(node.uuid)
except exception.SwiftObjectNotFoundError:
raise exception.NodeInventoryNotFound(node=node.uuid)
- return node_inventory
-def _store_introspection_data_in_swift(node_uuid, inventory_data, plugin_data):
- """Uploads introspection data to Swift.
+def _store_inspection_data_in_swift(node_uuid, inventory_data, plugin_data):
+ """Uploads inspection data to Swift.
:param data: data to store in Swift
:param node_id: ID of the Ironic node that the data came from
:returns: name of the Swift object that the data is stored in
"""
swift_api = swift.SwiftAPI()
- swift_object_name = '%s-%s' % (_OBJECT_NAME_PREFIX, node_uuid)
+ swift_object_name = f'{_OBJECT_NAME_PREFIX}-{node_uuid}'
container = CONF.inventory.swift_data_container
- swift_api.create_object_from_data(swift_object_name + '-inventory',
+ swift_api.create_object_from_data(f'{swift_object_name}-inventory',
inventory_data,
container)
- swift_api.create_object_from_data(swift_object_name + '-plugin',
+ swift_api.create_object_from_data(f'{swift_object_name}-plugin',
plugin_data,
container)
return swift_object_name
-def _get_introspection_data_from_swift(node_uuid):
- """Get introspection data from Swift.
+def _get_inspection_data_from_swift(node_uuid):
+ """Get inspection data from Swift.
:param node_uuid: UUID of the Ironic node that the data came from
:returns: dictionary with ``inventory`` and ``plugin_data`` fields
"""
swift_api = swift.SwiftAPI()
- swift_object_name = '%s-%s' % (_OBJECT_NAME_PREFIX, node_uuid)
container = CONF.inventory.swift_data_container
- inv_obj = swift_object_name + '-inventory'
- plug_obj = swift_object_name + '-plugin'
+ inv_obj = f'{_OBJECT_NAME_PREFIX}-{node_uuid}-inventory'
+ plug_obj = f'{_OBJECT_NAME_PREFIX}-{node_uuid}-plugin'
try:
inventory_data = swift_api.get_object(inv_obj, container)
except exception.SwiftOperationError:
- LOG.error("Failed to retrieve object %(obj)s from swift",
- {'obj': inv_obj})
+ LOG.error("Failed to retrieve object %(obj)s from container %(cont)s",
+ {'obj': inv_obj, 'cont': container})
raise exception.SwiftObjectNotFoundError(obj=inv_obj,
container=container,
operation='get')
try:
plugin_data = swift_api.get_object(plug_obj, container)
except exception.SwiftOperationError:
- LOG.error("Failed to retrieve object %(obj)s from swift",
- {'obj': plug_obj})
+ LOG.error("Failed to retrieve object %(obj)s from container %(cont)s",
+ {'obj': plug_obj, 'cont': container})
raise exception.SwiftObjectNotFoundError(obj=plug_obj,
container=container,
operation='get')
diff --git a/ironic/drivers/modules/inspector/interface.py b/ironic/drivers/modules/inspector/interface.py
index 8792b7b88..e72077003 100644
--- a/ironic/drivers/modules/inspector/interface.py
+++ b/ironic/drivers/modules/inspector/interface.py
@@ -299,15 +299,17 @@ def _check_status(task):
_inspection_error_handler(task, error)
elif status.is_finished:
_clean_up(task)
- store_data = CONF.inventory.data_backend
- if store_data == 'none':
- LOG.debug('Introspection data storage is disabled, the data will '
- 'not be saved for node %(node)s', {'node': node.uuid})
+ if CONF.inventory.data_backend == 'none':
+ LOG.debug('Inspection data storage is disabled, the data will '
+ 'not be saved for node %s', node.uuid)
return
introspection_data = inspector_client.get_introspection_data(
node.uuid, processed=True)
- inspect_utils.store_introspection_data(node, introspection_data,
- task.context)
+ # TODO(dtantsur): having no inventory is an abnormal state, handle it.
+ inventory = introspection_data.pop('inventory', {})
+ inspect_utils.store_inspection_data(node, inventory,
+ introspection_data,
+ task.context)
def _clean_up(task):
diff --git a/ironic/drivers/modules/pxe.py b/ironic/drivers/modules/pxe.py
index fe93acefd..a55f5b9fd 100644
--- a/ironic/drivers/modules/pxe.py
+++ b/ironic/drivers/modules/pxe.py
@@ -27,6 +27,7 @@ from ironic.conductor import utils as manager_utils
from ironic.conf import CONF
from ironic.drivers import base
from ironic.drivers.modules import agent_base
+from ironic.drivers.modules import boot_mode_utils
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules import pxe_base
LOG = logging.getLogger(__name__)
@@ -114,21 +115,11 @@ class PXEAnacondaDeploy(agent_base.AgentBaseMixin, agent_base.HeartbeatMixin,
def reboot_to_instance(self, task):
node = task.node
try:
- # anaconda deploy will install the bootloader and the node is ready
- # to boot from disk.
-
- deploy_utils.try_set_boot_device(task, boot_devices.DISK)
- except Exception as e:
- msg = (_("Failed to change the boot device to %(boot_dev)s "
- "when deploying node %(node)s. Error: %(error)s") %
- {'boot_dev': boot_devices.DISK, 'node': node.uuid,
- 'error': e})
- agent_base.log_and_raise_deployment_error(task, msg)
-
- try:
task.process_event('resume')
self.clean_up(task)
manager_utils.node_power_action(task, states.POWER_OFF)
+ deploy_utils.try_set_boot_device(task, boot_devices.DISK)
+ boot_mode_utils.configure_secure_boot_if_needed(task)
task.driver.network.remove_provisioning_network(task)
task.driver.network.configure_tenant_networks(task)
manager_utils.node_power_action(task, states.POWER_ON)
diff --git a/ironic/drivers/modules/pxe_base.py b/ironic/drivers/modules/pxe_base.py
index daa90ba8d..f3ac49890 100644
--- a/ironic/drivers/modules/pxe_base.py
+++ b/ironic/drivers/modules/pxe_base.py
@@ -231,11 +231,12 @@ class PXEBaseMixin(object):
:returns: None
"""
boot_mode_utils.sync_boot_mode(task)
- boot_mode_utils.configure_secure_boot_if_needed(task)
-
node = task.node
- boot_option = deploy_utils.get_boot_option(node)
boot_device = None
+ boot_option = deploy_utils.get_boot_option(node)
+ if boot_option != "kickstart":
+ boot_mode_utils.configure_secure_boot_if_needed(task)
+
instance_image_info = {}
if boot_option == "ramdisk" or boot_option == "kickstart":
instance_image_info = pxe_utils.get_instance_image_info(
diff --git a/ironic/tests/unit/api/controllers/v1/test_node.py b/ironic/tests/unit/api/controllers/v1/test_node.py
index 6f2c1fc97..87a029057 100644
--- a/ironic/tests/unit/api/controllers/v1/test_node.py
+++ b/ironic/tests/unit/api/controllers/v1/test_node.py
@@ -7985,20 +7985,15 @@ class TestNodeInventory(test_api_base.BaseApiTest):
self.node = obj_utils.create_test_node(
self.context,
provision_state=states.AVAILABLE, name='node-81')
- self.node.save()
- self.node.obj_reset_changes()
-
- def _add_inventory(self):
- self.inventory = objects.NodeInventory(
- node_id=self.node.id, inventory_data=self.fake_inventory_data,
- plugin_data=self.fake_plugin_data)
- self.inventory.create()
+ CONF.set_override('data_backend', 'database', group='inventory')
- def test_get_old_version(self):
+ @mock.patch.object(inspect_utils, 'get_inspection_data', autospec=True)
+ def test_get_old_version(self, mock_get):
ret = self.get_json('/nodes/%s/inventory' % self.node.uuid,
headers={api_base.Version.string: "1.80"},
expect_errors=True)
self.assertEqual(http_client.NOT_FOUND, ret.status_code)
+ mock_get.assert_not_called()
def test_get_inventory_no_inventory(self):
ret = self.get_json('/nodes/%s/inventory' % self.node.uuid,
@@ -8007,33 +8002,10 @@ class TestNodeInventory(test_api_base.BaseApiTest):
self.assertEqual(http_client.NOT_FOUND, ret.status_code)
def test_get_inventory(self):
- self._add_inventory()
- CONF.set_override('data_backend', 'database',
- group='inventory')
- ret = self.get_json('/nodes/%s/inventory' % self.node.uuid,
- headers={api_base.Version.string: self.version})
- self.assertEqual({'inventory': self.fake_inventory_data,
- 'plugin_data': self.fake_plugin_data}, ret)
-
- @mock.patch.object(inspect_utils, 'get_introspection_data',
- autospec=True)
- def test_get_inventory_exception(self, mock_get_data):
- CONF.set_override('data_backend', 'database',
- group='inventory')
- mock_get_data.side_effect = [
- exception.NodeInventoryNotFound]
- ret = self.get_json('/nodes/%s/inventory' % self.node.uuid,
- headers={api_base.Version.string: self.version},
- expect_errors=True)
- self.assertEqual(http_client.NOT_FOUND, ret.status_int)
-
- @mock.patch.object(inspect_utils, '_get_introspection_data_from_swift',
- autospec=True)
- def test_get_inventory_swift(self, mock_get_data):
- CONF.set_override('data_backend', 'swift',
- group='inventory')
- mock_get_data.return_value = {"inventory": self.fake_inventory_data,
- "plugin_data": self.fake_plugin_data}
+ obj_utils.create_test_inventory(
+ self.context, self.node,
+ inventory_data=self.fake_inventory_data,
+ plugin_data=self.fake_plugin_data)
ret = self.get_json('/nodes/%s/inventory' % self.node.uuid,
headers={api_base.Version.string: self.version})
self.assertEqual({'inventory': self.fake_inventory_data,
diff --git a/ironic/tests/unit/conductor/test_inspection.py b/ironic/tests/unit/conductor/test_inspection.py
new file mode 100644
index 000000000..c64b883e4
--- /dev/null
+++ b/ironic/tests/unit/conductor/test_inspection.py
@@ -0,0 +1,118 @@
+# 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.
+
+from unittest import mock
+
+from ironic.common import exception
+from ironic.common import states
+from ironic.conductor import inspection
+from ironic.conductor import task_manager
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.objects import utils as obj_utils
+
+
+@mock.patch('ironic.drivers.modules.fake.FakeInspect.inspect_hardware',
+ autospec=True)
+class TestInspectHardware(db_base.DbTestCase):
+
+ def test_inspect_hardware_ok(self, mock_inspect):
+ node = obj_utils.create_test_node(
+ self.context, driver='fake-hardware',
+ provision_state=states.INSPECTING,
+ driver_internal_info={'agent_url': 'url',
+ 'agent_secret_token': 'token'})
+ task = task_manager.TaskManager(self.context, node.uuid)
+ mock_inspect.return_value = states.MANAGEABLE
+ inspection.inspect_hardware(task)
+ node.refresh()
+ self.assertEqual(states.MANAGEABLE, node.provision_state)
+ self.assertEqual(states.NOSTATE, node.target_provision_state)
+ self.assertIsNone(node.last_error)
+ mock_inspect.assert_called_once_with(task.driver.inspect, task)
+ task.node.refresh()
+ self.assertNotIn('agent_url', task.node.driver_internal_info)
+ self.assertNotIn('agent_secret_token', task.node.driver_internal_info)
+
+ def test_inspect_hardware_return_inspecting(self, mock_inspect):
+ node = obj_utils.create_test_node(self.context, driver='fake-hardware',
+ provision_state=states.INSPECTING)
+ task = task_manager.TaskManager(self.context, node.uuid)
+ mock_inspect.return_value = states.INSPECTING
+ self.assertRaises(exception.HardwareInspectionFailure,
+ inspection.inspect_hardware, task)
+
+ node.refresh()
+ self.assertIn('driver returned unexpected state', node.last_error)
+ self.assertEqual(states.INSPECTFAIL, node.provision_state)
+ self.assertEqual(states.MANAGEABLE, node.target_provision_state)
+ mock_inspect.assert_called_once_with(task.driver.inspect, task)
+
+ def test_inspect_hardware_return_inspect_wait(self, mock_inspect):
+ node = obj_utils.create_test_node(self.context, driver='fake-hardware',
+ provision_state=states.INSPECTING)
+ task = task_manager.TaskManager(self.context, node.uuid)
+ mock_inspect.return_value = states.INSPECTWAIT
+ inspection.inspect_hardware(task)
+ node.refresh()
+ self.assertEqual(states.INSPECTWAIT, node.provision_state)
+ self.assertEqual(states.MANAGEABLE, node.target_provision_state)
+ self.assertIsNone(node.last_error)
+ mock_inspect.assert_called_once_with(task.driver.inspect, task)
+
+ @mock.patch.object(inspection, 'LOG', autospec=True)
+ def test_inspect_hardware_return_other_state(self, log_mock, mock_inspect):
+ node = obj_utils.create_test_node(self.context, driver='fake-hardware',
+ provision_state=states.INSPECTING)
+ task = task_manager.TaskManager(self.context, node.uuid)
+ mock_inspect.return_value = None
+ self.assertRaises(exception.HardwareInspectionFailure,
+ inspection.inspect_hardware, task)
+ node.refresh()
+ self.assertEqual(states.INSPECTFAIL, node.provision_state)
+ self.assertEqual(states.MANAGEABLE, node.target_provision_state)
+ self.assertIsNotNone(node.last_error)
+ mock_inspect.assert_called_once_with(task.driver.inspect, task)
+ self.assertTrue(log_mock.error.called)
+
+ def test_inspect_hardware_raises_error(self, mock_inspect):
+ mock_inspect.side_effect = exception.HardwareInspectionFailure('test')
+ state = states.MANAGEABLE
+ node = obj_utils.create_test_node(self.context, driver='fake-hardware',
+ provision_state=states.INSPECTING,
+ target_provision_state=state)
+ task = task_manager.TaskManager(self.context, node.uuid)
+
+ self.assertRaisesRegex(exception.HardwareInspectionFailure, '^test$',
+ inspection.inspect_hardware, task)
+ node.refresh()
+ self.assertEqual(states.INSPECTFAIL, node.provision_state)
+ self.assertEqual(states.MANAGEABLE, node.target_provision_state)
+ self.assertEqual('test', node.last_error)
+ self.assertTrue(mock_inspect.called)
+
+ def test_inspect_hardware_unexpected_error(self, mock_inspect):
+ mock_inspect.side_effect = RuntimeError('x')
+ state = states.MANAGEABLE
+ node = obj_utils.create_test_node(self.context, driver='fake-hardware',
+ provision_state=states.INSPECTING,
+ target_provision_state=state)
+ task = task_manager.TaskManager(self.context, node.uuid)
+
+ self.assertRaisesRegex(exception.HardwareInspectionFailure,
+ 'Unexpected exception of type RuntimeError: x',
+ inspection.inspect_hardware, task)
+ node.refresh()
+ self.assertEqual(states.INSPECTFAIL, node.provision_state)
+ self.assertEqual(states.MANAGEABLE, node.target_provision_state)
+ self.assertEqual('Unexpected exception of type RuntimeError: x',
+ node.last_error)
+ self.assertTrue(mock_inspect.called)
diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py
index 6a6f7e08f..7278486a5 100644
--- a/ironic/tests/unit/conductor/test_manager.py
+++ b/ironic/tests/unit/conductor/test_manager.py
@@ -46,6 +46,7 @@ from ironic.common import nova
from ironic.common import states
from ironic.conductor import cleaning
from ironic.conductor import deployments
+from ironic.conductor import inspection
from ironic.conductor import manager
from ironic.conductor import notification_utils
from ironic.conductor import steps as conductor_steps
@@ -6461,77 +6462,6 @@ class ManagerSyncLocalStateTestCase(mgr_utils.CommonMixIn, db_base.DbTestCase):
@mgr_utils.mock_record_keepalive
class NodeInspectHardware(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
- @mock.patch('ironic.drivers.modules.fake.FakeInspect.inspect_hardware',
- autospec=True)
- def test_inspect_hardware_ok(self, mock_inspect):
- self._start_service()
- node = obj_utils.create_test_node(
- self.context, driver='fake-hardware',
- provision_state=states.INSPECTING,
- driver_internal_info={'agent_url': 'url',
- 'agent_secret_token': 'token'})
- task = task_manager.TaskManager(self.context, node.uuid)
- mock_inspect.return_value = states.MANAGEABLE
- manager._do_inspect_hardware(task)
- node.refresh()
- self.assertEqual(states.MANAGEABLE, node.provision_state)
- self.assertEqual(states.NOSTATE, node.target_provision_state)
- self.assertIsNone(node.last_error)
- mock_inspect.assert_called_once_with(task.driver.inspect, task)
- task.node.refresh()
- self.assertNotIn('agent_url', task.node.driver_internal_info)
- self.assertNotIn('agent_secret_token', task.node.driver_internal_info)
-
- @mock.patch('ironic.drivers.modules.fake.FakeInspect.inspect_hardware',
- autospec=True)
- def test_inspect_hardware_return_inspecting(self, mock_inspect):
- self._start_service()
- node = obj_utils.create_test_node(self.context, driver='fake-hardware',
- provision_state=states.INSPECTING)
- task = task_manager.TaskManager(self.context, node.uuid)
- mock_inspect.return_value = states.INSPECTING
- self.assertRaises(exception.HardwareInspectionFailure,
- manager._do_inspect_hardware, task)
-
- node.refresh()
- self.assertIn('driver returned unexpected state', node.last_error)
- self.assertEqual(states.INSPECTFAIL, node.provision_state)
- self.assertEqual(states.MANAGEABLE, node.target_provision_state)
- mock_inspect.assert_called_once_with(task.driver.inspect, task)
-
- @mock.patch('ironic.drivers.modules.fake.FakeInspect.inspect_hardware',
- autospec=True)
- def test_inspect_hardware_return_inspect_wait(self, mock_inspect):
- self._start_service()
- node = obj_utils.create_test_node(self.context, driver='fake-hardware',
- provision_state=states.INSPECTING)
- task = task_manager.TaskManager(self.context, node.uuid)
- mock_inspect.return_value = states.INSPECTWAIT
- manager._do_inspect_hardware(task)
- node.refresh()
- self.assertEqual(states.INSPECTWAIT, node.provision_state)
- self.assertEqual(states.MANAGEABLE, node.target_provision_state)
- self.assertIsNone(node.last_error)
- mock_inspect.assert_called_once_with(task.driver.inspect, task)
-
- @mock.patch.object(manager, 'LOG', autospec=True)
- @mock.patch('ironic.drivers.modules.fake.FakeInspect.inspect_hardware',
- autospec=True)
- def test_inspect_hardware_return_other_state(self, mock_inspect, log_mock):
- self._start_service()
- node = obj_utils.create_test_node(self.context, driver='fake-hardware',
- provision_state=states.INSPECTING)
- task = task_manager.TaskManager(self.context, node.uuid)
- mock_inspect.return_value = None
- self.assertRaises(exception.HardwareInspectionFailure,
- manager._do_inspect_hardware, task)
- node.refresh()
- self.assertEqual(states.INSPECTFAIL, node.provision_state)
- self.assertEqual(states.MANAGEABLE, node.target_provision_state)
- self.assertIsNotNone(node.last_error)
- mock_inspect.assert_called_once_with(task.driver.inspect, task)
- self.assertTrue(log_mock.error.called)
-
def test__check_inspect_wait_timeouts(self):
self._start_service()
CONF.set_override('inspect_wait_timeout', 1, group='conductor')
@@ -6609,46 +6539,6 @@ class NodeInspectHardware(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
def test_inspect_hardware_power_validate_fail(self, mock_validate):
self._test_inspect_hardware_validate_fail(mock_validate)
- @mock.patch('ironic.drivers.modules.fake.FakeInspect.inspect_hardware',
- autospec=True)
- def test_inspect_hardware_raises_error(self, mock_inspect):
- self._start_service()
- mock_inspect.side_effect = exception.HardwareInspectionFailure('test')
- state = states.MANAGEABLE
- node = obj_utils.create_test_node(self.context, driver='fake-hardware',
- provision_state=states.INSPECTING,
- target_provision_state=state)
- task = task_manager.TaskManager(self.context, node.uuid)
-
- self.assertRaisesRegex(exception.HardwareInspectionFailure, '^test$',
- manager._do_inspect_hardware, task)
- node.refresh()
- self.assertEqual(states.INSPECTFAIL, node.provision_state)
- self.assertEqual(states.MANAGEABLE, node.target_provision_state)
- self.assertEqual('test', node.last_error)
- self.assertTrue(mock_inspect.called)
-
- @mock.patch('ironic.drivers.modules.fake.FakeInspect.inspect_hardware',
- autospec=True)
- def test_inspect_hardware_unexpected_error(self, mock_inspect):
- self._start_service()
- mock_inspect.side_effect = RuntimeError('x')
- state = states.MANAGEABLE
- node = obj_utils.create_test_node(self.context, driver='fake-hardware',
- provision_state=states.INSPECTING,
- target_provision_state=state)
- task = task_manager.TaskManager(self.context, node.uuid)
-
- self.assertRaisesRegex(exception.HardwareInspectionFailure,
- 'Unexpected exception of type RuntimeError: x',
- manager._do_inspect_hardware, task)
- node.refresh()
- self.assertEqual(states.INSPECTFAIL, node.provision_state)
- self.assertEqual(states.MANAGEABLE, node.target_provision_state)
- self.assertEqual('Unexpected exception of type RuntimeError: x',
- node.last_error)
- self.assertTrue(mock_inspect.called)
-
@mock.patch.object(conductor_utils, 'node_history_record',
mock.Mock(spec=conductor_utils.node_history_record))
@@ -8247,7 +8137,7 @@ class NodeTraitsTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
class DoNodeInspectAbortTestCase(mgr_utils.CommonMixIn,
mgr_utils.ServiceSetUpMixin,
db_base.DbTestCase):
- @mock.patch.object(manager, 'LOG', autospec=True)
+ @mock.patch.object(inspection, 'LOG', autospec=True)
@mock.patch('ironic.drivers.modules.fake.FakeInspect.abort', autospec=True)
@mock.patch('ironic.conductor.task_manager.acquire', autospec=True)
def test_do_inspect_abort_interface_not_support(self, mock_acquire,
@@ -8268,7 +8158,7 @@ class DoNodeInspectAbortTestCase(mgr_utils.CommonMixIn,
exc.exc_info[0])
self.assertTrue(mock_log.error.called)
- @mock.patch.object(manager, 'LOG', autospec=True)
+ @mock.patch.object(inspection, 'LOG', autospec=True)
@mock.patch('ironic.drivers.modules.fake.FakeInspect.abort', autospec=True)
@mock.patch('ironic.conductor.task_manager.acquire', autospec=True)
def test_do_inspect_abort_interface_return_failed(self, mock_acquire,
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_boot.py b/ironic/tests/unit/drivers/modules/ilo/test_boot.py
index 8aa6f78da..8ebaa14fa 100644
--- a/ironic/tests/unit/drivers/modules/ilo/test_boot.py
+++ b/ironic/tests/unit/drivers/modules/ilo/test_boot.py
@@ -1132,6 +1132,45 @@ class IloPXEBootTestCase(test_common.BaseIloTest):
self.assertIsNone(task.node.driver_internal_info.get(
'ilo_uefi_iscsi_boot'))
+ @mock.patch.object(ilo_boot, 'prepare_node_for_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'get_boot_option', autospec=True)
+ @mock.patch.object(deploy_utils, 'is_iscsi_boot',
+ spec_set=True, autospec=True)
+ @mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance', spec_set=True,
+ autospec=True)
+ def _test_prepare_instance_anaconda(self, pxe_prepare_instance_mock,
+ update_boot_mode_mock,
+ get_boot_mode_mock,
+ is_iscsi_boot_mock,
+ mock_get_boot_opt,
+ mock_prep_node_fr_deploy, prov_state):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = prov_state
+ mock_get_boot_opt.return_value = 'kickstart'
+ is_iscsi_boot_mock.return_value = False
+ get_boot_mode_mock.return_value = 'uefi'
+ task.driver.boot.prepare_instance(task)
+ update_boot_mode_mock.assert_called_once_with(task)
+ pxe_prepare_instance_mock.assert_called_once_with(mock.ANY, task)
+ self.assertIsNone(task.node.driver_internal_info.get(
+ 'ilo_uefi_iscsi_boot'))
+ mock_prep_node_fr_deploy.assert_called_once_with(task)
+
+ def test_prepare_instance_anaconda_deploying(self):
+ self._test_prepare_instance_anaconda(prov_state=states.DEPLOYING)
+
+ def test_prepare_instance_anaconda_rescuing(self):
+ self._test_prepare_instance_anaconda(prov_state=states.RESCUING)
+
+ def test_prepare_instance_anaconda_cleaning(self):
+ self._test_prepare_instance_anaconda(prov_state=states.CLEANING)
+
@mock.patch.object(deploy_utils, 'is_iscsi_boot',
spec_set=True, autospec=True)
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
@@ -1299,6 +1338,45 @@ class IloiPXEBootTestCase(test_common.BaseIloTest):
self.assertIsNone(task.node.driver_internal_info.get(
'ilo_uefi_iscsi_boot'))
+ @mock.patch.object(ilo_boot, 'prepare_node_for_deploy', spec_set=True,
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'get_boot_option', autospec=True)
+ @mock.patch.object(deploy_utils, 'is_iscsi_boot',
+ spec_set=True, autospec=True)
+ @mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ipxe.iPXEBoot, 'prepare_instance', spec_set=True,
+ autospec=True)
+ def _test_prepare_instance_anaconda(self, pxe_prepare_instance_mock,
+ update_boot_mode_mock,
+ get_boot_mode_mock,
+ is_iscsi_boot_mock,
+ mock_get_boot_opt,
+ mock_prep_node_fr_deploy, prov_state):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.provision_state = prov_state
+ mock_get_boot_opt.return_value = 'kickstart'
+ is_iscsi_boot_mock.return_value = False
+ get_boot_mode_mock.return_value = 'uefi'
+ task.driver.boot.prepare_instance(task)
+ update_boot_mode_mock.assert_called_once_with(task)
+ pxe_prepare_instance_mock.assert_called_once_with(mock.ANY, task)
+ self.assertIsNone(task.node.driver_internal_info.get(
+ 'ilo_uefi_iscsi_boot'))
+ mock_prep_node_fr_deploy.assert_called_once_with(task)
+
+ def test_prepare_instance_anaconda_deploying(self):
+ self._test_prepare_instance_anaconda(prov_state=states.DEPLOYING)
+
+ def test_prepare_instance_anaconda_rescuing(self):
+ self._test_prepare_instance_anaconda(prov_state=states.RESCUING)
+
+ def test_prepare_instance_anaconda_cleaning(self):
+ self._test_prepare_instance_anaconda(prov_state=states.CLEANING)
+
@mock.patch.object(deploy_utils, 'is_iscsi_boot',
spec_set=True, autospec=True)
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
diff --git a/ironic/tests/unit/drivers/modules/inspector/test_interface.py b/ironic/tests/unit/drivers/modules/inspector/test_interface.py
index f4dbdd4b0..42bb55f2b 100644
--- a/ironic/tests/unit/drivers/modules/inspector/test_interface.py
+++ b/ironic/tests/unit/drivers/modules/inspector/test_interface.py
@@ -505,22 +505,24 @@ class CheckStatusTestCase(BaseTestCase):
self.task)
self.driver.boot.clean_up_ramdisk.assert_called_once_with(self.task)
- @mock.patch.object(inspect_utils, 'store_introspection_data',
- autospec=True)
+ @mock.patch.object(inspect_utils, 'store_inspection_data', autospec=True)
def test_status_ok_store_inventory(self, mock_store_data, mock_client):
mock_get = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=True,
error=None,
spec=['is_finished', 'error'])
- fake_introspection_data = {
- "inventory": {"cpu": "amd"}, "disks": [{"name": "/dev/vda"}]}
+ fake_inventory = {"cpu": "amd"}
+ fake_plugin_data = {"disks": [{"name": "/dev/vda"}]}
+ fake_introspection_data = dict(fake_plugin_data,
+ inventory=fake_inventory)
mock_get_data = mock_client.return_value.get_introspection_data
mock_get_data.return_value = fake_introspection_data
inspector._check_status(self.task)
mock_get.assert_called_once_with(self.node.uuid)
mock_get_data.assert_called_once_with(self.node.uuid, processed=True)
mock_store_data.assert_called_once_with(self.node,
- fake_introspection_data,
+ fake_inventory,
+ fake_plugin_data,
self.task.context)
def test_status_ok_store_inventory_nostore(self, mock_client):
diff --git a/ironic/tests/unit/drivers/modules/test_inspect_utils.py b/ironic/tests/unit/drivers/modules/test_inspect_utils.py
index 473b0ee7c..57980d5a2 100644
--- a/ironic/tests/unit/drivers/modules/test_inspect_utils.py
+++ b/ironic/tests/unit/drivers/modules/test_inspect_utils.py
@@ -103,7 +103,7 @@ class SwiftCleanUp(db_base.DbTestCase):
@mock.patch.object(swift, 'SwiftAPI', autospec=True)
def test_clean_up_swift_entries(self, swift_api_mock):
CONF.set_override('data_backend', 'swift', group='inventory')
- container = 'introspection_data'
+ container = 'inspection_data'
CONF.set_override('swift_data_container', container, group='inventory')
swift_obj_mock = swift_api_mock.return_value
with task_manager.acquire(self.context, self.node.uuid,
@@ -117,7 +117,7 @@ class SwiftCleanUp(db_base.DbTestCase):
@mock.patch.object(swift, 'SwiftAPI', autospec=True)
def test_clean_up_swift_entries_with_404_exception(self, swift_api_mock):
CONF.set_override('data_backend', 'swift', group='inventory')
- container = 'introspection_data'
+ container = 'inspection_data'
CONF.set_override('swift_data_container', container, group='inventory')
swift_obj_mock = swift_api_mock.return_value
with task_manager.acquire(self.context, self.node.uuid,
@@ -132,7 +132,7 @@ class SwiftCleanUp(db_base.DbTestCase):
@mock.patch.object(swift, 'SwiftAPI', autospec=True)
def test_clean_up_swift_entries_with_fail_exception(self, swift_api_mock):
CONF.set_override('data_backend', 'swift', group='inventory')
- container = 'introspection_data'
+ container = 'inspection_data'
CONF.set_override('swift_data_container', container, group='inventory')
swift_obj_mock = swift_api_mock.return_value
with task_manager.acquire(self.context, self.node.uuid,
@@ -148,7 +148,7 @@ class SwiftCleanUp(db_base.DbTestCase):
@mock.patch.object(swift, 'SwiftAPI', autospec=True)
def test_clean_up_swift_entries_with_fail_exceptions(self, swift_api_mock):
CONF.set_override('data_backend', 'swift', group='inventory')
- container = 'introspection_data'
+ container = 'inspection_data'
CONF.set_override('swift_data_container', container, group='inventory')
swift_obj_mock = swift_api_mock.return_value
with task_manager.acquire(self.context, self.node.uuid,
@@ -171,115 +171,93 @@ class IntrospectionDataStorageFunctionsTestCase(db_base.DbTestCase):
super(IntrospectionDataStorageFunctionsTestCase, self).setUp()
self.node = obj_utils.create_test_node(self.context)
- def test_store_introspection_data_db(self):
- CONF.set_override('data_backend', 'database',
- group='inventory')
- fake_introspection_data = {'inventory': self.fake_inventory_data,
- **self.fake_plugin_data}
+ def test_store_inspection_data_db(self):
+ CONF.set_override('data_backend', 'database', group='inventory')
fake_context = ironic_context.RequestContext()
- utils.store_introspection_data(self.node, fake_introspection_data,
- fake_context)
+ utils.store_inspection_data(self.node, self.fake_inventory_data,
+ self.fake_plugin_data, fake_context)
stored = objects.NodeInventory.get_by_node_id(self.context,
self.node.id)
self.assertEqual(self.fake_inventory_data, stored["inventory_data"])
self.assertEqual(self.fake_plugin_data, stored["plugin_data"])
- @mock.patch.object(utils, '_store_introspection_data_in_swift',
+ @mock.patch.object(utils, '_store_inspection_data_in_swift',
autospec=True)
- def test_store_introspection_data_swift(self, mock_store_data):
+ def test_store_inspection_data_swift(self, mock_store_data):
CONF.set_override('data_backend', 'swift', group='inventory')
CONF.set_override(
- 'swift_data_container', 'introspection_data',
+ 'swift_data_container', 'inspection_data',
group='inventory')
- fake_introspection_data = {
- "inventory": self.fake_inventory_data, **self.fake_plugin_data}
fake_context = ironic_context.RequestContext()
- utils.store_introspection_data(self.node, fake_introspection_data,
- fake_context)
+ utils.store_inspection_data(self.node, self.fake_inventory_data,
+ self.fake_plugin_data, fake_context)
mock_store_data.assert_called_once_with(
self.node.uuid, inventory_data=self.fake_inventory_data,
plugin_data=self.fake_plugin_data)
- def test_store_introspection_data_nostore(self):
+ def test_store_inspection_data_nostore(self):
CONF.set_override('data_backend', 'none', group='inventory')
- fake_introspection_data = {
- "inventory": self.fake_inventory_data, **self.fake_plugin_data}
fake_context = ironic_context.RequestContext()
- ret = utils.store_introspection_data(self.node,
- fake_introspection_data,
- fake_context)
- self.assertIsNone(ret)
-
- def test__node_inventory_convert(self):
- required_output = {"inventory": self.fake_inventory_data,
- "plugin_data": self.fake_plugin_data}
- input_given = {}
- input_given["inventory_data"] = self.fake_inventory_data
- input_given["plugin_data"] = self.fake_plugin_data
- input_given["booom"] = "boom"
- ret = utils._node_inventory_convert(input_given)
- self.assertEqual(required_output, ret)
-
- @mock.patch.object(utils, '_node_inventory_convert', autospec=True)
- @mock.patch.object(objects, 'NodeInventory', spec_set=True, autospec=True)
- def test_get_introspection_data_db(self, mock_inventory, mock_convert):
- CONF.set_override('data_backend', 'database',
- group='inventory')
- fake_introspection_data = {'inventory': self.fake_inventory_data,
- 'plugin_data': self.fake_plugin_data}
+ utils.store_inspection_data(self.node, self.fake_inventory_data,
+ self.fake_plugin_data, fake_context)
+ self.assertRaises(exception.NodeInventoryNotFound,
+ objects.NodeInventory.get_by_node_id,
+ self.context, self.node.id)
+
+ def test_get_inspection_data_db(self):
+ CONF.set_override('data_backend', 'database', group='inventory')
+ obj_utils.create_test_inventory(
+ self.context, self.node,
+ inventory_data=self.fake_inventory_data,
+ plugin_data=self.fake_plugin_data)
fake_context = ironic_context.RequestContext()
- mock_inventory.get_by_node_id.return_value = fake_introspection_data
- utils.get_introspection_data(self.node, fake_context)
- mock_convert.assert_called_once_with(fake_introspection_data)
-
- @mock.patch.object(objects, 'NodeInventory', spec_set=True, autospec=True)
- def test_get_introspection_data_db_exception(self, mock_inventory):
- CONF.set_override('data_backend', 'database',
- group='inventory')
+ ret = utils.get_inspection_data(self.node, fake_context)
+ fake_inspection_data = {'inventory': self.fake_inventory_data,
+ 'plugin_data': self.fake_plugin_data}
+ self.assertEqual(ret, fake_inspection_data)
+
+ def test_get_inspection_data_db_exception(self):
+ CONF.set_override('data_backend', 'database', group='inventory')
fake_context = ironic_context.RequestContext()
- mock_inventory.get_by_node_id.side_effect = [
- exception.NodeInventoryNotFound(self.node.uuid)]
self.assertRaises(
- exception.NodeInventoryNotFound, utils.get_introspection_data,
+ exception.NodeInventoryNotFound, utils.get_inspection_data,
self.node, fake_context)
- @mock.patch.object(utils, '_get_introspection_data_from_swift',
- autospec=True)
- def test_get_introspection_data_swift(self, mock_get_data):
+ @mock.patch.object(utils, '_get_inspection_data_from_swift', autospec=True)
+ def test_get_inspection_data_swift(self, mock_get_data):
CONF.set_override('data_backend', 'swift', group='inventory')
CONF.set_override(
- 'swift_data_container', 'introspection_data',
+ 'swift_data_container', 'inspection_data',
group='inventory')
fake_context = ironic_context.RequestContext()
- utils.get_introspection_data(self.node, fake_context)
- mock_get_data.assert_called_once_with(
- self.node.uuid)
+ ret = utils.get_inspection_data(self.node, fake_context)
+ mock_get_data.assert_called_once_with(self.node.uuid)
+ self.assertEqual(mock_get_data.return_value, ret)
- @mock.patch.object(utils, '_get_introspection_data_from_swift',
- autospec=True)
- def test_get_introspection_data_swift_exception(self, mock_get_data):
+ @mock.patch.object(utils, '_get_inspection_data_from_swift', autospec=True)
+ def test_get_inspection_data_swift_exception(self, mock_get_data):
CONF.set_override('data_backend', 'swift', group='inventory')
CONF.set_override(
- 'swift_data_container', 'introspection_data',
+ 'swift_data_container', 'inspection_data',
group='inventory')
fake_context = ironic_context.RequestContext()
mock_get_data.side_effect = exception.SwiftObjectNotFoundError()
self.assertRaises(
- exception.NodeInventoryNotFound, utils.get_introspection_data,
+ exception.NodeInventoryNotFound, utils.get_inspection_data,
self.node, fake_context)
- def test_get_introspection_data_nostore(self):
+ def test_get_inspection_data_nostore(self):
CONF.set_override('data_backend', 'none', group='inventory')
fake_context = ironic_context.RequestContext()
self.assertRaises(
- exception.NodeInventoryNotFound, utils.get_introspection_data,
+ exception.NodeInventoryNotFound, utils.get_inspection_data,
self.node, fake_context)
@mock.patch.object(swift, 'SwiftAPI', autospec=True)
- def test__store_introspection_data_in_swift(self, swift_api_mock):
- container = 'introspection_data'
+ def test__store_inspection_data_in_swift(self, swift_api_mock):
+ container = 'inspection_data'
CONF.set_override('swift_data_container', container, group='inventory')
- utils._store_introspection_data_in_swift(
+ utils._store_inspection_data_in_swift(
self.node.uuid, self.fake_inventory_data, self.fake_plugin_data)
swift_obj_mock = swift_api_mock.return_value
object_name = 'inspector_data-' + str(self.node.uuid)
@@ -290,23 +268,22 @@ class IntrospectionDataStorageFunctionsTestCase(db_base.DbTestCase):
container)])
@mock.patch.object(swift, 'SwiftAPI', autospec=True)
- def test__get_introspection_data_from_swift(self, swift_api_mock):
- container = 'introspection_data'
+ def test__get_inspection_data_from_swift(self, swift_api_mock):
+ container = 'inspection_data'
CONF.set_override('swift_data_container', container, group='inventory')
swift_obj_mock = swift_api_mock.return_value
swift_obj_mock.get_object.side_effect = [
self.fake_inventory_data,
self.fake_plugin_data
]
- ret = utils._get_introspection_data_from_swift(self.node.uuid)
+ ret = utils._get_inspection_data_from_swift(self.node.uuid)
req_ret = {"inventory": self.fake_inventory_data,
"plugin_data": self.fake_plugin_data}
self.assertEqual(req_ret, ret)
@mock.patch.object(swift, 'SwiftAPI', autospec=True)
- def test__get_introspection_data_from_swift_exception(self,
- swift_api_mock):
- container = 'introspection_data'
+ def test__get_inspection_data_from_swift_exception(self, swift_api_mock):
+ container = 'inspection_data'
CONF.set_override('swift_data_container', container, group='inventory')
swift_obj_mock = swift_api_mock.return_value
swift_obj_mock.get_object.side_effect = [
@@ -314,5 +291,5 @@ class IntrospectionDataStorageFunctionsTestCase(db_base.DbTestCase):
self.fake_plugin_data
]
self.assertRaises(exception.SwiftObjectNotFoundError,
- utils._get_introspection_data_from_swift,
+ utils._get_inspection_data_from_swift,
self.node.uuid)
diff --git a/ironic/tests/unit/drivers/modules/test_pxe.py b/ironic/tests/unit/drivers/modules/test_pxe.py
index e7d444104..f16366470 100644
--- a/ironic/tests/unit/drivers/modules/test_pxe.py
+++ b/ironic/tests/unit/drivers/modules/test_pxe.py
@@ -550,6 +550,8 @@ class PXEBootTestCase(db_base.DbTestCase):
def test_prepare_instance_ramdisk_pxe_conf_exists(self):
self._test_prepare_instance_ramdisk(config_file_exits=False)
+ @mock.patch.object(boot_mode_utils, 'configure_secure_boot_if_needed',
+ autospec=True)
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
@mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True)
@mock.patch.object(pxe_utils, 'create_pxe_config', autospec=True)
@@ -567,7 +569,7 @@ class PXEBootTestCase(db_base.DbTestCase):
self, exec_mock, write_file_mock, render_mock, api_url_mock,
boot_opt_mock, get_image_info_mock, cache_mock, dhcp_factory_mock,
create_pxe_config_mock, switch_pxe_config_mock,
- set_boot_device_mock):
+ set_boot_device_mock, mock_conf_sec_boot):
image_info = {'kernel': ['ins_kernel_id', '/path/to/kernel'],
'ramdisk': ['ins_ramdisk_id', '/path/to/ramdisk'],
'stage2': ['ins_stage2_id', '/path/to/stage2'],
@@ -611,6 +613,7 @@ class PXEBootTestCase(db_base.DbTestCase):
set_boot_device_mock.assert_called_once_with(task,
boot_devices.PXE,
persistent=True)
+ self.assertFalse(mock_conf_sec_boot.called)
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
@mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True)
@@ -786,11 +789,13 @@ class PXEAnacondaDeployTestCase(db_base.DbTestCase):
task.driver.deploy.prepare(task)
mock_prepare_instance.assert_called_once_with(mock.ANY, task)
+ @mock.patch.object(boot_mode_utils, 'configure_secure_boot_if_needed',
+ autospec=True)
@mock.patch.object(pxe_utils, 'clean_up_pxe_env', autospec=True)
@mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True)
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
def test_reboot_to_instance(self, mock_set_boot_dev, mock_image_info,
- mock_cleanup_pxe_env):
+ mock_cleanup_pxe_env, mock_conf_sec_boot):
image_info = {'kernel': ('', '/path/to/kernel'),
'ramdisk': ('', '/path/to/ramdisk'),
'stage2': ('', '/path/to/stage2'),
@@ -802,6 +807,7 @@ class PXEAnacondaDeployTestCase(db_base.DbTestCase):
with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.deploy.reboot_to_instance(task)
mock_set_boot_dev.assert_called_once_with(task, boot_devices.DISK)
+ mock_conf_sec_boot.assert_called_once_with(task)
mock_cleanup_pxe_env.assert_called_once_with(task, image_info,
ipxe_enabled=False)
diff --git a/ironic/tests/unit/objects/utils.py b/ironic/tests/unit/objects/utils.py
index 26c3a22e7..979ab28a2 100644
--- a/ironic/tests/unit/objects/utils.py
+++ b/ironic/tests/unit/objects/utils.py
@@ -380,3 +380,15 @@ class SchemasTestMixIn(object):
"for %s, schema key %s has invalid %s "
"field %s" % (payload, schema_key, resource,
key))
+
+
+def create_test_inventory(ctxt, node, **kw):
+ """Create and return a test node inventory object."""
+ inv = objects.NodeInventory(ctxt)
+ if not isinstance(node, str):
+ node = node.id
+ kw['node_id'] = node
+ for key, value in kw.items():
+ setattr(inv, key, value)
+ inv.create()
+ return inv
diff --git a/playbooks/metal3-ci/run.yaml b/playbooks/metal3-ci/run.yaml
index 66886b26a..d49ddc6de 100644
--- a/playbooks/metal3-ci/run.yaml
+++ b/playbooks/metal3-ci/run.yaml
@@ -5,24 +5,22 @@
set_fact:
metal3_dev_env_src_dir: '{{ ansible_user_dir }}/metal3-dev-env'
metal3_environment:
+ ANSIBLE_VERBOSITY: 2
CONTROL_PLANE_MACHINE_COUNT: 1
IMAGE_OS: ubuntu
IMAGE_USERNAME: zuul
# NOTE(dtantsur): we don't have enough resources to provision even
# a 2-node cluster, so only provision a control plane node.
NUM_NODES: 2
+ LIBVIRT_DOMAIN_TYPE: "qemu"
WORKER_MACHINE_COUNT: 1
# TODO(dtantsur): add metal3-io/metal3-dev-env as a recognized project to
# https://opendev.org/openstack/project-config/src/commit/e15b9cae77bdc243322cee64b3688a2a43dd193c/zuul/main.yaml#L1416
- # TODO(dtantsur): replace my fork with the upstream source once all fixes
- # merge there.
- # TODO(rpittau): move back to dtantsur or metal3-io after we merge the changes
- name: Clone metal3-dev-env
git:
dest: "{{ metal3_dev_env_src_dir }}"
- repo: "https://github.com/elfosardo/metal3-dev-env"
- version: ironic-ci
+ repo: "https://github.com/metal3-io/metal3-dev-env"
- name: Build a metal3 environment
command: make
diff --git a/releasenotes/notes/fix_boot_mode_switch_with_anaconda_deploy_with_ilo_drivers-16637adb62f0ed2f.yaml b/releasenotes/notes/fix_boot_mode_switch_with_anaconda_deploy_with_ilo_drivers-16637adb62f0ed2f.yaml
new file mode 100644
index 000000000..c3096be79
--- /dev/null
+++ b/releasenotes/notes/fix_boot_mode_switch_with_anaconda_deploy_with_ilo_drivers-16637adb62f0ed2f.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - |
+ Enables boot mode switching during anaconda deploy for ``ilo``
+ and ``ilo5`` hardware types.
diff --git a/releasenotes/notes/fix_secure_boot_with_anaconda_deploy-84d7c1e3bbfa40f2.yaml b/releasenotes/notes/fix_secure_boot_with_anaconda_deploy-84d7c1e3bbfa40f2.yaml
new file mode 100644
index 000000000..a03289c42
--- /dev/null
+++ b/releasenotes/notes/fix_secure_boot_with_anaconda_deploy-84d7c1e3bbfa40f2.yaml
@@ -0,0 +1,4 @@
+---
+fixes:
+ - |
+ Fixes secure boot with anaconda deploy.
diff --git a/tools/benchmark/generate-statistics.py b/tools/benchmark/generate-statistics.py
index e8327f3ac..e9fe0f56d 100644
--- a/tools/benchmark/generate-statistics.py
+++ b/tools/benchmark/generate-statistics.py
@@ -235,6 +235,7 @@ def _assess_db_object_and_api_performance_ports(mock_log, mock_request):
node_ident=None,
address=None,
portgroup_ident=None,
+ shard=None,
marker=None,
limit=None,
sort_key="id",
@@ -249,6 +250,7 @@ def _assess_db_object_and_api_performance_ports(mock_log, mock_request):
node_ident=None,
address=None,
portgroup_ident=None,
+ shard=None,
marker=res['ports'][-1]['uuid'],
limit=None,
sort_key="id",