ironic-inspector/ironic_inspector/introspect.py
Dmitry Tantsur 0423d93736 Track node identification during the whole processing
Currently our logging in processing is very inconsistent:
some log strings mention node UUID, some - node BMC IP, some nothing.
This change introduces a common prefix for all processing logs
based on as much information as possible.
Only code that actually have some context about the node (either
NodeInfo or introspection data) is updated.

Also logging BMC addresses can be disabled now.

Updates example.conf (a lot of updated comments from oslo).

Change-Id: Ib20f2acdc60bfaceed7a33467557b92857c32798
2016-01-13 12:23:15 +01:00

177 lines
6.7 KiB
Python

# 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.
"""Handling introspection request."""
import re
import string
import time
from eventlet import semaphore
from ironicclient import exceptions
from oslo_config import cfg
from ironic_inspector.common.i18n import _, _LI, _LW
from ironic_inspector import firewall
from ironic_inspector import node_cache
from ironic_inspector import utils
CONF = cfg.CONF
LOG = utils.getProcessingLogger(__name__)
PASSWORD_ACCEPTED_CHARS = set(string.ascii_letters + string.digits)
PASSWORD_MAX_LENGTH = 20 # IPMI v2.0
_LAST_INTROSPECTION_TIME = 0
_LAST_INTROSPECTION_LOCK = semaphore.BoundedSemaphore()
def _validate_ipmi_credentials(node, new_ipmi_credentials):
if not CONF.processing.enable_setting_ipmi_credentials:
raise utils.Error(
_('IPMI credentials setup is disabled in configuration'))
new_username, new_password = new_ipmi_credentials
if not new_username:
new_username = node.driver_info.get('ipmi_username')
if not new_username:
raise utils.Error(_('Setting IPMI credentials requested, but neither '
'new user name nor driver_info[ipmi_username] '
'are provided'),
node_info=node)
wrong_chars = {c for c in new_password
if c not in PASSWORD_ACCEPTED_CHARS}
if wrong_chars:
raise utils.Error(_('Forbidden characters encountered in new IPMI '
'password: "%s"; use only letters and numbers')
% ''.join(wrong_chars), node_info=node)
if not 0 < len(new_password) <= PASSWORD_MAX_LENGTH:
raise utils.Error(_('IPMI password length should be > 0 and <= %d')
% PASSWORD_MAX_LENGTH, node_info=node)
return new_username, new_password
def introspect(uuid, new_ipmi_credentials=None, token=None):
"""Initiate hardware properties introspection for a given node.
:param uuid: node uuid
:param new_ipmi_credentials: tuple (new username, new password) or None
:param token: authentication token
:raises: Error
"""
ironic = utils.get_client(token)
try:
node = ironic.node.get(uuid)
except exceptions.NotFound:
raise utils.Error(_("Cannot find node %s") % uuid, code=404)
except exceptions.HttpError as exc:
raise utils.Error(_("Cannot get node %(node)s: %(exc)s") %
{'node': uuid, 'exc': exc})
utils.check_provision_state(node, with_credentials=new_ipmi_credentials)
if new_ipmi_credentials:
new_ipmi_credentials = (
_validate_ipmi_credentials(node, new_ipmi_credentials))
else:
validation = ironic.node.validate(node.uuid)
if not validation.power['result']:
msg = _('Failed validation of power interface, reason: %s')
raise utils.Error(msg % validation.power['reason'],
node_info=node)
node_info = node_cache.add_node(node.uuid,
bmc_address=utils.get_ipmi_address(node),
ironic=ironic)
node_info.set_option('new_ipmi_credentials', new_ipmi_credentials)
def _handle_exceptions():
try:
_background_introspect(ironic, node_info)
except utils.Error as exc:
# Logging has already happened in Error.__init__
node_info.finished(error=str(exc))
except Exception as exc:
msg = _('Unexpected exception in background introspection thread')
LOG.exception(msg, node_info=node_info)
node_info.finished(error=msg)
utils.spawn_n(_handle_exceptions)
def _background_introspect(ironic, node_info):
global _LAST_INTROSPECTION_TIME
if not node_info.options.get('new_ipmi_credentials'):
if re.match(CONF.introspection_delay_drivers, node_info.node().driver):
LOG.debug('Attempting to acquire lock on last introspection time')
with _LAST_INTROSPECTION_LOCK:
delay = (_LAST_INTROSPECTION_TIME - time.time()
+ CONF.introspection_delay)
if delay > 0:
LOG.debug('Waiting %d seconds before sending the next '
'node on introspection', delay)
time.sleep(delay)
_LAST_INTROSPECTION_TIME = time.time()
node_info.acquire_lock()
try:
_background_introspect_locked(ironic, node_info)
finally:
node_info.release_lock()
def _background_introspect_locked(ironic, node_info):
# TODO(dtantsur): pagination
macs = list(node_info.ports())
if macs:
node_info.add_attribute(node_cache.MACS_ATTRIBUTE, macs)
LOG.info(_LI('Whitelisting MAC\'s %s on the firewall'), macs,
node_info=node_info)
firewall.update_filters(ironic)
attrs = node_info.attributes
if CONF.processing.node_not_found_hook is None and not attrs:
raise utils.Error(
_('No lookup attributes were found, inspector won\'t '
'be able to find it after introspection, consider creating '
'ironic ports or providing an IPMI address'),
node_info=node_info)
LOG.info(_LI('The following attributes will be used for look up: %s'),
attrs, node_info=node_info)
if not node_info.options.get('new_ipmi_credentials'):
try:
ironic.node.set_boot_device(node_info.uuid, 'pxe',
persistent=False)
except Exception as exc:
LOG.warning(_LW('Failed to set boot device to PXE: %s'),
exc, node_info=node_info)
try:
ironic.node.set_power_state(node_info.uuid, 'reboot')
except Exception as exc:
raise utils.Error(_('Failed to power on the node, check it\'s '
'power management configuration: %s'),
exc, node_info=node_info)
LOG.info(_LI('Introspection started successfully'),
node_info=node_info)
else:
LOG.info(_LI('Introspection environment is ready, manual power on is '
'required within %d seconds'), CONF.timeout,
node_info=node_info)