diff options
author | Ilya Etingof <etingof@gmail.com> | 2019-04-10 08:14:22 +0200 |
---|---|---|
committer | Julia Kreger <juliaashleykreger@gmail.com> | 2020-03-21 18:45:01 +0000 |
commit | 263fd021b23889b139a7971d93a18268c6b0be71 (patch) | |
tree | 2cea42eb0dab386c97e27671a679a8ae82a5b94c /ironic/api | |
parent | 9f07ad1b6eaa5a96c9fec8d6cd57ffe474a33e2e (diff) | |
download | ironic-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.py | 181 | ||||
-rw-r--r-- | ironic/api/controllers/v1/versions.py | 8 |
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) |