diff --git a/ironicclient/common/http.py b/ironicclient/common/http.py index b44959996..0608e3a4a 100644 --- a/ironicclient/common/http.py +++ b/ironicclient/common/http.py @@ -37,7 +37,7 @@ from ironicclient import exc # http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa # for full details. DEFAULT_VER = '1.9' -LAST_KNOWN_API_VERSION = 77 +LAST_KNOWN_API_VERSION = 78 LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION) LOG = logging.getLogger(__name__) diff --git a/ironicclient/osc/v1/baremetal_node.py b/ironicclient/osc/v1/baremetal_node.py index 1f1b947e6..3fe329a7e 100755 --- a/ironicclient/osc/v1/baremetal_node.py +++ b/ironicclient/osc/v1/baremetal_node.py @@ -2131,3 +2131,77 @@ class BIOSSettingShowBaremetalNode(command.ShowOne): parsed_args.node, parsed_args.setting_name) setting.pop("links", None) return self.dict2columns(setting) + + +class NodeHistoryList(command.Lister): + """Get history events for a baremetal node.""" + + log = logging.getLogger(__name__ + ".NodeHistoryList") + + def get_parser(self, prog_name): + parser = super(NodeHistoryList, self).get_parser(prog_name) + + parser.add_argument( + 'node', + metavar='', + help=_("Name or UUID of the node.") + ) + parser.add_argument( + '--long', + default=False, + help=_("Show detailed information about the BIOS settings."), + action='store_true') + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + baremetal_client = self.app.client_manager.baremetal + if parsed_args.long: + labels = res_fields.NODE_HISTORY_DETAILED_RESOURCE.labels + fields = res_fields.NODE_HISTORY_DETAILED_RESOURCE.fields + else: + labels = res_fields.NODE_HISTORY_RESOURCE.labels + fields = res_fields.NODE_HISTORY_RESOURCE.fields + + data = baremetal_client.node.get_history_list( + parsed_args.node, + parsed_args.long) + + return (labels, + (oscutils.get_dict_properties(s, fields) for s in data)) + + +class NodeHistoryEventGet(command.ShowOne): + """Get history event for a baremetal node.""" + + log = logging.getLogger(__name__ + ".NodeHistoryEventGet") + + def get_parser(self, prog_name): + parser = super(NodeHistoryEventGet, self).get_parser(prog_name) + + parser.add_argument( + 'node', + metavar='', + help=_("Name or UUID of the node.") + ) + + parser.add_argument( + 'event', + metavar='', + help=_("UUID of the event.") + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + baremetal_client = self.app.client_manager.baremetal + + data = baremetal_client.node.get_history_event( + parsed_args.node, + parsed_args.event) + data.pop('links') + + return self.dict2columns(data) diff --git a/ironicclient/tests/unit/osc/v1/fakes.py b/ironicclient/tests/unit/osc/v1/fakes.py index 111fd7fc3..5745998b3 100644 --- a/ironicclient/tests/unit/osc/v1/fakes.py +++ b/ironicclient/tests/unit/osc/v1/fakes.py @@ -229,6 +229,18 @@ DEPLOY_TEMPLATE = { 'steps': baremetal_deploy_template_steps, 'extra': baremetal_deploy_template_extra, } +NODE_HISTORY = [ + { + 'uuid': 'abcdef1', + 'created_at': 'time', + 'severity': 'info', + 'event': 'meow', + 'event_type': 'purring', + 'conductor': 'lap-conductor', + 'user': '0191', + 'links': {'href': 'url', 'rel': 'self'}, + } +] class TestBaremetal(utils.TestCommand): diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py index 5a1b2fe1c..6426eef1e 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py @@ -4188,3 +4188,59 @@ class TestBIOSSettingShow(TestBaremetal): 'node_uuid', 'bios_name_1') expected_data = ('bios_name_1', 'bios_value_1') self.assertEqual(expected_data, tuple(data)) + + +class TestNodeHistoryEventList(TestBaremetal): + def setUp(self): + super(TestNodeHistoryEventList, self).setUp() + + self.baremetal_mock.node.get_history_list.return_value = ( + baremetal_fakes.NODE_HISTORY) + + # Get the command object to test + self.cmd = baremetal_node.NodeHistoryList(self.app, None) + + def test_baremetal_node_history_list(self): + arglist = ['node_uuid', '--long'] + verifylist = [('node', 'node_uuid'), ('long', True)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.baremetal_mock.node.get_history_list.assert_called_once_with( + 'node_uuid', True) + expected_columns = ('UUID', 'Created At', 'Severity', + 'Event Origin Type', 'Description of the event', + 'Conductor', 'User') + expected_data = (('abcdef1', 'time', 'info', 'purring', 'meow', + 'lap-conductor', '0191'),) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, tuple(data)) + + +class TestNodeHistoryEventGet(TestBaremetal): + def setUp(self): + super(TestNodeHistoryEventGet, self).setUp() + + self.baremetal_mock.node.get_history_event.return_value = ( + baremetal_fakes.NODE_HISTORY[0]) + + # Get the command object to test + self.cmd = baremetal_node.NodeHistoryEventGet(self.app, None) + + def test_baremetal_node_history_list(self): + arglist = ['node_uuid', 'event_uuid'] + verifylist = [('node', 'node_uuid'), ('event', 'event_uuid')] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.baremetal_mock.node.get_history_event.assert_called_once_with( + 'node_uuid', 'event_uuid') + expected_columns = ('conductor', 'created_at', 'event', 'event_type', + 'severity', 'user', 'uuid') + expected_data = ('lap-conductor', 'time', 'meow', 'purring', 'info', + '0191', 'abcdef1') + + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, tuple(data)) diff --git a/ironicclient/v1/node.py b/ironicclient/v1/node.py index d438d0e4e..84def2526 100644 --- a/ironicclient/v1/node.py +++ b/ironicclient/v1/node.py @@ -1008,3 +1008,56 @@ class NodeManager(base.CreateManager): '%(state)s, the current state is %(actual)s', {'node': node_ident, 'state': expected_state, 'actual': node.provision_state}) + + def get_history_list(self, + node_ident, + detail=False, + os_ironic_api_version=None, + global_request_id=None): + """Get node history event list. + + Provides the ability to query a node event history list from + the API and return the API response to the caller. + + Requires API version 1.78. + + :param node_ident: The name or UUID of the node. + :param detail: If detailed data should be returned in the + event list entry. Default False. + :param os_ironic_api_version: String version (e.g. "1.35") to use for + the request. If not specified, the client's default is used. + :param global_request_id: String containing global request ID header + value (in form "req-") to use for the request. + """ + path = "%s/history" % node_ident + + if detail: + path = path + '/detail' + + return self._list_primitives( + self._path(path), 'history', + os_ironic_api_version=os_ironic_api_version, + global_request_id=global_request_id) + + def get_history_event(self, + node_ident, + event, + os_ironic_api_version=None, + global_request_id=None): + """Get a single event record for a node. + + Provides the ability to request, and return + a node's single vent hisotyr entry. + + :param node_ident: The name or UUID of the node. + :param event: The UUID of the event entry as listed + in the node event history list. + :param os_ironic_api_version: String version (e.g. "1.35") to use for + the request. If not specified, the client's default is used. + :param global_request_id: String containing global request ID header + value (in form "req-") to use for the request. + """ + path = "%s/history/%s" % (node_ident, event) + return self._get_as_dict( + path, os_ironic_api_version=os_ironic_api_version, + global_request_id=global_request_id) diff --git a/ironicclient/v1/resource_fields.py b/ironicclient/v1/resource_fields.py index 76cfacaa2..1c9aebe58 100644 --- a/ironicclient/v1/resource_fields.py +++ b/ironicclient/v1/resource_fields.py @@ -79,6 +79,8 @@ class Resource(object): 'enabled_storage_interfaces': 'Enabled Storage Interfaces', 'enabled_vendor_interfaces': 'Enabled Vendor Interfaces', 'extra': 'Extra', + 'event': 'Description of the event', + 'event_type': 'Event Origin Type', 'hostname': 'Hostname', 'hosts': 'Active host(s)', 'http_methods': 'Supported HTTP methods', @@ -140,8 +142,10 @@ class Resource(object): 'raid_interface': 'RAID Interface', 'rescue_interface': 'Rescue Interface', 'storage_interface': 'Storage Interface', + 'severity': 'Severity', 'unique': 'Unique', 'upper_bound': 'Upper Bound', + 'user': 'User', 'vendor_interface': 'Vendor Interface', 'standalone_ports_supported': 'Standalone Ports Supported', 'physical_network': 'Physical Network', @@ -570,3 +574,21 @@ DEPLOY_TEMPLATE_RESOURCE = Resource( 'name', ], ) + + +NODE_HISTORY_RESOURCE = Resource( + ['uuid', + 'created_at', + 'severity', + 'event'] +) + +NODE_HISTORY_DETAILED_RESOURCE = Resource( + ['uuid', + 'created_at', + 'severity', + 'event_type', + 'event', + 'conductor', + 'user'] +) diff --git a/releasenotes/notes/add-node-history-b9b9beeb0200f185.yaml b/releasenotes/notes/add-node-history-b9b9beeb0200f185.yaml new file mode 100644 index 000000000..dcdc20297 --- /dev/null +++ b/releasenotes/notes/add-node-history-b9b9beeb0200f185.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Adds support for API version ``1.78`` and "node history" functionality + allowing users to query a list of events, and retrieve a single history + event from a baremetal node. + - Adds ``get_history_list`` and ``get_history_event`` methods to the python + client library. These methods allow operators to query recorded node event + information from an ironic API, where supported and enabled. diff --git a/setup.cfg b/setup.cfg index d8de9bbb8..b06b87d54 100644 --- a/setup.cfg +++ b/setup.cfg @@ -71,6 +71,8 @@ openstack.baremetal.v1 = baremetal_node_create = ironicclient.osc.v1.baremetal_node:CreateBaremetalNode baremetal_node_delete = ironicclient.osc.v1.baremetal_node:DeleteBaremetalNode baremetal_node_deploy = ironicclient.osc.v1.baremetal_node:DeployBaremetalNode + baremetal_node_history_list = ironicclient.osc.v1.baremetal_node:NodeHistoryList + baremetal_node_history_get = ironicclient.osc.v1.baremetal_node:NodeHistoryEventGet baremetal_node_inspect = ironicclient.osc.v1.baremetal_node:InspectBaremetalNode baremetal_node_list = ironicclient.osc.v1.baremetal_node:ListBaremetalNode baremetal_node_maintenance_set = ironicclient.osc.v1.baremetal_node:MaintenanceSetBaremetalNode