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
This commit is contained in:
Debayan Ray 2017-04-12 02:41:21 -04:00
parent 641813c18f
commit 4db1e580e2
2 changed files with 238 additions and 10 deletions

View File

@ -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."),

View File

@ -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')