diff --git a/scciclient/irmc/__init__.py b/scciclient/irmc/__init__.py index e69de29..bb4b41b 100644 --- a/scciclient/irmc/__init__.py +++ b/scciclient/irmc/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2015 FUJITSU LIMITED +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 . import elcm # noqa +from . import scci # noqa diff --git a/scciclient/irmc/elcm.py b/scciclient/irmc/elcm.py index 5a9d773..0ae461d 100644 --- a/scciclient/irmc/elcm.py +++ b/scciclient/irmc/elcm.py @@ -16,6 +16,8 @@ eLCM functionality. """ +import time + from oslo_serialization import jsonutils import requests @@ -46,6 +48,7 @@ Timeout values """ PROFILE_CREATE_TIMEOUT = 300 # 300 secs PROFILE_SET_TIMEOUT = 300 # 300 secs +BIOS_CONFIG_SESSION_TIMEOUT = 30 * 60 # 30 mins class ELCMInvalidResponse(scci.SCCIError): @@ -66,6 +69,12 @@ class ELCMSessionNotFound(scci.SCCIError): super(ELCMSessionNotFound, self).__init__(message) +class ELCMSessionTimeout(scci.SCCIError): + """ELCMSessionTimeout""" + def __init__(self, message): + super(ELCMSessionTimeout, self).__init__(message) + + def _parse_elcm_response_body_as_json(response): """parse eLCM response body as json data @@ -495,3 +504,175 @@ def elcm_session_delete(irmc_info, session_id, terminate=False): '"%(session)s" with error code %(error)s' % {'session': session_id, 'error': resp.status_code})) + + +def _process_session_bios_config(irmc_info, operation, session_id, + session_timeout=BIOS_CONFIG_SESSION_TIMEOUT): + """process session for Bios config backup/restore operation + + :param irmc_info: node info + :param operation: one of 'BACKUP' and 'RESTORE' + :param session_id: session id + :param session_timeout: session timeout + :return: a dict with following values: + { + 'bios_config': , + 'warning': + } + """ + session_expiration = time.time() + session_timeout + + while time.time() < session_expiration: + # Get session status to check + session = elcm_session_get_status(irmc_info=irmc_info, + session_id=session_id) + + status = session['Session']['Status'] + if status == 'running' or status == 'activated': + # Sleep a bit + time.sleep(5) + elif status == 'terminated regularly': + result = {} + + if operation == 'BACKUP': + # Bios profile is created, get the data now + result['bios_config'] = elcm_profile_get( + irmc_info=irmc_info, + profile_name=PROFILE_BIOS_CONFIG) + elif operation == 'RESTORE': + # Bios config applied successfully + pass + + # Cleanup operation by deleting related session and profile. + # In case of error, report it as warning instead of error. + try: + elcm_session_delete(irmc_info=irmc_info, + session_id=session_id, + terminate=True) + elcm_profile_delete(irmc_info=irmc_info, + profile_name=PROFILE_BIOS_CONFIG) + except scci.SCCIError as e: + result['warning'] = e + + return result + else: + # Error occurred, get session log to see what happened + session_log = elcm_session_get_log(irmc_info=irmc_info, + session_id=session_id) + + raise scci.SCCIClientError( + ('Failed to %(operation)s bios config. ' + 'Session log is "%(session_log)s".' % + {'operation': operation, + 'session_log': jsonutils.dumps(session_log)})) + + else: + raise ELCMSessionTimeout( + ('Failed to %(operation)s bios config. ' + 'Session %(session_id)s log is timeout.' % + {'operation': operation, + 'session_id': session_id})) + + +def backup_bios_config(irmc_info): + """backup current bios configuration + + This function sends a BACKUP request to the server. Then when the bios + config data are ready for retrieving, it will returns the data to the + caller. Note that this operation may take time. + + :param irmc_info: node info + :return: a dict with following values: + { + 'bios_config': , + 'warning': + } + """ + # 1. Make sure there is no BiosConfig profile in the store + try: + # Get the profile first, if not found, then an exception + # will be raised. + elcm_profile_get(irmc_info=irmc_info, + profile_name=PROFILE_BIOS_CONFIG) + # Profile found, delete it + elcm_profile_delete(irmc_info=irmc_info, + profile_name=PROFILE_BIOS_CONFIG) + except ELCMProfileNotFound: + # Ignore this error as it's not an error in this case + pass + + # 2. Send request to create a new profile for BiosConfig + session = elcm_profile_create(irmc_info=irmc_info, + param_path=PARAM_PATH_BIOS_CONFIG) + + # 3. Profile creation is in progress, we monitor the session + session_timeout = irmc_info.get('irmc_bios_session_timeout', + BIOS_CONFIG_SESSION_TIMEOUT) + return _process_session_bios_config( + irmc_info=irmc_info, + operation='BACKUP', + session_id=session['Session']['Id'], + session_timeout=session_timeout) + + +def restore_bios_config(irmc_info, bios_config): + """restore bios configuration + + This function sends a RESTORE request to the server. Then when the bios + is ready for restoring, it will apply the provided settings and return. + Note that this operation may take time. + + :param irmc_info: node info + :param bios_config: bios config + """ + def _process_bios_config(): + try: + if isinstance(bios_config, dict): + input_data = bios_config + else: + input_data = jsonutils.loads(bios_config) + + # The input data must contain flag "@Processing":"execute" in the + # equivalent section. + bios_cfg = input_data['Server']['SystemConfig']['BiosConfig'] + bios_cfg['@Processing'] = 'execute' + + # NOTE: It seems without 2 sub profiles IrmcConfig and + # OSInstallation present in the input_data, the process will fail. + # The info for this error can be found in the session log. + # Work-around: add 2 sub profiles with empty content. + input_data['Server']['SystemConfig']['IrmcConfig'] = {} + input_data['Server']['SystemConfig']['OSInstallation'] = {} + + return input_data + except (TypeError, ValueError, KeyError): + raise scci.SCCIInvalidInputError( + ('Invalid input bios config "%s".' % bios_config)) + + # 1. Parse the bios config and create the input data + input_data = _process_bios_config() + + # 2. Make sure there is no BiosConfig profile in the store + try: + # Get the profile first, if not found, then an exception + # will be raised. + elcm_profile_get(irmc_info=irmc_info, + profile_name=PROFILE_BIOS_CONFIG) + # Profile found, delete it + elcm_profile_delete(irmc_info=irmc_info, + profile_name=PROFILE_BIOS_CONFIG) + except ELCMProfileNotFound: + # Ignore this error as it's not an error in this case + pass + + # 3. Send a request to apply the param values + session = elcm_profile_set(irmc_info=irmc_info, + input_data=input_data) + + # 4. Param values applying is in progress, we monitor the session + session_timeout = irmc_info.get('irmc_bios_session_timeout', + BIOS_CONFIG_SESSION_TIMEOUT) + _process_session_bios_config(irmc_info=irmc_info, + operation='RESTORE', + session_id=session['Session']['Id'], + session_timeout=session_timeout) diff --git a/scciclient/tests/irmc/test_elcm.py b/scciclient/tests/irmc/test_elcm.py index 401155c..2196cfa 100644 --- a/scciclient/tests/irmc/test_elcm.py +++ b/scciclient/tests/irmc/test_elcm.py @@ -16,6 +16,7 @@ Test class for iRMC eLCM functionality. """ +import mock from oslo_utils import encodeutils import requests from requests_mock.contrib import fixture as rm_fixture @@ -684,3 +685,247 @@ class ELCMTestCase(testtools.TestCase): session_id=session_id) self.assertIsNone(result) + + @mock.patch.object(elcm, 'elcm_profile_delete') + @mock.patch.object(elcm, 'elcm_profile_get') + @mock.patch.object(elcm, 'elcm_session_delete') + @mock.patch.object(elcm, 'elcm_session_get_status') + @mock.patch.object(elcm.time, 'sleep') + def test__process_session_bios_config_get_ok(self, mock_sleep, + mock_session_get, + mock_session_delete, + mock_profile_get, + mock_profile_delete): + session_id = 123 + expected_bios_cfg = { + 'Server': { + 'SystemConfig': { + 'BiosConfig': { + 'key1': 'val1' + } + } + } + } + mock_session_get.side_effect = [ + {'Session': {'Id': session_id, + 'Status': 'activated'}}, + {'Session': {'Id': session_id, + 'Status': 'running'}}, + {'Session': {'Id': session_id, + 'Status': 'terminated regularly'}}] + mock_profile_get.return_value = expected_bios_cfg + + result = elcm._process_session_bios_config(irmc_info=self.irmc_info, + operation='BACKUP', + session_id=session_id) + self.assertEqual(expected_bios_cfg, result['bios_config']) + + mock_session_get.assert_has_calls([ + mock.call(irmc_info=self.irmc_info, session_id=session_id), + mock.call(irmc_info=self.irmc_info, session_id=session_id), + mock.call(irmc_info=self.irmc_info, session_id=session_id)]) + mock_profile_get.assert_called_once_with( + irmc_info=self.irmc_info, + profile_name=elcm.PROFILE_BIOS_CONFIG) + + self.assertEqual(2, mock_sleep.call_count) + self.assertEqual(1, mock_session_delete.call_count) + self.assertEqual(1, mock_profile_delete.call_count) + + @mock.patch.object(elcm, 'elcm_profile_delete') + @mock.patch.object(elcm, 'elcm_profile_get') + @mock.patch.object(elcm, 'elcm_session_delete') + @mock.patch.object(elcm, 'elcm_session_get_status') + @mock.patch.object(elcm.time, 'sleep') + def test__process_session_bios_config_set_ok(self, mock_sleep, + mock_session_get, + mock_session_delete, + mock_profile_get, + mock_profile_delete): + session_id = 123 + mock_session_get.side_effect = [ + {'Session': {'Id': session_id, + 'Status': 'activated'}}, + {'Session': {'Id': session_id, + 'Status': 'running'}}, + {'Session': {'Id': session_id, + 'Status': 'terminated regularly'}}] + + elcm._process_session_bios_config(irmc_info=self.irmc_info, + operation='RESTORE', + session_id=session_id) + + mock_session_get.assert_has_calls([ + mock.call(irmc_info=self.irmc_info, session_id=session_id), + mock.call(irmc_info=self.irmc_info, session_id=session_id), + mock.call(irmc_info=self.irmc_info, session_id=session_id)]) + mock_profile_get.assert_not_called() + self.assertEqual(2, mock_sleep.call_count) + self.assertEqual(1, mock_session_delete.call_count) + self.assertEqual(1, mock_profile_delete.call_count) + + @mock.patch.object(elcm, 'elcm_profile_delete') + @mock.patch.object(elcm, 'elcm_profile_get') + @mock.patch.object(elcm, 'elcm_session_delete') + @mock.patch.object(elcm, 'elcm_session_get_status') + @mock.patch.object(elcm.time, 'sleep') + def test__process_session_bios_config_timeout(self, mock_sleep, + mock_session_get, + mock_session_delete, + mock_profile_get, + mock_profile_delete): + session_id = 123 + mock_session_get.return_value = {'Session': {'Id': session_id, + 'Status': 'running'}} + + self.assertRaises(elcm.ELCMSessionTimeout, + elcm._process_session_bios_config, + irmc_info=self.irmc_info, + operation='BACKUP', + session_id=session_id, + session_timeout=0.5) + + self.assertEqual(True, mock_sleep.called) + self.assertEqual(True, mock_session_get.called) + mock_profile_get.assert_not_called() + mock_session_delete.assert_not_called() + mock_profile_delete.assert_not_called() + + @mock.patch.object(elcm, 'elcm_profile_delete') + @mock.patch.object(elcm, 'elcm_session_delete') + @mock.patch.object(elcm, 'elcm_session_get_log') + @mock.patch.object(elcm, 'elcm_session_get_status') + def test__process_session_bios_config_error(self, + mock_session_get, + mock_session_get_log, + mock_session_delete, + mock_profile_delete): + session_id = 123 + mock_session_get.return_value = {'Session': {'Id': session_id, + 'Status': 'error'}} + + self.assertRaises(scci.SCCIClientError, + elcm._process_session_bios_config, + irmc_info=self.irmc_info, + operation='RESTORE', + session_id=session_id, + session_timeout=0.5) + + self.assertEqual(True, mock_session_get.called) + self.assertEqual(True, mock_session_get_log.called) + mock_session_delete.assert_not_called() + mock_profile_delete.assert_not_called() + + @mock.patch.object(elcm, 'elcm_profile_delete') + @mock.patch.object(elcm, 'elcm_profile_create') + @mock.patch.object(elcm, 'elcm_profile_get') + @mock.patch.object(elcm, 'elcm_session_delete') + @mock.patch.object(elcm, 'elcm_session_get_status') + @mock.patch.object(elcm.time, 'sleep') + def test_backup_bios_config_ok(self, mock_sleep, mock_session_get, + mock_session_delete, mock_profile_get, + mock_profile_create, mock_profile_delete): + session_id = 123 + expected_bios_cfg = { + 'Server': { + 'SystemConfig': { + 'BiosConfig': { + 'key1': 'val1' + } + } + } + } + mock_session_get.side_effect = [ + {'Session': {'Id': session_id, + 'Status': 'activated'}}, + {'Session': {'Id': session_id, + 'Status': 'running'}}, + {'Session': {'Id': session_id, + 'Status': 'terminated regularly'}}] + mock_profile_get.return_value = expected_bios_cfg + mock_profile_create.return_value = {'Session': {'Id': session_id, + 'Status': 'activated'}} + + result = elcm.backup_bios_config(irmc_info=self.irmc_info) + self.assertEqual(expected_bios_cfg, result['bios_config']) + + self.assertEqual(2, mock_sleep.call_count) + self.assertEqual(True, mock_session_get.called) + self.assertEqual(1, mock_session_delete.call_count) + self.assertEqual(2, mock_profile_get.call_count) + self.assertEqual(1, mock_profile_create.call_count) + self.assertEqual(2, mock_profile_delete.call_count) + + @mock.patch.object(elcm, 'elcm_profile_delete') + @mock.patch.object(elcm, 'elcm_profile_get') + @mock.patch.object(elcm, 'elcm_profile_set') + @mock.patch.object(elcm, 'elcm_session_delete') + @mock.patch.object(elcm, 'elcm_session_get_status') + @mock.patch.object(elcm.time, 'sleep') + def _test_restore_bios_config_ok(self, mock_sleep, mock_session_get, + mock_session_delete, mock_profile_set, + mock_profile_get, mock_profile_delete, + bios_cfg): + session_id = 123 + mock_session_get.side_effect = [ + {'Session': {'Id': session_id, + 'Status': 'activated'}}, + {'Session': {'Id': session_id, + 'Status': 'running'}}, + {'Session': {'Id': session_id, + 'Status': 'terminated regularly'}}] + mock_profile_set.return_value = {'Session': {'Id': session_id, + 'Status': 'activated'}} + + elcm.restore_bios_config(irmc_info=self.irmc_info, + bios_config=bios_cfg) + + self.assertEqual(2, mock_sleep.call_count) + self.assertEqual(True, mock_session_get.called) + self.assertEqual(1, mock_session_delete.call_count) + self.assertEqual(1, mock_profile_get.call_count) + self.assertEqual(1, mock_profile_set.call_count) + self.assertEqual(2, mock_profile_delete.call_count) + + def test_restore_bios_config_ok_with_dict(self): + bios_cfg = { + 'Server': { + 'SystemConfig': { + 'BiosConfig': { + 'key1': 'val1' + } + } + } + } + self._test_restore_bios_config_ok(bios_cfg=bios_cfg) + + def test_restore_bios_config_ok_with_str(self): + bios_cfg = ('{"Server":' + ' {"SystemConfig":' + ' {"BiosConfig":' + ' {' + ' "key1": "val1"' + ' }' + ' }' + ' }' + '}') + self._test_restore_bios_config_ok(bios_cfg=bios_cfg) + + def _test_restore_bios_config_invalid_input(self, bios_cfg): + self.assertRaises(scci.SCCIInvalidInputError, + elcm.restore_bios_config, + irmc_info=self.irmc_info, + bios_config=bios_cfg) + + def test_restore_bios_config_invalid_input_dict(self): + bios_cfg = { + 'Server': { + 'SystemConfig': { + } + } + } + self._test_restore_bios_config_invalid_input(bios_cfg=bios_cfg) + + def test_restore_bios_config_invalid_input_str(self): + bios_cfg = '{"key": "val"}' + self._test_restore_bios_config_invalid_input(bios_cfg=bios_cfg)