Add support for parent node
- Updates the API client version to 1.83 - Adds support to get/set/update a parent_node value on a node. - Adds support to ask the API for a list of nodes which related. - Adds support to filter query list by parent_node as well. Change-Id: Iea24e96f0360c6e5ac61cb57ab0c6f5d47c57f2b
This commit is contained in:
parent
0e9a476610
commit
b57429ffdb
@ -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 = 82
|
||||
LAST_KNOWN_API_VERSION = 83
|
||||
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -527,18 +527,21 @@ class CreateBaremetalNode(command.ShowOne):
|
||||
'--shard',
|
||||
metavar='<shard>',
|
||||
help=_("Shard for the node."))
|
||||
parser.add_argument(
|
||||
'--parent-node',
|
||||
metavar='<parent_node>',
|
||||
help=_('Parent node for the node being created.'))
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
|
||||
baremetal_client = self.app.client_manager.baremetal
|
||||
|
||||
field_list = ['automated_clean', 'chassis_uuid', 'driver',
|
||||
'driver_info', 'properties', 'extra', 'uuid', 'name',
|
||||
'conductor_group', 'owner', 'description', 'lessee',
|
||||
'shard', 'resource_class'
|
||||
'shard', 'resource_class', 'parent_node',
|
||||
] + ['%s_interface' % iface
|
||||
for iface in SUPPORTED_INTERFACES]
|
||||
fields = dict((k, v) for (k, v) in vars(parsed_args).items()
|
||||
@ -788,6 +791,18 @@ class ListBaremetalNode(command.Lister):
|
||||
help=_("One or more node fields. Only these fields will be "
|
||||
"fetched from the server. Can not be used when '--long' "
|
||||
"is specified."))
|
||||
children_group = parser.add_mutually_exclusive_group(required=False)
|
||||
children_group.add_argument(
|
||||
'--include-children',
|
||||
action='store_true',
|
||||
help=_("Include children in the node list."),
|
||||
)
|
||||
children_group.add_argument(
|
||||
'--parent-node',
|
||||
dest='parent_node',
|
||||
metavar="<parent_node>",
|
||||
help=_('List only nodes associated with a parent node.'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
@ -815,9 +830,11 @@ class ListBaremetalNode(command.Lister):
|
||||
params[field] = getattr(parsed_args, field)
|
||||
for field in ['provision_state', 'driver', 'resource_class',
|
||||
'chassis', 'conductor', 'owner', 'lessee',
|
||||
'description_contains', 'shards']:
|
||||
'description_contains', 'shards', 'parent_node']:
|
||||
if getattr(parsed_args, field):
|
||||
params[field] = getattr(parsed_args, field)
|
||||
if parsed_args.include_children:
|
||||
params['include_children'] = True
|
||||
if parsed_args.long:
|
||||
params['detail'] = parsed_args.long
|
||||
columns = res_fields.NODE_DETAILED_RESOURCE.fields
|
||||
@ -1429,6 +1446,11 @@ class SetBaremetalNode(command.Command):
|
||||
metavar='<shard>',
|
||||
help=_('Set the shard for the node'),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--parent-node",
|
||||
metavar='<parent_node>',
|
||||
help=_('Set the parent node for the node'),
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
@ -1459,7 +1481,7 @@ class SetBaremetalNode(command.Command):
|
||||
'chassis_uuid', 'driver', 'resource_class',
|
||||
'conductor_group', 'protected', 'protected_reason',
|
||||
'retired', 'retired_reason', 'owner', 'lessee',
|
||||
'description', 'shard']:
|
||||
'description', 'shard', 'parent_node']:
|
||||
value = getattr(parsed_args, field)
|
||||
if value:
|
||||
properties.extend(utils.args_array_to_patch(
|
||||
@ -1780,6 +1802,11 @@ class UnsetBaremetalNode(command.Command):
|
||||
action="store_true",
|
||||
help=_('Unset the shard field of the node'),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--parent-node",
|
||||
action="store_true",
|
||||
help=_('Unset the parent node field of the node'),
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
@ -1805,7 +1832,7 @@ class UnsetBaremetalNode(command.Command):
|
||||
'storage_interface', 'vendor_interface',
|
||||
'protected', 'protected_reason', 'retired',
|
||||
'retired_reason', 'owner', 'lessee', 'description',
|
||||
'shard', ]:
|
||||
'shard', 'parent_node']:
|
||||
if getattr(parsed_args, field):
|
||||
properties.extend(utils.args_array_to_patch('remove', [field]))
|
||||
|
||||
@ -2300,3 +2327,31 @@ class NodeInventorySave(command.Command):
|
||||
json.dump(inventory, fp)
|
||||
else:
|
||||
json.dump(inventory, sys.stdout)
|
||||
|
||||
|
||||
class NodeChildrenList(command.ShowOne):
|
||||
"""Get a list of nodes assocated as children."""
|
||||
|
||||
log = logging.getLogger(__name__ + ".NodeChildrenList")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(NodeChildrenList, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'node',
|
||||
metavar='<node>',
|
||||
help=_("Name or UUID of the node.")
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
|
||||
baremetal_client = self.app.client_manager.baremetal
|
||||
|
||||
labels = res_fields.CHILDREN_RESOURCE.labels
|
||||
|
||||
data = baremetal_client.node.list_children_of_node(
|
||||
parsed_args.node)
|
||||
return (labels, [[node] for node in data])
|
||||
|
@ -149,6 +149,8 @@ PORTGROUP = {'uuid': baremetal_portgroup_uuid,
|
||||
|
||||
VIFS = {'vifs': [{'id': 'aaa-aa'}]}
|
||||
TRAITS = ['CUSTOM_FOO', 'CUSTOM_BAR']
|
||||
CHILDREN = ['53da080f-6de7-4a3e-bcb6-b7889b380ad0',
|
||||
'48467e9b-3cd1-45b5-a57e-169e01370169']
|
||||
BIOS_SETTINGS = [{'name': 'bios_name_1', 'value': 'bios_value_1', 'links': []},
|
||||
{'name': 'bios_name_2', 'value': 'bios_value_2', 'links': []}]
|
||||
|
||||
|
@ -539,7 +539,8 @@ class TestBaremetalCreate(TestBaremetal):
|
||||
def check_with_options(self, addl_arglist, addl_verifylist, addl_kwargs):
|
||||
arglist = copy.copy(self.arglist) + addl_arglist
|
||||
verifylist = copy.copy(self.verifylist) + addl_verifylist
|
||||
|
||||
print(verifylist)
|
||||
print(arglist)
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# DisplayCommandBase.take_action() returns two tuples
|
||||
@ -736,6 +737,11 @@ class TestBaremetalCreate(TestBaremetal):
|
||||
[('shard', 'myshard')],
|
||||
{'shard': 'myshard'})
|
||||
|
||||
def test_baremetal_create_with_parent_node(self):
|
||||
self.check_with_options(['--parent-node', 'nodex'],
|
||||
[('parent_node', 'nodex')],
|
||||
{'parent_node': 'nodex'})
|
||||
|
||||
|
||||
class TestBaremetalDelete(TestBaremetal):
|
||||
def setUp(self):
|
||||
@ -916,6 +922,7 @@ class TestBaremetalList(TestBaremetal):
|
||||
'Network Configuration',
|
||||
'Network Interface',
|
||||
'Owner',
|
||||
'Parent Node',
|
||||
'Power Interface',
|
||||
'Power State',
|
||||
'Properties',
|
||||
@ -1498,6 +1505,52 @@ class TestBaremetalList(TestBaremetal):
|
||||
self.assertRaises(oscutils.ParserException, self.check_parser,
|
||||
self.cmd, arglist, verifylist)
|
||||
|
||||
def test_baremetal_list_by_parent_node(self):
|
||||
parent_node = 'node1'
|
||||
arglist = [
|
||||
'--parent-node', parent_node,
|
||||
]
|
||||
verifylist = [
|
||||
('parent_node', parent_node),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
print(parsed_args)
|
||||
|
||||
# DisplayCommandBase.take_action() returns two tuples
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
kwargs = {
|
||||
'marker': None,
|
||||
'limit': None,
|
||||
'parent_node': parent_node,
|
||||
}
|
||||
|
||||
self.baremetal_mock.node.list.assert_called_with(
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def test_baremetal_list_include_children(self):
|
||||
arglist = [
|
||||
'--include-children',
|
||||
]
|
||||
verifylist = [
|
||||
('include_children', True),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# DisplayCommandBase.take_action() returns two tuples
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
kwargs = {
|
||||
'marker': None,
|
||||
'limit': None,
|
||||
'include_children': True,
|
||||
}
|
||||
|
||||
self.baremetal_mock.node.list.assert_called_with(
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
class TestBaremetalMaintenanceSet(TestBaremetal):
|
||||
def setUp(self):
|
||||
@ -4448,3 +4501,27 @@ class TestNodeInventorySave(TestBaremetal):
|
||||
'boot': {'current_boot_mode': 'uefi'}}
|
||||
inventory = json.loads(buf.getvalue())
|
||||
self.assertEqual(expected_data, inventory['inventory'])
|
||||
|
||||
|
||||
class TestNodeChildrenList(TestBaremetal):
|
||||
def setUp(self):
|
||||
super(TestNodeChildrenList, self).setUp()
|
||||
|
||||
self.baremetal_mock.node.list_children_of_node.return_value = (
|
||||
baremetal_fakes.CHILDREN)
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = baremetal_node.NodeChildrenList(self.app, None)
|
||||
|
||||
def test_child_node_list(self):
|
||||
arglist = ['node_uuid']
|
||||
verifylist = [('node', 'node_uuid')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.baremetal_mock.node.list_children_of_node \
|
||||
.assert_called_once_with('node_uuid')
|
||||
self.assertEqual(('Child Nodes',), columns)
|
||||
self.assertEqual([[node] for node in baremetal_fakes.CHILDREN], data)
|
||||
|
@ -39,7 +39,8 @@ NODE1 = {'uuid': '66666666-7777-8888-9999-000000000000',
|
||||
'network_data': {},
|
||||
'resource_class': 'foo',
|
||||
'extra': {},
|
||||
'conductor_group': 'in-the-closet-to-the-left'}
|
||||
'conductor_group': 'in-the-closet-to-the-left',
|
||||
'parent_node': None}
|
||||
NODE2 = {'uuid': '66666666-7777-8888-9999-111111111111',
|
||||
'instance_uuid': '66666666-7777-8888-9999-222222222222',
|
||||
'chassis_uuid': 'aaaaaaaa-1111-bbbb-2222-cccccccccccc',
|
||||
@ -53,7 +54,8 @@ NODE2 = {'uuid': '66666666-7777-8888-9999-111111111111',
|
||||
'owner': '33333333-2222-1111-0000-111111111111',
|
||||
'retired': True,
|
||||
'lessee': '77777777-8888-5555-2222-999999999999',
|
||||
'shard': 'myshard'}
|
||||
'shard': 'myshard',
|
||||
'parent_node': NODE1['uuid']}
|
||||
PORT = {'uuid': '11111111-2222-3333-4444-555555555555',
|
||||
'node_uuid': '66666666-7777-8888-9999-000000000000',
|
||||
'address': 'AA:AA:AA:AA:AA:AA',
|
||||
@ -282,6 +284,27 @@ fake_responses = {
|
||||
{"nodes": [NODE2]}
|
||||
)
|
||||
},
|
||||
'/v1/nodes/?include_children=True':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"nodes": [NODE1, NODE2]}
|
||||
)
|
||||
},
|
||||
'/v1/nodes/?parent_node=%s' % NODE1['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"nodes": [NODE2]}
|
||||
)
|
||||
},
|
||||
'/v1/nodes/%s/children' % NODE1['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"children": [NODE2['uuid']]}
|
||||
)
|
||||
},
|
||||
'/v1/nodes/detail?instance_uuid=%s' % NODE2['instance_uuid']:
|
||||
{
|
||||
'GET': (
|
||||
@ -1016,6 +1039,35 @@ class NodeManagerTest(testtools.TestCase):
|
||||
self.assertThat(nodes, HasLength(1))
|
||||
self.assertEqual(NODE2['uuid'], getattr(nodes[0], 'uuid'))
|
||||
|
||||
def test_node_list_include_chidlren(self):
|
||||
nodes = self.mgr.list(include_children=True)
|
||||
expect = [
|
||||
('GET', '/v1/nodes/?include_children=True', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(nodes, HasLength(2))
|
||||
self.assertEqual(NODE1['uuid'], getattr(nodes[0], 'uuid'))
|
||||
self.assertEqual(NODE2['uuid'], getattr(nodes[1], 'uuid'))
|
||||
|
||||
def test_node_list_nodes_by_parent_node(self):
|
||||
nodes = self.mgr.list(parent_node=NODE1['uuid'])
|
||||
expect = [
|
||||
('GET', '/v1/nodes/?parent_node=%s' % NODE1['uuid'],
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(nodes, HasLength(1))
|
||||
self.assertEqual(NODE2['uuid'], getattr(nodes[0], 'uuid'))
|
||||
|
||||
def test_node_list_children_of_node(self):
|
||||
children = self.mgr.list_children_of_node(NODE1['uuid'])
|
||||
expect = [
|
||||
('GET', '/v1/nodes/%s/children' % NODE1['uuid'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(children))
|
||||
self.assertEqual(NODE2['uuid'], children[0])
|
||||
|
||||
def test_node_list_detail(self):
|
||||
nodes = self.mgr.list(detail=True)
|
||||
expect = [
|
||||
|
@ -53,7 +53,8 @@ class NodeManager(base.CreateManager):
|
||||
'raid_interface', 'rescue_interface',
|
||||
'storage_interface', 'vendor_interface',
|
||||
'resource_class', 'conductor_group',
|
||||
'automated_clean', 'network_data']
|
||||
'automated_clean', 'network_data',
|
||||
'parent_node']
|
||||
_resource_name = 'nodes'
|
||||
|
||||
def list(self, associated=None, maintenance=None, marker=None,
|
||||
@ -62,7 +63,8 @@ class NodeManager(base.CreateManager):
|
||||
resource_class=None, chassis=None, fault=None,
|
||||
os_ironic_api_version=None, conductor_group=None,
|
||||
conductor=None, owner=None, retired=None, lessee=None,
|
||||
shards=None, sharded=None, global_request_id=None):
|
||||
shards=None, sharded=None, parent_node=None,
|
||||
include_children=None, global_request_id=None):
|
||||
"""Retrieve a list of nodes.
|
||||
|
||||
:param associated: Optional. Either a Boolean or a string
|
||||
@ -134,7 +136,13 @@ class NodeManager(base.CreateManager):
|
||||
:param sharded: Optional. Boolean value, when true get only nodes
|
||||
with a non-null node.shard value, when false get only
|
||||
nodes with a null node.shard value. None is a noop.
|
||||
|
||||
with a non-null node.shard value.
|
||||
:param parent_node: Optional. String value used to retreive child
|
||||
nodes with the supplied parent node.
|
||||
:param include_children: Optional. Boolean Value, only True is valid.
|
||||
Tells the ironic API to enumerate all child
|
||||
nodes which are normally hidden from the
|
||||
node list.
|
||||
:returns: A list of nodes.
|
||||
|
||||
"""
|
||||
@ -175,6 +183,11 @@ class NodeManager(base.CreateManager):
|
||||
filters.append('sharded=%s' % sharded)
|
||||
if shards is not None:
|
||||
filters.append('shard=%s' % ','.join(shards))
|
||||
if parent_node is not None:
|
||||
filters.append('parent_node=%s' % parent_node)
|
||||
if include_children:
|
||||
# NOTE(TheJulia): Only valid if True.
|
||||
filters.append('include_children=True')
|
||||
|
||||
path = ''
|
||||
if detail:
|
||||
@ -382,6 +395,29 @@ class NodeManager(base.CreateManager):
|
||||
self._path(path), response_key="targets", limit=limit,
|
||||
obj_class=volume_target.VolumeTarget, **header_values)
|
||||
|
||||
def list_children_of_node(
|
||||
self, node_id,
|
||||
os_ironic_api_version=None,
|
||||
global_request_id=None):
|
||||
"""Get a list of child nodes for the supplied node_id.
|
||||
|
||||
:param node_id: The name or UUID of a node.
|
||||
|
||||
: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-<UUID>") to use for the request.
|
||||
|
||||
:returns: A list of UUIDs representing child nodes for the supplied
|
||||
node_id..
|
||||
"""
|
||||
path = "%s/children" % node_id
|
||||
header_values = {"os_ironic_api_version": os_ironic_api_version,
|
||||
"global_request_id": global_request_id}
|
||||
return self._list_primitives(self._path(path), "children",
|
||||
**header_values)
|
||||
|
||||
def get(self, node_id, fields=None, os_ironic_api_version=None,
|
||||
global_request_id=None):
|
||||
return self._get(resource_id=node_id, fields=fields,
|
||||
|
@ -152,6 +152,8 @@ class Resource(object):
|
||||
'id': 'ID',
|
||||
'connector_id': 'Connector ID',
|
||||
'is_smartnic': 'Is Smart NIC port',
|
||||
'parent_node': 'Parent Node',
|
||||
'children': 'Child Nodes',
|
||||
}
|
||||
|
||||
def __init__(self, field_ids, sort_excluded=None, override_labels=None):
|
||||
@ -268,6 +270,7 @@ NODE_DETAILED_RESOURCE = Resource(
|
||||
'network_data',
|
||||
'network_interface',
|
||||
'owner',
|
||||
'parent_node',
|
||||
'power_interface',
|
||||
'power_state',
|
||||
'properties',
|
||||
@ -392,6 +395,10 @@ TRAIT_RESOURCE = Resource(
|
||||
['traits'],
|
||||
)
|
||||
|
||||
CHILDREN_RESOURCE = Resource(
|
||||
['children'],
|
||||
)
|
||||
|
||||
BIOS_RESOURCE = Resource(
|
||||
['name', 'value'],
|
||||
override_labels={'name': 'BIOS setting name',
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Increments the maximum API version to ``1.83`` which allows the Node field
|
||||
``parent_node`` to become visible.
|
||||
- |
|
||||
Adds client support to allow users to get, set, and update the
|
||||
``parent_node`` field on a bare metal node.
|
@ -64,6 +64,7 @@ openstack.baremetal.v1 =
|
||||
baremetal_node_boot_device_show = ironicclient.osc.v1.baremetal_node:BootdeviceShowBaremetalNode
|
||||
baremetal_node_boot_mode_set = ironicclient.osc.v1.baremetal_node:BootmodeSetBaremetalNode
|
||||
baremetal_node_clean = ironicclient.osc.v1.baremetal_node:CleanBaremetalNode
|
||||
baremetal_node_children_list = ironicclient.osc.v1.baremetal_node:NodeChildrenList
|
||||
baremetal_node_console_disable = ironicclient.osc.v1.baremetal_node:ConsoleDisableBaremetalNode
|
||||
baremetal_node_console_enable = ironicclient.osc.v1.baremetal_node:ConsoleEnableBaremetalNode
|
||||
baremetal_node_console_show = ironicclient.osc.v1.baremetal_node:ConsoleShowBaremetalNode
|
||||
|
Loading…
Reference in New Issue
Block a user