From 0b6b282d40507a188df0e824a406a71652290952 Mon Sep 17 00:00:00 2001 From: Mahnoor Asghar Date: Mon, 22 May 2023 08:31:31 -0400 Subject: [PATCH] Add support for node inventory Adds REST API version 1.81, which supports querying a node's hardware inventory using the python client library, and also using the baremetal CLI using the `baremetal node inventory save [--file ] ` command. Change-Id: Idf6ba4cbf7035e0617edc67f55c93b434d9a76aa --- ironicclient/common/http.py | 2 +- ironicclient/osc/v1/baremetal_node.py | 31 +++++++++++++++ ironicclient/tests/unit/osc/v1/fakes.py | 17 ++++++++ .../tests/unit/osc/v1/test_baremetal_node.py | 39 +++++++++++++++++++ ironicclient/tests/unit/v1/test_node.py | 22 +++++++++++ ironicclient/v1/node.py | 19 +++++++++ .../add-node-inventory-74e856c019cfa7e2.yaml | 7 ++++ setup.cfg | 1 + 8 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-node-inventory-74e856c019cfa7e2.yaml diff --git a/ironicclient/common/http.py b/ironicclient/common/http.py index 0608e3a4a..513d30332 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 = 78 +LAST_KNOWN_API_VERSION = 81 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 bc4821d9f..50ec9afcf 100755 --- a/ironicclient/osc/v1/baremetal_node.py +++ b/ironicclient/osc/v1/baremetal_node.py @@ -18,6 +18,7 @@ import argparse import itertools import json import logging +import sys from osc_lib.command import command from osc_lib import utils as oscutils @@ -2233,3 +2234,33 @@ class NodeHistoryEventGet(command.ShowOne): data.pop('links') return self.dict2columns(data) + + +class NodeInventorySave(command.Command): + """Get hardware inventory of a node (in JSON format) or save it to file.""" + + log = logging.getLogger(__name__ + ".NodeInventorySave") + + def get_parser(self, prog_name): + parser = super(NodeInventorySave, self).get_parser(prog_name) + parser.add_argument( + "node", + metavar="", + help=_("Name or UUID of the node")) + parser.add_argument("--file", + metavar="", + help="Save inspection data to file with name " + "(default: stdout).") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + baremetal_client = self.app.client_manager.baremetal + inventory = baremetal_client.node.get_inventory(parsed_args.node) + + if parsed_args.file: + with open(parsed_args.file, 'w') as fp: + json.dump(inventory, fp) + else: + json.dump(inventory, sys.stdout) diff --git a/ironicclient/tests/unit/osc/v1/fakes.py b/ironicclient/tests/unit/osc/v1/fakes.py index 5745998b3..2e15420a3 100644 --- a/ironicclient/tests/unit/osc/v1/fakes.py +++ b/ironicclient/tests/unit/osc/v1/fakes.py @@ -241,6 +241,23 @@ NODE_HISTORY = [ 'links': {'href': 'url', 'rel': 'self'}, } ] +NODE_INVENTORY = [ + { + 'inventory': + { + 'memory': {'physical_mb': 3072}, + 'cpu': {'count': 1, + 'model_name': 'qemu64', + 'architecture': 'x86_64'}, + 'disks': [{'name': 'testvm2.qcow2', + 'size': 11811160064}], + 'interfaces': [{'mac_address': '52:54:00:11:2d:26'}], + 'system_vendor': {'product_name': 'testvm2', + 'manufacturer': 'Sushy Emulator'}, + 'boot': {'current_boot_mode': 'uefi'} + } + } +] 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 8771f3a42..01c7db1e2 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py @@ -15,7 +15,9 @@ # import copy +import io import json +import sys from unittest import mock from osc_lib.tests import utils as oscutils @@ -4302,3 +4304,40 @@ class TestNodeHistoryEventGet(TestBaremetal): self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, tuple(data)) + + +class TestNodeInventorySave(TestBaremetal): + def setUp(self): + super(TestNodeInventorySave, self).setUp() + + self.baremetal_mock.node.get_inventory.return_value = ( + baremetal_fakes.NODE_INVENTORY[0]) + + # Get the command object to test + self.cmd = baremetal_node.NodeInventorySave(self.app, None) + + def test_baremetal_node_inventory_save(self): + arglist = ['node_uuid'] + verifylist = [('node', 'node_uuid')] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + buf = io.StringIO() + with mock.patch.object(sys, 'stdout', buf): + self.cmd.take_action(parsed_args) + + self.baremetal_mock.node.get_inventory.assert_called_once_with( + 'node_uuid') + + expected_data = {'memory': {'physical_mb': 3072}, + 'cpu': {'count': 1, + 'model_name': 'qemu64', + 'architecture': 'x86_64'}, + 'disks': [{'name': 'testvm2.qcow2', + 'size': 11811160064}], + 'interfaces': [{'mac_address': '52:54:00:11:2d:26'}], + 'system_vendor': {'product_name': 'testvm2', + 'manufacturer': 'Sushy Emulator'}, + 'boot': {'current_boot_mode': 'uefi'}} + inventory = json.loads(buf.getvalue()) + self.assertEqual(expected_data, inventory['inventory']) diff --git a/ironicclient/tests/unit/v1/test_node.py b/ironicclient/tests/unit/v1/test_node.py index f395b51c6..416bb52c5 100644 --- a/ironicclient/tests/unit/v1/test_node.py +++ b/ironicclient/tests/unit/v1/test_node.py @@ -108,6 +108,13 @@ NODE_VENDOR_PASSTHRU_METHOD = {"heartbeat": {"attach": "false", VIFS = {'vifs': [{'id': 'aaa-aaa'}]} TRAITS = {'traits': ['CUSTOM_FOO', 'CUSTOM_BAR']} +INVENTORY = {'inventory': [{'memory': {'physical_mb': '3072'}, + 'cpu': {'count': 1, 'architecture': 'x86_64', + 'model_name': 'qemu64'}, + 'disks': [{'name': 'testvm2.qcow2', + 'size': 11811160064}], + 'interfaces': + [{'mac_address': '52:54:00:c7:02:45'}]}]} CREATE_NODE = copy.deepcopy(NODE1) del CREATE_NODE['uuid'] @@ -543,6 +550,13 @@ fake_responses = { {}, None, ), + }, + '/v1/nodes/%s/inventory' % NODE1['uuid']: + { + 'GET': ( + {}, + INVENTORY, + ), } } @@ -2148,3 +2162,11 @@ class NodeManagerTest(testtools.TestCase): ] self.assertEqual(expect, self.api.calls) self.assertIsNone(resp) + + def test_node_get_inventory(self): + inventory = self.mgr.get_inventory(NODE1['uuid']) + expect = [ + ('GET', '/v1/nodes/%s/inventory' % NODE1['uuid'], {}, None), + ] + self.assertEqual(expect, self.api.calls) + self.assertEqual(INVENTORY, inventory) diff --git a/ironicclient/v1/node.py b/ironicclient/v1/node.py index dc0d0cd34..7595f8b53 100644 --- a/ironicclient/v1/node.py +++ b/ironicclient/v1/node.py @@ -1099,3 +1099,22 @@ class NodeManager(base.CreateManager): return self._get_as_dict( path, os_ironic_api_version=os_ironic_api_version, global_request_id=global_request_id) + + def get_inventory(self, + node_ident, + os_ironic_api_version=None, + global_request_id=None): + """Get the hardware inventory of the node. + + Requires API version 1.81. + + :param node_ident: The name or UUID of the node. + :param os_ironic_api_version: String version (e.g. "1.81") 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/inventory" % node_ident + return self._get_as_dict( + path, os_ironic_api_version=os_ironic_api_version, + global_request_id=global_request_id) diff --git a/releasenotes/notes/add-node-inventory-74e856c019cfa7e2.yaml b/releasenotes/notes/add-node-inventory-74e856c019cfa7e2.yaml new file mode 100644 index 000000000..a7d861dc3 --- /dev/null +++ b/releasenotes/notes/add-node-inventory-74e856c019cfa7e2.yaml @@ -0,0 +1,7 @@ +features: + - | + Adds support for API version ``1.81``, allowing users to retrieve a node's + hardware inventory using the + ``baremetal node inventory [--file ] save `` command. + - Adds a ``get_inventory`` method to the python client library. This method + allows operators to query recorded node inventory from an ironic API. diff --git a/setup.cfg b/setup.cfg index efe9266dd..e86eec08b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -73,6 +73,7 @@ openstack.baremetal.v1 = 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_inventory_save = ironicclient.osc.v1.baremetal_node:NodeInventorySave baremetal_node_list = ironicclient.osc.v1.baremetal_node:ListBaremetalNode baremetal_node_maintenance_set = ironicclient.osc.v1.baremetal_node:MaintenanceSetBaremetalNode baremetal_node_maintenance_unset = ironicclient.osc.v1.baremetal_node:MaintenanceUnsetBaremetalNode