Add a plugin for capabilities detection
Supports boot_mode and CPU flags. Change-Id: Idee87a9fa0c89e51993735e69906f5688bfe23aa Closes-Bug: #1571580
This commit is contained in:
parent
6a1083d7a1
commit
b2c2767147
@ -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
|
||||
|
@ -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.
|
||||
|
70
example.conf
70
example.conf
@ -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.
|
||||
|
@ -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. '
|
||||
|
101
ironic_inspector/plugins/capabilities.py
Normal file
101
ironic_inspector/plugins/capabilities.py
Normal 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)
|
77
ironic_inspector/test/unit/test_plugins_capabilities.py
Normal file
77
ironic_inspector/test/unit/test_plugins_capabilities.py
Normal 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')
|
4
releasenotes/notes/capabilities-15cc2268d661f0a0.yaml
Normal file
4
releasenotes/notes/capabilities-15cc2268d661f0a0.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Added a new "capabilities" processing hook detecting the CPU and boot mode
|
||||
capabilities (the latter disabled by default).
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user