From a4e1880993fd79b0d08a412a1139820e4d4b2708 Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Tue, 2 May 2017 13:19:25 +0200 Subject: [PATCH] Add support for enrolling nodes with Redfish hardware type Part of blueprint redfish-support Change-Id: I9a0d68f8c0ce5cd12b88099eb9b48c41bc8ca92e --- .../notes/redfish-550a0e0f0fd4ea41.yaml | 6 ++ tripleo_common/tests/utils/test_nodes.py | 101 ++++++++++++++++++ tripleo_common/utils/nodes.py | 35 ++++++ 3 files changed, 142 insertions(+) create mode 100644 releasenotes/notes/redfish-550a0e0f0fd4ea41.yaml diff --git a/releasenotes/notes/redfish-550a0e0f0fd4ea41.yaml b/releasenotes/notes/redfish-550a0e0f0fd4ea41.yaml new file mode 100644 index 000000000..9bb223a12 --- /dev/null +++ b/releasenotes/notes/redfish-550a0e0f0fd4ea41.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add support for enrolling nodes using Redfish protocol for management. + Requires additional field ``pm_system_id``, see `documentation + `_. diff --git a/tripleo_common/tests/utils/test_nodes.py b/tripleo_common/tests/utils/test_nodes.py index a4ef125a7..5dc4e9781 100644 --- a/tripleo_common/tests/utils/test_nodes.py +++ b/tripleo_common/tests/utils/test_nodes.py @@ -125,6 +125,42 @@ class PrefixedDriverInfoTestWithPort(base.TestCase): self.driver_info.unique_id_from_node(node)) +class RedfishDriverInfoTest(base.TestCase): + driver_info = nodes.RedfishDriverInfo() + + def test_convert_key(self): + keys = {'pm_addr': 'redfish_address', + 'pm_user': 'redfish_username', + 'pm_password': 'redfish_password', + 'pm_system_id': 'redfish_system_id', + 'redfish_verify_ca': 'redfish_verify_ca'} + for key, expected in keys.items(): + self.assertEqual(expected, self.driver_info.convert_key(key)) + + self.assertIsNone(self.driver_info.convert_key('unknown')) + + def test_unique_id_from_fields(self): + for address in ['example.com', + 'http://example.com/', + 'https://example.com/']: + fields = {'pm_addr': address, + 'pm_user': 'user', + 'pm_password': '123456', + 'pm_system_id': '/redfish/v1/Systems/1'} + self.assertEqual('example.com/redfish/v1/Systems/1', + self.driver_info.unique_id_from_fields(fields)) + + def test_unique_id_from_node(self): + for address in ['example.com', + 'http://example.com/', + 'https://example.com/']: + node = mock.Mock(driver_info={ + 'redfish_address': address, + 'redfish_system_id': '/redfish/v1/Systems/1'}) + self.assertEqual('example.com/redfish/v1/Systems/1', + self.driver_info.unique_id_from_node(node)) + + class iBootDriverInfoTest(base.TestCase): def setUp(self): super(iBootDriverInfoTest, self).setUp() @@ -406,6 +442,17 @@ class NodesTest(base.TestCase): def test_update_node_ironic_pxe_irmc(self): self._update_by_type('pxe_irmc') + def test_update_node_ironic_redfish(self): + ironic = mock.MagicMock() + node_map = {'mac': {}, 'pm_addr': {}} + node = self._get_node() + node.update({'pm_type': 'redfish', + 'pm_system_id': '/path'}) + node_map['pm_addr']['foo.bar/path'] = ironic.node.get.return_value.uuid + nodes._update_or_register_ironic_node(node, node_map, client=ironic) + ironic.node.update.assert_called_once_with( + ironic.node.get.return_value.uuid, mock.ANY) + def test_register_node_update(self): node = self._get_node() node['mac'][0] = node['mac'][0].upper() @@ -561,6 +608,24 @@ class NodesTest(base.TestCase): driver_info={'drac_password': 'random', 'drac_host': 'foo.bar', 'drac_username': 'test', 'drac_port': '6230'}) + def test_register_ironic_node_redfish(self): + node_properties = {"cpus": "1", + "memory_mb": "2048", + "local_gb": "30", + "cpu_arch": "amd64", + "capabilities": "num_nics:6"} + node = self._get_node() + node['pm_type'] = 'redfish' + node['pm_system_id'] = '/redfish/v1/Systems/1' + client = mock.MagicMock() + nodes.register_ironic_node(node, client=client) + client.node.create.assert_called_once_with( + driver='redfish', name='node1', properties=node_properties, + driver_info={'redfish_password': 'random', + 'redfish_address': 'foo.bar', + 'redfish_username': 'test', + 'redfish_system_id': '/redfish/v1/Systems/1'}) + def test_register_ironic_node_update_int_values(self): node = self._get_node() ironic = mock.MagicMock() @@ -708,6 +773,11 @@ VALID_NODE_JSON = [ 'memory': 1024, 'disk': 40, 'arch': 'x86_64'}, + {'pm_type': 'redfish', + 'pm_addr': '1.2.3.4', + 'pm_user': 'root', + 'pm_password': 'foobar', + 'pm_system_id': '/redfish/v1/Systems/1'}, ] @@ -824,3 +894,34 @@ class TestValidateNodes(base.TestCase): self.assertRaisesRegex(exception.InvalidNode, 'fields are missing: %s' % field, nodes.validate_nodes, nodes_json) + + def test_duplicate_redfish_node(self): + nodes_json = [ + {'pm_type': 'redfish', + 'pm_addr': 'example.com', + 'pm_user': 'root', + 'pm_password': 'p@$$w0rd', + 'pm_system_id': '/redfish/v1/Systems/1'}, + {'pm_type': 'redfish', + 'pm_addr': 'https://example.com', + 'pm_user': 'root', + 'pm_password': 'p@$$w0rd', + 'pm_system_id': '/redfish/v1/Systems/1'}, + ] + self.assertRaisesRegex( + exception.InvalidNode, + 'Node identified by example.com/redfish/v1/Systems/1 ' + 'is already present', + nodes.validate_nodes, nodes_json) + + def test_redfish_missing_system_id(self): + nodes_json = [ + {'pm_type': 'redfish', + 'pm_addr': '1.1.1.1', + 'pm_user': 'root', + 'pm_password': 'p@$$w0rd'}, + ] + + self.assertRaisesRegex(exception.InvalidNode, + 'fields are missing: pm_system_id', + nodes.validate_nodes, nodes_json) diff --git a/tripleo_common/utils/nodes.py b/tripleo_common/utils/nodes.py index f3f0a2412..842130ec6 100644 --- a/tripleo_common/utils/nodes.py +++ b/tripleo_common/utils/nodes.py @@ -135,6 +135,40 @@ class PrefixedDriverInfo(DriverInfo): return result +class RedfishDriverInfo(DriverInfo): + def __init__(self): + mapping = { + 'pm_addr': 'redfish_address', + 'pm_user': 'redfish_username', + 'pm_password': 'redfish_password', + 'pm_system_id': 'redfish_system_id' + } + mandatory_fields = list(mapping) + + super(RedfishDriverInfo, self).__init__( + 'redfish', mapping, + deprecated_mapping=None, + mandatory_fields=mandatory_fields, + ) + + def _build_id(self, address, system): + address = re.sub(r'https?://', '', address, count=1, flags=re.I) + return '%s/%s' % (address.rstrip('/'), system.lstrip('/')) + + def unique_id_from_fields(self, fields): + try: + return self._build_id(fields['pm_addr'], fields['pm_system_id']) + except KeyError: + return + + def unique_id_from_node(self, node): + try: + return self._build_id(node.driver_info['redfish_address'], + node.driver_info['redfish_system_id']) + except KeyError: + return + + class SshDriverInfo(DriverInfo): DEFAULTS = {'ssh_virt_type': 'virsh'} @@ -206,6 +240,7 @@ DRIVER_INFO = { 'pm_sensor_method': 'irmc_sensor_method', 'pm_deploy_iso': 'irmc_deploy_iso', }), + 'redfish': RedfishDriverInfo(), # test drivers '.*_ssh': SshDriverInfo(), '.*_iboot': iBootDriverInfo(),