diff --git a/driver-requirements.txt b/driver-requirements.txt index 356deba03e..b707d9c0c3 100644 --- a/driver-requirements.txt +++ b/driver-requirements.txt @@ -9,6 +9,8 @@ pysnmp python-ironic-inspector-client>=1.5.0 python-oneviewclient<3.0.0,>=2.5.2 python-scciclient>=0.5.0 +python-ilorest-library>=1.9.2 +hpOneView<4.0.0,>=3.2.1 UcsSdk==0.8.2.2 python-dracclient>=1.3.0 diff --git a/ironic/drivers/modules/oneview/common.py b/ironic/drivers/modules/oneview/common.py index dfc62b9bc8..8c16895b09 100644 --- a/ironic/drivers/modules/oneview/common.py +++ b/ironic/drivers/modules/oneview/common.py @@ -13,9 +13,9 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - from oslo_log import log as logging from oslo_utils import importutils +from six.moves.urllib import parse from ironic.common import exception from ironic.common.i18n import _ @@ -25,11 +25,18 @@ from ironic.drivers import utils LOG = logging.getLogger(__name__) +# NOTE(mrtenio): hpOneView will be the default library for OneView. It +# is being introduced together with the python-oneviewclient to be used +# generally by other patches. python-oneviewclient will be removed +# subsequently. client = importutils.try_import('oneview_client.client') oneview_utils = importutils.try_import('oneview_client.utils') oneview_states = importutils.try_import('oneview_client.states') oneview_exceptions = importutils.try_import('oneview_client.exceptions') +hponeview_client = importutils.try_import('hpOneView.oneview_client') +redfish = importutils.try_import('redfish') + REQUIRED_ON_DRIVER_INFO = { 'server_hardware_uri': _("Server Hardware URI. Required in driver_info."), } @@ -49,6 +56,8 @@ OPTIONAL_ON_PROPERTIES = { "Enclosure Group URI. Optional in properties/capabilities."), } +ILOREST_BASE_PORT = "443" + COMMON_PROPERTIES = {} COMMON_PROPERTIES.update(REQUIRED_ON_DRIVER_INFO) COMMON_PROPERTIES.update(REQUIRED_ON_PROPERTIES) @@ -83,6 +92,74 @@ def get_oneview_client(): return oneview_client +def prepare_manager_url(manager_url): + # NOTE(mrtenio) python-oneviewclient uses https or http in the manager_url + # while python-hpOneView does not. This will not be necessary when + # python-hpOneView client is the only OneView library. + if manager_url: + url_parse = parse.urlparse(manager_url) + manager_url = url_parse.netloc + return manager_url + + +def get_hponeview_client(): + """Generate an instance of the hpOneView client. + + Generates an instance of the hpOneView client using the hpOneView library. + + :returns: an instance of the OneViewClient + :raises: InvalidParameterValue if mandatory information is missing on the + node or on invalid input. + """ + manager_url = prepare_manager_url(CONF.oneview.manager_url) + config = { + "ip": manager_url, + "credentials": { + "userName": CONF.oneview.username, + "password": CONF.oneview.password + } + } + return hponeview_client.OneViewClient(config) + + +def get_ilorest_client(oneview_client, server_hardware): + """Generate an instance of the iLORest library client. + + :param oneview_client: an instance of a python-hpOneView + :param: server_hardware: a server hardware uuid or uri + :returns: an instance of the iLORest client + :raises: InvalidParameterValue if mandatory information is missing on the + node or on invalid input. + """ + remote_console = oneview_client.server_hardware.get_remote_console_url( + server_hardware + ) + host_ip, ilo_token = _get_ilo_access(remote_console) + base_url = "https://%s:%s" % (host_ip, ILOREST_BASE_PORT) + return redfish.rest_client(base_url=base_url, sessionkey=ilo_token) + + +def _get_ilo_access(remote_console): + """Get the needed information to access ilo. + + Get the host_ip and a token of an iLO remote console instance which can be + used to perform operations on that controller. + + The Remote Console url has the following format: + hplocons://addr=1.2.3.4&sessionkey=a79659e3b3b7c8209c901ac3509a6719 + + :param: remote_console: OneView Remote Console object with a + remoteConsoleUrl + :returns: A tuple with the Host IP and Token to access ilo, for + example: ('1.2.3.4', 'a79659e3b3b7c8209c901ac3509a6719') + """ + url = remote_console.get('remoteConsoleUrl') + url_parse = parse.urlparse(url) + host_ip = parse.parse_qs(url_parse.netloc).get('addr')[0] + token = parse.parse_qs(url_parse.netloc).get('sessionkey')[0] + return host_ip, token + + def verify_node_info(node): """Verifies if fields and namespaces of a node are valid. diff --git a/ironic/tests/unit/drivers/modules/oneview/test_common.py b/ironic/tests/unit/drivers/modules/oneview/test_common.py index d42d819fa6..5fa2a8553a 100644 --- a/ironic/tests/unit/drivers/modules/oneview/test_common.py +++ b/ironic/tests/unit/drivers/modules/oneview/test_common.py @@ -25,6 +25,7 @@ from ironic.tests.unit.db import base as db_base from ironic.tests.unit.db import utils as db_utils from ironic.tests.unit.objects import utils as obj_utils +hponeview_client = importutils.try_import('oneview_client.client') oneview_states = importutils.try_import('oneview_client.states') @@ -58,6 +59,34 @@ class OneViewCommonTestCase(db_base.DbTestCase): "server_hardware_type_uri"): common.verify_node_info(self.node) + def test_prepare_manager_url(self): + self.assertEqual( + common.prepare_manager_url("https://1.2.3.4/"), "1.2.3.4") + self.assertEqual( + common.prepare_manager_url("http://oneview"), "oneview") + self.assertEqual( + common.prepare_manager_url("http://oneview:8080"), "oneview:8080") + self.assertEqual( + common.prepare_manager_url("http://oneview/something"), "oneview") + self.assertEqual( + common.prepare_manager_url("oneview/something"), "") + self.assertEqual( + common.prepare_manager_url("oneview"), "") + + @mock.patch.object(hponeview_client, 'OneViewClient', spec_set=True, + autospec=True) + def test_get_hponeview_client(self, mock_hponeview_client): + common.get_hponeview_client() + mock_hponeview_client.assert_called_once_with(self.config) + + def test_get_ilo_access(self): + url = ("hplocons://addr=1.2.3.4&sessionkey" + + "=a79659e3b3b7c8209c901ac3509a6719") + remote_console = {'remoteConsoleUrl': url} + host_ip, token = common._get_ilo_access(remote_console) + self.assertEqual(host_ip, "1.2.3.4") + self.assertEqual(token, "a79659e3b3b7c8209c901ac3509a6719") + def test_verify_node_info_missing_node_driver_info(self): self.node.driver_info = {} diff --git a/ironic/tests/unit/drivers/modules/oneview/test_inspect.py b/ironic/tests/unit/drivers/modules/oneview/test_inspect.py index b574550d04..d676b5e74e 100644 --- a/ironic/tests/unit/drivers/modules/oneview/test_inspect.py +++ b/ironic/tests/unit/drivers/modules/oneview/test_inspect.py @@ -31,6 +31,7 @@ class AgentPXEOneViewInspectTestCase(db_base.DbTestCase): def setUp(self): super(AgentPXEOneViewInspectTestCase, self).setUp() + self.config(manager_url='https://1.2.3.4', group='oneview') self.config(enabled=True, group='inspector') mgr_utils.mock_the_extension_manager(driver="agent_pxe_oneview") self.node = obj_utils.create_test_node( @@ -69,6 +70,7 @@ class ISCSIPXEOneViewInspectTestCase(db_base.DbTestCase): def setUp(self): super(ISCSIPXEOneViewInspectTestCase, self).setUp() + self.config(manager_url='https://1.2.3.4', group='oneview') self.config(enabled=True, group='inspector') mgr_utils.mock_the_extension_manager(driver="iscsi_pxe_oneview") self.node = obj_utils.create_test_node( diff --git a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py index 8f59f10720..4e7e8b7c05 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py +++ b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py @@ -143,6 +143,24 @@ ONEVIEWCLIENT_STATES_SPEC = ( 'ONEVIEW_ERROR', ) +HPONEVIEW_SPEC = ( + 'oneview_client', + 'resources', + 'exceptions', +) + +HPONEVIEW_CLS_SPEC = ( +) + +HPONEVIEW_STATES_SPEC = ( + 'ONEVIEW_POWER_OFF', + 'ONEVIEW_POWERING_OFF', + 'ONEVIEW_POWER_ON', + 'ONEVIEW_POWERING_ON', + 'ONEVIEW_RESETTING', + 'ONEVIEW_ERROR', +) + SUSHY_CONSTANTS_SPEC = ( 'BOOT_SOURCE_TARGET_PXE', 'BOOT_SOURCE_TARGET_HDD', diff --git a/ironic/tests/unit/drivers/third_party_driver_mocks.py b/ironic/tests/unit/drivers/third_party_driver_mocks.py index 31fec78b75..ed5992f504 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mocks.py +++ b/ironic/tests/unit/drivers/third_party_driver_mocks.py @@ -26,6 +26,7 @@ Current list of mocked libraries: - pysnmp - scciclient - oneview_client +- hpOneView - pywsman - python-dracclient """ @@ -99,6 +100,22 @@ if 'ironic.drivers.oneview' in sys.modules: six.moves.reload_module(sys.modules['ironic.drivers.modules.oneview']) +hpOneView = importutils.try_import('hpOneView') +if not hpOneView: + hpOneView = mock.MagicMock(spec_set=mock_specs.HPONEVIEW_SPEC) + sys.modules['hpOneView'] = hpOneView + sys.modules['hpOneView.oneview_client'] = hpOneView.oneview_client + sys.modules['hpOneView.resources'] = hpOneView.resources + sys.modules['hpOneView.exceptions'] = hpOneView.exceptions + hpOneView.exceptions.HPOneViewException = type('HPOneViewException', + (Exception,), {}) +sys.modules['hpOneView.oneview_client'].OneViewClient = mock.MagicMock( + spec_set=mock_specs.HPONEVIEW_CLS_SPEC +) +if 'ironic.drivers.oneview' in sys.modules: + six.moves.reload_module(sys.modules['ironic.drivers.modules.oneview']) + + # attempt to load the external 'python-dracclient' library, which is required # by the optional drivers.modules.drac module dracclient = importutils.try_import('dracclient') diff --git a/releasenotes/notes/introduce-hponeview-client-6652d468034963b2.yaml b/releasenotes/notes/introduce-hponeview-client-6652d468034963b2.yaml new file mode 100644 index 0000000000..12ba47f333 --- /dev/null +++ b/releasenotes/notes/introduce-hponeview-client-6652d468034963b2.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + Adds the ``hpOneView`` and ``ilorest`` library requirement + to the OneView Driver, in order to prepare for the eventual + removal of dependency on ``python-oneviewclient`` library.