From 1d5668953f84c5f64047a8df2c002d583ca8778e Mon Sep 17 00:00:00 2001 From: Nguyen Van Trung Date: Wed, 5 Sep 2018 16:44:04 +0700 Subject: [PATCH] Support firmware upgrade for iRMC and BIOS This change will implement firmware upgrade for iRMC and BIOS along with suitable images. Tested on Fujitsu TX2540M1 successfully. Change-Id: I097997031eaf54f7634ba97d05786201fc8b1e9e --- scciclient/irmc/scci.py | 132 ++++++++++++++- scciclient/tests/irmc/test_scci.py | 263 +++++++++++++++++++++++++++++ 2 files changed, 393 insertions(+), 2 deletions(-) diff --git a/scciclient/irmc/scci.py b/scciclient/irmc/scci.py index a1bcbbd..6dc6d30 100755 --- a/scciclient/irmc/scci.py +++ b/scciclient/irmc/scci.py @@ -68,6 +68,11 @@ class SCCIRAIDNotReady(SCCIError): super(SCCIRAIDNotReady, self).__init__(message) +class SCCISessionTimeout(SCCIError): + def __init__(self, message): + super(SCCISessionTimeout, self).__init__(message) + + """ List of iRMC S4 supported SCCI commands @@ -275,13 +280,25 @@ def scci_cmd(host, userid, password, cmd, port=443, auth_method='basic', try: header = {'Content-type': 'application/x-www-form-urlencoded'} - r = requests.post(protocol + '://' + host + '/config', - data=cmd, + if kwargs.get('upgrade_type') == 'irmc': + with open(cmd, 'rb') as file: + data = file.read() + config_type = '/irmcupdate?flashSelect=255' + elif kwargs.get('upgrade_type') == 'bios': + with open(cmd, 'rb') as file: + data = file.read() + config_type = '/biosupdate' + else: + data = cmd + config_type = '/config' + r = requests.post(protocol + '://' + host + config_type, + data=data, headers=header, verify=False, timeout=client_timeout, allow_redirects=False, auth=auth_obj) + if not do_async: time.sleep(5) if DEBUG: @@ -306,6 +323,9 @@ def scci_cmd(host, userid, password, cmd, port=443, auth_method='basic', else: return r + except IOError as input_error: + raise SCCIClientError(input_error) + except ET.ParseError as parse_error: raise SCCIClientError(parse_error) @@ -560,6 +580,54 @@ def get_capabilities_properties(d_info, raise SCCIClientError('Capabilities inspection failed: %s' % err) +def process_session_status(irmc_info, session_timeout, upgrade_type): + """process session for Bios config backup/restore or RAID config operation + + :param irmc_info: node info + :param session_timeout: session timeout + :param upgrade_type: flag to check upgrade with bios or irmc + :return: a dict with following values: + { + 'upgrade_message': , + 'upgrade_status' + } + """ + session_expiration = time.time() + session_timeout + + while time.time() < session_expiration: + try: + # Get session status to check + session = get_firmware_upgrade_status(irmc_info, upgrade_type) + except SCCIClientError: + # Ignore checking during rebooted server + time.sleep(10) + continue + + status = session.find("./Value").text + severity = session.find("./Severity").text + message = session.find("./Message").text + result = {} + + if severity == 'Information' and status != '0': + if 'FLASH successful' in message: + result['upgrade_status'] = 'Complete' + return result + # Sleep a bit + time.sleep(5) + elif severity == 'Error': + result['upgrade_status'] = 'Error' + return result + else: + # Error occurred, get session log to see what happened + session_log = message + raise SCCIClientError('Failed to set firmware upgrade. ' + 'Session log is %s.' % session_log) + + else: + raise SCCISessionTimeout('Failed to time out mechanism with %s.' + % session_expiration) + + def get_raid_fgi_status(report): """Gather fgi(foreground initialization) information of raid configuration @@ -590,3 +658,63 @@ def get_raid_fgi_status(report): fgi_status.update({name: status}) return fgi_status + + +def get_firmware_upgrade_status(irmc_info, upgrade_type): + """get firmware upgrade status of bios or irmc + + :param irmc_info: dict of iRMC params to access the server node + { + 'irmc_address': host, + 'irmc_username': user_id, + 'irmc_password': password, + 'irmc_port': 80 or 443, default is 443, + 'irmc_auth_method': 'basic' or 'digest', default is 'digest', + 'irmc_client_timeout': timeout, default is 60, + ... + } + :param upgrade_type: flag to check upgrade with bios or irmc + :raises: ISCCIInvalidInputError if port and/or auth_method params + are invalid + :raises: SCCIClientError if SCCI failed + """ + + host = irmc_info.get('irmc_address') + userid = irmc_info.get('irmc_username') + password = irmc_info.get('irmc_password') + port = irmc_info.get('irmc_port', 443) + auth_method = irmc_info.get('irmc_auth_method', 'digest') + client_timeout = irmc_info.get('irmc_client_timeout', 60) + + auth_obj = None + try: + protocol = {80: 'http', 443: 'https'}[port] + auth_obj = { + 'basic': requests.auth.HTTPBasicAuth(userid, password), + 'digest': requests.auth.HTTPDigestAuth(userid, password) + }[auth_method.lower()] + except KeyError: + raise SCCIInvalidInputError( + ("Invalid port %(port)d or " + + "auth_method for method %(auth_method)s") % + {'port': port, 'auth_method': auth_method}) + try: + if upgrade_type == 'bios': + upgrade_type = '/biosprogress' + elif upgrade_type == 'irmc': + upgrade_type = '/irmcprogress' + r = requests.get(protocol + '://' + host + upgrade_type, + verify=False, + timeout=(10, client_timeout), + allow_redirects=False, + auth=auth_obj) + + if r.status_code not in (200, 201): + raise SCCIClientError( + ('HTTP PROTOCOL ERROR, STATUS CODE = %s' % + str(r.status_code))) + + upgrade_status_xml = ET.fromstring(r.text) + return upgrade_status_xml + except ET.ParseError as parse_error: + raise SCCIClientError(parse_error) diff --git a/scciclient/tests/irmc/test_scci.py b/scciclient/tests/irmc/test_scci.py index 3f1628a..2573e15 100644 --- a/scciclient/tests/irmc/test_scci.py +++ b/scciclient/tests/irmc/test_scci.py @@ -21,12 +21,18 @@ import xml.etree.ElementTree as ET import mock from requests_mock.contrib import fixture as rm_fixture +import six +import six.moves.builtins as __builtin__ import testtools from scciclient.irmc import ipmi from scciclient.irmc import scci from scciclient.irmc import snmp +if six.PY3: + import io + file = io.BytesIO + class SCCITestCase(testtools.TestCase): """Tests for SCCI @@ -912,3 +918,260 @@ class SCCITestCase(testtools.TestCase): fgi_status_expect = {'0': 'Idle'} result = scci.get_raid_fgi_status(report_fake) self.assertEqual(result, fgi_status_expect) + + @mock.patch('scciclient.irmc.scci.requests') + def test_fail_get_bios_firmware_status(self, mock_requests): + mock_requests.get.return_value = mock.Mock( + status_code=404, + text=""" + +
+
+
+
+
+ File not found +
+
+
+ + """) + upgrade_type = 'biosss' + self.assertRaises(scci.SCCIClientError, + scci.get_firmware_upgrade_status, self.irmc_info, + upgrade_type=upgrade_type) + + @mock.patch('scciclient.irmc.scci.requests') + def test_success_get_bios_firmware_status(self, mock_requests): + mock_requests.get.return_value = mock.Mock( + return_value='ok', + status_code=200, + text=""" + + 0 + Information + No Error + """) + expected_status = "0" + expected_severity = "Information" + expected_message = "No Error" + upgrade_type = 'bios' + result = scci.get_firmware_upgrade_status(self.irmc_info, upgrade_type) + self.assertEqual(expected_status, result.find("./Value").text) + self.assertEqual(expected_severity, result.find("./Severity").text) + self.assertEqual(expected_message, result.find("./Message").text) + + @mock.patch('scciclient.irmc.scci.requests') + def test_fail_get_irmc_firmware_status(self, mock_requests): + mock_requests.get.return_value = mock.Mock( + status_code=404, + text=""" + +
+
+
+
+
+ File not found +
+
+
+ + """) + upgrade_type = 'irmcccc' + self.assertRaises(scci.SCCIClientError, + scci.get_firmware_upgrade_status, self.irmc_info, + upgrade_type=upgrade_type) + + @mock.patch('scciclient.irmc.scci.requests') + def test_success_get_irmc_firmware_status(self, mock_requests): + mock_requests.get.return_value = mock.Mock( + return_value='ok', + status_code=200, + text=""" + + 0 + Information + No Error + """) + expected_status = "0" + expected_severity = "Information" + expected_message = "No Error" + upgrade_type = 'irmc' + result = scci.get_firmware_upgrade_status(self.irmc_info, upgrade_type) + self.assertEqual(expected_status, result.find("./Value").text) + self.assertEqual(expected_severity, result.find("./Severity").text) + self.assertEqual(expected_message, result.find("./Message").text) + + @mock.patch('scciclient.irmc.scci.get_firmware_upgrade_status') + def test_failed_process_session_bios_status(self, mock_status): + session_timeout = 180 + upgrade_type = 'bios' + # Fake status from server + status_fake = ET.Element(self) + status_fake.append(ET.Element("Value", name="Value")) + status_fake.append(ET.Element("Severity", name="Severity")) + status_fake.append(ET.Element("Message", name="Message")) + status_fake.find("./Value").text = '0' + status_fake.find("./Severity").text = 'Error' + status_fake.find("./Message").text = 'File not provided' + + mock_status.return_value = status_fake + expected_status = 'Error' + result = scci.process_session_status(self.irmc_info, session_timeout, + upgrade_type) + self.assertEqual(expected_status, result['upgrade_status']) + + @mock.patch('scciclient.irmc.scci.get_firmware_upgrade_status') + def test_success_process_session_bios_status(self, mock_status): + session_timeout = 180 + upgrade_type = 'bios' + # Fake status from server + status_fake = ET.Element(self) + status_fake.append(ET.Element("Value", name="Value")) + status_fake.append(ET.Element("Severity", name="Severity")) + status_fake.append(ET.Element("Message", name="Message")) + status_fake.find("./Value").text = '9' + status_fake.find("./Severity").text = 'Information' + status_fake.find("./Message").text = 'FLASH successful' + + mock_status.return_value = status_fake + expected_status = 'Complete' + result = scci.process_session_status(self.irmc_info, session_timeout, + upgrade_type) + self.assertEqual(expected_status, result['upgrade_status']) + + @mock.patch('scciclient.irmc.scci.get_firmware_upgrade_status') + def test_failed_process_session_irmc_status(self, mock_status): + session_timeout = 180 + upgrade_type = 'irmc' + # Fake status from server + status_fake = ET.Element(self) + status_fake.append(ET.Element("Value", name="Value")) + status_fake.append(ET.Element("Severity", name="Severity")) + status_fake.append(ET.Element("Message", name="Message")) + status_fake.find("./Value").text = '0' + status_fake.find("./Severity").text = 'Error' + status_fake.find("./Message").text = 'File not provided' + + mock_status.return_value = status_fake + expected_status = 'Error' + result = scci.process_session_status(self.irmc_info, session_timeout, + upgrade_type) + self.assertEqual(expected_status, result['upgrade_status']) + + @mock.patch('scciclient.irmc.scci.get_firmware_upgrade_status') + def test_success_process_session_irmc_status(self, mock_status): + session_timeout = 180 + upgrade_type = 'irmc' + # Fake status from server + status_fake = ET.Element(self) + status_fake.append(ET.Element("Value", name="Value")) + status_fake.append(ET.Element("Severity", name="Severity")) + status_fake.append(ET.Element("Message", name="Message")) + status_fake.find("./Value").text = '9' + status_fake.find("./Severity").text = 'Information' + status_fake.find("./Message").text = 'FLASH successful' + + mock_status.return_value = status_fake + expected_status = 'Complete' + result = scci.process_session_status(self.irmc_info, session_timeout, + upgrade_type) + self.assertEqual(expected_status, result['upgrade_status']) + + @mock.patch.object(__builtin__, 'open', autospec=True) + def test_create_bios_firmware_upgrade(self, open_mock): + upgrade_type = 'bios' + self.requests_mock.post("http://" + self.irmc_address + "/biosupdate", + text=""" + + 0 + Information + No Error + """) + bios_input = '/media/DATA/D3099-B1.UPC' + open_mock.return_value = mock.mock_open(read_data="file").return_value + client = scci.get_client(self.irmc_address, + self.irmc_username, + self.irmc_password, + port=self.irmc_port, + auth_method=self.irmc_auth_method, + client_timeout=self.irmc_client_timeout, + upgrade_type=upgrade_type) + r = client(bios_input) + self.assertEqual(r.status_code, 200) + + @mock.patch.object(__builtin__, 'open', side_effect=IOError, autospec=True) + def test_create_fail_bios_firmware_upgrade(self, open_mock): + upgrade_type = 'bios' + self.requests_mock.post("http://" + self.irmc_address + "/biosupdate", + text=""" + + 6 + Error + File not provided + + """) + # Fake wrong file directory + bios_input = '/media/DATA/D3099-B101.UPC' + open_mock.return_value = mock.mock_open(read_data="file").return_value + client = scci.get_client(self.irmc_address, + self.irmc_username, + self.irmc_password, + port=self.irmc_port, + auth_method=self.irmc_auth_method, + client_timeout=self.irmc_client_timeout, + upgrade_type=upgrade_type) + + self.assertRaises(scci.SCCIClientError, client, bios_input) + + @mock.patch.object(__builtin__, 'open', autospec=True) + def test_create_irmc_firmware_upgrade(self, open_mock): + upgrade_type = 'irmc' + self.requests_mock.post("http://" + self.irmc_address + + "/irmcupdate?flashSelect=255", + text=""" + + 0 + Information + No Error + """) + irmc_input = '/media/DATA/TX2540M1.bin' + open_mock.return_value = mock.mock_open(read_data="file").return_value + client = scci.get_client(self.irmc_address, + self.irmc_username, + self.irmc_password, + port=self.irmc_port, + auth_method=self.irmc_auth_method, + client_timeout=self.irmc_client_timeout, + upgrade_type=upgrade_type) + r = client(irmc_input) + self.assertEqual(r.status_code, 200) + + @mock.patch.object(__builtin__, 'open', side_effect=IOError, autospec=True) + def test_create_fail_irmc_firmware_upgrade(self, open_mock): + upgrade_type = 'irmc' + self.requests_mock.post("http://" + self.irmc_address + + "/irmcupdate?flashSelect=255", + text=""" + + 6 + Error + File not provided + + """) + # Fake wrong file directory + irmc_input = '/media/DATA/TX2540M1111.bin' + mock_file_handle = mock.MagicMock(spec=file) + open_mock.return_value = mock_file_handle + client = scci.get_client(self.irmc_address, + self.irmc_username, + self.irmc_password, + port=self.irmc_port, + auth_method=self.irmc_auth_method, + client_timeout=self.irmc_client_timeout, + upgrade_type=upgrade_type) + + self.assertRaises(scci.SCCIClientError, client, irmc_input)