Implement backup/restore bios config functionality using eLCM

This implements backup/restore functions for bios config using
eLCM api.

Related-Bug: #1639688

Change-Id: I8d4bd0d7508873e892b010597f87ca76b045ad93
This commit is contained in:
Dao Cong Tien
2016-12-27 14:23:18 +07:00
parent 689212e270
commit 4bb308734e
3 changed files with 442 additions and 0 deletions

View File

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

View File

@@ -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': <data in case of BACKUP operation>,
'warning': <warning message if there is>
}
"""
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': <bios config data>,
'warning': <warning message if there is>
}
"""
# 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)

View File

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