From 4db1e580e28f9b9e4a5bee94165816e41fa3a95a Mon Sep 17 00:00:00 2001 From: Debayan Ray Date: Wed, 12 Apr 2017 02:41:21 -0400 Subject: [PATCH] Instrumenting redfish into proliantutils' client This patch introduces the instrumentation of redfish object's method invocation from ``ilo.client.IloClient`` object. When a method is invoked on client, based on the target hardware the execution of that corresponding method bestows on RIBCL, RIS or Redfish. Again, when the context is Redfish then there RIBCL can remain enabled/disabled. In case of RIBCL enabled, we fall back to RIBCL for any method invocation which is not (yet) implemented in Redfish. For RIBCL being disabled, we raise ``NotImplementedError``. This also introduces a parameter in client constructor named 'use_redfish_only' which when set to True will force the use of redfish. Default value is False. Change-Id: Iaf261a17da137a6fa49e62d9ff2a7cf34c97c2e3 Partial-Bug: 1646685 --- proliantutils/ilo/client.py | 86 +++++++++++-- proliantutils/tests/ilo/test_client.py | 162 +++++++++++++++++++++++++ 2 files changed, 238 insertions(+), 10 deletions(-) diff --git a/proliantutils/ilo/client.py b/proliantutils/ilo/client.py index e62a5269..fcc52152 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 f9d5edbe..f2b1192f 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')