diff --git a/proliantutils/ilo/client.py b/proliantutils/ilo/client.py index e62a526..fcc5215 100644 --- a/proliantutils/ilo/client.py +++ b/proliantutils/ilo/client.py @@ -20,6 +20,7 @@ from proliantutils.ilo import ribcl from proliantutils.ilo import ris from proliantutils.ilo.snmp import snmp_cpqdisk_sizes as snmp from proliantutils import log +from proliantutils.redfish import redfish SUPPORTED_RIS_METHODS = [ 'activate_license', @@ -56,27 +57,73 @@ SUPPORTED_RIS_METHODS = [ 'update_persistent_boot', ] +SUPPORTED_REDFISH_METHODS = [ + 'get_product_name', + 'get_host_power_status', +] + LOG = log.get_logger(__name__) class IloClient(operations.IloOperations): def __init__(self, host, login, password, timeout=60, port=443, - bios_password=None, cacert=None, snmp_credentials=None): + bios_password=None, cacert=None, snmp_credentials=None, + use_redfish_only=False): self.ribcl = ribcl.RIBCLOperations(host, login, password, timeout, port, cacert=cacert) - self.ris = ris.RISOperations(host, login, password, - bios_password=bios_password, - cacert=cacert) self.info = {'address': host, 'username': login, 'password': password} self.host = host - self.model = self.ribcl.get_product_name() - self.ribcl.init_model_based_tags(self.model) + self.use_redfish_only = use_redfish_only + + if use_redfish_only: + self._init_redfish_object(None, host, login, password, + bios_password=bios_password, + cacert=cacert) + LOG.debug(self._("Forced to use 'redfish' way to interact " + "with iLO. Model: %(model)s"), + {'model': self.model}) + else: + try: + self.model = self.ribcl.get_product_name() + except exception.IloError: + # Note(deray): This can be a potential scenario where + # RIBCL is disabled on a Gen10 (iLO 5) hardware. + # So, trying out the redfish operation object instantiation. + # If that passes we know that our assumption is right. + # If that errors out, then alas! we are left with no other + # choice. + self._init_redfish_object(False, host, login, password, + bios_password=bios_password, + cacert=cacert) + else: + self.ribcl.init_model_based_tags(self.model) + if ('Gen10' in self.model): + self._init_redfish_object(True, host, login, password, + bios_password=bios_password, + cacert=cacert, + should_set_model=False) + else: + # Gen9 + self.ris = ris.RISOperations( + host, login, password, bios_password=bios_password, + cacert=cacert) + self.snmp_credentials = snmp_credentials self._validate_snmp() LOG.debug(self._("IloClient object created. " "Model: %(model)s"), {'model': self.model}) + def _init_redfish_object(self, is_ribcl_enabled, redfish_controller_ip, + username, password, bios_password=None, + cacert=None, should_set_model=True): + self.redfish = redfish.RedfishOperations( + redfish_controller_ip, username, password, + bios_password=bios_password, cacert=cacert) + self.is_ribcl_enabled = is_ribcl_enabled + if should_set_model: + self.model = self.redfish.get_product_name() + def _validate_snmp(self): """Validates SNMP credentials. @@ -130,10 +177,29 @@ class IloClient(operations.IloOperations): 'inspection will not be performed.')) def _call_method(self, method_name, *args, **kwargs): - """Call the corresponding method using either RIBCL or RIS.""" - the_operation_object = self.ribcl - if ('Gen9' in self.model) and (method_name in SUPPORTED_RIS_METHODS): - the_operation_object = self.ris + """Call the corresponding method using RIBCL, RIS or REDFISH + + Make the decision to invoke the corresponding method using RIBCL, + RIS or REDFISH way. In case of none, throw out ``NotImplementedError`` + """ + if self.use_redfish_only: + if method_name in SUPPORTED_REDFISH_METHODS: + the_operation_object = self.redfish + else: + raise NotImplementedError() + else: + the_operation_object = self.ribcl + if 'Gen10' in self.model: + if method_name in SUPPORTED_REDFISH_METHODS: + the_operation_object = self.redfish + else: + if (self.is_ribcl_enabled is not None + and not self.is_ribcl_enabled): + raise NotImplementedError() + elif ('Gen9' in self.model) and (method_name in + SUPPORTED_RIS_METHODS): + the_operation_object = self.ris + method = getattr(the_operation_object, method_name) LOG.debug(self._("Using %(class)s for method %(method)s."), diff --git a/proliantutils/tests/ilo/test_client.py b/proliantutils/tests/ilo/test_client.py index f9d5edb..f2b1192 100644 --- a/proliantutils/tests/ilo/test_client.py +++ b/proliantutils/tests/ilo/test_client.py @@ -23,6 +23,7 @@ from proliantutils.ilo import ipmi from proliantutils.ilo import ribcl from proliantutils.ilo import ris from proliantutils.ilo.snmp import snmp_cpqdisk_sizes +from proliantutils.redfish import redfish class IloClientInitTestCase(testtools.TestCase): @@ -49,6 +50,82 @@ class IloClientInitTestCase(testtools.TestCase): c.info) self.assertEqual('product', c.model) + @mock.patch.object(ribcl, 'RIBCLOperations') + @mock.patch.object(redfish, 'RedfishOperations') + def test_init_for_redfish_with_ribcl_enabled( + self, redfish_mock, ribcl_mock): + ribcl_obj_mock = mock.MagicMock() + ribcl_mock.return_value = ribcl_obj_mock + ribcl_obj_mock.get_product_name.return_value = 'ProLiant DL180 Gen10' + + c = client.IloClient("1.2.3.4", "admin", "Admin", + timeout=120, port=4430, + bios_password='foo', + cacert='/somewhere') + + ribcl_mock.assert_called_once_with( + "1.2.3.4", "admin", "Admin", 120, 4430, cacert='/somewhere') + redfish_mock.assert_called_once_with( + "1.2.3.4", "admin", "Admin", bios_password='foo', + cacert='/somewhere') + self.assertEqual( + {'address': "1.2.3.4", 'username': "admin", 'password': "Admin"}, + c.info) + self.assertEqual('ProLiant DL180 Gen10', c.model) + self.assertIsNotNone(c.redfish) + self.assertTrue(c.is_ribcl_enabled) + self.assertFalse(hasattr(c, 'ris')) + + @mock.patch.object(ribcl, 'RIBCLOperations') + @mock.patch.object(redfish, 'RedfishOperations') + def test_init_for_redfish_with_ribcl_disabled( + self, redfish_mock, ribcl_mock): + ribcl_obj_mock = mock.MagicMock() + ribcl_mock.return_value = ribcl_obj_mock + ribcl_obj_mock.get_product_name.side_effect = ( + exception.IloError('RIBCL is disabled')) + + c = client.IloClient("1.2.3.4", "admin", "Admin", + timeout=120, port=4430, + bios_password='foo', + cacert='/somewhere') + + ribcl_mock.assert_called_once_with( + "1.2.3.4", "admin", "Admin", 120, 4430, cacert='/somewhere') + redfish_mock.assert_called_once_with( + "1.2.3.4", "admin", "Admin", bios_password='foo', + cacert='/somewhere') + self.assertEqual( + {'address': "1.2.3.4", 'username': "admin", 'password': "Admin"}, + c.info) + self.assertIsNotNone(c.model) + self.assertIsNotNone(c.redfish) + self.assertFalse(c.is_ribcl_enabled) + self.assertFalse(hasattr(c, 'ris')) + + @mock.patch.object(ribcl, 'RIBCLOperations') + @mock.patch.object(redfish, 'RedfishOperations') + def test_init_with_use_redfish_only_set( + self, redfish_mock, ribcl_mock): + c = client.IloClient("1.2.3.4", "admin", "Admin", + timeout=120, port=4430, + bios_password='foo', cacert='/somewhere', + use_redfish_only=True) + + ribcl_mock.assert_called_once_with( + "1.2.3.4", "admin", "Admin", 120, 4430, cacert='/somewhere') + redfish_mock.assert_called_once_with( + "1.2.3.4", "admin", "Admin", bios_password='foo', + cacert='/somewhere') + self.assertEqual( + {'address': "1.2.3.4", 'username': "admin", 'password': "Admin"}, + c.info) + self.assertIsNotNone(c.model) + self.assertIsNotNone(c.redfish) + self.assertIsNone(c.is_ribcl_enabled) + self.assertFalse(hasattr(c, 'ris')) + self.assertTrue(c.use_redfish_only) + @mock.patch.object(client.IloClient, '_validate_snmp') @mock.patch.object(ribcl, 'RIBCLOperations') @mock.patch.object(ris, 'RISOperations') @@ -193,6 +270,91 @@ class IloClientTestCase(testtools.TestCase): self.client._call_method('reset_ilo') ilo_mock.assert_called_once_with() + """ + Testing ``_call_method`` with Redfish support. + + Testing the redfish methods based on the following scenarios, + which are depicted in this table:: + + redfish | ribcl | method implemented | name of test method + supported? | enabled? | on redfish? | + ===========|==========|====================|============================= + true | true | true | test__call_method_redfish_1 + true | true | false | test__call_method_redfish_2 + true | false | true | test__call_method_redfish_3 + true | false | false | test__call_method_redfish_4 + ===========|==========|====================|============================= + """ + @mock.patch.object(ribcl.RIBCLOperations, 'get_product_name') + @mock.patch.object(redfish, 'RedfishOperations') + def test__call_method_redfish_1(self, redfish_mock, + ribcl_product_name_mock): + ribcl_product_name_mock.return_value = 'Gen10' + self.client = client.IloClient("1.2.3.4", "admin", "secret") + redfish_get_host_power_mock = (redfish.RedfishOperations.return_value. + get_host_power_status) + + self.client._call_method('get_host_power_status') + redfish_get_host_power_mock.assert_called_once_with() + + @mock.patch.object(ribcl.RIBCLOperations, 'get_product_name') + @mock.patch.object(redfish, 'RedfishOperations') + @mock.patch.object(ribcl.RIBCLOperations, 'reset_ilo') + def test__call_method_redfish_2(self, ribcl_reset_ilo_mock, + redfish_mock, ribcl_product_name_mock): + ribcl_product_name_mock.return_value = 'Gen10' + self.client = client.IloClient("1.2.3.4", "admin", "secret") + + self.client._call_method('reset_ilo') + ribcl_reset_ilo_mock.assert_called_once_with() + + @mock.patch.object(ribcl.RIBCLOperations, 'get_product_name') + @mock.patch.object(redfish, 'RedfishOperations') + def test__call_method_redfish_3(self, redfish_mock, + ribcl_product_name_mock): + ribcl_product_name_mock.side_effect = ( + exception.IloError('RIBCL is disabled')) + redfish_mock.return_value.get_product_name.return_value = 'Gen10' + self.client = client.IloClient("1.2.3.4", "admin", "secret") + redfish_get_host_power_mock = (redfish.RedfishOperations.return_value. + get_host_power_status) + + self.client._call_method('get_host_power_status') + redfish_get_host_power_mock.assert_called_once_with() + + @mock.patch.object(ribcl.RIBCLOperations, 'get_product_name') + @mock.patch.object(redfish, 'RedfishOperations') + def test__call_method_redfish_4(self, redfish_mock, + ribcl_product_name_mock): + ribcl_product_name_mock.side_effect = ( + exception.IloError('RIBCL is disabled')) + redfish_mock.return_value.get_product_name.return_value = 'Gen10' + self.client = client.IloClient("1.2.3.4", "admin", "secret") + + self.assertRaises(NotImplementedError, + self.client._call_method, 'reset_ilo') + + @mock.patch.object(redfish, 'RedfishOperations', + spec_set=True, autospec=True) + def test__call_method_with_use_redfish_only_set(self, redfish_mock): + self.client = client.IloClient("1.2.3.4", "admin", "secret", + use_redfish_only=True) + redfish_get_host_power_mock = (redfish.RedfishOperations.return_value. + get_host_power_status) + + self.client._call_method('get_host_power_status') + redfish_get_host_power_mock.assert_called_once_with() + + @mock.patch.object(redfish, 'RedfishOperations', + spec_set=True, autospec=True) + def test__call_method_use_redfish_only_set_but_not_implemented( + self, redfish_mock): + self.client = client.IloClient("1.2.3.4", "admin", "secret", + use_redfish_only=True) + + self.assertRaises(NotImplementedError, + self.client._call_method, 'reset_ilo') + @mock.patch.object(client.IloClient, '_call_method') def test_set_http_boot_url(self, call_mock): self.client.set_http_boot_url('fake-url')