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
namespace = ironic_inspector.common.ironic namespace = ironic_inspector.common.ironic
namespace = ironic_inspector.common.swift namespace = ironic_inspector.common.swift
namespace = ironic_inspector.plugins.capabilities
namespace = ironic_inspector.plugins.discovery namespace = ironic_inspector.plugins.discovery
namespace = keystonemiddleware.auth_token namespace = keystonemiddleware.auth_token
namespace = oslo.db 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, These are plugins that are enabled by default and should not be disabled,
unless you understand what you're doing: 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`` ``scheduler``
validates and updates basic hardware scheduling properties: CPU number and validates and updates basic hardware scheduling properties: CPU number and
architecture, memory and disk size. architecture, memory and disk size.
``validate_interfaces`` ``validate_interfaces``
validates network interfaces information. 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: Here are some plugins that can be additionally enabled:
``example`` ``example``
@ -330,3 +337,42 @@ Limitations:
* the unprocessed data is never cleaned from the store * the unprocessed data is never cleaned from the store
* check for stored data presence is performed in background; * check for stored data presence is performed in background;
missing data situation still results in a ``202`` response 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 # If set to true, the logging level will be set to DEBUG instead of
# the default INFO level. (boolean value) # the default INFO level. (boolean value)
# Note: This option can be changed without restarting.
#debug = false #debug = false
# If set to false, the logging level will be set to WARNING instead of # DEPRECATED: If set to false, the logging level will be set to
# the default INFO level. (boolean value) # WARNING instead of the default INFO level. (boolean value)
# This option is deprecated for removal. # This option is deprecated for removal.
# Its value may be silently ignored in the future. # Its value may be silently ignored in the future.
#verbose = true #verbose = true
@ -168,6 +169,20 @@
#fatal_deprecations = false #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] [cors]
# #
@ -175,7 +190,9 @@
# #
# Indicate whether this resource may be shared with the domain # 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> #allowed_origin = <None>
# Indicate that the actual request can include user credentials # 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 # Indicate which headers are safe to expose to the API. Defaults to
# HTTP Simple Headers. (list value) # 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) # Maximum cache age of CORS preflight requests. (integer value)
#max_age = 3600 #max_age = 3600
@ -205,7 +222,9 @@
# #
# Indicate whether this resource may be shared with the domain # 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> #allowed_origin = <None>
# Indicate that the actual request can include user credentials # 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 # Indicate which headers are safe to expose to the API. Defaults to
# HTTP Simple Headers. (list value) # 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) # Maximum cache age of CORS preflight requests. (integer value)
#max_age = 3600 #max_age = 3600
@ -340,8 +359,8 @@
# From ironic_inspector # From ironic_inspector
# #
# SQLite3 database to store nodes under introspection, required. Do # DEPRECATED: SQLite3 database to store nodes under introspection,
# not use :memory: here, it won't work. DEPRECATED: use # required. Do not use :memory: here, it won't work. DEPRECATED: use
# [database]/connection. (string value) # [database]/connection. (string value)
# This option is deprecated for removal. # This option is deprecated for removal.
# Its value may be silently ignored in the future. # Its value may be silently ignored in the future.
@ -423,8 +442,9 @@
# Domain name to scope to (unknown value) # Domain name to scope to (unknown value)
#domain_name = <None> #domain_name = <None>
# Keystone admin endpoint. DEPRECATED: Use [keystone_authtoken] # DEPRECATED: Keystone admin endpoint. DEPRECATED: Use
# section for keystone token validation. (string value) # [keystone_authtoken] section for keystone token validation. (string
# value)
# Deprecated group/name - [discoverd]/identity_uri # Deprecated group/name - [discoverd]/identity_uri
# This option is deprecated for removal. # This option is deprecated for removal.
# Its value may be silently ignored in the future. # Its value may be silently ignored in the future.
@ -445,9 +465,9 @@
# (integer value) # (integer value)
#max_retries = 30 #max_retries = 30
# Keystone authentication endpoint for accessing Ironic API. Use # DEPRECATED: Keystone authentication endpoint for accessing Ironic
# [keystone_authtoken] section for keystone token validation. (string # API. Use [keystone_authtoken] section for keystone token validation.
# value) # (string value)
# Deprecated group/name - [discoverd]/os_auth_url # Deprecated group/name - [discoverd]/os_auth_url
# This option is deprecated for removal. # This option is deprecated for removal.
# Its value may be silently ignored in the future. # Its value may be silently ignored in the future.
@ -457,8 +477,9 @@
# Ironic endpoint type. (string value) # Ironic endpoint type. (string value)
#os_endpoint_type = internalURL #os_endpoint_type = internalURL
# Password for accessing Ironic API. Use [keystone_authtoken] section # DEPRECATED: Password for accessing Ironic API. Use
# for keystone token validation. (string value) # [keystone_authtoken] section for keystone token validation. (string
# value)
# Deprecated group/name - [discoverd]/os_password # Deprecated group/name - [discoverd]/os_password
# This option is deprecated for removal. # This option is deprecated for removal.
# Its value may be silently ignored in the future. # Its value may be silently ignored in the future.
@ -471,16 +492,18 @@
# Ironic service type. (string value) # Ironic service type. (string value)
#os_service_type = baremetal #os_service_type = baremetal
# Tenant name for accessing Ironic API. Use [keystone_authtoken] # DEPRECATED: Tenant name for accessing Ironic API. Use
# section for keystone token validation. (string value) # [keystone_authtoken] section for keystone token validation. (string
# value)
# Deprecated group/name - [discoverd]/os_tenant_name # Deprecated group/name - [discoverd]/os_tenant_name
# This option is deprecated for removal. # This option is deprecated for removal.
# Its value may be silently ignored in the future. # Its value may be silently ignored in the future.
# Reason: Use options presented by configured keystone auth plugin. # Reason: Use options presented by configured keystone auth plugin.
#os_tenant_name = #os_tenant_name =
# User name for accessing Ironic API. Use [keystone_authtoken] section # DEPRECATED: User name for accessing Ironic API. Use
# for keystone token validation. (string value) # [keystone_authtoken] section for keystone token validation. (string
# value)
# Deprecated group/name - [discoverd]/os_username # Deprecated group/name - [discoverd]/os_username
# This option is deprecated for removal. # This option is deprecated for removal.
# Its value may be silently ignored in the future. # Its value may be silently ignored in the future.
@ -598,7 +621,8 @@
# Determines the frequency at which the list of revoked tokens is # Determines the frequency at which the list of revoked tokens is
# retrieved from the Identity service (in seconds). A high number of # retrieved from the Identity service (in seconds). A high number of
# revocation events combined with a low cache duration may # 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 #revocation_cache_time = 10
# (Optional) If defined, indicate whether token data should be # (Optional) If defined, indicate whether token data should be
@ -718,7 +742,7 @@
# the Nova scheduler. Hook 'validate_interfaces' ensures that valid # the Nova scheduler. Hook 'validate_interfaces' ensures that valid
# NIC data was provided by the ramdisk.Do not exclude these two unless # NIC data was provided by the ramdisk.Do not exclude these two unless
# you really know what you're doing. (string value) # 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 # Comma-separated list of enabled hooks for processing pipeline. The
# default for this is $default_processing_hooks, hooks can be added # default for this is $default_processing_hooks, hooks can be added
@ -815,13 +839,13 @@
# (integer value) # (integer value)
#max_retries = 2 #max_retries = 2
# Keystone authentication URL (string value) # DEPRECATED: Keystone authentication URL (string value)
# This option is deprecated for removal. # This option is deprecated for removal.
# Its value may be silently ignored in the future. # Its value may be silently ignored in the future.
# Reason: Use options presented by configured keystone auth plugin. # Reason: Use options presented by configured keystone auth plugin.
#os_auth_url = #os_auth_url =
# Keystone authentication API version (string value) # DEPRECATED: Keystone authentication API version (string value)
# This option is deprecated for removal. # This option is deprecated for removal.
# Its value may be silently ignored in the future. # Its value may be silently ignored in the future.
# Reason: Use options presented by configured keystone auth plugin. # Reason: Use options presented by configured keystone auth plugin.

