Add a plugin for capabilities detection

Supports boot_mode and CPU flags.

Change-Id: Idee87a9fa0c89e51993735e69906f5688bfe23aa
Closes-Bug: #1571580
This commit is contained in:
Dmitry Tantsur 2016-06-06 09:40:27 +02:00 committed by Dmitry Tantsur
parent 6a1083d7a1
commit b2c2767147
8 changed files with 282 additions and 27 deletions

View File

@ -3,6 +3,7 @@ output_file = example.conf
namespace = ironic_inspector
namespace = ironic_inspector.common.ironic
namespace = ironic_inspector.common.swift
namespace = ironic_inspector.plugins.capabilities
namespace = ironic_inspector.plugins.discovery
namespace = keystonemiddleware.auth_token
namespace = oslo.db

View File

@ -184,15 +184,22 @@ introspection data. Note that order does matter in this option.
These are plugins that are enabled by default and should not be disabled,
unless you understand what you're doing:
``ramdisk_error``
reports error, if ``error`` field is set by the ramdisk, also optionally
stores logs from ``logs`` field, see :ref:`api` for details.
``scheduler``
validates and updates basic hardware scheduling properties: CPU number and
architecture, memory and disk size.
``validate_interfaces``
validates network interfaces information.
The following plugins are enabled by default, but can be disabled if not
needed:
``ramdisk_error``
reports error, if ``error`` field is set by the ramdisk, also optionally
stores logs from ``logs`` field, see :ref:`api` for details.
``capabilities``
detect node capabilities: CPU, boot mode, etc. See `Capabilities
Detection`_ for more details.
Here are some plugins that can be additionally enabled:
``example``
@ -330,3 +337,42 @@ Limitations:
* the unprocessed data is never cleaned from the store
* check for stored data presence is performed in background;
missing data situation still results in a ``202`` response
Capabilities Detection
~~~~~~~~~~~~~~~~~~~~~~
Starting with the Newton release, **Ironic Inspector** can optionally discover
several node capabilities. A recent (Newton or newer) IPA image is required
for it to work.
Boot mode
^^^^^^^^^
The current boot mode (BIOS or UEFI) can be detected and recorded as
``boot_mode`` capability in Ironic. It will make some drivers to change their
behaviour to account for this capability. Set the ``[capabilities]boot_mode``
configuration option to ``True`` to enable.
CPU capabilities
^^^^^^^^^^^^^^^^
Several CPU flags are detected by default and recorded as following
capabilities:
* ``cpu_aes`` AES instructions.
* ``cpu_vt`` virtualization support.
* ``cpu_txt`` TXT support.
* ``cpu_hugepages`` huge pages (2 MiB) support.
* ``cpu_hugepages_1g`` huge pages (1 GiB) support.
It is possible to define your own rules for detecting CPU capabilities.
Set the ``[capabilities]cpu_flags`` configuration option to a mapping between
a CPU flag and a capability, for example::
cpu_flags = aes:cpu_aes,svm:cpu_vt,vmx:cpu_vt
See the default value of this option for a more detail example.

View File

