summaryrefslogtreecommitdiff
path: root/ironic/api
diff options
context:
space:
mode:
authorIlya Etingof <etingof@gmail.com>2019-04-10 08:14:22 +0200
committerJulia Kreger <juliaashleykreger@gmail.com>2020-03-21 18:45:01 +0000
commit263fd021b23889b139a7971d93a18268c6b0be71 (patch)
tree2cea42eb0dab386c97e27671a679a8ae82a5b94c /ironic/api
parent9f07ad1b6eaa5a96c9fec8d6cd57ffe474a33e2e (diff)
downloadironic-263fd021b23889b139a7971d93a18268c6b0be71.tar.gz
Add indicators REST API endpoints
Added REST API endpoints for indicator management: * GET /v1/nodes/<node_ident>/management/indicators` to list all available indicators names for each of the hardware component. * GET /v1/nodes/<node_ident>/management/indicators/<indicator_ident> to retrieve the state of given indicator. * PUT /v1/nodes/<node_ident>/management/indicators/<indicator_ident>` change state of the desired indicator. This implementation slightly deviates from the original spec in part of having component name in the URL - this implementation flattens component out. The spec: https://review.opendev.org/#/c/655685/7/specs/approved/expose-hardware-indicators.rst Change-Id: I3a36f58b12487e18a6898aef6b077d4221f8a5b8 Story: 2005342 Task: 30291
Diffstat (limited to 'ironic/api')
-rw-r--r--ironic/api/controllers/v1/node.py181
-rw-r--r--ironic/api/controllers/v1/versions.py8
2 files changed, 186 insertions, 3 deletions
diff --git a/ironic/api/controllers/v1/node.py b/ironic/api/controllers/v1/node.py
index 791704009..caa73267f 100644
--- a/ironic/api/controllers/v1/node.py
+++ b/ironic/api/controllers/v1/node.py
@@ -266,6 +266,184 @@ class BootDeviceController(rest.RestController):
return {'supported_boot_devices': boot_devices}
+class IndicatorAtComponent(object):
+
+ def __init__(self, **kwargs):
+ name = kwargs.get('name')
+ component = kwargs.get('component')
+ unique_name = kwargs.get('unique_name')
+
+ if name and component:
+ self.unique_name = name + '@' + component
+ self.name = name
+ self.component = component
+
+ elif unique_name:
+ try:
+ index = unique_name.index('@')
+
+ except ValueError:
+ raise exception.InvalidParameterValue(
+ _('Malformed indicator name "%s"') % unique_name)
+
+ self.component = unique_name[index + 1:]
+ self.name = unique_name[:index]
+ self.unique_name = unique_name
+
+ else:
+ raise exception.MissingParameterValue(
+ _('Missing indicator name "%s"'))
+
+
+class IndicatorState(base.APIBase):
+ """API representation of indicator state."""
+
+ state = wsme.wsattr(wtypes.text)
+
+ def __init__(self, **kwargs):
+ self.state = kwargs.get('state')
+
+
+class Indicator(base.APIBase):
+ """API representation of an indicator."""
+
+ name = wsme.wsattr(wtypes.text)
+
+ component = wsme.wsattr(wtypes.text)
+
+ readonly = types.BooleanType()
+
+ states = wtypes.ArrayType(str)
+
+ links = wsme.wsattr([link.Link], readonly=True)
+
+ def __init__(self, **kwargs):
+ self.name = kwargs.get('name')
+ self.component = kwargs.get('component')
+ self.readonly = kwargs.get('readonly', True)
+ self.states = kwargs.get('states', [])
+
+ @staticmethod
+ def _convert_with_links(node_uuid, indicator, url):
+ """Add links to the indicator."""
+ indicator.links = [
+ link.Link.make_link(
+ 'self', url, 'nodes',
+ '%s/management/indicators/%s' % (
+ node_uuid, indicator.name)),
+ link.Link.make_link(
+ 'bookmark', url, 'nodes',
+ '%s/management/indicators/%s' % (
+ node_uuid, indicator.name),
+ bookmark=True)]
+ return indicator
+
+ @classmethod
+ def convert_with_links(cls, node_uuid, rpc_component, rpc_name,
+ **rpc_fields):
+ """Add links to the indicator."""
+ indicator = Indicator(
+ component=rpc_component, name=rpc_name, **rpc_fields)
+ return cls._convert_with_links(
+ node_uuid, indicator, pecan.request.host_url)
+
+
+class IndicatorsCollection(wtypes.Base):
+ """API representation of the indicators for a node."""
+
+ indicators = [Indicator]
+ """Node indicators list"""
+
+ @staticmethod
+ def collection_from_dict(node_ident, indicators):
+ col = IndicatorsCollection()
+
+ indicator_list = []
+ for component, names in indicators.items():
+ for name, fields in names.items():
+ indicator_at_component = IndicatorAtComponent(
+ component=component, name=name)
+ indicator = Indicator.convert_with_links(
+ node_ident, component, indicator_at_component.unique_name,
+ **fields)
+ indicator_list.append(indicator)
+ col.indicators = indicator_list
+ return col
+
+
+class IndicatorController(rest.RestController):
+
+ @METRICS.timer('IndicatorController.put')
+ @expose.expose(None, types.uuid_or_name, wtypes.text, wtypes.text,
+ status_code=http_client.NO_CONTENT)
+ def put(self, node_ident, indicator, state):
+ """Set node hardware component indicator to the desired state.
+
+ :param node_ident: the UUID or logical name of a node.
+ :param indicator: Indicator ID (as reported by
+ `get_supported_indicators`).
+ :param state: Indicator state, one of
+ mod:`ironic.common.indicator_states`.
+
+ """
+ cdict = pecan.request.context.to_policy_values()
+ policy.authorize('baremetal:node:set_indicator_state', cdict, cdict)
+
+ rpc_node = api_utils.get_rpc_node(node_ident)
+ topic = pecan.request.rpcapi.get_topic_for(rpc_node)
+ indicator_at_component = IndicatorAtComponent(unique_name=indicator)
+ pecan.request.rpcapi.set_indicator_state(
+ pecan.request.context, rpc_node.uuid,
+ indicator_at_component.component, indicator_at_component.name,
+ state, topic=topic)
+
+ @METRICS.timer('IndicatorController.get_one')
+ @expose.expose(IndicatorState, types.uuid_or_name, wtypes.text)
+ def get_one(self, node_ident, indicator):
+ """Get node hardware component indicator and its state.
+
+ :param node_ident: the UUID or logical name of a node.
+ :param indicator: Indicator ID (as reported by
+ `get_supported_indicators`).
+ :returns: a dict with the "state" key and one of
+ mod:`ironic.common.indicator_states` as a value.
+ """
+ cdict = pecan.request.context.to_policy_values()
+ policy.authorize('baremetal:node:get_indicator_state', cdict, cdict)
+
+ rpc_node = api_utils.get_rpc_node(node_ident)
+ topic = pecan.request.rpcapi.get_topic_for(rpc_node)
+ indicator_at_component = IndicatorAtComponent(unique_name=indicator)
+ state = pecan.request.rpcapi.get_indicator_state(
+ pecan.request.context, rpc_node.uuid,
+ indicator_at_component.component, indicator_at_component.name,
+ topic=topic)
+ return IndicatorState(state=state)
+
+ @METRICS.timer('IndicatorController.get_all')
+ @expose.expose(IndicatorsCollection, types.uuid_or_name, wtypes.text,
+ ignore_extra_args=True)
+ def get_all(self, node_ident):
+ """Get node hardware components and their indicators.
+
+ :param node_ident: the UUID or logical name of a node.
+ :returns: A json object of hardware components
+ (:mod:`ironic.common.components`) as keys with indicator IDs
+ (from `get_supported_indicators`) as values.
+
+ """
+ cdict = pecan.request.context.to_policy_values()
+ policy.authorize('baremetal:node:get_indicator_state', cdict, cdict)
+
+ rpc_node = api_utils.get_rpc_node(node_ident)
+ topic = pecan.request.rpcapi.get_topic_for(rpc_node)
+ indicators = pecan.request.rpcapi.get_supported_indicators(
+ pecan.request.context, rpc_node.uuid, topic=topic)
+
+ return IndicatorsCollection.collection_from_dict(
+ node_ident, indicators)
+
+
class InjectNmiController(rest.RestController):
@METRICS.timer('InjectNmiController.put')
@@ -308,6 +486,9 @@ class NodeManagementController(rest.RestController):
inject_nmi = InjectNmiController()
"""Expose inject_nmi as a sub-element of management"""
+ indicators = IndicatorController()
+ """Expose indicators as a sub-element of management"""
+
class ConsoleInfo(base.Base):
"""API representation of the console information for a node."""
diff --git a/ironic/api/controllers/v1/versions.py b/ironic/api/controllers/v1/versions.py
index f39c077b2..901f91dbc 100644
--- a/ironic/api/controllers/v1/versions.py
+++ b/ironic/api/controllers/v1/versions.py
@@ -23,8 +23,8 @@ CONF = cfg.CONF
BASE_VERSION = 1
# Here goes a short log of changes in every version.
-# Refer to doc/source/dev/webapi-version-history.rst for a detailed explanation
-# of what each version contains.
+# Refer to doc/source/contributor/webapi-version-history.rst for a detailed
+# explanation of what each version contains.
#
# v1.0: corresponds to Juno API, not supported since Kilo
# v1.1: API at the point in time when versioning support was added,
@@ -100,6 +100,7 @@ BASE_VERSION = 1
# v1.60: Add owner to the allocation object.
# v1.61: Add retired and retired_reason to the node object.
# v1.62: Add agent_token support for agent communication.
+# v1.63: Add support for indicators
MINOR_0_JUNO = 0
MINOR_1_INITIAL_VERSION = 1
@@ -164,6 +165,7 @@ MINOR_59_CONFIGDRIVE_VENDOR_DATA = 59
MINOR_60_ALLOCATION_OWNER = 60
MINOR_61_NODE_RETIRED = 61
MINOR_62_AGENT_TOKEN = 62
+MINOR_63_INDICATORS = 63
# When adding another version, update:
# - MINOR_MAX_VERSION
@@ -171,7 +173,7 @@ MINOR_62_AGENT_TOKEN = 62
# explanation of what changed in the new version
# - common/release_mappings.py, RELEASE_MAPPING['master']['api']
-MINOR_MAX_VERSION = MINOR_62_AGENT_TOKEN
+MINOR_MAX_VERSION = MINOR_63_INDICATORS
# String representations of the minor and maximum versions
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)