View File

@ -79,7 +79,7 @@ PROCESSING_OPTS = [
deprecated_group='discoverd'), deprecated_group='discoverd'),
cfg.StrOpt('default_processing_hooks', cfg.StrOpt('default_processing_hooks',
default='ramdisk_error,root_disk_selection,scheduler,' default='ramdisk_error,root_disk_selection,scheduler,'
'validate_interfaces', 'validate_interfaces,capabilities',
help='Comma-separated list of default hooks for processing ' help='Comma-separated list of default hooks for processing '
'pipeline. Hook \'scheduler\' updates the node with the ' 'pipeline. Hook \'scheduler\' updates the node with the '
'minimum properties required by the Nova scheduler. ' '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 example = ironic_inspector.plugins.example:ExampleProcessingHook
extra_hardware = ironic_inspector.plugins.extra_hardware:ExtraHardwareHook extra_hardware = ironic_inspector.plugins.extra_hardware:ExtraHardwareHook
raid_device = ironic_inspector.plugins.raid_device:RaidDeviceDetection 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 # Deprecated name for raid_device, don't confuse with root_disk_selection
root_device_hint = ironic_inspector.plugins.raid_device:RootDeviceHintHook root_device_hint = ironic_inspector.plugins.raid_device:RootDeviceHintHook
ironic_inspector.hooks.node_not_found = 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.ironic = ironic_inspector.common.ironic:list_opts
ironic_inspector.common.swift = ironic_inspector.common.swift: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.discovery = ironic_inspector.plugins.discovery:list_opts
ironic_inspector.plugins.capabilities = ironic_inspector.plugins.capabilities:list_opts
oslo.config.opts.defaults = oslo.config.opts.defaults =
ironic_inspector = ironic_inspector.conf:set_config_defaults ironic_inspector = ironic_inspector.conf:set_config_defaults