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 <filename>] <node>` command. Change-Id: Idf6ba4cbf7035e0617edc67f55c93b434d9a76aa
This commit is contained in:
parent
a01ba938c2
commit
0b6b282d40
@ -37,7 +37,7 @@ from ironicclient import exc
|
|||||||
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
|
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
|
||||||
# for full details.
|
# for full details.
|
||||||
DEFAULT_VER = '1.9'
|
DEFAULT_VER = '1.9'
|
||||||
LAST_KNOWN_API_VERSION = 78
|
LAST_KNOWN_API_VERSION = 81
|
||||||
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
|
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -18,6 +18,7 @@ import argparse
|
|||||||
import itertools
|
import itertools
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
from osc_lib.command import command
|
from osc_lib.command import command
|
||||||
from osc_lib import utils as oscutils
|
from osc_lib import utils as oscutils
|
||||||
@ -2233,3 +2234,33 @@ class NodeHistoryEventGet(command.ShowOne):
|
|||||||
data.pop('links')
|
data.pop('links')
|
||||||
|
|
||||||
return self.dict2columns(data)
|
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="<node>",
|
||||||
|
help=_("Name or UUID of the node"))
|
||||||
|
parser.add_argument("--file",
|
||||||
|
metavar="<filename>",
|
||||||
|
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)
|
||||||
|
@ -241,6 +241,23 @@ NODE_HISTORY = [
|
|||||||
'links': {'href': 'url', 'rel': 'self'},
|
'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):
|
class TestBaremetal(utils.TestCommand):
|
||||||
|
@ -15,7 +15,9 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from osc_lib.tests import utils as oscutils
|
from osc_lib.tests import utils as oscutils
|
||||||
@ -4302,3 +4304,40 @@ class TestNodeHistoryEventGet(TestBaremetal):
|
|||||||
|
|
||||||
self.assertEqual(expected_columns, columns)
|
self.assertEqual(expected_columns, columns)
|
||||||
self.assertEqual(expected_data, tuple(data))
|
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'])
|
||||||
|
@ -108,6 +108,13 @@ NODE_VENDOR_PASSTHRU_METHOD = {"heartbeat": {"attach": "false",
|
|||||||
|
|
||||||
VIFS = {'vifs': [{'id': 'aaa-aaa'}]}
|
VIFS = {'vifs': [{'id': 'aaa-aaa'}]}
|
||||||
TRAITS = {'traits': ['CUSTOM_FOO', 'CUSTOM_BAR']}
|
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)
|
CREATE_NODE = copy.deepcopy(NODE1)
|
||||||
del CREATE_NODE['uuid']
|
del CREATE_NODE['uuid']
|
||||||
@ -543,6 +550,13 @@ fake_responses = {
|
|||||||
{},
|
{},
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
|
},
|
||||||
|
'/v1/nodes/%s/inventory' % NODE1['uuid']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
INVENTORY,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2148,3 +2162,11 @@ class NodeManagerTest(testtools.TestCase):
|
|||||||
]
|
]
|
||||||
self.assertEqual(expect, self.api.calls)
|
self.assertEqual(expect, self.api.calls)
|
||||||
self.assertIsNone(resp)
|
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)
|
||||||
|
@ -1099,3 +1099,22 @@ class NodeManager(base.CreateManager):
|
|||||||
return self._get_as_dict(
|
return self._get_as_dict(
|
||||||
path, os_ironic_api_version=os_ironic_api_version,
|
path, os_ironic_api_version=os_ironic_api_version,
|
||||||
global_request_id=global_request_id)
|
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-<UUID>") 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)
|
||||||
|
@ -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 <filename>] save <node>`` command.
|
||||||
|
- Adds a ``get_inventory`` method to the python client library. This method
|
||||||
|
allows operators to query recorded node inventory from an ironic API.
|
@ -73,6 +73,7 @@ openstack.baremetal.v1 =
|
|||||||
baremetal_node_history_list = ironicclient.osc.v1.baremetal_node:NodeHistoryList
|
baremetal_node_history_list = ironicclient.osc.v1.baremetal_node:NodeHistoryList
|
||||||
baremetal_node_history_get = ironicclient.osc.v1.baremetal_node:NodeHistoryEventGet
|
baremetal_node_history_get = ironicclient.osc.v1.baremetal_node:NodeHistoryEventGet
|
||||||
baremetal_node_inspect = ironicclient.osc.v1.baremetal_node:InspectBaremetalNode
|
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_list = ironicclient.osc.v1.baremetal_node:ListBaremetalNode
|
||||||
baremetal_node_maintenance_set = ironicclient.osc.v1.baremetal_node:MaintenanceSetBaremetalNode
|
baremetal_node_maintenance_set = ironicclient.osc.v1.baremetal_node:MaintenanceSetBaremetalNode
|
||||||
baremetal_node_maintenance_unset = ironicclient.osc.v1.baremetal_node:MaintenanceUnsetBaremetalNode
|
baremetal_node_maintenance_unset = ironicclient.osc.v1.baremetal_node:MaintenanceUnsetBaremetalNode
|
||||||
|
Loading…
x
Reference in New Issue
Block a user