From 8dffdf53f308153bc97782b548da9dc299e65670 Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Tue, 25 Jul 2017 20:43:35 +0200 Subject: [PATCH] Allow setting *_interface fields via instackenv.json This is required to fully use the power of the new-style drivers, but was missing when support for hardware types was originally introduced. Change-Id: I6ad2b669023cf7a08acb69fce7e1058bb7e699d6 Closes-Bug: #1706411 --- .../notes/interfaces-cd94c12dd4744c50.yaml | 11 +++ tripleo_common/actions/base.py | 2 +- tripleo_common/tests/actions/test_base.py | 2 +- tripleo_common/tests/utils/test_nodes.py | 75 +++++++++++++++++++ tripleo_common/utils/nodes.py | 14 ++++ 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/interfaces-cd94c12dd4744c50.yaml diff --git a/releasenotes/notes/interfaces-cd94c12dd4744c50.yaml b/releasenotes/notes/interfaces-cd94c12dd4744c50.yaml new file mode 100644 index 000000000..65d2ff2a8 --- /dev/null +++ b/releasenotes/notes/interfaces-cd94c12dd4744c50.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + It is now possible to set various interface fields when enrolling nodes + via ``instackenv.json``. This only works for new-style drivers like + ``ipmi`` or ``redfish``. + ironicclient 1.15 is required for setting the ``storage_interface`` field. +upgrade: + - | + The minimum required Bare Metal (Ironic) API version was bumped to 1.33 + (late Pike). diff --git a/tripleo_common/actions/base.py b/tripleo_common/actions/base.py index ced6421fa..15e8f4184 100644 --- a/tripleo_common/actions/base.py +++ b/tripleo_common/actions/base.py @@ -59,7 +59,7 @@ class TripleOAction(actions.Action): ironic_endpoint.url, token=context.auth_token, region_name=ironic_endpoint.region, - os_ironic_api_version='1.15', + os_ironic_api_version='1.33', # FIXME(lucasagomes):Paramtetize max_retries and # max_interval. At the moment since we are dealing with # a critical bug (#1612622) let's just hardcode the times diff --git a/tripleo_common/tests/actions/test_base.py b/tripleo_common/tests/actions/test_base.py index 1bd745acc..b237c8800 100644 --- a/tripleo_common/tests/actions/test_base.py +++ b/tripleo_common/tests/actions/test_base.py @@ -39,7 +39,7 @@ class TestActionsBase(tests_base.TestCase): url='http://ironic/v1', region='ironic-region') self.action.get_baremetal_client(mock_cxt) mock_client.assert_called_once_with( - 'http://ironic/v1', max_retries=12, os_ironic_api_version='1.15', + 'http://ironic/v1', max_retries=12, os_ironic_api_version='1.33', region_name='ironic-region', retry_interval=5, token=mock.ANY) mock_endpoint.assert_called_once_with(mock_cxt, 'ironic') mock_cxt.assert_not_called() diff --git a/tripleo_common/tests/utils/test_nodes.py b/tripleo_common/tests/utils/test_nodes.py index 9651e2b37..96cedafec 100644 --- a/tripleo_common/tests/utils/test_nodes.py +++ b/tripleo_common/tests/utils/test_nodes.py @@ -352,8 +352,54 @@ 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_interfaces(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', + 'storage_interface': 'cinder', + 'vendor_interface': 'ipmitool'} + + node_list = [self._get_node()] + node_list[0].update(interfaces) + node_properties = {"cpus": "1", + "memory_mb": "2048", + "local_gb": "30", + "cpu_arch": "amd64", + "capabilities": "num_nics:6"} + 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, + properties=node_properties, + **interfaces) + port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid, + address='aaa') + ironic.node.create.assert_has_calls([pxe_node, mock.ANY]) + ironic.port.create.assert_has_calls([port_call]) + def test_register_update(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', + 'storage_interface': 'cinder', + 'vendor_interface': 'ipmitool'} + node = self._get_node() + node.update(interfaces) ironic = mock.MagicMock() node_map = {'mac': {'aaa': 1}} @@ -368,6 +414,8 @@ class NodesTest(base.TestCase): {'path': '/properties/cpus', 'value': '1'}, {'path': '/properties/capabilities', 'value': 'num_nics:6'}, {'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, @@ -410,6 +458,33 @@ 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_with_interfaces(self): + node = self._get_node() + 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'}, + {'path': '/driver_info/ipmi_username', 'value': 'test'}] + 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 _update_by_type(self, pm_type): ironic = mock.MagicMock() node_map = {'mac': {}, 'pm_addr': {}} diff --git a/tripleo_common/utils/nodes.py b/tripleo_common/utils/nodes.py index d1cc51ccf..f31f12516 100644 --- a/tripleo_common/utils/nodes.py +++ b/tripleo_common/utils/nodes.py @@ -25,6 +25,12 @@ from tripleo_common.utils import glance LOG = logging.getLogger(__name__) +_KNOWN_INTERFACE_FIELDS = [ + '%s_interface' % field for field in ('boot', 'console', 'deploy', + 'inspect', 'management', 'network', + 'power', 'raid', 'storage', 'vendor') +] + class DriverInfo(object): """Class encapsulating field conversion logic.""" @@ -282,6 +288,10 @@ def register_ironic_node(node, client): if "ramdisk_id" in node: driver_info["deploy_ramdisk"] = node["ramdisk_id"] + interface_fields = {field: node.pop(field) + for field in _KNOWN_INTERFACE_FIELDS + if field in node} + driver_info.update(handler.convert(node)) mapping = {'cpus': 'cpu', @@ -301,6 +311,7 @@ def register_ironic_node(node, client): create_map = {"driver": node["pm_type"], "properties": properties, "driver_info": driver_info} + create_map.update(interface_fields) for field in ('name', 'uuid'): if field in node: @@ -375,6 +386,9 @@ _NON_DRIVER_FIELDS = {'cpu': '/properties/cpus', 'ramdisk_id': '/driver_info/deploy_ramdisk', 'capabilities': '/properties/capabilities'} +_NON_DRIVER_FIELDS.update({field: '/%s' % field + for field in _KNOWN_INTERFACE_FIELDS}) + def _update_or_register_ironic_node(node, node_map, client): handler = _find_node_handler(node)