# 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 oslo_log import log as logging
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import states
from ironic.common import utils
from ironic.conductor import utils as cond_utils
from ironic.conf import CONF
from ironic.drivers import base
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules import inspect_utils
LOG = logging.getLogger(__name__)
# Internal field to mark whether ironic or inspector manages boot for the node
_IRONIC_MANAGES_BOOT = 'inspector_manage_boot'
[docs]
def tear_down_managed_boot(task, always_power_off=False):
errors = []
ironic_manages_boot = utils.pop_node_nested_field(
task.node, 'driver_internal_info', _IRONIC_MANAGES_BOOT)
power_off_done = False
if ironic_manages_boot:
# First, perform a graceful shutdown BEFORE ejecting virtual media
# to avoid filesystem corruption errors on the node. The OS needs
# access to the virtual media to shut down cleanly.
if (CONF.inspector.power_off
and not utils.fast_track_enabled(task.node)
and not task.node.disable_power_off):
try:
cond_utils.node_power_action(task, states.SOFT_POWER_OFF)
power_off_done = True
except Exception as exc:
errors.append(_('unable to power off the node: %s') % exc)
LOG.exception('Unable to power off node %s for inspection',
task.node.uuid)
# Now it's safe to eject virtual media since the OS has shut down
try:
task.driver.boot.clean_up_ramdisk(task)
except Exception as exc:
errors.append(_('unable to clean up ramdisk boot: %s') % exc)
LOG.exception('Unable to clean up ramdisk boot for node %s',
task.node.uuid)
try:
with cond_utils.power_state_for_network_configuration(task):
task.driver.network.remove_inspection_network(task)
except Exception as exc:
errors.append(_('unable to remove inspection ports: %s') % exc)
LOG.exception('Unable to remove inspection network for node %s',
task.node.uuid)
if ((ironic_manages_boot or always_power_off)
and CONF.inspector.power_off
and not utils.fast_track_enabled(task.node)
and not power_off_done):
if task.node.disable_power_off:
LOG.debug('Rebooting node %s instead of powering it off because '
'disable_power_off is set to True', task.node.uuid)
power_state = states.REBOOT
err_msg = _('unable to reboot the node: %s')
else:
power_state = states.POWER_OFF
err_msg = _('unable to power off the node: %s')
try:
cond_utils.node_power_action(task, power_state)
except Exception as exc:
errors.append(err_msg % exc)
LOG.exception(err_msg, task.node.uuid)
return errors
[docs]
def inspection_error_handler(task, error, raise_exc=False, clean_up=True):
if clean_up:
tear_down_managed_boot(task)
task.node.last_error = error
if raise_exc:
task.node.save()
raise exception.HardwareInspectionFailure(error=error)
else:
task.process_event('fail')
[docs]
def ironic_manages_boot(task, raise_exc=False):
"""Whether ironic should manage boot for this node."""
try:
task.driver.boot.validate_inspection(task)
except exception.UnsupportedDriverExtension as e:
LOG.debug('The boot interface %(iface)s of the node %(node)s does '
'not support managed boot for in-band inspection or '
'the required options are not populated: %(exc)s',
{'node': task.node.uuid,
'iface': task.node.get_interface('boot'),
'exc': e})
if raise_exc:
raise
return False
try:
task.driver.network.validate_inspection(task)
except exception.UnsupportedDriverExtension as e:
LOG.debug('The network interface %(iface)s of the node %(node)s does '
'not support managed boot for in-band inspection or '
'the required options are not populated: %(exc)s',
{'node': task.node.uuid,
'iface': task.node.get_interface('network'),
'exc': e})
if raise_exc:
raise
return False
return True
[docs]
def prepare_managed_inspection(task, endpoint):
"""Prepare the boot interface for managed inspection."""
params = dict(
utils.parse_kernel_params(CONF.inspector.extra_kernel_params),
**{'ipa-inspection-callback-url': endpoint})
if CONF.inspector.force_dhcp:
# Ensure LLDP collection for inspection on all interfaces.
params.setdefault('ipa-collect-lldp', '1')
if utils.fast_track_enabled(task.node):
params['ipa-api-url'] = deploy_utils.get_ironic_api_url()
if not task.node.disable_power_off:
cond_utils.node_power_action(task, states.POWER_OFF)
with cond_utils.power_state_for_network_configuration(task):
task.driver.network.add_inspection_network(task)
task.driver.boot.prepare_ramdisk(task, ramdisk_params=params)
[docs]
class Common(base.InspectInterface):
[docs]
def get_properties(self):
"""Return the properties of the interface.
:returns: dictionary of <property name>:<property description> entries.
"""
return {} # no properties
[docs]
def validate(self, task):
"""Validate the driver-specific inspection information.
If invalid, raises an exception; otherwise returns None.
:param task: a task from TaskManager.
:raises: UnsupportedDriverExtension
"""
utils.parse_kernel_params(CONF.inspector.extra_kernel_params)
if CONF.inspector.require_managed_boot:
ironic_manages_boot(task, raise_exc=True)
[docs]
def inspect_hardware(self, task):
"""Inspect hardware to obtain the hardware properties.
Results will be checked in a periodic task.
:param task: a task from TaskManager.
:returns: states.INSPECTWAIT
:raises: HardwareInspectionFailure on failure
"""
try:
inspect_utils.create_ports_if_not_exist(task)
except exception.UnsupportedDriverExtension:
LOG.debug('Pre-creating ports prior to inspection not supported'
' on node %s.', task.node.uuid)
manage_boot = ironic_manages_boot(
task, raise_exc=CONF.inspector.require_managed_boot)
utils.set_node_nested_field(task.node, 'driver_internal_info',
_IRONIC_MANAGES_BOOT, manage_boot)
# Make this interface work with the Ironic own /continue_inspection
# endpoint to simplify migration to the new in-band inspection
# implementation.
inspect_utils.cache_lookup_addresses(task.node)
task.node.save()
LOG.debug('Starting inspection for node %(uuid)s. Booting is '
'%(managed)s by ironic',
{'uuid': task.node.uuid,
'managed': manage_boot})
if manage_boot:
try:
self._start_managed_inspection(task)
except Exception as exc:
LOG.exception('Unable to start managed inspection for node '
'%(uuid)s: %(err)s',
{'uuid': task.node.uuid, 'err': exc})
error = _('unable to start inspection: %s') % exc
inspection_error_handler(task, error, raise_exc=True)
else:
self._start_unmanaged_inspection(task)
return states.INSPECTWAIT
def _power_on_or_reboot(self, task):
# Handles disable_power_off properly
next_state = (states.REBOOT if task.node.disable_power_off
else states.POWER_ON)
cond_utils.node_power_action(task, next_state)
[docs]
def clean_up(task, finish=True, always_power_off=False):
errors = tear_down_managed_boot(task, always_power_off=always_power_off)
if errors:
errors = ', '.join(errors)
LOG.error('Inspection clean up failed for node %(uuid)s: %(err)s',
{'uuid': task.node.uuid, 'err': errors})
msg = _('Inspection clean up failed: %s') % errors
inspection_error_handler(task, msg, raise_exc=False, clean_up=False)
elif finish:
LOG.info('Inspection finished successfully for node %s',
task.node.uuid)
task.process_event('done')