@ -76,10 +76,11 @@
# If set to true, the logging level will be set to DEBUG instead of
# the default INFO level. (boolean value)
# Note: This option can be changed without restarting.
#debug = false
# If set to false, the logging level will be set to WARNING instead of
# the default INFO level. (boolean value)
# DEPRECATED: If set to false, the logging level will be set to
# WARNING instead of the default INFO level. (boolean value)
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
#verbose = true
@ -168,6 +169,20 @@
#fatal_deprecations = false
[capabilities]
#
# From ironic_inspector.plugins.capabilities
#
# Whether to store the boot mode (BIOS or UEFI). (boolean value)
#boot_mode = false
# Mapping between a CPU flag and a capability to set if this flag is
# present. (dict value)
#cpu_flags = aes:cpu_aes,pdpe1gb:cpu_hugepages_1g,pse:cpu_hugepages,smx:cpu_txt,svm:cpu_vt,vmx:cpu_vt
[cors]
#
@ -175,7 +190,9 @@
#
# Indicate whether this resource may be shared with the domain
# received in the requests "origin" header. (list value)
# received in the requests "origin" header. Format:
# "<protocol>://<host>[:<port>]", no trailing slash. Example:
# https://horizon.example.com (list value)
#allowed_origin = <None>
# Indicate that the actual request can include user credentials
@ -184,7 +201,7 @@
# Indicate which headers are safe to expose to the API. Defaults to
# HTTP Simple Headers. (list value)
#expose_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma
#expose_headers =
# Maximum cache age of CORS preflight requests. (integer value)
#max_age = 3600
@ -205,7 +222,9 @@
#
# Indicate whether this resource may be shared with the domain
# received in the requests "origin" header. (list value)
# received in the requests "origin" header. Format:
# "<protocol>://<host>[:<port>]", no trailing slash. Example:
# https://horizon.example.com (list value)
#allowed_origin = <None>
# Indicate that the actual request can include user credentials
@ -214,7 +233,7 @@
# Indicate which headers are safe to expose to the API. Defaults to
# HTTP Simple Headers. (list value)
#expose_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma
#expose_headers =
# Maximum cache age of CORS preflight requests. (integer value)
#max_age = 3600
@ -340,8 +359,8 @@
# From ironic_inspector
#
# SQLite3 database to store nodes under introspection, required. Do
# not use :memory: here, it won't work. DEPRECATED: use
# DEPRECATED: SQLite3 database to store nodes under introspection,
# required. Do not use :memory: here, it won't work. DEPRECATED: use
# [database]/connection. (string value)
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
@ -423,8 +442,9 @@
# Domain name to scope to (unknown value)
#domain_name = <None>
# Keystone admin endpoint. DEPRECATED: Use [keystone_authtoken]
# section for keystone token validation. (string value)
# DEPRECATED: Keystone admin endpoint. DEPRECATED: Use
# [keystone_authtoken] section for keystone token validation. (string
# value)
# Deprecated group/name - [discoverd]/identity_uri
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
@ -445,9 +465,9 @@
# (integer value)
#max_retries = 30
# Keystone authentication endpoint for accessing Ironic API. Use
# [keystone_authtoken] section for keystone token validation. (string
# value)
# DEPRECATED: Keystone authentication endpoint for accessing Ironic
# API. Use [keystone_authtoken] section for keystone token validation.
# (string value)
# Deprecated group/name - [discoverd]/os_auth_url
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
@ -457,8 +477,9 @@
# Ironic endpoint type. (string value)
#os_endpoint_type = internalURL
# Password for accessing Ironic API. Use [keystone_authtoken] section
# for keystone token validation. (string value)
# DEPRECATED: Password for accessing Ironic API. Use
# [keystone_authtoken] section for keystone token validation. (string
# value)
# Deprecated group/name - [discoverd]/os_password
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
@ -471,16 +492,18 @@
# Ironic service type. (string value)
#os_service_type = baremetal
# Tenant name for accessing Ironic API. Use [keystone_authtoken]
# section for keystone token validation. (string value)
# DEPRECATED: Tenant name for accessing Ironic API. Use
# [keystone_authtoken] section for keystone token validation. (string
# value)
# Deprecated group/name - [discoverd]/os_tenant_name
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
# Reason: Use options presented by configured keystone auth plugin.
#os_tenant_name =
# User name for accessing Ironic API. Use [keystone_authtoken] section
# for keystone token validation. (string value)
# DEPRECATED: User name for accessing Ironic API. Use
# [keystone_authtoken] section for keystone token validation. (string
# value)
# Deprecated group/name - [discoverd]/os_username
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
@ -598,7 +621,8 @@
# Determines the frequency at which the list of revoked tokens is
# retrieved from the Identity service (in seconds). A high number of
# revocation events combined with a low cache duration may
# significantly reduce performance. (integer value)
# significantly reduce performance. Only valid for PKI tokens.
# (integer value)
#revocation_cache_time = 10
# (Optional) If defined, indicate whether token data should be
@ -718,7 +742,7 @@
# the Nova scheduler. Hook 'validate_interfaces' ensures that valid
# NIC data was provided by the ramdisk.Do not exclude these two unless
# you really know what you're doing. (string value)
#default_processing_hooks = ramdisk_error,root_disk_selection,scheduler,validate_interfaces
#default_processing_hooks = ramdisk_error,root_disk_selection,scheduler,validate_interfaces,capabilities
# Comma-separated list of enabled hooks for processing pipeline. The
# default for this is $default_processing_hooks, hooks can be added
@ -815,13 +839,13 @@
# (integer value)
#max_retries = 2
# Keystone authentication URL (string value)
# DEPRECATED: Keystone authentication URL (string value)
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
# Reason: Use options presented by configured keystone auth plugin.
#os_auth_url =
# Keystone authentication API version (string value)
# DEPRECATED: Keystone authentication API version (string value)
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
# Reason: Use options presented by configured keystone auth plugin.

