From 254318f608577523c1727d42ca05e1cf258b832b Mon Sep 17 00:00:00 2001 From: paresh-sao Date: Mon, 24 Sep 2018 08:22:46 +0000 Subject: [PATCH] Added support for IPv6 network This commit added ilo client changes and redfish connector changes to enable support for IPv6 network. Change-Id: Ifefc1f0ee190d5420c0c63ae7674350facde8e27 --- proliantutils/ilo/client.py | 15 +++- proliantutils/ilo/operations.py | 2 +- proliantutils/redfish/connector.py | 10 ++- proliantutils/tests/ilo/test_client.py | 86 +++++++++++++++---- proliantutils/tests/ilo/test_operations.py | 5 ++ proliantutils/tests/redfish/test_connector.py | 42 +++++++-- 6 files changed, 130 insertions(+), 30 deletions(-) diff --git a/proliantutils/ilo/client.py b/proliantutils/ilo/client.py index 46748c27..23995631 100644 --- a/proliantutils/ilo/client.py +++ b/proliantutils/ilo/client.py @@ -13,6 +13,7 @@ # under the License. """IloClient module""" +import netaddr from proliantutils import exception from proliantutils.ilo import ipmi from proliantutils.ilo import operations @@ -125,9 +126,18 @@ class IloClient(operations.IloOperations): def __init__(self, host, login, password, timeout=60, port=443, bios_password=None, cacert=None, snmp_credentials=None, use_redfish_only=False): + + # IPv6 Check + # TODO(paresh) Need to test with Global IPv6 address + # IPMI supports IPv6 without square brackets + self.ipmi_host_info = {'address': host, 'username': login, + 'password': password} + + if netaddr.valid_ipv6(host.split('%')[0]): + host = '[' + host + ']' + self.ribcl = ribcl.RIBCLOperations(host, login, password, timeout, port, cacert=cacert) - self.info = {'address': host, 'username': login, 'password': password} self.host = host self.use_redfish_only = use_redfish_only @@ -640,7 +650,8 @@ class IloClient(operations.IloOperations): # NOTE(vmud213): Even if it is None, pass it on to get_nic_capacity # as we still want to try getting nic capacity through ipmitool # irrespective of what firmware we are using. - nic_capacity = ipmi.get_nic_capacity(self.info, major_minor) + nic_capacity = ipmi.get_nic_capacity(self.ipmi_host_info, + major_minor) if nic_capacity: capabilities.update({'nic_capacity': nic_capacity}) diff --git a/proliantutils/ilo/operations.py b/proliantutils/ilo/operations.py index 7d7dcc33..fdbe4583 100644 --- a/proliantutils/ilo/operations.py +++ b/proliantutils/ilo/operations.py @@ -28,7 +28,7 @@ class IloOperations(object): def _(self, msg): """Prepends host information if available to msg and returns it.""" try: - return "[iLO %s] %s" % (self.host, msg) + return "[iLO %s] %s" % (self.host.replace('%', '%%'), msg) except AttributeError: return "[iLO ] %s" % msg diff --git a/proliantutils/redfish/connector.py b/proliantutils/redfish/connector.py index 651ef652..dc8f05c2 100644 --- a/proliantutils/redfish/connector.py +++ b/proliantutils/redfish/connector.py @@ -15,6 +15,7 @@ __author__ = 'HPE' import retrying +from six.moves.urllib.parse import urlparse from sushy import connector from sushy import exceptions @@ -44,4 +45,11 @@ class HPEConnector(connector.Connector): :param headers: Optional dictionary of headers. :returns: The response from the connector.Connector's _op method. """ - return super(HPEConnector, self)._op(method, path, data, headers) + resp = super(HPEConnector, self)._op(method, path, data, + headers, allow_redirects=False) + # With IPv6, Gen10 server gives redirection response with new path with + # a prefix of '/' so this check is required + if resp.status_code == 308: + path = urlparse(resp.headers['Location']).path + resp = super(HPEConnector, self)._op(method, path, data, headers) + return resp diff --git a/proliantutils/tests/ilo/test_client.py b/proliantutils/tests/ilo/test_client.py index 4cf020f3..0150cf48 100644 --- a/proliantutils/tests/ilo/test_client.py +++ b/proliantutils/tests/ilo/test_client.py @@ -47,7 +47,57 @@ class IloClientInitTestCase(testtools.TestCase): "1.2.3.4", "admin", "Admin", 120, 4430, cacert='/somewhere') self.assertEqual( {'address': "1.2.3.4", 'username': "admin", 'password': "Admin"}, - c.info) + c.ipmi_host_info) + self.assertEqual('product', c.model) + + @mock.patch.object(ribcl, 'RIBCLOperations') + @mock.patch.object(ris, 'RISOperations') + def test_init_for_ipv6_link_address(self, ris_mock, ribcl_mock): + ribcl_obj_mock = mock.MagicMock() + ribcl_mock.return_value = ribcl_obj_mock + ribcl_obj_mock.get_product_name.return_value = 'product' + + c = client.IloClient("FE80::9AF2:B3FF:FEEE:F884%eth0", "admin", + "Admin", timeout=120, port=4430, + bios_password='foo', + cacert='/somewhere') + + ris_mock.assert_called_once_with( + "[FE80::9AF2:B3FF:FEEE:F884%eth0]", + "admin", "Admin", bios_password='foo', + cacert='/somewhere') + ribcl_mock.assert_called_once_with( + "[FE80::9AF2:B3FF:FEEE:F884%eth0]", + "admin", "Admin", 120, 4430, cacert='/somewhere') + self.assertEqual( + {'address': "FE80::9AF2:B3FF:FEEE:F884%eth0", + 'username': "admin", 'password': "Admin"}, + c.ipmi_host_info) + self.assertEqual('product', c.model) + + @mock.patch.object(ribcl, 'RIBCLOperations') + @mock.patch.object(ris, 'RISOperations') + def test_init_for_ipv6_global_address(self, ris_mock, ribcl_mock): + ribcl_obj_mock = mock.MagicMock() + ribcl_mock.return_value = ribcl_obj_mock + ribcl_obj_mock.get_product_name.return_value = 'product' + + c = client.IloClient("2001:0db8:85a3::8a2e:0370:7334", "admin", + "Admin", timeout=120, port=4430, + bios_password='foo', + cacert='/somewhere') + + ris_mock.assert_called_once_with( + "[2001:0db8:85a3::8a2e:0370:7334]", + "admin", "Admin", bios_password='foo', + cacert='/somewhere') + ribcl_mock.assert_called_once_with( + "[2001:0db8:85a3::8a2e:0370:7334]", + "admin", "Admin", 120, 4430, cacert='/somewhere') + self.assertEqual( + {'address': "2001:0db8:85a3::8a2e:0370:7334", + 'username': "admin", 'password': "Admin"}, + c.ipmi_host_info) self.assertEqual('product', c.model) @mock.patch.object(ribcl, 'RIBCLOperations') @@ -70,7 +120,7 @@ class IloClientInitTestCase(testtools.TestCase): cacert='/somewhere') self.assertEqual( {'address': "1.2.3.4", 'username': "admin", 'password': "Admin"}, - c.info) + c.ipmi_host_info) self.assertEqual('ProLiant DL180 Gen10', c.model) self.assertIsNotNone(c.redfish) self.assertTrue(c.is_ribcl_enabled) @@ -97,7 +147,7 @@ class IloClientInitTestCase(testtools.TestCase): cacert='/somewhere') self.assertEqual( {'address': "1.2.3.4", 'username': "admin", 'password': "Admin"}, - c.info) + c.ipmi_host_info) self.assertIsNotNone(c.model) self.assertIsNotNone(c.redfish) self.assertFalse(c.is_ribcl_enabled) @@ -119,7 +169,7 @@ class IloClientInitTestCase(testtools.TestCase): cacert='/somewhere') self.assertEqual( {'address': "1.2.3.4", 'username': "admin", 'password': "Admin"}, - c.info) + c.ipmi_host_info) self.assertIsNotNone(c.model) self.assertIsNotNone(c.redfish) self.assertIsNone(c.is_ribcl_enabled) @@ -152,7 +202,7 @@ class IloClientInitTestCase(testtools.TestCase): "1.2.3.4", "admin", "Admin", 120, 4430, cacert='/somewhere') self.assertEqual( {'address': "1.2.3.4", 'username': "admin", 'password': "Admin"}, - c.info) + c.ipmi_host_info) self.assertEqual('product', c.model) self.assertTrue(snmp_mock.called) @@ -599,9 +649,9 @@ class IloClientTestCase(testtools.TestCase): 'pci_gpu_devices': '2', 'nic_capacity': '10Gb'} cap_mock.assert_called_once_with() - nic_mock.assert_called_once_with(self.client.info, str_val) + nic_mock.assert_called_once_with(self.client.ipmi_host_info, str_val) self.assertEqual(expected_capabilities, capabilities) - self.assertEqual(info, self.client.info) + self.assertEqual(info, self.client.ipmi_host_info) @mock.patch.object(ipmi, 'get_nic_capacity') @mock.patch.object(ribcl.RIBCLOperations, @@ -622,9 +672,9 @@ class IloClientTestCase(testtools.TestCase): 'server_model': 'Gen8', 'pci_gpu_devices': '2'} cap_mock.assert_called_once_with() - nic_mock.assert_called_once_with(self.client.info, str_val) + nic_mock.assert_called_once_with(self.client.ipmi_host_info, str_val) self.assertEqual(expected_capabilities, capabilities) - self.assertEqual(info, self.client.info) + self.assertEqual(info, self.client.ipmi_host_info) @mock.patch.object(ipmi, 'get_nic_capacity') @mock.patch.object(ribcl.RIBCLOperations, @@ -642,7 +692,7 @@ class IloClientTestCase(testtools.TestCase): 'pci_gpu_devices': '2'} capabilities = self.client.get_server_capabilities() self.assertEqual(expected_capabilities, capabilities) - nic_mock.assert_called_once_with(self.client.info, None) + nic_mock.assert_called_once_with(self.client.ipmi_host_info, None) @mock.patch.object(ipmi, 'get_nic_capacity') @mock.patch.object(ribcl.RIBCLOperations, @@ -660,7 +710,7 @@ class IloClientTestCase(testtools.TestCase): 'pci_gpu_devices': '2'} capabilities = self.client.get_server_capabilities() self.assertEqual(expected_capabilities, capabilities) - nic_mock.assert_called_once_with(self.client.info, None) + nic_mock.assert_called_once_with(self.client.ipmi_host_info, None) @mock.patch.object(ris.RISOperations, 'get_ilo_firmware_version_as_major_minor') @@ -679,7 +729,7 @@ class IloClientTestCase(testtools.TestCase): 'secure_boot': 'true'} capabilities = self.client.get_server_capabilities() cap_mock.assert_called_once_with() - nic_mock.assert_called_once_with(self.client.info, str_val) + nic_mock.assert_called_once_with(self.client.ipmi_host_info, str_val) expected_capabilities = {'ilo_firmware_version': '2.10', 'rom_firmware_version': 'x', 'server_model': 'Gen9', @@ -702,7 +752,7 @@ class IloClientTestCase(testtools.TestCase): 'secure_boot': 'true'} capabilities = self.client.get_server_capabilities() cap_mock.assert_called_once_with() - nic_mock.assert_called_once_with(self.client.info, str_val) + nic_mock.assert_called_once_with(self.client.ipmi_host_info, str_val) expected_capabilities = {'ilo_firmware_version': '2.10', 'rom_firmware_version': 'x', 'server_model': 'Gen9', @@ -744,7 +794,7 @@ class IloClientTestCase(testtools.TestCase): 'secure_boot': 'true'} capabilities = self.client.get_server_capabilities() cap_mock.assert_called_once_with() - nic_mock.assert_called_once_with(self.client.info, str_val) + nic_mock.assert_called_once_with(self.client.ipmi_host_info, str_val) expected_capabilities = {'ilo_firmware_version': '2.10', 'rom_firmware_version': 'x', 'server_model': 'Gen9', @@ -1087,8 +1137,8 @@ class IloClientTestCase(testtools.TestCase): snmp_mock.return_value = 250 self.client.get_essential_properties() call_mock.assert_called_once_with('get_essential_properties') - snmp_mock.assert_called_once_with(self.client.info['address'], - snmp_credentials) + snmp_mock.assert_called_once_with( + self.client.ipmi_host_info['address'], snmp_credentials) self.assertTrue(mac_mock.called) @mock.patch.object(ris.RISOperations, 'get_active_macs') @@ -1112,8 +1162,8 @@ class IloClientTestCase(testtools.TestCase): snmp_mock.return_value = 0 self.client.get_essential_properties() call_mock.assert_called_once_with('get_essential_properties') - snmp_mock.assert_called_once_with(self.client.info['address'], - snmp_credentials) + snmp_mock.assert_called_once_with( + self.client.ipmi_host_info['address'], snmp_credentials) self.assertTrue(mac_mock.called) @mock.patch.object(ris.RISOperations, 'get_active_macs') diff --git a/proliantutils/tests/ilo/test_operations.py b/proliantutils/tests/ilo/test_operations.py index 4eb6cc5d..aaa3b1d6 100644 --- a/proliantutils/tests/ilo/test_operations.py +++ b/proliantutils/tests/ilo/test_operations.py @@ -33,3 +33,8 @@ class IloOperationsTestCase(unittest.TestCase): def test__no_host(self): self.assertEqual('[iLO ] foo', self.operations_object._('foo')) + + def test__ipv6_host(self): + self.operations_object.host = 'FE80::9EB6:54FF:FEB1:ACEE%ens37' + self.assertEqual('[iLO FE80::9EB6:54FF:FEB1:ACEE%%ens37] foo', + self.operations_object._('foo')) diff --git a/proliantutils/tests/redfish/test_connector.py b/proliantutils/tests/redfish/test_connector.py index d29698b1..c0a79a1a 100644 --- a/proliantutils/tests/redfish/test_connector.py +++ b/proliantutils/tests/redfish/test_connector.py @@ -28,29 +28,33 @@ class HPEConnectorTestCase(testtools.TestCase): @mock.patch.object(connector.Connector, '_op', autospec=True) def test__op_no_exception(self, conn_mock): - conn_mock.side_effect = ["Hello", exceptions.ConnectionError, - "Hello", "World"] - + response = mock.MagicMock() + type(response).status_code = mock.PropertyMock(return_value=200) + conn_mock.side_effect = [response, exceptions.ConnectionError, + response, response] hpe_conn = hpe_connector.HPEConnector( 'http://foo.bar:1234', verify=True) headers = {'X-Fake': 'header'} hpe_conn._op('GET', path='fake/path', data=None, headers=headers) conn_mock.assert_called_once_with(hpe_conn, 'GET', path='fake/path', - data=None, headers=headers) + data=None, headers=headers, + allow_redirects=False) self.assertEqual(1, conn_mock.call_count) @mock.patch.object(connector.Connector, '_op', autospec=True) def test__op_with_exception(self, conn_mock): + response = mock.MagicMock() + type(response).status_code = mock.PropertyMock(return_value=501) conn_mock.side_effect = [exceptions.ConnectionError, - exceptions.ConnectionError, "Hello", "World"] - + exceptions.ConnectionError, + response, response] hpe_conn = hpe_connector.HPEConnector( 'http://foo.bar:1234', verify=True) headers = {'X-Fake': 'header'} lval = hpe_conn._op('GET', path='fake/path', data=None, headers=headers) self.assertEqual(3, conn_mock.call_count) - self.assertEqual(lval, "Hello") + self.assertEqual(lval.status_code, 501) @mock.patch.object(connector.Connector, '_op', autospec=True) def test__op_all_exception(self, conn_mock): @@ -58,7 +62,6 @@ class HPEConnectorTestCase(testtools.TestCase): exceptions.ConnectionError] * ( hpe_connector.HPEConnector.MAX_RETRY_ATTEMPTS) + ( ["Hello", "World"]) - hpe_conn = hpe_connector.HPEConnector( 'http://foo.bar:1234', verify=True) headers = {'X-Fake': 'header'} @@ -67,3 +70,26 @@ class HPEConnectorTestCase(testtools.TestCase): 'GET', path='fake/path', data=None, headers=headers) self.assertEqual(hpe_connector.HPEConnector.MAX_RETRY_ATTEMPTS, conn_mock.call_count) + + @mock.patch.object(connector.Connector, '_op', autospec=True) + def test__op_with_redirection_false_status_308(self, conn_mock): + response = mock.MagicMock() + type(response).status_code = mock.PropertyMock(return_value=308) + headers = {'X-Fake': 'header', + 'Location': 'http://foo.bar:1234/new/path'} + type(response).headers = headers + response_redirect = mock.MagicMock() + type(response_redirect).status_code = ( + mock.PropertyMock(return_value=200)) + conn_mock.side_effect = [response, response_redirect] + hpe_conn = hpe_connector.HPEConnector( + 'http://foo.bar:1234', verify=True) + headers = {'X-Fake': 'header'} + res = hpe_conn._op('GET', path='fake/path', + data=None, headers=headers) + calls = [mock.call(hpe_conn, 'GET', path='fake/path', data=None, + headers=headers, allow_redirects=False), + mock.call(hpe_conn, 'GET', path='/new/path', data=None, + headers=headers)] + conn_mock.assert_has_calls(calls) + self.assertEqual(res.status_code, 200)