From e3a410f699efb3fbdc34f95299c68c75bbc2de0a Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Thu, 8 Nov 2018 15:52:19 +0100 Subject: [PATCH] Supports specifying profile explicitly in instackenv.json One day Nova will deprecate capabilities, and we'll have to use something else for profiles. As a preparational steps, start accepting profile as a separate instackenv.json field, thus decoupling it from the underlying implementation. Partial-Bug: #1793134 Change-Id: Ife97a0955a480bcc41b3453d58ef7fe6488b476c --- .../notes/profile-17e2650c8da9e8b5.yaml | 9 +++ tripleo_common/tests/utils/test_nodes.py | 73 +++++++++++++++++++ tripleo_common/utils/nodes.py | 24 ++++-- 3 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/profile-17e2650c8da9e8b5.yaml diff --git a/releasenotes/notes/profile-17e2650c8da9e8b5.yaml b/releasenotes/notes/profile-17e2650c8da9e8b5.yaml new file mode 100644 index 000000000..a3e5128e3 --- /dev/null +++ b/releasenotes/notes/profile-17e2650c8da9e8b5.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Node's profile can now be specified as a separate ``profile`` field in + the ``instackenv.json`` instead of inside capabilities. +deprecations: + - | + Specifying profile in capabilities when enrolling nodes is deprecated. + Please use the new ``profile`` field instead. diff --git a/tripleo_common/tests/utils/test_nodes.py b/tripleo_common/tests/utils/test_nodes.py index 450debb80..8d22f902f 100644 --- a/tripleo_common/tests/utils/test_nodes.py +++ b/tripleo_common/tests/utils/test_nodes.py @@ -467,6 +467,32 @@ class NodesTest(base.TestCase): ironic.node.create.assert_has_calls([pxe_node, mock.ANY]) ironic.port.create.assert_has_calls([port_call]) + def test_register_all_nodes_with_profile(self): + node_list = [self._get_node()] + node_list[0]['root_device'] = {"serial": "abcdef"} + node_list[0]['profile'] = "compute" + node_properties = {"cpus": "1", + "memory_mb": "2048", + "local_gb": "30", + "cpu_arch": "amd64", + "capabilities": "num_nics:6,profile:compute", + "root_device": {"serial": "abcdef"}} + ironic = mock.MagicMock() + nodes.register_all_nodes(node_list, client=ironic) + pxe_node_driver_info = {"ipmi_address": "foo.bar", + "ipmi_username": "test", + "ipmi_password": "random"} + pxe_node = mock.call(driver="ipmi", + name='node1', + driver_info=pxe_node_driver_info, + resource_class='baremetal', + properties=node_properties) + port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid, + address='aaa', physical_network='ctlplane', + local_link_connection=None) + ironic.node.create.assert_has_calls([pxe_node, mock.ANY]) + ironic.port.create.assert_has_calls([port_call]) + def test_register_all_nodes_with_interfaces(self): interfaces = {'boot_interface': 'pxe', 'console_interface': 'ipmitool-socat', @@ -596,6 +622,53 @@ class NodesTest(base.TestCase): nodes._update_or_register_ironic_node(node, node_map, client=ironic) ironic.node.update.assert_called_once_with(1, mock.ANY) + def test_register_update_profile(self): + interfaces = {'boot_interface': 'pxe', + 'console_interface': 'ipmitool-socat', + 'deploy_interface': 'direct', + 'inspect_interface': 'inspector', + 'management_interface': 'ipmitool', + 'network_interface': 'neutron', + 'power_interface': 'ipmitool', + 'raid_interface': 'agent', + 'rescue_interface': 'agent', + 'storage_interface': 'cinder', + 'vendor_interface': 'ipmitool'} + + node = self._get_node() + node.update(interfaces) + node['root_device'] = {'serial': 'abcdef'} + node['profile'] = 'compute' + ironic = mock.MagicMock() + node_map = {'mac': {'aaa': 1}} + + def side_effect(*args, **kwargs): + update_patch = [ + {'path': '/name', 'value': 'node1'}, + {'path': '/driver_info/ipmi_password', 'value': 'random'}, + {'path': '/driver_info/ipmi_address', 'value': 'foo.bar'}, + {'path': '/properties/memory_mb', 'value': '2048'}, + {'path': '/properties/local_gb', 'value': '30'}, + {'path': '/properties/cpu_arch', 'value': 'amd64'}, + {'path': '/properties/cpus', 'value': '1'}, + {'path': '/properties/capabilities', + 'value': 'num_nics:6,profile:compute'}, + {'path': '/properties/root_device', + 'value': {'serial': 'abcdef'}}, + {'path': '/driver_info/ipmi_username', 'value': 'test'}] + for iface, value in interfaces.items(): + update_patch.append({'path': '/%s' % iface, 'value': value}) + for key in update_patch: + key['op'] = 'add' + self.assertThat(update_patch, + matchers.MatchesSetwise(*(map(matchers.Equals, + args[1])))) + return mock.Mock(uuid='uuid1') + + ironic.node.update.side_effect = side_effect + nodes._update_or_register_ironic_node(node, node_map, client=ironic) + ironic.node.update.assert_called_once_with(1, mock.ANY) + def test_register_update_with_images(self): node = self._get_node() node['kernel_id'] = 'image-k' diff --git a/tripleo_common/utils/nodes.py b/tripleo_common/utils/nodes.py index 060d8948a..71b018dff 100644 --- a/tripleo_common/utils/nodes.py +++ b/tripleo_common/utils/nodes.py @@ -389,10 +389,15 @@ def register_ironic_node(node, client): extra = dict(tripleo_platform=platform) if 'capabilities' in node: - caps = node['capabilities'] - if isinstance(caps, dict): - caps = dict_to_capabilities(caps) - properties.update({"capabilities": six.text_type(caps)}) + caps = capabilities_to_dict(node['capabilities']) + else: + caps = {} + + if 'profile' in node: + caps['profile'] = node['profile'] + + if caps: + properties["capabilities"] = dict_to_capabilities(caps) driver = node['pm_type'] if handler.hardware_type and handler.hardware_type != driver: @@ -516,8 +521,15 @@ def _update_or_register_ironic_node(node, node_map, client): patched[path] = value if 'capabilities' in node: - patched['/properties/capabilities'] = dict_to_capabilities( - node.pop('capabilities')) + caps = capabilities_to_dict(node.pop('capabilities')) + else: + caps = {} + + if 'profile' in node: + caps['profile'] = node.pop('profile') + + if caps: + patched['/properties/capabilities'] = dict_to_capabilities(caps) driver_info = handler.convert(node) for key, value in driver_info.items():