View File

@ -79,7 +79,7 @@ PROCESSING_OPTS = [
deprecated_group='discoverd'),
cfg.StrOpt('default_processing_hooks',
default='ramdisk_error,root_disk_selection,scheduler,'
'validate_interfaces',
'validate_interfaces,capabilities',
help='Comma-separated list of default hooks for processing '
'pipeline. Hook \'scheduler\' updates the node with the '
'minimum properties required by the Nova scheduler. '

View File

@ -0,0 +1,101 @@
# 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.
"""Gather capabilities from inventory."""
from oslo_config import cfg
from ironic_inspector.common.i18n import _LI, _LW
from ironic_inspector.plugins import base
from ironic_inspector import utils
DEFAULT_CPU_FLAGS_MAPPING = {
'vmx': 'cpu_vt',
'svm': 'cpu_vt',
'aes': 'cpu_aes',
'pse': 'cpu_hugepages',
'pdpe1gb': 'cpu_hugepages_1g',
'smx': 'cpu_txt',
}
CAPABILITIES_OPTS = [
cfg.BoolOpt('boot_mode',
default=False,
help='Whether to store the boot mode (BIOS or UEFI).'),
cfg.DictOpt('cpu_flags',
default=DEFAULT_CPU_FLAGS_MAPPING,
help='Mapping between a CPU flag and a capability to set '
'if this flag is present.'),
]
def list_opts():
return [
('capabilities', CAPABILITIES_OPTS)
]
CONF = cfg.CONF
CONF.register_opts(CAPABILITIES_OPTS, group='capabilities')
LOG = utils.getProcessingLogger(__name__)
class CapabilitiesHook(base.ProcessingHook):
"""Processing hook for detecting capabilities."""
def _detect_boot_mode(self, inventory, node_info, data=None):
boot_mode = inventory.get('boot', {}).get('current_boot_mode')
if boot_mode is not None:
LOG.info(_LI('Boot mode was %s'), boot_mode,
data=data, node_info=node_info)
return {'boot_mode': boot_mode}
else:
LOG.warning(_LW('No boot mode information available'),
data=data, node_info=node_info)
return {}
def _detect_cpu_flags(self, inventory, node_info, data=None):
flags = inventory['cpu'].get('flags')
if not flags:
LOG.warning(_LW('No CPU flags available, please update your '
'introspection ramdisk'),
data=data, node_info=node_info)
return {}
flags = set(flags)
caps = {}
for flag, name in CONF.capabilities.cpu_flags.items():
if flag in flags:
caps[name] = 'true'
LOG.info(_LI('CPU capabilities: %s'), list(caps),
data=data, node_info=node_info)
return caps
def before_update(self, introspection_data, node_info, **kwargs):
inventory = utils.get_inventory(introspection_data)
caps = {}
if CONF.capabilities.boot_mode:
caps.update(self._detect_boot_mode(inventory, node_info,
introspection_data))
caps.update(self._detect_cpu_flags(inventory, node_info,
introspection_data))
if caps:
LOG.debug('New capabilities: %s', caps, node_info=node_info,
data=introspection_data)
node_info.update_capabilities(**caps)
else:
LOG.debug('No new capabilities detected', node_info=node_info,
data=introspection_data)

View File

@ -0,0 +1,77 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from oslo_config import cfg
from ironic_inspector import node_cache
from ironic_inspector.plugins import base
from ironic_inspector.plugins import capabilities
from ironic_inspector.test import base as test_base
CONF = cfg.CONF
@mock.patch.object(node_cache.NodeInfo, 'update_capabilities', autospec=True)
class TestCapabilitiesHook(test_base.NodeTest):
hook = capabilities.CapabilitiesHook()
def test_loadable_by_name(self, mock_caps):
base.CONF.set_override('processing_hooks', 'capabilities',
'processing')
ext = base.processing_hooks_manager()['capabilities']
self.assertIsInstance(ext.obj, capabilities.CapabilitiesHook)
def test_no_data(self, mock_caps):
self.hook.before_update(self.data, self.node_info)
self.assertFalse(mock_caps.called)
def test_boot_mode(self, mock_caps):
CONF.set_override('boot_mode', True, 'capabilities')
self.inventory['boot'] = {'current_boot_mode': 'uefi'}
self.hook.before_update(self.data, self.node_info)
mock_caps.assert_called_once_with(self.node_info, boot_mode='uefi')
def test_boot_mode_disabled(self, mock_caps):
self.inventory['boot'] = {'current_boot_mode': 'uefi'}
self.hook.before_update(self.data, self.node_info)
self.assertFalse(mock_caps.called)
def test_cpu_flags(self, mock_caps):
self.inventory['cpu']['flags'] = ['fpu', 'vmx', 'aes', 'pse', 'smx']
self.hook.before_update(self.data, self.node_info)
mock_caps.assert_called_once_with(self.node_info,
cpu_vt='true',
cpu_hugepages='true',
cpu_txt='true',
cpu_aes='true')
def test_cpu_no_known_flags(self, mock_caps):
self.inventory['cpu']['flags'] = ['fpu']
self.hook.before_update(self.data, self.node_info)
self.assertFalse(mock_caps.called)
def test_cpu_flags_custom(self, mock_caps):
CONF.set_override('cpu_flags', {'fpu': 'new_cap'},
'capabilities')
self.inventory['cpu']['flags'] = ['fpu', 'vmx', 'aes', 'pse']
self.hook.before_update(self.data, self.node_info)
mock_caps.assert_called_once_with(self.node_info,
new_cap='true')

View File

@ -0,0 +1,4 @@
---
features:
- Added a new "capabilities" processing hook detecting the CPU and boot mode
capabilities (the latter disabled by default).

View File

@ -31,6 +31,7 @@ ironic_inspector.hooks.processing =
example = ironic_inspector.plugins.example:ExampleProcessingHook
extra_hardware = ironic_inspector.plugins.extra_hardware:ExtraHardwareHook
raid_device = ironic_inspector.plugins.raid_device:RaidDeviceDetection
capabilities = ironic_inspector.plugins.capabilities:CapabilitiesHook
# Deprecated name for raid_device, don't confuse with root_disk_selection
root_device_hint = ironic_inspector.plugins.raid_device:RootDeviceHintHook
ironic_inspector.hooks.node_not_found =
@ -58,6 +59,7 @@ oslo.config.opts =
ironic_inspector.common.ironic = ironic_inspector.common.ironic:list_opts
ironic_inspector.common.swift = ironic_inspector.common.swift:list_opts
ironic_inspector.plugins.discovery = ironic_inspector.plugins.discovery:list_opts
ironic_inspector.plugins.capabilities = ironic_inspector.plugins.capabilities:list_opts
oslo.config.opts.defaults =
ironic_inspector = ironic_inspector.conf:set_config_defaults