summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ironic/api/controllers/v1/__init__.py146
-rw-r--r--ironic/api/controllers/v1/node.py13
-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/inspect_utils.py116
-rw-r--r--ironic/drivers/modules/inspector/interface.py14
-rw-r--r--ironic/drivers/modules/snmp.py4
-rw-r--r--ironic/tests/unit/api/controllers/v1/test_node.py102
-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/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_snmp.py35
-rw-r--r--ironic/tests/unit/objects/utils.py12
-rw-r--r--playbooks/metal3-ci/run.yaml8
-rw-r--r--releasenotes/notes/bug-2010613-3ab1f32aaa776f28.yaml7
-rw-r--r--tools/benchmark/generate-statistics.py2
18 files changed, 528 insertions, 516 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 d21b075c8..65adee544 100644
--- a/ironic/api/controllers/v1/node.py
+++ b/ironic/api/controllers/v1/node.py
@@ -1600,6 +1600,10 @@ def node_sanitize(node, fields, cdict=None,
node['driver_info'] = strutils.mask_dict_password(
node['driver_info'], "******")
+ _mask_fields(node['driver_info'],
+ ['snmp_auth_key', 'snmp_priv_key'],
+ "******")
+
if not show_instance_secrets and 'instance_info' in node_keys:
node['instance_info'] = strutils.mask_dict_password(
node['instance_info'], "******")
@@ -1663,6 +1667,12 @@ def _node_sanitize_extended(node, node_keys, target_dict, cdict):
'driver_internal_info permission. **'}
+def _mask_fields(dictionary, fields, secret):
+ for field in fields:
+ if dictionary.get(field):
+ dictionary[field] = secret
+
+
def node_list_convert_with_links(nodes, limit, url, fields=None, **kwargs):
cdict = api.request.context.to_policy_values()
target_dict = dict(cdict)
@@ -1964,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/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/snmp.py b/ironic/drivers/modules/snmp.py
index d544d5687..435d24b78 100644
--- a/ironic/drivers/modules/snmp.py
+++ b/ironic/drivers/modules/snmp.py
@@ -392,9 +392,9 @@ def _get_client(snmp_info):
snmp_info.get("read_community"),
snmp_info.get("write_community"),
snmp_info.get("user"),
- snmp_info.get("auth_proto"),
+ snmp_info.get("auth_protocol"),
snmp_info.get("auth_key"),
- snmp_info.get("priv_proto"),
+ snmp_info.get("priv_protocol"),
snmp_info.get("priv_key"),
snmp_info.get("context_engine_id"),
snmp_info.get("context_name"))
diff --git a/ironic/tests/unit/api/controllers/v1/test_node.py b/ironic/tests/unit/api/controllers/v1/test_node.py
index 27061737d..87a029057 100644
--- a/ironic/tests/unit/api/controllers/v1/test_node.py
+++ b/ironic/tests/unit/api/controllers/v1/test_node.py
@@ -843,6 +843,64 @@ class TestListNodes(test_api_base.BaseApiTest):
self.assertIn('retired_reason', data['nodes'][0])
self.assertIn('network_data', data['nodes'][0])
+ def test_detail_snmpv3(self):
+ driver_info = {
+ 'snmp_version': 3,
+ 'snmp_user': 'test-user',
+ 'snmp_auth_protocol': 'sha',
+ 'snmp_auth_key': 'test-auth-key',
+ 'snmp_priv_protocol': 'aes',
+ 'snmp_priv_key': 'test-priv-key'
+ }
+ sanitized_driver_info = driver_info.copy()
+ sanitized_driver_info['snmp_auth_key'] = '******'
+ sanitized_driver_info['snmp_priv_key'] = '******'
+
+ node = obj_utils.create_test_node(self.context,
+ chassis_id=self.chassis.id,
+ driver_info=driver_info)
+ data = self.get_json(
+ '/nodes/detail',
+ headers={api_base.Version.string: str(api_v1.max_version())})
+ self.assertEqual(node.uuid, data['nodes'][0]["uuid"])
+ self.assertIn('name', data['nodes'][0])
+ self.assertIn('driver', data['nodes'][0])
+ self.assertIn('driver_info', data['nodes'][0])
+ self.assertEqual(sanitized_driver_info,
+ data['nodes'][0]['driver_info'])
+ self.assertIn('extra', data['nodes'][0])
+ self.assertIn('properties', data['nodes'][0])
+ self.assertIn('chassis_uuid', data['nodes'][0])
+ self.assertIn('reservation', data['nodes'][0])
+ self.assertIn('maintenance', data['nodes'][0])
+ self.assertIn('console_enabled', data['nodes'][0])
+ self.assertIn('target_power_state', data['nodes'][0])
+ self.assertIn('target_provision_state', data['nodes'][0])
+ self.assertIn('provision_updated_at', data['nodes'][0])
+ self.assertIn('inspection_finished_at', data['nodes'][0])
+ self.assertIn('inspection_started_at', data['nodes'][0])
+ self.assertIn('raid_config', data['nodes'][0])
+ self.assertIn('target_raid_config', data['nodes'][0])
+ self.assertIn('network_interface', data['nodes'][0])
+ self.assertIn('resource_class', data['nodes'][0])
+ for field in api_utils.V31_FIELDS:
+ self.assertIn(field, data['nodes'][0])
+ self.assertIn('storage_interface', data['nodes'][0])
+ self.assertIn('traits', data['nodes'][0])
+ self.assertIn('conductor_group', data['nodes'][0])
+ self.assertIn('automated_clean', data['nodes'][0])
+ self.assertIn('protected', data['nodes'][0])
+ self.assertIn('protected_reason', data['nodes'][0])
+ self.assertIn('owner', data['nodes'][0])
+ self.assertIn('lessee', data['nodes'][0])
+ # never expose the chassis_id
+ self.assertNotIn('chassis_id', data['nodes'][0])
+ self.assertNotIn('allocation_id', data['nodes'][0])
+ self.assertIn('allocation_uuid', data['nodes'][0])
+ self.assertIn('retired', data['nodes'][0])
+ self.assertIn('retired_reason', data['nodes'][0])
+ self.assertIn('network_data', data['nodes'][0])
+
def test_detail_instance_uuid(self):
instance_uuid = '6eccd391-961c-4da5-b3c5-e2fa5cfbbd9d'
node = obj_utils.create_test_node(
@@ -7927,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,
@@ -7949,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/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_snmp.py b/ironic/tests/unit/drivers/modules/test_snmp.py
index 5391d7ac5..e1b6fc1df 100644
--- a/ironic/tests/unit/drivers/modules/test_snmp.py
+++ b/ironic/tests/unit/drivers/modules/test_snmp.py
@@ -46,6 +46,41 @@ class SNMPClientTestCase(base.TestCase):
self.value = 'value'
@mock.patch.object(pysnmp, 'SnmpEngine', autospec=True)
+ def test__get_client(self, mock_snmpengine):
+ driver_info = db_utils.get_test_snmp_info(
+ snmp_address=self.address,
+ snmp_port=self.port,
+ snmp_user='test-user',
+ snmp_auth_protocol='sha',
+ snmp_auth_key='test-auth-key',
+ snmp_priv_protocol='aes',
+ snmp_priv_key='test-priv-key',
+ snmp_context_engine_id='test-engine-id',
+ snmp_context_name='test-context-name',
+ snmp_version='3')
+ node = obj_utils.get_test_node(
+ self.context,
+ driver_info=driver_info)
+ info = snmp._parse_driver_info(node)
+
+ client = snmp._get_client(info)
+
+ mock_snmpengine.assert_called_once_with()
+ self.assertEqual(self.address, client.address)
+ self.assertEqual(int(self.port), client.port)
+ self.assertEqual(snmp.SNMP_V3, client.version)
+ self.assertNotIn('read_community', client.__dict__)
+ self.assertNotIn('write_community', client.__dict__)
+ self.assertEqual('test-user', client.user)
+ self.assertEqual(pysnmp.usmHMACSHAAuthProtocol, client.auth_proto)
+ self.assertEqual('test-auth-key', client.auth_key)
+ self.assertEqual(pysnmp.usmAesCfb128Protocol, client.priv_proto)
+ self.assertEqual('test-priv-key', client.priv_key)
+ self.assertEqual('test-engine-id', client.context_engine_id)
+ self.assertEqual('test-context-name', client.context_name)
+ self.assertEqual(mock_snmpengine.return_value, client.snmp_engine)
+
+ @mock.patch.object(pysnmp, 'SnmpEngine', autospec=True)
def test___init__(self, mock_snmpengine):
client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V1)
mock_snmpengine.assert_called_once_with()
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/bug-2010613-3ab1f32aaa776f28.yaml b/releasenotes/notes/bug-2010613-3ab1f32aaa776f28.yaml
new file mode 100644
index 000000000..89769bfb8
--- /dev/null
+++ b/releasenotes/notes/bug-2010613-3ab1f32aaa776f28.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - |
+ [`bug 2010613 <https://storyboard.openstack.org/#!/story/2010613>`_]
+ Fixes issue with SNMP v3 auth protocol and priv protocol set in
+ driver info not being retrieved correctly when a SNMP client is
+ initialized.
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",