From c0ca2d71265c4a256bd32b734bfd5d66816e7982 Mon Sep 17 00:00:00 2001 From: Aparna Date: Wed, 15 Mar 2017 10:45:54 +0000 Subject: [PATCH] Support to return logs for hpsum firmware update This commit adds supports to return the log files for the hpsum based firmware update as gzipped and base64 encoded string. Closes-Bug: #1673037 Change-Id: Ie9f165c75e6b47be29927864cbdf2d8b050ef887 --- proliantutils/hpsum/hpsum_controller.py | 40 ++++++-- .../tests/hpsum/test_hpsum_controller.py | 94 +++++++++++++++---- requirements.txt | 1 + 3 files changed, 109 insertions(+), 26 deletions(-) diff --git a/proliantutils/hpsum/hpsum_controller.py b/proliantutils/hpsum/hpsum_controller.py index e00f9a1..a101cf1 100644 --- a/proliantutils/hpsum/hpsum_controller.py +++ b/proliantutils/hpsum/hpsum_controller.py @@ -14,23 +14,28 @@ import fnmatch +import io import os import re import shutil +import tarfile import tempfile import time from oslo_concurrency import processutils +from oslo_serialization import base64 from proliantutils import exception from proliantutils.ilo import client from proliantutils import utils -OUTPUT_FILE = '/var/hp/log/localhost/hpsum_log.txt' - HPSUM_LOCATION = 'hp/swpackages/hpsum' +# List of log files created by hpsum based firmware update. +OUTPUT_FILES = ['/var/hp/log/localhost/hpsum_log.txt', + '/var/hp/log/localhost/hpsum_detail_log.txt'] + EXIT_CODE_TO_STRING = { 0: "The smart component was installed successfully.", 1: ("The smart component was installed successfully, but the system " @@ -70,6 +75,22 @@ def _execute_hpsum(hpsum_file_path, components=None): raise exception.HpsumOperationError(reason=msg) +def _get_log_file_data_as_encoded_content(): + """Gzip and base64 encode files and BytesIO buffers. + + This method gets the log files created by hpsum based + firmware update and tar zip the files. + :returns: A gzipped and base64 encoded string as text. + """ + with io.BytesIO() as fp: + with tarfile.open(fileobj=fp, mode='w:gz') as tar: + for f in OUTPUT_FILES: + tar.add(f) + + fp.seek(0) + return base64.encode_as_bytes(fp.getvalue()) + + def _parse_hpsum_ouput(exit_code): """Parse the hpsum output log file. @@ -88,8 +109,8 @@ def _parse_hpsum_ouput(exit_code): return "Summary: %s" % EXIT_CODE_TO_STRING.get(exit_code) if exit_code in (0, 1, 253): - if os.path.exists(OUTPUT_FILE): - with open(OUTPUT_FILE, 'r') as f: + if os.path.exists(OUTPUT_FILES[0]): + with open(OUTPUT_FILES[0], 'r') as f: output_data = f.read() ret_data = output_data[(output_data.find('Deployed Components:') + @@ -105,12 +126,15 @@ def _parse_hpsum_ouput(exit_code): else: success += 1 - return ("Summary: %(return_string)s Status of updated components:" - " Total: %(total)s Success: %(success)s Failed: " - "%(failed)s." % + return { + 'Summary': ( + "%(return_string)s Status of updated components: Total: " + "%(total)s Success: %(success)s Failed: %(failed)s." % {'return_string': EXIT_CODE_TO_STRING.get(exit_code), 'total': (success + failed), 'success': success, - 'failed': failed}) + 'failed': failed}), + 'Log Data': _get_log_file_data_as_encoded_content() + } return "UPDATE STATUS: UNKNOWN" diff --git a/proliantutils/tests/hpsum/test_hpsum_controller.py b/proliantutils/tests/hpsum/test_hpsum_controller.py index b66da37..abcbb4f 100644 --- a/proliantutils/tests/hpsum/test_hpsum_controller.py +++ b/proliantutils/tests/hpsum/test_hpsum_controller.py @@ -14,10 +14,12 @@ import os import shutil +import tarfile import tempfile import mock from oslo_concurrency import processutils +from oslo_serialization import base64 import testtools from proliantutils import exception @@ -43,19 +45,25 @@ class HpsumFirmwareUpdateTest(testtools.TestCase): self.node = {'driver_info': self.info, 'clean_step': clean_step} + @mock.patch.object(hpsum_controller, + '_get_log_file_data_as_encoded_content') @mock.patch.object(hpsum_controller, 'open', mock.mock_open(read_data=constants.HPSUM_OUTPUT_DATA)) @mock.patch.object(os.path, 'exists') @mock.patch.object(processutils, 'execute') - def test_execute_hpsum(self, execute_mock, exists_mock): + def test_execute_hpsum(self, execute_mock, exists_mock, log_mock): exists_mock.return_value = True + log_mock.return_value = "aaabbbcccdddd" value = ("hpsum_service_x64 started successfully. Sending Shutdown " "request to engine. Successfully shutdown the service.") execute_mock.side_effect = processutils.ProcessExecutionError( stdout=value, stderr=None, exit_code=0) - ret_value = ("Summary: The smart component was installed successfully." - " Status of updated components: Total: 2 Success: 2 " - "Failed: 0.") + ret_value = { + 'Log Data': 'aaabbbcccdddd', + 'Summary': ("The smart component was installed successfully." + " Status of updated components: Total: 2 Success: 2 " + "Failed: 0.") + } stdout = hpsum_controller._execute_hpsum("hpsum", components=None) @@ -78,15 +86,23 @@ class HpsumFirmwareUpdateTest(testtools.TestCase): "hpsum", "--s", "--romonly", " --c foo --c bar") self.assertEqual(ret_value, stdout) + @mock.patch.object(hpsum_controller, + '_get_log_file_data_as_encoded_content') @mock.patch.object( hpsum_controller, 'open', mock.mock_open(read_data=constants.HPSUM_OUTPUT_DATA_FAILURE)) @mock.patch.object(os.path, 'exists') @mock.patch.object(processutils, 'execute') - def test_execute_hpsum_update_fails(self, execute_mock, exists_mock): + def test_execute_hpsum_update_fails(self, execute_mock, exists_mock, + log_mock): exists_mock.return_value = True - ret = ("Summary: The installation of the component failed. Status " - "of updated components: Total: 2 Success: 1 Failed: 1.") + log_mock.return_value = "aaabbbcccdddd" + ret = { + 'Log Data': 'aaabbbcccdddd', + 'Summary': ("The installation of the component failed. Status " + "of updated components: Total: 2 Success: 1 " + "Failed: 1.") + } value = ("hpsum_service_x64 started successfully. Sending Shutdown " "request to engine. Successfully shutdown the service.") execute_mock.side_effect = processutils.ProcessExecutionError( @@ -111,6 +127,27 @@ class HpsumFirmwareUpdateTest(testtools.TestCase): None) self.assertIn(value, str(ex)) + def test_get_log_file_data_as_encoded_content(self): + log_file_content = b'Sample Data for testing hpsum log output' + file_object = tempfile.NamedTemporaryFile(delete=False) + file_object.write(log_file_content) + file_object.close() + hpsum_controller.OUTPUT_FILES = [file_object.name] + + base64_encoded_text = (hpsum_controller. + _get_log_file_data_as_encoded_content()) + + tar_gzipped_content = base64.decode_as_bytes(base64_encoded_text) + tar_file = tempfile.NamedTemporaryFile(suffix='.tar.gz', delete=False) + tar_file.write(tar_gzipped_content) + tar_file.close() + + with tarfile.open(name=tar_file.name) as tar: + f = tar.extractfile(file_object.name.lstrip('/')) + self.assertEqual(log_file_content, f.read()) + os.remove(file_object.name) + os.remove(tar_file.name) + @mock.patch.object(utils, 'validate_href') @mock.patch.object(utils, 'verify_image_checksum') @mock.patch.object(hpsum_controller, '_execute_hpsum') @@ -150,6 +187,19 @@ class HpsumFirmwareUpdateTest(testtools.TestCase): rmtree_mock.assert_called_once_with("/tempdir", ignore_errors=True) self.assertEqual('SUCCESS', ret_val) + @mock.patch.object(utils, 'validate_href') + def test_update_firmware_throws_for_nonexistent_file(self, + validate_href_mock): + invalid_file_path = '/some/invalid/file/path' + value = ("Got HTTP code 503 instead of 200 in response to " + "HEAD request.") + validate_href_mock.side_effect = exception.ImageRefValidationFailed( + reason=value, image_href=invalid_file_path) + + exc = self.assertRaises(exception.HpsumOperationError, + hpsum_controller.update_firmware, self.node) + self.assertIn(value, str(exc)) + @mock.patch.object(utils, 'validate_href') @mock.patch.object(ilo_client, 'IloClient', spec_set=True, autospec=True) def test_update_firmware_vmedia_attach_fails(self, client_mock, @@ -219,33 +269,41 @@ class HpsumFirmwareUpdateTest(testtools.TestCase): 'CDROM') exists_mock.assert_called_once_with("/dev/disk/by-label/SPP_LABEL") + @mock.patch.object(hpsum_controller, + '_get_log_file_data_as_encoded_content') @mock.patch.object(hpsum_controller, 'open', mock.mock_open(read_data=constants.HPSUM_OUTPUT_DATA)) @mock.patch.object(os.path, 'exists') - def test__parse_hpsum_ouput(self, exists_mock): + def test__parse_hpsum_ouput(self, exists_mock, log_mock): exists_mock.return_value = True - expt_ret = ("Summary: The smart component was installed successfully. " - "Status of updated components: Total: 2 Success: 2 " - "Failed: 0.") + log_mock.return_value = "aaabbbcccdddd" + expt_ret = {'Log Data': 'aaabbbcccdddd', + 'Summary': ("The smart component was installed " + "successfully. Status of updated components: " + "Total: 2 Success: 2 Failed: 0.")} ret = hpsum_controller._parse_hpsum_ouput(0) - exists_mock.assert_called_once_with(hpsum_controller.OUTPUT_FILE) + exists_mock.assert_called_once_with(hpsum_controller.OUTPUT_FILES[0]) self.assertEqual(expt_ret, ret) + @mock.patch.object(hpsum_controller, + '_get_log_file_data_as_encoded_content') @mock.patch.object( hpsum_controller, 'open', mock.mock_open(read_data=constants.HPSUM_OUTPUT_DATA_FAILURE)) @mock.patch.object(os.path, 'exists') - def test__parse_hpsum_ouput_some_failed(self, exists_mock): + def test__parse_hpsum_ouput_some_failed(self, exists_mock, log_mock): exists_mock.return_value = True - expt_ret = ("Summary: The installation of the component failed. " - "Status of updated components: Total: 2 Success: 1 " - "Failed: 1.") + log_mock.return_value = "aaabbbcccdddd" + expt_ret = {'Log Data': 'aaabbbcccdddd', + 'Summary': ("The installation of the component failed. " + "Status of updated components: Total: 2 " + "Success: 1 Failed: 1.")} ret = hpsum_controller._parse_hpsum_ouput(253) - exists_mock.assert_called_once_with(hpsum_controller.OUTPUT_FILE) + exists_mock.assert_called_once_with(hpsum_controller.OUTPUT_FILES[0]) self.assertEqual(expt_ret, ret) @mock.patch.object(os.path, 'exists') @@ -255,5 +313,5 @@ class HpsumFirmwareUpdateTest(testtools.TestCase): ret = hpsum_controller._parse_hpsum_ouput(1) - exists_mock.assert_called_once_with(hpsum_controller.OUTPUT_FILE) + exists_mock.assert_called_once_with(hpsum_controller.OUTPUT_FILES[0]) self.assertEqual(expt_ret, ret) diff --git a/requirements.txt b/requirements.txt index 3ef704e..25420ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ pbr>=2.0.0 # Apache-2.0 six>=1.9.0 # MIT oslo.concurrency>=3.8.0 # Apache-2.0 +oslo.serialization>=1.10.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0