From afe6ac047d54e012052f85be06e11c55f0e9c049 Mon Sep 17 00:00:00 2001 From: john feng Date: Tue, 14 Nov 2023 23:15:51 -0800 Subject: [PATCH] refactor the testCoreMaintruncation into statushandlertruncation --- .../src/service_interfaces/StatusHandler.py | 3 +- src/core/tests/Test_CoreMainTruncation.py | 780 ---------- .../tests/Test_StatusHandlerTruncation.py | 1324 +++++++---------- 3 files changed, 560 insertions(+), 1547 deletions(-) delete mode 100644 src/core/tests/Test_CoreMainTruncation.py diff --git a/src/core/src/service_interfaces/StatusHandler.py b/src/core/src/service_interfaces/StatusHandler.py index 1d565dda..be6e98f6 100644 --- a/src/core/src/service_interfaces/StatusHandler.py +++ b/src/core/src/service_interfaces/StatusHandler.py @@ -754,6 +754,7 @@ def add_error_to_status(self, message, error_code=Constants.PatchOperationErrorC current_operation = self.__current_operation if current_operation_override_for_error == Constants.DEFAULT_UNSPECIFIED_VALUE else current_operation_override_for_error if current_operation == Constants.ASSESSMENT: + print('did this get called', current_operation) if self.__try_add_error(self.__assessment_errors, error_detail): self.__assessment_total_error_count += 1 # retain previously set status and code for assessment substatus @@ -932,7 +933,7 @@ def __apply_truncation_process(self, assessment_packages, installation_packages, max_package_list_capacity = max_package_list_capacity - self.__calc_package_payload_size_on_disk(min_assessment_patches_to_retain) # Apply high priority (Failed, Installed) and low priority (Pending, Excluded, Not_Selected) installation logic, and keep min 5 assessment packages - if low_pri_index: + if low_pri_index is not None: installation_high_pri = installation_packages[:low_pri_index] installation_low_pri = installation_packages[low_pri_index:] diff --git a/src/core/tests/Test_CoreMainTruncation.py b/src/core/tests/Test_CoreMainTruncation.py deleted file mode 100644 index b520ca18..00000000 --- a/src/core/tests/Test_CoreMainTruncation.py +++ /dev/null @@ -1,780 +0,0 @@ -# Copyright 2020 Microsoft Corporation -# -# 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. -# -# Requires Python 2.7+ -import json -import os -import random -import re -import unittest -from core.src.CoreMain import CoreMain -from core.src.bootstrap.Constants import Constants -from core.tests.library.ArgumentComposer import ArgumentComposer -from core.tests.library.RuntimeCompositor import RuntimeCompositor - - -class TestCoreMainTruncation(unittest.TestCase): - def setUp(self): - # Had to move runtime init and stop to individual test functions, since every test uses a different maintenance_run_id which has to be set before runtime init - # self.argument_composer = ArgumentComposer().get_composed_arguments() - # self.runtime = RuntimeCompositor(self.argument_composer, True, package_manager_name=Constants.ZYPPER) - # self.container = self.runtime.container - pass - - def tearDown(self): - # self.runtime.stop() - pass - - def test_only_assessment_packages_truncation_under_size_limit(self): - """ Perform no truncation on assessment packages list. - Expecting: - assessment substatus status: success, - no assessment tombstone records, - assessment errors code: 0 (success), - assessment errors details code: 0 (success). """ - - argument_composer = ArgumentComposer() - argument_composer.operation = Constants.ASSESSMENT - runtime = RuntimeCompositor(argument_composer.get_composed_arguments(), True, Constants.ZYPPER) - runtime.set_legacy_test_type('HappyPath') - CoreMain(argument_composer.get_composed_arguments()) - - # Check telemetry events - self.__check_telemetry_events(runtime) - - # HappyPath has already added 3 packages under assessment, we are adding more (anywhere between 200-432) which will still keep the status file size under AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES, thereby no truncation will take place - # {\"patchId\": \"kernel-default_4.4.49-92.11.1_Ubuntu_16.04\", \"name\": \"kernel-default\", \"version\": \"4.4.49-92.11.1\", \"classifications\": [\"Security\"]}, - # {\"patchId\": \"libgcc_5.60.7-8.1_Ubuntu_16.04\", \"name\": \"libgcc\", \"version\": \"5.60.7-8.1\", \"classifications\": [\"Other\"]}, - # {\"patchId\": \"libgoa-1_0-0_3.20.5-9.6_Ubuntu_16.04\", \"name\": \"libgoa-1_0-0\", \"version\": \"3.20.5-9.6\", \"classifications\": [\"Other\"]} - patch_count = random.randint(200, 432) - test_packages, test_package_versions = self.__set_up_packages_func(patch_count) - runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions) - runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) - - # Assert complete status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - self.assertTrue(len(json.dumps(substatus_file_data)) < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.__assert_patch_summary_from_status(substatus_file_data, Constants.ASSESSMENT, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count+3, error_count=0) - - # Assert truncated status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - message = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) - self.assertTrue(len(json.dumps(substatus_file_data)) < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertEqual(substatus_file_data[0]["status"]["substatus"][0]["name"], Constants.PATCH_ASSESSMENT_SUMMARY,) - self.assertEqual(substatus_file_data[0]["status"]["substatus"][0]["status"], Constants.STATUS_SUCCESS.lower()) - self.assertEqual(len(message["patches"]), patch_count + 3) - self.assertNotEqual(message["patches"][-1]['patchId'], "Truncated_patch_list_id") - self.assertTrue('Truncated_patch_list_id' not in message["patches"][-1]['name']) - self.assertEqual(message["errors"]["code"], 0) - self.assertEqual(len(message["errors"]["details"]), 0) - self.assertFalse("review this log file on the machine" in message) - runtime.stop() - - def test_only_assessment_packages_truncation_over_size_limit(self): - """ Perform truncation on assessment packages list. - Expecting: - assessment substatus status: warning, - assessment tombstone records, - assessment errors code: 2 (warning), - assessment errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ - - argument_composer = ArgumentComposer() - argument_composer.operation = Constants.ASSESSMENT - runtime = RuntimeCompositor(argument_composer.get_composed_arguments(), True, Constants.ZYPPER) - runtime.set_legacy_test_type('HappyPath') - CoreMain(argument_composer.get_composed_arguments()) - - # Check telemetry events - self.__check_telemetry_events(runtime) - - # HappyPath has already added 3 packages under assessment, we are adding more (anywhere between 780-1000) which will make the status file size over AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES, thereby truncation will take place - # {\"patchId\": \"kernel-default_4.4.49-92.11.1_Ubuntu_16.04\", \"name\": \"kernel-default\", \"version\": \"4.4.49-92.11.1\", \"classifications\": [\"Security\"]}, - # {\"patchId\": \"libgcc_5.60.7-8.1_Ubuntu_16.04\", \"name\": \"libgcc\", \"version\": \"5.60.7-8.1\", \"classifications\": [\"Other\"]}, - # {\"patchId\": \"libgoa-1_0-0_3.20.5-9.6_Ubuntu_16.04\", \"name\": \"libgoa-1_0-0\", \"version\": \"3.20.5-9.6\", \"classifications\": [\"Other\"]} - - patch_count = random.randint(780, 1000) - test_packages, test_package_versions = self.__set_up_packages_func(patch_count) - runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions, "Critical") - runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) - - # Assert complete status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - assessment_msg = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) - self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.__assert_patch_summary_from_status(substatus_file_data, Constants.ASSESSMENT, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count + 3, error_count=0) - - # Assert assessment truncated status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - message_patches = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["patches"] - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(len(message_patches) < patch_count + 3) - - self.__assert_patch_summary_from_status(substatus_file_data, Constants.ASSESSMENT, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_WARNING) - - # Assert assessment truncated error - self.__assert_truncated_error(substatus_file_data[0]["status"]["substatus"][0], error_count=0) - - # Assert all assessment fields in the message json object are equal in both status files - self.__assert_assessment_truncated_msg_fields(assessment_msg, json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])) - runtime.stop() - - def test_only_assessment_packages_truncation_large_size_limit_for_extra_chars(self): - """ Perform truncation on ver large assessment packages list for time performance. - Expecting: - assessment substatus status: warning, - assessment tombstone records, - assessment errors code: 2 (warning), - assessment errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ - - argument_composer = ArgumentComposer() - argument_composer.operation = Constants.ASSESSMENT - runtime = RuntimeCompositor(argument_composer.get_composed_arguments(), True, Constants.ZYPPER) - runtime.set_legacy_test_type('HappyPath') - CoreMain(argument_composer.get_composed_arguments()) - - # Check telemetry events - self.__check_telemetry_events(runtime) - - # # HappyPath has already added 3 packages under assessment, we are adding more (anywhere between 99997) which will make the status file size over AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES, thereby truncation will take place - # {\"patchId\": \"kernel-default_4.4.49-92.11.1_Ubuntu_16.04\", \"name\": \"kernel-default\", \"version\": \"4.4.49-92.11.1\", \"classifications\": [\"Security\"]}, - # {\"patchId\": \"libgcc_5.60.7-8.1_Ubuntu_16.04\", \"name\": \"libgcc\", \"version\": \"5.60.7-8.1\", \"classifications\": [\"Other\"]}, - # {\"patchId\": \"libgoa-1_0-0_3.20.5-9.6_Ubuntu_16.04\", \"name\": \"libgoa-1_0-0\", \"version\": \"3.20.5-9.6\", \"classifications\": [\"Other\"]} - - patch_count = 99997 - test_packages, test_package_versions = self.__set_up_packages_func(patch_count) - runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions, "Security") - runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) - - # Assert complete status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - assessment_msg = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) - self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.__assert_patch_summary_from_status(substatus_file_data, Constants.ASSESSMENT, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count + 3, error_count=0) - - # Assert assessment truncated status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - message_patches = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["patches"] - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(len(message_patches) < patch_count + 3) - - self.__assert_patch_summary_from_status(substatus_file_data, Constants.ASSESSMENT, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_WARNING) - - # Assert assessment truncated error - self.__assert_truncated_error(substatus_file_data[0]["status"]["substatus"][0], error_count=0) - - # Assert all assessment fields in the message json object are equal in both status files - self.__assert_assessment_truncated_msg_fields(assessment_msg, json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])) - runtime.stop() - - def test_both_assessment_and_installation_truncation_over_size_limit(self): - """ Perform truncation on assessment packages list. - Expecting: - assessment substatus status: warning, - assessment tombstone records and message json fields match with pre-truncation message json fields, - assessment errors code: 2 (warning), - assessment errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. - - Perform truncation on installation packages list. - Expecting: - installation substatus status: warning, - installation tombstone records and message json fields match with pre-truncation message json fields, - installation errors code: 2 (warning), - installation errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ - - argument_composer = ArgumentComposer() - argument_composer.operation = Constants.INSTALLATION - runtime = RuntimeCompositor(argument_composer.get_composed_arguments(), True, Constants.ZYPPER) - runtime.set_legacy_test_type('SuccessInstallPath') - CoreMain(argument_composer.get_composed_arguments()) - - # Check telemetry events - self.__check_telemetry_events(runtime) - - # SuccessInstallPath add 2 additional packages - # {\"patchId\": \"kernel-default_4.4.49-92.11.1_Ubuntu_16.04\", \"name\": \"kernel-default\", \"version\": \"4.4.49-92.11.1\", \"classifications\": [\"Other\"]}, - # {\"patchId\": \"libgoa-1_0-0_3.20.5-9.6_Ubuntu_16.04\", \"name\": \"libgoa-1_0-0\", \"version\": \"3.20.5-9.6\", \"classifications\": [\"Other\"]} - - patch_count_for_assessment = random.randint(798, 1100) - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_assessment) - runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions) - runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) - - patch_count_for_installation = random.randint(500, 1100) - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_installation) - runtime.status_handler.set_package_install_status(test_packages, test_package_versions) - runtime.status_handler.set_installation_substatus_json(status=Constants.STATUS_SUCCESS) - - # Assert complete status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - assessment_msg = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) - installation_msg = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"]) - self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - # Assert assessment summary - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count_for_assessment + 2, error_count=0) - - # Assert installation summary - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][1], patch_count_for_installation + 2, error_count=0) - - # Assert truncated status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - assessment_msg_patches = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["patches"] - installation_msg_patches = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"])["patches"] - - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(len(assessment_msg_patches) + len(installation_msg_patches) < patch_count_for_assessment + 2 + patch_count_for_installation + 2) - # Assert assessment truncation - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_WARNING) - - # Assert assessment truncated error - self.__assert_truncated_error(substatus_file_data[0]["status"]["substatus"][0], error_count=0) - - # Assert all assessment fields in the message json object are equal in both status files - self.__assert_assessment_truncated_msg_fields(assessment_msg, json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])) - - # Assert installation truncation - installation_truncated_substatus = substatus_file_data[0]["status"]["substatus"][1] - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_WARNING) - - # Assert installation truncated error - self.__assert_truncated_error(installation_truncated_substatus, error_count=0) - - # Assert all installation fields in the message json object are equal in both status files - self.__assert_installation_truncated_msg_fields(installation_msg, json.loads(installation_truncated_substatus["formattedMessage"]["message"])) - runtime.stop() - - def test_only_installation_packages_truncation_keep_min_5_assessment_size_limit(self): - """ Perform no truncation on assessment packages list. - Expecting: - assessment substatus status: success, - no assessment tombstone records but keep 5 assessment packages, - assessment errors code: 0 (success), - assessment errors details code: 0 (success). - - Perform truncation on installation packages list. - Expecting: - installation substatus status: warning, - installation tombstone records, - installation errors code: 2 (warning), - installation errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ - - argument_composer = ArgumentComposer() - argument_composer.operation = Constants.INSTALLATION - runtime = RuntimeCompositor(argument_composer.get_composed_arguments(), True, Constants.ZYPPER) - runtime.set_legacy_test_type('SuccessInstallPath') - CoreMain(argument_composer.get_composed_arguments()) - - # check telemetry events - self.__check_telemetry_events(runtime) - - # SuccessInstallPath add 2 additional packages - # {\"patchId\": \"kernel-default_4.4.49-92.11.1_Ubuntu_16.04\", \"name\": \"kernel-default\", \"version\": \"4.4.49-92.11.1\", \"classifications\": [\"Other\"]}, - # {\"patchId\": \"libgoa-1_0-0_3.20.5-9.6_Ubuntu_16.04\", \"name\": \"libgoa-1_0-0\", \"version\": \"3.20.5-9.6\", \"classifications\": [\"Other\"]} - - patch_count_for_assessment = 3 - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_assessment) - runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions) - runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) - - patch_count_for_installation = 1000 - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_installation) - runtime.status_handler.set_package_install_status(test_packages, test_package_versions) - runtime.status_handler.set_installation_substatus_json(status=Constants.STATUS_SUCCESS) - - # Test Complete status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - installation_msg = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"]) - self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - # Assert assessment summary - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count_for_assessment + 2, error_count=0) - - # Assert installation summary - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][1], patch_count_for_installation + 2, error_count=0) - - # Assert truncated status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - assessment_msg_patches = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["patches"] - installation_msg_patches = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"])["patches"] - - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue((len(assessment_msg_patches) + len(installation_msg_patches)) < (patch_count_for_assessment + 2 + patch_count_for_installation + 2)) - - # Assert assessment truncation keep 5 - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count_for_assessment + 2, error_count=0) - - # Assert installation truncation - installation_truncated_substatus = substatus_file_data[0]["status"]["substatus"][1] - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_WARNING) - - # Assert installation truncated error - self.__assert_truncated_error(installation_truncated_substatus, error_count=0) - - # Assert all installation fields in the message json object are equal in both status files - self.__assert_installation_truncated_msg_fields(installation_msg, json.loads(installation_truncated_substatus["formattedMessage"]["message"])) - runtime.stop() - - def test_both_assessment_and_installation_truncation_with_only_install_packages_over_size_limit(self): - """ Installation packages with install status are consider high priority because it's over size limit (126kb), therefore truncation will apply and keep 5 assessment packages - Perform truncation on assessment packages list. - Expecting: - assessment substatus status: warning, - assessment tombstone records, - assessment errors code: 2 (warning), - assessment errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. - - Perform truncation on installation with only 'install' status packages. - Expecting: - installation substatus status: warning, - installation tombstone records, - installation errors code: 2 (warning), - installation errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ - - argument_composer = ArgumentComposer() - argument_composer.operation = Constants.INSTALLATION - runtime = RuntimeCompositor(argument_composer.get_composed_arguments(), True, Constants.ZYPPER) - runtime.set_legacy_test_type('SuccessInstallPath') - CoreMain(argument_composer.get_composed_arguments()) - - # check telemetry events - self.__check_telemetry_events(runtime) - - # SuccessInstallPath add 2 additional packages - # {\"patchId\": \"kernel-default_4.4.49-92.11.1_Ubuntu_16.04\", \"name\": \"kernel-default\", \"version\": \"4.4.49-92.11.1\", \"classifications\": [\"Other\"]}, - # {\"patchId\": \"libgoa-1_0-0_3.20.5-9.6_Ubuntu_16.04\", \"name\": \"libgoa-1_0-0\", \"version\": \"3.20.5-9.6\", \"classifications\": [\"Other\"]} - - patch_count_for_assessment = 7 - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_assessment) - runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions) - runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) - - patch_count_for_installation = 1000 - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_installation) - runtime.status_handler.set_package_install_status(test_packages, test_package_versions, Constants.INSTALLED) - runtime.status_handler.set_installation_substatus_json(status=Constants.STATUS_SUCCESS) - - # Assert complete status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - assessment_msg = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) - installation_msg = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"]) - self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - # Assert assessment summary - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count_for_assessment + 2, error_count=0) - - # Assert installation summary - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][1], patch_count_for_installation + 2, error_count=0) - - # Assert truncated status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - assessment_msg_patches = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["patches"] - installation_msg_patches = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"])["patches"] - - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue((len(assessment_msg_patches) + len(installation_msg_patches)) < (patch_count_for_assessment + 2 + patch_count_for_installation + 2)) - - # Assert assessment truncation - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_WARNING) - - # Assert assessment truncated error - self.__assert_truncated_error(substatus_file_data[0]["status"]["substatus"][0], error_count=0) - - # Assert all assessment fields in the message json object are equal in both status files - self.__assert_assessment_truncated_msg_fields(assessment_msg, json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])) - - # Assert installation truncation - installation_truncated_substatus = substatus_file_data[0]["status"]["substatus"][1] - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_WARNING) - - # Assert installation truncated error - self.__assert_truncated_error(installation_truncated_substatus, error_count=0) - - # Assert all installation fields in the message json object are equal in both status files - self.__assert_installation_truncated_msg_fields(installation_msg, json.loads(installation_truncated_substatus["formattedMessage"]["message"])) - runtime.stop() - - def test_both_assessment_and_installation_truncation_over_size_limit_success_path(self): - """ Perform truncation on very large assessment packages list for time performance. - Expecting: - assessment substatus status: warning, - assessment tombstone records, - assessment errors code: 2 (warning), - assessment errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. - - Perform truncation on very large installation packages list for time performance. - Expecting: - installation substatus status: warning, - installation tombstone records, - installation errors code: 2 (warning), - installation errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ - - argument_composer = ArgumentComposer() - argument_composer.operation = Constants.INSTALLATION - runtime = RuntimeCompositor(argument_composer.get_composed_arguments(), True, Constants.ZYPPER) - runtime.set_legacy_test_type('SuccessInstallPath') - CoreMain(argument_composer.get_composed_arguments()) - - # check telemetry events - self.__check_telemetry_events(runtime) - - # SuccessInstallPath add 2 additional packages - # {\"patchId\": \"kernel-default_4.4.49-92.11.1_Ubuntu_16.04\", \"name\": \"kernel-default\", \"version\": \"4.4.49-92.11.1\", \"classifications\": [\"Other\"]}, - # {\"patchId\": \"libgoa-1_0-0_3.20.5-9.6_Ubuntu_16.04\", \"name\": \"libgoa-1_0-0\", \"version\": \"3.20.5-9.6\", \"classifications\": [\"Other\"]} - - patch_count_for_assessment = 19998 - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_assessment) - runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions) - runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) - - patch_count_for_installation = 19998 - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_installation) - runtime.status_handler.set_package_install_status(test_packages, test_package_versions) - runtime.status_handler.set_installation_substatus_json(status=Constants.STATUS_SUCCESS) - - # Assert complete status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - assessment_msg = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) - installation_msg = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"]) - - self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - - # Assert assessment summary - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count_for_assessment + 2, error_count=0) - - # Assert installation summary - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][1], patch_count_for_installation + 2, error_count=0) - - # Assert truncated status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - assessment_msg_patches = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["patches"] - installation_msg_patches = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"])["patches"] - - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue((len(assessment_msg_patches) + len(installation_msg_patches)) < (patch_count_for_assessment + 2 + patch_count_for_installation + 2)) - - # Assert assessment truncation - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_WARNING) - - # Assert assessment truncated error - self.__assert_truncated_error(substatus_file_data[0]["status"]["substatus"][0], error_count=0) - - # Assert all assessment fields in the message json object are equal in both status files - self.__assert_assessment_truncated_msg_fields(assessment_msg, json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])) - - # Assert installation truncation - installation_truncated_substatus = substatus_file_data[0]["status"]["substatus"][1] - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_WARNING) - - # Assert installation truncated error - self.__assert_truncated_error(installation_truncated_substatus, error_count=0) - - # Assert all installation fields in the message json object are equal in both status files - self.__assert_installation_truncated_msg_fields(installation_msg, json.loads(installation_truncated_substatus["formattedMessage"]["message"])) - runtime.stop() - - def test_both_assessment_and_installation_truncation_both_over_size_limit_happy_path(self): - """ Perform truncation on assessment packages list. - Expecting: - assessment substatus status: warning, - assessment tombstone records, - assessment errors code: 2 (warning), - assessment errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. - - Perform truncation on installation packages list with errors ensure error status is not overwritten. - Expecting: - installation substatus status: error, - installation tombstone records, - installation errors code: 1 (error), - installation errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ - - argument_composer = ArgumentComposer() - runtime = RuntimeCompositor(argument_composer.get_composed_arguments(), True, Constants.ZYPPER) - runtime.set_legacy_test_type('HappyPath') - CoreMain(argument_composer.get_composed_arguments()) - - patch_count_for_assessment = random.randint(950, 1200) - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_assessment) - runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions) - runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) - - patch_count_for_installation = random.randint(875, 1200) - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_installation) - runtime.status_handler.set_package_install_status(test_packages, test_package_versions) - runtime.status_handler.set_installation_substatus_json(status=Constants.STATUS_ERROR) - - # Check telemetry events - self.__check_telemetry_events(runtime) - - # HappyPath has already added 3 packages under assessment, we are adding more (anywhere between 875-1200) which will make the status file size over AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES, thereby truncation will take place, - # HappyPath contains failed, pending, installed packages for installation - # {\"patchId\": \"kernel-default_4.4.49-92.11.1_Ubuntu_16.04\", \"name\": \"kernel-default\", \"version\": \"4.4.49-92.11.1\", \"classifications\": [\"Security\"]}, - # {\"patchId\": \"libgcc_5.60.7-8.1_Ubuntu_16.04\", \"name\": \"libgcc\", \"version\": \"5.60.7-8.1\", \"classifications\": [\"Other\"]}, - # {\"patchId\": \"libgoa-1_0-0_3.20.5-9.6_Ubuntu_16.04\", \"name\": \"libgoa-1_0-0\", \"version\": \"3.20.5-9.6\", \"classifications\": [\"Other\"]} - - # Assert Complete status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - assessment_msg = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) - installation_msg = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"]) - - self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - # Assert assessment summary - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count_for_assessment + 3, error_count=0) - - # Assert installation summary - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_ERROR) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][1], patch_count_for_installation + 3, error_code=Constants.PatchOperationTopLevelErrorCode.ERROR, error_count=1) - - # Assert truncated status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - assessment_msg_patches = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["patches"] - installation_msg_patches = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"])["patches"] - - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(len(assessment_msg_patches) + len(installation_msg_patches) < patch_count_for_assessment + 3 + patch_count_for_installation + 3) - # Assert assessment truncation - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_WARNING) - - # Assert assessment truncated error - self.__assert_truncated_error(substatus_file_data[0]["status"]["substatus"][0], error_count=0) - - # Assert all assessment fields in the message json object are equal in both status files - self.__assert_assessment_truncated_msg_fields(assessment_msg, json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])) - - # Assert installation truncation - installation_truncated_substatus = substatus_file_data[0]["status"]["substatus"][1] - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_ERROR) - - # Assert installation truncated error - self.__assert_truncated_error(installation_truncated_substatus, error_count=1) - - # Assert all installation fields in the message json object are equal in both status files - self.__assert_installation_truncated_msg_fields(installation_msg, json.loads(installation_truncated_substatus["formattedMessage"]["message"])) - runtime.stop() - - def test_both_assessment_and_installation_truncation_with_errors_over_size_limit(self): - """ Perform truncation on assessment packages list. - Expecting: - assessment substatus status: warning, - assessment tombstone records, - assessment errors code: 2 (warning), - assessment errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. - - Perform truncation on installation packages list with multiple errors and truncation to ensure __try_add_error is working as expected. - Expecting: - installation substatus status: error, - installation tombstone records, - installation errors code: 1 (error), - installation errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ - - argument_composer = ArgumentComposer() - runtime = RuntimeCompositor(argument_composer.get_composed_arguments(), True, Constants.ZYPPER) - runtime.set_legacy_test_type('FailInstallPath') - CoreMain(argument_composer.get_composed_arguments()) - - # check telemetry events - self.__check_telemetry_events(runtime) - - # Test code add 2 additional packages - # {\"patchId\": \"kernel-default_4.4.49-92.11.1_Ubuntu_16.04\", \"name\": \"kernel-default\", \"version\": \"4.4.49-92.11.1\", \"classifications\": [\"Security\"]}, - - patch_count_for_assessment = 598 - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_assessment) - runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions) - - runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) - - patch_count_for_installation = 318 - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_installation) - runtime.status_handler.set_package_install_status(test_packages, test_package_versions) - - # Adding multiple exceptions - runtime.status_handler.add_error_to_status("exception0", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - runtime.status_handler.add_error_to_status("exception1", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - runtime.status_handler.add_error_to_status("exception2", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - runtime.status_handler.add_error_to_status("exception3", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - runtime.status_handler.add_error_to_status("exception4", Constants.PatchOperationErrorCodes.PACKAGE_MANAGER_FAILURE) - runtime.status_handler.add_error_to_status("exception5", Constants.PatchOperationErrorCodes.OPERATION_FAILED) - - runtime.status_handler.set_installation_substatus_json(status=Constants.STATUS_ERROR) - - # Assert Complete status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - installation_msg = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"]) - - self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - - # Assert assessment summary - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count_for_assessment + 2, error_count=0) - - # Assert installation summary - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_ERROR) - self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][1], patch_count_for_installation + 2, error_code=Constants.PatchOperationTopLevelErrorCode.ERROR, error_count=5) - - # Assert truncated status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - assessment_msg_patches = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["patches"] - installation_msg_patches = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"])["patches"] - - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - - # Assert assessment truncation - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) - - # Assert assessment no truncation - self.assertEqual(len(assessment_msg_patches), patch_count_for_assessment + 2) - - # Assert assessment truncated error - self.__assert_truncated_error(substatus_file_data[0]["status"]["substatus"][0], error_count=0) - - # Test installation truncation - self.assertTrue(len(installation_msg_patches) < patch_count_for_installation + 2) - installation_truncated_substatus = substatus_file_data[0]["status"]["substatus"][1] - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_ERROR) - - # Assert installation truncated error - self.__assert_truncated_error(installation_truncated_substatus, error_count=5) - - # Assert all installation fields in the message json object are equal in both status files - self.__assert_installation_truncated_msg_fields(installation_msg, json.loads(installation_truncated_substatus["formattedMessage"]["message"])) - runtime.stop() - - # Setup functions for testing - def __check_telemetry_events(self, runtime): - all_events = os.listdir(runtime.telemetry_writer.events_folder_path) - self.assertTrue(len(all_events) > 0) - latest_event_file = [pos_json for pos_json in os.listdir(runtime.telemetry_writer.events_folder_path) if re.search('^[0-9]+.json$', pos_json)][-1] - with open(os.path.join(runtime.telemetry_writer.events_folder_path, latest_event_file), 'r+') as f: - events = json.load(f) - self.assertTrue(events is not None) - self.assertTrue('Core' in events[0]['TaskName']) - f.close() - - def __assert_patch_summary_from_status(self, substatus_file_data, operation, patch_summary, status): - self.assertEqual(substatus_file_data[0]["status"]["operation"], operation) - - if patch_summary == Constants.PATCH_ASSESSMENT_SUMMARY: - substatus_file_data = substatus_file_data[0]["status"]["substatus"][0] - else: - substatus_file_data = substatus_file_data[0]["status"]["substatus"][1] - - self.assertEqual(substatus_file_data["name"], patch_summary) - self.assertEqual(substatus_file_data["status"], status.lower()) - - def __asert_message_json_from_status(self, substatus_file_data, patch_count, error_code=Constants.PatchOperationTopLevelErrorCode.SUCCESS, error_count=0): - message = json.loads(substatus_file_data["formattedMessage"]["message"]) - self.assertEqual(len(message["patches"]), patch_count) - self.assertEqual(message["errors"]["code"], error_code) - self.assertEqual(len(message["errors"]["details"]), error_count) - - def __assert_truncated_error(self, substatus_file_data, error_count): - # assert error - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), error_count) - - def __assert_assessment_truncated_msg_fields(self, assessment_msg, truncated_assessment_msg): - self.assertEqual(assessment_msg['assessmentActivityId'], truncated_assessment_msg['assessmentActivityId']) - self.assertEqual(assessment_msg['rebootPending'], truncated_assessment_msg['rebootPending']) - self.assertEqual(assessment_msg['criticalAndSecurityPatchCount'], truncated_assessment_msg['criticalAndSecurityPatchCount']) - self.assertEqual(assessment_msg['otherPatchCount'], truncated_assessment_msg['otherPatchCount']) - self.assertEqual(assessment_msg['startTime'], truncated_assessment_msg['startTime']) - self.assertEqual(assessment_msg['lastModifiedTime'], truncated_assessment_msg['lastModifiedTime']) - self.assertEqual(assessment_msg['startedBy'], truncated_assessment_msg['startedBy']) - - def __assert_installation_truncated_msg_fields(self, installation_msg, truncated_installation_msg): - self.assertEqual(installation_msg['installationActivityId'], truncated_installation_msg['installationActivityId']) - self.assertEqual(installation_msg['rebootStatus'], truncated_installation_msg['rebootStatus']) - self.assertEqual(installation_msg['maintenanceWindowExceeded'], truncated_installation_msg['maintenanceWindowExceeded']) - self.assertEqual(installation_msg['notSelectedPatchCount'], truncated_installation_msg['notSelectedPatchCount']) - self.assertEqual(installation_msg['excludedPatchCount'], truncated_installation_msg['excludedPatchCount']) - self.assertEqual(installation_msg['pendingPatchCount'], truncated_installation_msg['pendingPatchCount']) - self.assertEqual(installation_msg['installedPatchCount'], truncated_installation_msg['installedPatchCount']) - self.assertEqual(installation_msg['failedPatchCount'], truncated_installation_msg['failedPatchCount']) - self.assertEqual(installation_msg['startTime'], truncated_installation_msg['startTime']) - self.assertEqual(installation_msg['lastModifiedTime'], truncated_installation_msg['lastModifiedTime']) - self.assertEqual(installation_msg['maintenanceRunId'], truncated_installation_msg['maintenanceRunId']) - - def __set_up_packages_func(self, val): - test_packages = [] - test_package_versions = [] - - for i in range(0, val): - test_packages.append('python-samba' + str(i)) - test_package_versions.append('2:4.4.5+dfsg-2ubuntu5.4') - - return test_packages, test_package_versions - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/src/core/tests/Test_StatusHandlerTruncation.py b/src/core/tests/Test_StatusHandlerTruncation.py index 3d58f0bf..3f7c4b81 100644 --- a/src/core/tests/Test_StatusHandlerTruncation.py +++ b/src/core/tests/Test_StatusHandlerTruncation.py @@ -21,6 +21,7 @@ import tempfile import os import sys +import string from core.src.bootstrap.Constants import Constants from core.src.service_interfaces.StatusHandler import StatusHandler from core.tests.library.ArgumentComposer import ArgumentComposer @@ -125,7 +126,8 @@ def test_assessment_packages_map(self): expected_value_critical = {'version': '2:4.4.5+dfsg-2ubuntu5.4', 'classifications': ['Critical'], 'name': 'python-samba0', 'patchId': 'python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04'} - status_handler = StatusHandler(self.runtime.env_layer, self.runtime.execution_config, self.runtime.composite_logger, self.runtime.telemetry_writer, self.runtime.vm_cloud_type) + status_handler = StatusHandler(self.runtime.env_layer, self.runtime.execution_config, self.runtime.composite_logger, self.runtime.telemetry_writer, + self.runtime.vm_cloud_type) self.runtime.execution_config.operation = Constants.ASSESSMENT self.runtime.status_handler.set_current_operation(Constants.ASSESSMENT) @@ -140,11 +142,11 @@ def test_assessment_packages_map(self): def test_installation_packages_map(self): patch_id_other = 'python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04' expected_value_other = {'version': '2:4.4.5+dfsg-2ubuntu5.4', 'classifications': ['Other'], 'name': 'python-samba0', - 'patchId': 'python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04', 'patchInstallationState': 'Installed'} + 'patchId': 'python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04', 'patchInstallationState': 'Installed'} patch_id_critical = 'python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04' expected_value_critical = {'version': '2:4.4.5+dfsg-2ubuntu5.4', 'classifications': ['Critical'], 'name': 'python-samba0', - 'patchId': 'python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04', 'patchInstallationState': 'Installed'} + 'patchId': 'python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04', 'patchInstallationState': 'Installed'} self.runtime.execution_config.operation = Constants.INSTALLATION self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) @@ -171,7 +173,11 @@ def test_load_status_and_set_package_install_status(self): test_packages, test_package_versions = self.__set_up_packages_func(patch_count) file_path = self.runtime.execution_config.status_folder example_file1 = os.path.join(file_path, '123.complete.status') - sample_json = [{"version": 1.0, "timestampUTC": "2023-06-17T02:06:19Z", "status": {"name": "Azure Patch Management", "operation": "Installation", "status": "success", "code": 0, "formattedMessage": {"lang": "en-US", "message": ""}, "substatus": [{"name": "PatchInstallationSummary", "status": "transitioning", "code": 0, "formattedMessage": {"lang": "en-US", "message": "{\"installationActivityId\": \"c365ab46-a12a-4388-853b-5240a0702124\", \"rebootStatus\": \"NotNeeded\", \"maintenanceWindowExceeded\": false, \"notSelectedPatchCount\": 0, \"excludedPatchCount\": 0, \"pendingPatchCount\": 0, \"installedPatchCount\": 5, \"failedPatchCount\": 0, \"patches\": [{\"patchId\": \"python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba0\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Other\"], \"patchInstallationState\": \"Pending\"}, {\"patchId\": \"python-samba1_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba1\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Security\"], \"patchInstallationState\": \"Failed\"}, {\"patchId\": \"python-samba2_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba2\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Other\"], \"patchInstallationState\": \"Not_Selected\"}, {\"patchId\": \"python-samba3_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba3\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Other\"], \"patchInstallationState\": \"Pending\"}, {\"patchId\": \"python-samba4_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba4\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Unclassified\"], \"patchInstallationState\": \"Failed\"}], \"startTime\": \"2023-06-17T02:06:19.480634Z\", \"lastModifiedTime\": \"2023-06-17T02:06:19Z\", \"maintenanceRunId\": \"\", \"errors\": {\"code\": 0, \"details\": [], \"message\": \"0 error/s reported.\"}}"}}]}}] + sample_json = [{"version": 1.0, "timestampUTC": "2023-06-17T02:06:19Z", + "status": {"name": "Azure Patch Management", "operation": "Installation", "status": "success", "code": 0, + "formattedMessage": {"lang": "en-US", "message": ""}, "substatus": [ + {"name": "PatchInstallationSummary", "status": "transitioning", "code": 0, "formattedMessage": {"lang": "en-US", + "message": "{\"installationActivityId\": \"c365ab46-a12a-4388-853b-5240a0702124\", \"rebootStatus\": \"NotNeeded\", \"maintenanceWindowExceeded\": false, \"notSelectedPatchCount\": 0, \"excludedPatchCount\": 0, \"pendingPatchCount\": 0, \"installedPatchCount\": 5, \"failedPatchCount\": 0, \"patches\": [{\"patchId\": \"python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba0\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Other\"], \"patchInstallationState\": \"Pending\"}, {\"patchId\": \"python-samba1_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba1\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Security\"], \"patchInstallationState\": \"Failed\"}, {\"patchId\": \"python-samba2_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba2\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Other\"], \"patchInstallationState\": \"Not_Selected\"}, {\"patchId\": \"python-samba3_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba3\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Other\"], \"patchInstallationState\": \"Pending\"}, {\"patchId\": \"python-samba4_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba4\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Unclassified\"], \"patchInstallationState\": \"Failed\"}], \"startTime\": \"2023-06-17T02:06:19.480634Z\", \"lastModifiedTime\": \"2023-06-17T02:06:19Z\", \"maintenanceRunId\": \"\", \"errors\": {\"code\": 0, \"details\": [], \"message\": \"0 error/s reported.\"}}"}}]}}] with open(example_file1, 'w') as f: f.write(json.dumps(sample_json)) self.runtime.status_handler.status_file_path = example_file1 @@ -195,7 +201,13 @@ def test_load_status_and_set_package_install_status(self): self.runtime.env_layer.file_system.delete_files_from_dir(self.runtime.status_handler.status_file_path, '*.complete.status') def test_only_assessment_packages_truncation_under_size_limit(self): - """ Perform no truncation on assessment packages list """ + """ Perform no truncation on assessment packages list. + Expecting: + assessment substatus status: success, + no assessment tombstone records, + assessment errors code: 0 (success), + assessment errors details code: 0 (success). """ + self.runtime.execution_config.operation = Constants.ASSESSMENT self.runtime.status_handler.set_current_operation(Constants.ASSESSMENT) @@ -204,10 +216,11 @@ def test_only_assessment_packages_truncation_under_size_limit(self): self.runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions) self.runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) - # Assert Complete status file + # Assert complete status file with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle) + # Assert complete status file size < 128kb self.assertTrue(len(json.dumps(substatus_file_data)) < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) self.__assert_patch_summary_from_status(substatus_file_data, Constants.ASSESSMENT, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count, error_count=0) @@ -218,7 +231,7 @@ def test_only_assessment_packages_truncation_under_size_limit(self): message = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) self.assertTrue(len(json.dumps(substatus_file_data)) < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertEqual(substatus_file_data[0]["status"]["substatus"][0]["name"], Constants.PATCH_ASSESSMENT_SUMMARY,) + self.assertEqual(substatus_file_data[0]["status"]["substatus"][0]["name"], Constants.PATCH_ASSESSMENT_SUMMARY, ) self.assertEqual(substatus_file_data[0]["status"]["substatus"][0]["status"], Constants.STATUS_SUCCESS.lower()) self.assertEqual(len(message["patches"]), patch_count) self.assertNotEqual(message["patches"][-1]['patchId'], "Truncated_patch_list_id") @@ -228,7 +241,13 @@ def test_only_assessment_packages_truncation_under_size_limit(self): self.assertFalse("review this log file on the machine" in message) def test_only_assessment_packages_truncation_over_size_limit(self): - """ Perform truncation on only assessment packages list """ + """ Perform truncation on assessment packages list. + Expecting: + assessment substatus status: warning, + assessment tombstone records, + assessment errors code: 2 (warning), + assessment errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ + self.runtime.execution_config.operation = Constants.ASSESSMENT self.runtime.status_handler.set_current_operation(Constants.ASSESSMENT) @@ -241,26 +260,41 @@ def test_only_assessment_packages_truncation_over_size_limit(self): with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle) + assessment_msg = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) + + # Assert complete status file size > 128kb self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) self.__assert_patch_summary_from_status(substatus_file_data, Constants.ASSESSMENT, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count, error_count=0) # Assert assessment truncated status file with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) + truncated_substatus_file_data = json.load(file_handle) + + truncated_substatus_file_byte_size = len(json.dumps(truncated_substatus_file_data)) + assessment_truncated_msg = json.loads(truncated_substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - message_patches = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["patches"] - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(len(message_patches) < patch_count) - self.__assert_patch_summary_from_status(substatus_file_data, Constants.ASSESSMENT, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_WARNING) + # Assert truncated status file size < 128kb n 126kb and length of truncated patches < length of complete status file patches + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(len(assessment_truncated_msg["patches"]) < patch_count) + self.__assert_patch_summary_from_status(truncated_substatus_file_data, Constants.ASSESSMENT, Constants.PATCH_ASSESSMENT_SUMMARY, + Constants.STATUS_WARNING) # Assert truncated error - self.__assert_truncated_error(substatus_file_data[0]["status"]["substatus"][0], error_count=0) + self.__assert_truncated_error(truncated_substatus_file_data[0]["status"]["substatus"][0], error_count=0) + + # Assert all assessment fields in the message json are equal in both status files + self.__assert_assessment_truncated_msg_fields(assessment_msg, assessment_truncated_msg) + + def test_only_assessment_packages_truncation_large_size_limit_for_extra_chars(self): + """ Perform truncation on very large assessment packages list for time performance. + Expecting: + assessment substatus status: warning, + assessment tombstone records, + assessment errors code: 2 (warning), + assessment errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ - def test_only_assessment_packages_truncation_over_large_size_limit_for_extra_chars(self): - """ Perform truncation on large assessment package list, the 2 times json.dumps() will escape " adding \, adding 1 additional byte check if total byte size over the size limit """ self.runtime.execution_config.operation = Constants.ASSESSMENT self.runtime.status_handler.set_current_operation(Constants.ASSESSMENT) @@ -273,26 +307,41 @@ def test_only_assessment_packages_truncation_over_large_size_limit_for_extra_cha with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle) + assessment_msg = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) + + # Assert complete status file size > 128kb self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) self.__assert_patch_summary_from_status(substatus_file_data, Constants.ASSESSMENT, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count, error_count=0) # Assert assessment truncated status file with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) + truncated_substatus_file_data = json.load(file_handle) - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - message_patches = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["patches"] - self.assertTrue(len(message_patches) < patch_count) - self.__assert_patch_summary_from_status(substatus_file_data, Constants.ASSESSMENT, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_WARNING) + # Assert truncated status file size < 128kb n 126kb and length of truncated patches < length of completed patches + truncated_substatus_file_byte_size = len(json.dumps(truncated_substatus_file_data)) + assessment_truncated_msg = json.loads(truncated_substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) + + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(len(assessment_truncated_msg["patches"]) < patch_count) + self.__assert_patch_summary_from_status(truncated_substatus_file_data, Constants.ASSESSMENT, Constants.PATCH_ASSESSMENT_SUMMARY, + Constants.STATUS_WARNING) # Assert truncated error - self.__assert_truncated_error(substatus_file_data[0]["status"]["substatus"][0], error_count=0) + self.__assert_truncated_error(truncated_substatus_file_data[0]["status"]["substatus"][0], error_count=0) + + # Assert all assessment fields in the message json are equal in both status files + self.__assert_assessment_truncated_msg_fields(assessment_msg, assessment_truncated_msg) def test_only_assessment_packages_truncation_over_size_limit_with_errors(self): - """ Perform truncation on only assessment packages list with multiple errors """ + """ Perform truncation on assessment packages list with multiple errors to ensure __try_add_error is working as expected. + Expecting: + assessment substatus status: error, + assessment tombstone records, + assessment errors code: 1 (error), + assessment errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ + self.runtime.execution_config.operation = Constants.ASSESSMENT self.runtime.status_handler.set_current_operation(Constants.ASSESSMENT) @@ -300,20 +349,77 @@ def test_only_assessment_packages_truncation_over_size_limit_with_errors(self): test_packages, test_package_versions = self.__set_up_packages_func(patch_count) self.runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions, "Security") - # Assert complete status file + # Set up complete status file before errors with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), 0) + substatus_file_data = json.load(file_handle) + + # Assert complete status file size > 128kb + self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + self.assertEqual(len(json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["errors"]["details"]), 0) + + # Set up complete status file after errors + self.__add_multiple_exception_errors() self.runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_ERROR) - # Assert complete status file - self.__assert_complete_status_errors(Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_ERROR, patch_count) + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: + substatus_file_data = json.load(file_handle) + + # Assert complete status file after errors + self.__assert_status_file_data_multi_errors(substatus_file_data, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_ERROR, patch_count, + count_errors_detail=5) # Assert assessment truncated status file with multi errors - self.__assert_truncated_status_multi_errors(Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_ERROR, error_count=5) + self.__assert_truncated_status_multi_errors(Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_ERROR, patch_count, error_count=5) + + def test_only_installation_truncation_under_size_limit(self): + """ Perform no truncation on installation packages list. + Expecting: + installation substatus status: success, + no installation tombstone records, + installation errors code: 0 (success), + installation errors details code: 0 (success). """ + + self.runtime.execution_config.operation = Constants.INSTALLATION + self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) + + patch_count = 500 + test_packages, test_package_versions = self.__set_up_packages_func(patch_count) + self.runtime.status_handler.set_package_install_status(test_packages, test_package_versions, Constants.INSTALLED) + self.runtime.status_handler.set_installation_substatus_json(status=Constants.STATUS_SUCCESS) + + # Assert complete status file + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: + substatus_file_data = json.load(file_handle) + + # Assert complete status file size < 128kb + self.assertTrue(len(json.dumps(substatus_file_data)) < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_SUCCESS, + substatus_index=0) + self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count, error_count=0) + + # Assert no truncated status file + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: + substatus_file_data = json.load(file_handle) + + message = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) + self.assertTrue(len(json.dumps(substatus_file_data)) < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + self.assertEqual(substatus_file_data[0]["status"]["substatus"][0]["name"], Constants.PATCH_INSTALLATION_SUMMARY) + self.assertEqual(substatus_file_data[0]["status"]["substatus"][0]["status"], Constants.STATUS_SUCCESS.lower()) + self.assertEqual(len(message["patches"]), patch_count) + self.assertNotEqual(message["patches"][-1]['patchId'], "Truncated_patch_list_id") + self.assertTrue('Truncated_patch_list_id' not in message["patches"][-1]['name']) + self.assertEqual(message["errors"]["code"], 0) + self.assertEqual(len(message["errors"]["details"]), 0) + self.assertFalse("review this log file on the machine" in message) def test_only_installation_packages_truncation_over_size_limit(self): - """ Perform truncation on only installation packages list """ + """ Perform truncation on installation packages list. + Expecting: + installation substatus status: warning, + installation tombstone records, + installation errors code: 2 (warning), + installation errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ + self.runtime.execution_config.operation = Constants.INSTALLATION self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) @@ -326,59 +432,103 @@ def test_only_installation_packages_truncation_over_size_limit(self): with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle) + installation_msg = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) + + # Assert complete status file size > 128kb self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_SUCCESS) + self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_SUCCESS, + substatus_index=0) self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count, error_count=0) # Assert installation truncated status file with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) + truncated_substatus_file_data = json.load(file_handle) - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - message_patches = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["patches"] - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(len(message_patches) < patch_count) - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_WARNING) + truncated_substatus_file_byte_size = len(json.dumps(truncated_substatus_file_data)) + installation_truncated_msg = json.loads(truncated_substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) + + # Assert truncated status file size < 128kb n 126kb and length of truncated patches < length of complete status file patches + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(len(installation_truncated_msg["patches"]) < patch_count) + self.__assert_patch_summary_from_status(truncated_substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, + Constants.STATUS_WARNING) # Assert truncated error - self.__assert_truncated_error(substatus_file_data[0]["status"]["substatus"][0], error_count=0) + self.__assert_truncated_error(truncated_substatus_file_data[0]["status"]["substatus"][0], error_count=0) + + # Assert all installation fields in the message json are equal in both status files + self.__assert_installation_truncated_msg_fields(installation_msg, installation_truncated_msg) def test_only_installation_packages_truncation_over_size_limit_low_priority_packages(self): - """ Perform truncation on only installation low priority (Pending, Excluded, Not_Selected) packages list """ + """ Perform truncation on only installation with low priority packages list (Pending, Exclude, Not_Selected). + Expecting: + installation substatus status: warning, + installation tombstone records, + installation errors code: 2 (warning), + installation errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ self.runtime.execution_config.operation = Constants.INSTALLATION self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) - patch_count = random.randint(780, 1100) - test_packages, test_package_versions = self.__set_up_packages_func(patch_count) - self.runtime.status_handler.set_package_install_status(test_packages, test_package_versions, Constants.PENDING) + patch_count_pending = random.randint(1, 400) + patch_count_exclude = random.randint(1, 100) + patch_count_not_selected = random.randint(780, 1000) + + # random_char=random.choice(string.ascii_letters) ensure the packages are unique + test_packages, test_package_versions = self.__set_up_packages_func(patch_count_pending, random_char=random.choice(string.ascii_letters)) + self.runtime.status_handler.set_package_install_status(test_packages, test_package_versions) + + test_packages, test_package_versions = self.__set_up_packages_func(patch_count_exclude, random_char=random.choice(string.ascii_letters)) + self.runtime.status_handler.set_package_install_status(test_packages, test_package_versions, Constants.EXCLUDED) + + test_packages, test_package_versions = self.__set_up_packages_func(patch_count_not_selected, random_char=random.choice(string.ascii_letters)) + self.runtime.status_handler.set_package_install_status(test_packages, test_package_versions, Constants.NOT_SELECTED) + self.runtime.status_handler.set_installation_substatus_json(status=Constants.STATUS_SUCCESS) + patch_count = patch_count_pending + patch_count_exclude + patch_count_not_selected + # Assert complete status file with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle) + installation_msg = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) + + # Assert complete status file size > 128kb self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_SUCCESS) + self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_SUCCESS, + substatus_index=0) self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count, error_count=0) # Assert installation truncated status file with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) + truncated_substatus_file_data = json.load(file_handle) + + truncated_substatus_file_byte_size = len(json.dumps(truncated_substatus_file_data)) + installation_truncated_msg = json.loads(truncated_substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - message_patches = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["patches"] - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(len(message_patches) < patch_count) + # Assert truncated status file size < 128kb n 126kb and length of truncated patches < length of complete status file patches + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(len(installation_truncated_msg["patches"]) < patch_count) - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_WARNING) + self.__assert_patch_summary_from_status(truncated_substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, + Constants.STATUS_WARNING) # Assert truncated error - self.__assert_truncated_error(substatus_file_data[0]["status"]["substatus"][0], error_count=0) + self.__assert_truncated_error(truncated_substatus_file_data[0]["status"]["substatus"][0], error_count=0) + + # Assert all installation fields in the message json are equal in both status files + self.__assert_installation_truncated_msg_fields(installation_msg, installation_truncated_msg) def test_only_installation_packages_truncation_over_large_size_limit_with_extra_chars(self): - """ Perform truncation on only large installation packages list, the 2 times json.dumps() will escape " adding \, adding 1 additional byte check if total byte size over the size limit """ + """ Perform truncation on very large installation packages list for time performance. + Expecting: + installation substatus status: warning, + installation tombstone records, + installation errors code: 2 (warning), + installation errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ + self.runtime.execution_config.operation = Constants.INSTALLATION self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) @@ -391,26 +541,43 @@ def test_only_installation_packages_truncation_over_large_size_limit_with_extra_ with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle) + installation_msg = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) + + # Assert complete status file size > 128kb self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_SUCCESS) + self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_SUCCESS, + substatus_index=0) self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count, error_count=0) # Assert installation truncated status file with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) + truncated_substatus_file_data = json.load(file_handle) + + truncated_substatus_file_byte_size = len(json.dumps(truncated_substatus_file_data)) + installation_truncated_msg = json.loads(truncated_substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - message_patches = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["patches"] - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(len(message_patches) < patch_count) - self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_WARNING) + # Assert truncated status file size < 128kb n 126kb and length of truncated patches < length of complete status file patches + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(len(installation_truncated_msg["patches"]) < patch_count) + + self.__assert_patch_summary_from_status(truncated_substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, + Constants.STATUS_WARNING) # Assert truncated error self.__assert_truncated_error(substatus_file_data[0]["status"]["substatus"][0], error_count=0) - def test_only_installation_packages_truncation_over_size_limit_with_error(self): - """ Perform truncation on only installation packages list with multiple errors """ + # Assert all installation fields in the message json are equal in both status files + self.__assert_installation_truncated_msg_fields(installation_msg, installation_truncated_msg) + + def test_only_installation_packages_truncation_over_size_limit_with_multi_errors(self): + """ Perform truncation on installation packages list with multiple errors to ensure __try_add_error is working as expected. + Expecting: + installation substatus status: error, + installation tombstone records, + installation errors code: 1 (error), + installation errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ + self.runtime.execution_config.operation = Constants.INSTALLATION self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) @@ -418,668 +585,260 @@ def test_only_installation_packages_truncation_over_size_limit_with_error(self): test_packages, test_package_versions = self.__set_up_packages_func(patch_count) self.runtime.status_handler.set_package_install_status(test_packages, test_package_versions, Constants.INSTALLED) - # Assert complete status file + # Set up complete status file before errors with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), 0) + substatus_file_data = json.load(file_handle) + + # Assert complete status file size > 128kb + self.assertTrue( + len(json.dumps(substatus_file_data[0]["status"]["substatus"][0])) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + self.assertEqual(len(json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["errors"]["details"]), 0) + # Set up complete status file after errors + self.__add_multiple_exception_errors() self.runtime.status_handler.set_installation_substatus_json(status=Constants.STATUS_ERROR) - # Assert complete status file - self.__assert_complete_status_errors(Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_ERROR, patch_count) + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: + substatus_file_data = json.load(file_handle) + self.__assert_status_file_data_multi_errors(substatus_file_data, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_ERROR, patch_count, + count_errors_detail=5) # Assert installation truncated status file with multi errors - self.__assert_truncated_status_multi_errors(Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_ERROR, error_count=5) - - # Copyright 2020 Microsoft Corporation - # - # 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. - # - # Requires Python 2.7+ - import datetime - import glob - import json - import random - import time - import unittest - import tempfile - import os - import sys - from core.src.bootstrap.Constants import Constants - from core.src.service_interfaces.StatusHandler import StatusHandler - from core.tests.library.ArgumentComposer import ArgumentComposer - from core.tests.library.RuntimeCompositor import RuntimeCompositor - - class TestStatusHandler(unittest.TestCase): - def setUp(self): - self.runtime = RuntimeCompositor(ArgumentComposer().get_composed_arguments(), True) - self.container = self.runtime.container - - def tearDown(self): - self.runtime.stop() - - def __mock_os_remove(self, file_to_remove): - raise Exception("File could not be deleted") - - def test_set_package_assessment_status(self): - # startedBy should be set to User in status for Assessment - packages, package_versions = self.runtime.package_manager.get_all_updates() - self.runtime.status_handler.set_package_assessment_status(packages, package_versions) - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(substatus_file_data["name"], Constants.PATCH_ASSESSMENT_SUMMARY) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), 3) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["name"], "python-samba") - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["name"], "samba-common-bin") - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["name"], "samba-libs") - self.assertTrue( - "python-samba_2:4.4.5+dfsg-2ubuntu5.4" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["patchId"])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["startedBy"], Constants.PatchAssessmentSummaryStartedBy.USER) - - def test_set_package_assessment_status_for_auto_assessment(self): - # startedBy should be set to Platform in status for Auto Assessment - self.runtime.execution_config.exec_auto_assess_only = True - packages, package_versions = self.runtime.package_manager.get_all_updates() - self.runtime.status_handler.set_package_assessment_status(packages, package_versions) - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(substatus_file_data["name"], Constants.PATCH_ASSESSMENT_SUMMARY) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), 3) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["name"], "python-samba") - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["name"], "samba-common-bin") - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["name"], "samba-libs") - self.assertTrue( - "python-samba_2:4.4.5+dfsg-2ubuntu5.4" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["patchId"])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["startedBy"], Constants.PatchAssessmentSummaryStartedBy.PLATFORM) - - def test_set_package_install_status(self): - packages, package_versions = self.runtime.package_manager.get_all_updates() - self.runtime.status_handler.set_package_install_status(packages, package_versions) - substatus_file_data = [] - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(substatus_file_data["name"], Constants.PATCH_INSTALLATION_SUMMARY) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), 3) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["name"], "python-samba") - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["name"], "samba-common-bin") - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["name"], "samba-libs") - self.assertTrue( - "python-samba_2:4.4.5+dfsg-2ubuntu5.4" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["patchId"])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["patchInstallationState"], "Pending") - - def test_set_package_install_status_extended(self): - packages, package_versions = self.runtime.package_manager.get_all_updates() - self.runtime.status_handler.set_package_install_status(packages, package_versions) - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(substatus_file_data["name"], Constants.PATCH_INSTALLATION_SUMMARY) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["name"], "samba-common-bin") - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["patchInstallationState"], Constants.PENDING) - self.runtime.status_handler.set_package_install_status("samba-common-bin", "2:4.4.5+dfsg-2ubuntu5.4", Constants.INSTALLED) - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["name"], "samba-common-bin") - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["patchInstallationState"], Constants.INSTALLED) - - def test_set_package_install_status_classification(self): - packages, package_versions = self.runtime.package_manager.get_all_updates() - self.runtime.status_handler.set_package_install_status(packages, package_versions) - sec_packages, sec_package_versions = self.runtime.package_manager.get_security_updates() - self.runtime.status_handler.set_package_install_status_classification(sec_packages, sec_package_versions, "Security") - substatus_file_data = [] - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(substatus_file_data["name"], Constants.PATCH_INSTALLATION_SUMMARY) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), 3) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["name"], "python-samba") - self.assertTrue("Security" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["classifications"])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["name"], "samba-common-bin") - self.assertTrue("Security" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["classifications"])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["name"], "samba-libs") - self.assertTrue( - "python-samba_2:4.4.5+dfsg-2ubuntu5.4" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["patchId"])) - self.assertTrue("Security" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["classifications"])) - - def test_set_package_install_status_classification_not_set(self): - packages, package_versions = self.runtime.package_manager.get_all_updates() - self.runtime.status_handler.set_package_install_status(packages, package_versions) - sec_packages, sec_package_versions = self.runtime.package_manager.get_security_updates() - self.runtime.status_handler.set_package_install_status_classification(sec_packages, sec_package_versions) - substatus_file_data = [] - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(substatus_file_data["name"], Constants.PATCH_INSTALLATION_SUMMARY) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), 3) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["name"], "python-samba") - self.assertTrue("Other" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["classifications"])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["name"], "samba-common-bin") - self.assertTrue("Other" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["classifications"])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["name"], "samba-libs") - self.assertTrue( - "python-samba_2:4.4.5+dfsg-2ubuntu5.4" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["patchId"])) - self.assertTrue("Other" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["classifications"])) - - def test_set_package_install_unknown_patch_state_recorded(self): - packages, package_versions = self.runtime.package_manager.get_all_updates() - self.runtime.status_handler.set_package_install_status(packages, package_versions, Constants.AVAILABLE) - - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(substatus_file_data["name"], Constants.PATCH_INSTALLATION_SUMMARY) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), 3) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["name"], "python-samba") - self.assertTrue("Other" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["classifications"])) - self.assertEqual("Available", str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["patchInstallationState"])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["name"], "samba-common-bin") - self.assertTrue("Other" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["classifications"])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["name"], "samba-libs") - self.assertTrue( - "python-samba_2:4.4.5+dfsg-2ubuntu5.4" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["patchId"])) - self.assertTrue("Other" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["classifications"])) - - def test_set_installation_reboot_status(self): - self.assertRaises(Exception, self.runtime.status_handler.set_installation_reboot_status, "INVALID_STATUS") - - # Reboot status not updated as it fails state transition validation - self.runtime.status_handler.set_installation_substatus_json() - self.runtime.status_handler.set_installation_reboot_status(Constants.RebootStatus.COMPLETED) - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["rebootStatus"], Constants.RebootStatus.NOT_NEEDED) - - self.runtime.status_handler.set_installation_reboot_status(Constants.RebootStatus.REQUIRED) - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["rebootStatus"], Constants.RebootStatus.REQUIRED) - - def test_set_maintenance_window_exceeded(self): - self.runtime.status_handler.set_installation_substatus_json() - self.runtime.status_handler.set_maintenance_window_exceeded(True) - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertTrue(json.loads(substatus_file_data["formattedMessage"]["message"])["maintenanceWindowExceeded"]) - - def test_add_error(self): - # Setting operation to assessment to add all errors under assessment substatus - self.runtime.status_handler.set_current_operation(Constants.ASSESSMENT) - - # Unexpected input - self.runtime.status_handler.add_error_to_status(None) - self.runtime.status_handler.set_assessment_substatus_json() - substatus_file_data = [] - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), 0) - - self.runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) - - # Adding multiple exceptions - self.runtime.status_handler.add_error_to_status("exception1", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - self.runtime.status_handler.add_error_to_status("exception2", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - self.runtime.status_handler.add_error_to_status("exception3", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - self.runtime.status_handler.add_error_to_status("exception4", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - self.runtime.status_handler.add_error_to_status("exception5", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - self.runtime.status_handler.add_error_to_status("exception6", Constants.PatchOperationErrorCodes.OPERATION_FAILED) - - # Trying to add existing error - self.runtime.status_handler.add_error_to_status("Adding same exception " + Constants.ERROR_ADDED_TO_STATUS, - Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - - # Test message restrictions - self.runtime.status_handler.add_error_to_status("a" * 130, Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - - substatus_file_data = [] - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - - self.assertEqual("Success".lower(), str(substatus_file_data["status"]).lower()) - self.assertNotEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"], None) - self.assertTrue("Adding same exception" not in str(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"])) - self.assertEqual(substatus_file_data["name"], Constants.PATCH_ASSESSMENT_SUMMARY) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["code"], 1) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), 5) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"][1]["code"], - Constants.PatchOperationErrorCodes.OPERATION_FAILED) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"][0]["code"], - Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"][0]["message"], "a" * 125 + "...") - - # Adding installation error - self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) - self.runtime.status_handler.add_error_to_status("installexception1", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - substatus_file_data = [] - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][1] - self.assertNotEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"], None) - self.assertEqual(substatus_file_data["name"], Constants.PATCH_INSTALLATION_SUMMARY) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["code"], 1) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), 1) - - def test_add_duplicate_error(self): - # Setting operation to assessment to add all errors under assessment substatus - self.runtime.status_handler.set_current_operation(Constants.ASSESSMENT) - - # Unexpected input - self.runtime.status_handler.add_error_to_status(None) - self.runtime.status_handler.set_assessment_substatus_json() - substatus_file_data = [] - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), 0) - - self.runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) - - # Adding multiple, duplicate exceptions - self.runtime.status_handler.add_error_to_status("exception1", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - self.runtime.status_handler.add_error_to_status("exception1", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - self.runtime.status_handler.add_error_to_status("exception2", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - self.runtime.status_handler.add_error_to_status("exception2", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - self.runtime.status_handler.add_error_to_status("exception2: extra details", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - self.runtime.status_handler.add_error_to_status("exception2", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - self.runtime.status_handler.add_error_to_status("exception6", Constants.PatchOperationErrorCodes.OPERATION_FAILED) - - substatus_file_data = [] - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - - self.assertEqual("Success".lower(), str(substatus_file_data["status"]).lower()) - self.assertNotEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"], None) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["code"], 1) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), 3) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"][0]["code"], - Constants.PatchOperationErrorCodes.OPERATION_FAILED) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"][1]["code"], - Constants.PatchOperationErrorCodes.DEFAULT_ERROR) - - def test_add_error_fail(self): - self.runtime.status_handler.set_current_operation(Constants.ASSESSMENT) - - import tempfile - tempfile_backup = tempfile.NamedTemporaryFile - tempfile.NamedTemporaryFile = None - - # Error within retries for writing temporary file - error_raised = False - try: - self.runtime.status_handler.add_error_to_status("test") - except Exception as error: - error_raised = True - self.assertTrue("retries exhausted" in str(error)) - - self.assertTrue(error_raised) - tempfile.NamedTemporaryFile = tempfile_backup - - def test_status_file_initial_load(self): - # for non autopatching request, with Reboot started - self.runtime.status_handler.set_installation_reboot_status(Constants.RebootStatus.STARTED) - status_handler = StatusHandler(self.runtime.env_layer, self.runtime.execution_config, self.runtime.composite_logger, self.runtime.telemetry_writer, - self.runtime.vm_cloud_type) - self.assertTrue(status_handler is not None) - - # for autopatching request, with reboot started - self.runtime.status_handler.set_installation_reboot_status(Constants.RebootStatus.STARTED) - self.runtime.status_handler.set_patch_metadata_for_healthstore_substatus_json() - self.runtime.execution_config.maintenance_run_id = str(datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ")) - status_handler = StatusHandler(self.runtime.env_layer, self.runtime.execution_config, self.runtime.composite_logger, self.runtime.telemetry_writer, - self.runtime.vm_cloud_type) - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] - self.assertTrue(len(substatus_file_data) == 1) - - # for autopatching request, with reboot not started - self.runtime.status_handler.set_installation_reboot_status(Constants.RebootStatus.COMPLETED) - self.runtime.execution_config.maintenance_run_id = str(datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ")) - status_handler = StatusHandler(self.runtime.env_layer, self.runtime.execution_config, self.runtime.composite_logger, self.runtime.telemetry_writer, - self.runtime.vm_cloud_type) - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertTrue(status_handler is not None) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["shouldReportToHealthStore"], False) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patchVersion"], Constants.PATCH_VERSION_UNKNOWN) - self.assertEqual(substatus_file_data["status"].lower(), Constants.STATUS_SUCCESS.lower()) - - # fail to load status file - self.runtime.status_handler.set_installation_reboot_status(Constants.RebootStatus.STARTED) - backup_file_system_open = self.runtime.env_layer.file_system.open - self.runtime.env_layer.file_system.open = None - status_handler_failed = False - try: - status_handler = StatusHandler(self.runtime.env_layer, self.runtime.execution_config, self.runtime.composite_logger, - self.runtime.telemetry_writer, self.runtime.vm_cloud_type) - except Exception as error: - status_handler_failed = True - - self.assertTrue(status_handler_failed) - self.runtime.env_layer.file_system.open = backup_file_system_open - - def test_set_patch_metadata_for_healthstore_substatus_json(self): - # setting healthstore properties - self.runtime.status_handler.set_patch_metadata_for_healthstore_substatus_json(status=Constants.STATUS_SUCCESS, patch_version="2020-07-08", - report_to_healthstore=True, wait_after_update=True) - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["shouldReportToHealthStore"], True) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patchVersion"], "2020-07-08") - self.assertEqual(substatus_file_data["status"].lower(), Constants.STATUS_SUCCESS.lower()) - - # using default values - self.runtime.status_handler.set_patch_metadata_for_healthstore_substatus_json() - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["shouldReportToHealthStore"], False) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patchVersion"], Constants.PATCH_VERSION_UNKNOWN) - self.assertEqual(substatus_file_data["status"].lower(), Constants.STATUS_SUCCESS.lower()) - - def get_status_handler_substatus_maintenance_run_id(self): - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] - return json.loads(substatus_file_data[0]['formattedMessage']['message'])['maintenanceRunId'] - - def test_status_file_maintenance_run_id(self): - # Testing None/empty values for maintenance run id - self.runtime.status_handler.set_installation_reboot_status(Constants.RebootStatus.STARTED) - status_handler = StatusHandler(self.runtime.env_layer, self.runtime.execution_config, self.runtime.composite_logger, self.runtime.telemetry_writer, - self.runtime.vm_cloud_type) - self.assertTrue(status_handler is not None) - - # Expect datetime string - expected_maintenance_run_id = str(datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ")) - self.runtime.execution_config.maintenance_run_id = expected_maintenance_run_id - self.runtime.status_handler.set_installation_substatus_json() - self.assertEqual(expected_maintenance_run_id, self.get_status_handler_substatus_maintenance_run_id()) - - # Expect empty string - expected_maintenance_run_id = '' - self.runtime.execution_config.maintenance_run_id = expected_maintenance_run_id # Give empty string, expect empty string - self.runtime.status_handler.set_installation_substatus_json() - self.assertEqual(expected_maintenance_run_id, self.get_status_handler_substatus_maintenance_run_id()) - - self.runtime.execution_config.maintenance_run_id = None # Give None, expect empty string - self.runtime.status_handler.set_installation_substatus_json() - self.assertEqual(expected_maintenance_run_id, self.get_status_handler_substatus_maintenance_run_id()) - - def test_sequence_number_changed_termination_auto_assess_only(self): - self.runtime.execution_config.exec_auto_assess_only = True - self.runtime.status_handler.report_sequence_number_changed_termination() - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertTrue(substatus_file_data["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) - formatted_message = json.loads(substatus_file_data['formattedMessage']['message']) - self.assertTrue(formatted_message["errors"]["details"][0]["code"] == Constants.PatchOperationErrorCodes.NEWER_OPERATION_SUPERSEDED) - self.assertEqual(formatted_message["startedBy"], Constants.PatchAssessmentSummaryStartedBy.PLATFORM) - - def test_sequence_number_changed_termination_configuration_only(self): - self.runtime.execution_config.operation = Constants.CONFIGURE_PATCHING - self.runtime.status_handler.set_current_operation(Constants.CONFIGURE_PATCHING) - - self.runtime.status_handler.report_sequence_number_changed_termination() - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertTrue(substatus_file_data["name"] == Constants.CONFIGURE_PATCHING_SUMMARY) - self.assertEqual(substatus_file_data["status"], Constants.STATUS_ERROR.lower()) - formatted_message = json.loads(substatus_file_data['formattedMessage']['message']) - self.assertTrue(formatted_message["errors"]["details"][0]["code"] == Constants.PatchOperationErrorCodes.NEWER_OPERATION_SUPERSEDED) - - def test_sequence_number_changed_termination_installation(self): - self.runtime.execution_config.operation = Constants.INSTALLATION - self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) - - self.runtime.status_handler.report_sequence_number_changed_termination() - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertTrue(substatus_file_data["name"] == Constants.PATCH_INSTALLATION_SUMMARY) - self.assertEqual(substatus_file_data["status"], Constants.STATUS_ERROR.lower()) - formatted_message = json.loads(substatus_file_data['formattedMessage']['message']) - self.assertTrue(formatted_message["errors"]["details"][0]["code"] == Constants.PatchOperationErrorCodes.NEWER_OPERATION_SUPERSEDED) - - def test_set_patch_metadata_for_healthstore_substatus_json_auto_assess_transitioning(self): - self.runtime.execution_config.exec_auto_assess_only = True - self.assertRaises(Exception, - lambda: self.runtime.status_handler.set_patch_metadata_for_healthstore_substatus_json()) - - def test_set_configure_patching_substatus_json_auto_assess_transitioning(self): - self.runtime.execution_config.exec_auto_assess_only = True - self.assertRaises(Exception, - lambda: self.runtime.status_handler.set_configure_patching_substatus_json()) - - def test_set_current_operation_auto_assess_non_assessment(self): - self.runtime.execution_config.exec_auto_assess_only = True - self.assertRaises(Exception, - lambda: self.runtime.status_handler.set_current_operation(Constants.INSTALLATION)) - - def test_sort_packages_by_classification_and_state(self): - with self.runtime.env_layer.file_system.open("../../extension/tests/helpers/PatchOrderAssessmentSummary.json", 'r') as file_handle: - assessment_patches = json.load(file_handle)["patches"] - assessment_patches_sorted = self.runtime.status_handler.sort_packages_by_classification_and_state(assessment_patches) - # + Classifications | Patch State + - # |--------------------|-------------| - self.assertEqual(assessment_patches_sorted[0]["name"], "test-package-1") # | Critical | | - self.assertEqual(assessment_patches_sorted[1]["name"], "test-package-4") # | Security, Critical | | - self.assertEqual(assessment_patches_sorted[2]["name"], "test-package-5") # | Critical, Other | | - self.assertEqual(assessment_patches_sorted[3]["name"], "test-package-3") # | Other, Security | | - self.assertEqual(assessment_patches_sorted[4]["name"], "test-package-7") # | Security | | - self.assertEqual(assessment_patches_sorted[5]["name"], "test-package-2") # | Other | | - self.assertEqual(assessment_patches_sorted[6]["name"], "test-package-6") # | Unclassified | | - - with self.runtime.env_layer.file_system.open("../../extension/tests/helpers/PatchOrderInstallationSummary.json", 'r') as file_handle: - installation_patches = json.load(file_handle)["patches"] - installation_patches_sorted = self.runtime.status_handler.sort_packages_by_classification_and_state(installation_patches) - # + Classifications | Patch State + - # |--------------------|-------------| - self.assertEqual(installation_patches_sorted[0]["name"], "test-package-12") # | Critical, Security | Failed | - self.assertEqual(installation_patches_sorted[1]["name"], "test-package-14") # | Critical | Installed | - self.assertEqual(installation_patches_sorted[2]["name"], "test-package-13") # | Critical | Available | - self.assertEqual(installation_patches_sorted[3]["name"], "test-package-8") # | Security, Critical | Excluded | - - self.assertEqual(installation_patches_sorted[4]["name"], "test-package-6") # | Security | Failed | - self.assertEqual(installation_patches_sorted[5]["name"], "test-package-11") # | Security | Installed | - self.assertEqual(installation_patches_sorted[6]["name"], "test-package-10") # | Security | Available | - self.assertEqual(installation_patches_sorted[7]["name"], "test-package-9") # | Security | Pending | - self.assertEqual(installation_patches_sorted[8]["name"], "test-package-7") # | Security | NotSelected | - - self.assertEqual(installation_patches_sorted[9]["name"], "test-package-5") # | Other | Installed | - self.assertEqual(installation_patches_sorted[10]["name"], "test-package-4") # | Other | Available | - self.assertEqual(installation_patches_sorted[11]["name"], "test-package-3") # | Other | Pending | - self.assertEqual(installation_patches_sorted[12]["name"], "test-package-2") # | Other | Excluded | - self.assertEqual(installation_patches_sorted[13]["name"], "test-package-1") # | Other | NotSelected | - - def test_if_status_file_resets_on_load_if_malformed(self): - # Mock complete status file with malformed json - sample_json = '[{"version": 1.0, "timestampUTC": "2023-05-13T07:38:07Z", "statusx": {"name": "Azure Patch Management", "operation": "Installation", "status": "success", "code": 0, "formattedMessage": {"lang": "en-US", "message": ""}, "substatusx": []}}]' - file_path = self.runtime.execution_config.status_folder - example_file1 = os.path.join(file_path, '123.complete.status') - self.runtime.execution_config.complete_status_file_path = example_file1 - - with open(example_file1, 'w') as f: - f.write(sample_json) - - status_handler = StatusHandler(self.runtime.env_layer, self.runtime.execution_config, self.runtime.composite_logger, self.runtime.telemetry_writer, - self.runtime.vm_cloud_type) - # Mock complete status file with malformed json and being called in the load_status_file_components, and it will recreate a good complete_status_file - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0] - self.assertEqual(substatus_file_data["status"]["name"], "Azure Patch Management") - self.assertEqual(substatus_file_data["status"]["operation"], "Installation") - self.assertIsNotNone(substatus_file_data["status"]["substatus"]) - self.assertEqual(len(substatus_file_data["status"]["substatus"]), 0) - self.runtime.env_layer.file_system.delete_files_from_dir(example_file1, "*.complete.status") - - def test_if_complete_and_status_path_is_dir(self): - self.old_complete_status_path = self.runtime.execution_config.complete_status_file_path - self.runtime.execution_config.complete_status_file_path = self.runtime.execution_config.status_folder - self.runtime.status_handler.load_status_file_components(initial_load=True) - self.assertTrue(os.path.isfile(os.path.join(self.runtime.execution_config.status_folder, '1.complete.status'))) - - self.old_status_path = self.runtime.execution_config.status_file_path - self.runtime.execution_config.status_file_path = self.runtime.execution_config.status_folder - self.runtime.status_handler.load_status_file_components(initial_load=True) - self.assertTrue(os.path.isfile(os.path.join(self.runtime.execution_config.status_folder, '1.status'))) - - # reset the status path - self.runtime.execution_config.complete_status_file_path = self.old_complete_status_path - self.runtime.execution_config.status_file_path = self.old_status_path - - def test_remove_old_complete_status_files(self): - """ Create dummy files in status folder and check if the complete_status_file_path is the latest file and delete those dummy files """ - # Set up create temp file for log and set sys.stdout to it - self.__create_temp_file_and_set_stdout() - - file_path = self.runtime.execution_config.status_folder - for i in range(1, 15): - with open(os.path.join(file_path, str(i + 100) + '.complete.status'), 'w') as f: - f.write("test" + str(i)) - - packages, package_versions = self.runtime.package_manager.get_all_updates() - self.runtime.status_handler.set_package_assessment_status(packages, package_versions) - self.runtime.status_handler.load_status_file_components(initial_load=True) - - # remove 10 complete status files - count_status_files = glob.glob(os.path.join(file_path, '*.complete.status')) - self.assertEqual(10, len(count_status_files)) - self.assertTrue(os.path.isfile(self.runtime.execution_config.complete_status_file_path)) - self.runtime.env_layer.file_system.delete_files_from_dir(file_path, '*.complete.status') - self.assertFalse(os.path.isfile(os.path.join(file_path, '1.complete_status'))) - self.__read_temp_log_and_assert("Cleaned up older complete status files") - - # Reset sys.stdout, close and delete tmp - self.__remove_temp_file_reset_stdout() - - def test_remove_old_complete_status_files_throws_exception(self): - # Set up create temp file for log and set sys.stdout to it - self.__create_temp_file_and_set_stdout() - - file_path = self.runtime.execution_config.status_folder - for i in range(1, 16): - with open(os.path.join(file_path, str(i + 100) + '.complete.status'), 'w') as f: - f.write("test" + str(i)) - - self.backup_os_remove = os.remove - os.remove = self.__mock_os_remove - self.assertRaises(Exception, self.runtime.status_handler.load_status_file_components(initial_load=True)) - self.__read_temp_log_and_assert("Error deleting complete status file") - - # reset os.remove() mock and remove *complete.status files - os.remove = self.backup_os_remove - self.runtime.env_layer.file_system.delete_files_from_dir(file_path, '*.complete.status') - self.assertFalse(os.path.isfile(os.path.join(file_path, '1.complete_status'))) - - # Reset sys.stdout, close and delete tmp - self.__remove_temp_file_reset_stdout() - - def test_assessment_packages_map(self): - patch_count_for_test = 5 - expected_patch_id = 'python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04' - - status_handler = StatusHandler(self.runtime.env_layer, self.runtime.execution_config, self.runtime.composite_logger, self.runtime.telemetry_writer, - self.runtime.vm_cloud_type) - self.runtime.execution_config.operation = Constants.ASSESSMENT - self.runtime.status_handler.set_current_operation(Constants.ASSESSMENT) - - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_test) - status_handler.set_package_assessment_status(test_packages, test_package_versions, 'Critical') - - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - - self.assertTrue(substatus_file_data["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) - formatted_message = json.loads(substatus_file_data['formattedMessage']['message']) - self.assertEqual(len(formatted_message['patches']), patch_count_for_test) - self.assertEqual(formatted_message['patches'][0]['classifications'], ['Critical']) - self.assertEqual(formatted_message['patches'][0]['name'], 'python-samba0') - self.assertEqual(formatted_message['patches'][0]['patchId'], expected_patch_id) - - def test_installation_packages_map(self): - patch_id_other = 'python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04' - expected_value_other = {'version': '2:4.4.5+dfsg-2ubuntu5.4', 'classifications': ['Other'], 'name': 'python-samba0', - 'patchId': 'python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04', 'patchInstallationState': 'Installed'} - - patch_id_critical = 'python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04' - expected_value_critical = {'version': '2:4.4.5+dfsg-2ubuntu5.4', 'classifications': ['Critical'], 'name': 'python-samba0', - 'patchId': 'python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04', 'patchInstallationState': 'Installed'} - self.runtime.execution_config.operation = Constants.INSTALLATION - self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) - - patch_count_for_test = 50 - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_test) - - self.runtime.status_handler.set_package_install_status(test_packages, test_package_versions, 'Installed', 'Other') - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - - formatted_message = json.loads(substatus_file_data['formattedMessage']['message']) - self.assertEqual(len(formatted_message['patches']), patch_count_for_test) - self.assertEqual(formatted_message['patches'][0]['classifications'], ['Other']) - self.assertEqual(formatted_message['patches'][0]['patchId'], patch_id_other) - self.assertEqual(formatted_message['patches'][0], expected_value_other) - self.assertEqual(formatted_message['patches'][0]['name'], 'python-samba0') - - # Update the classification from Other to Critical - self.runtime.status_handler.set_package_install_status_classification(test_packages, test_package_versions, 'Critical') - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - - formatted_message = json.loads(substatus_file_data['formattedMessage']['message']) - self.assertEqual(len(formatted_message['patches']), patch_count_for_test) - self.assertEqual(formatted_message['patches'][0]['classifications'], ['Critical']) - self.assertEqual(formatted_message['patches'][0]['patchId'], patch_id_critical) - self.assertEqual(formatted_message['patches'][0], expected_value_critical) - self.assertEqual(formatted_message['patches'][0]['name'], 'python-samba0') - - def test_load_status_and_set_package_install_status(self): - patch_count_for_test = 5 - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_test) - file_path = self.runtime.execution_config.status_folder - example_file1 = os.path.join(file_path, '123.complete.status') - sample_json = [{"version": 1.0, "timestampUTC": "2023-06-17T02:06:19Z", - "status": {"name": "Azure Patch Management", "operation": "Installation", "status": "success", "code": 0, - "formattedMessage": {"lang": "en-US", "message": ""}, "substatus": [ - {"name": "PatchInstallationSummary", "status": "transitioning", "code": 0, "formattedMessage": {"lang": "en-US", - "message": "{\"installationActivityId\": \"c365ab46-a12a-4388-853b-5240a0702124\", \"rebootStatus\": \"NotNeeded\", \"maintenanceWindowExceeded\": false, \"notSelectedPatchCount\": 0, \"excludedPatchCount\": 0, \"pendingPatchCount\": 0, \"installedPatchCount\": 5, \"failedPatchCount\": 0, \"patches\": [{\"patchId\": \"python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba0\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Other\"], \"patchInstallationState\": \"Pending\"}, {\"patchId\": \"python-samba1_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba1\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Security\"], \"patchInstallationState\": \"Failed\"}, {\"patchId\": \"python-samba2_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba2\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Other\"], \"patchInstallationState\": \"Not_Selected\"}, {\"patchId\": \"python-samba3_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba3\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Other\"], \"patchInstallationState\": \"Pending\"}, {\"patchId\": \"python-samba4_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04\", \"name\": \"python-samba4\", \"version\": \"2:4.4.5+dfsg-2ubuntu5.4\", \"classifications\": [\"Unclassified\"], \"patchInstallationState\": \"Failed\"}], \"startTime\": \"2023-06-17T02:06:19.480634Z\", \"lastModifiedTime\": \"2023-06-17T02:06:19Z\", \"maintenanceRunId\": \"\", \"errors\": {\"code\": 0, \"details\": [], \"message\": \"0 error/s reported.\"}}"}}]}}] - with open(example_file1, 'w') as f: - f.write(json.dumps(sample_json)) - self.runtime.status_handler.status_file_path = example_file1 - self.runtime.status_handler.load_status_file_components(initial_load=True) - - # Test for set_package_install_status - self.runtime.status_handler.set_package_install_status(test_packages, test_package_versions, 'Installed', 'Critical') - with self.runtime.env_layer.file_system.open(self.runtime.status_handler.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(substatus_file_data["name"], Constants.PATCH_INSTALLATION_SUMMARY) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), patch_count_for_test) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["name"], "python-samba0") - self.assertTrue('Critical' in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["classifications"])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["name"], "python-samba1") - self.assertEqual('Critical', str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["classifications"][0])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["name"], "python-samba2") - self.assertEqual('python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04', - str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["patchId"])) - self.assertTrue('Critical' in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["classifications"])) - - # Test for set_package_install_status_classification - self.runtime.status_handler.set_package_install_status_classification(test_packages, test_package_versions, "Critical") - with self.runtime.env_layer.file_system.open(self.runtime.status_handler.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] - self.assertEqual(substatus_file_data["name"], Constants.PATCH_INSTALLATION_SUMMARY) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), patch_count_for_test) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["name"], "python-samba0") - self.assertTrue('Critical' in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["classifications"])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["name"], "python-samba1") - self.assertEqual('Critical', str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["classifications"][0])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["name"], "python-samba2") - self.assertEqual('python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04', - str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["patchId"])) - self.assertTrue('Critical' in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["classifications"])) - self.runtime.env_layer.file_system.delete_files_from_dir(self.runtime.status_handler.status_file_path, '*.complete.status') + self.__assert_truncated_status_multi_errors(Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_ERROR, patch_count, error_count=5) + + def test_both_assessment_and_installation_truncation_over_size_limit(self): + """ Perform truncation on very large assessment packages list for time performance. + Expecting: + assessment substatus status: warning, + assessment tombstone records, + assessment errors code: 2 (warning), + assessment errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. + + Perform truncation on very large installation packages list for time performance. + Expecting: + installation substatus status: warning, + installation tombstone records, + installation errors code: 2 (warning), + installation errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ + + self.runtime.execution_config.operation = Constants.INSTALLATION + self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) + + patch_count_for_assessment = 100000 + test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_assessment) + self.runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions) + self.runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) + + patch_count_for_installation = 100000 + test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_installation) + self.runtime.status_handler.set_package_install_status(test_packages, test_package_versions, Constants.INSTALLED) + self.runtime.status_handler.set_installation_substatus_json(status=Constants.STATUS_SUCCESS) + + # Test Complete status file + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: + substatus_file_data = json.load(file_handle) + + installation_msg = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"]) + self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + # Assert assessment summary + self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) + self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count_for_assessment, error_count=0) + + # Assert installation summary + self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_SUCCESS, + substatus_index=1) + self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][1], patch_count_for_installation, error_count=0) + + # Assert truncated status file + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: + truncated_substatus_file_data = json.load(file_handle) + + truncated_substatus_file_byte_size = len(json.dumps(truncated_substatus_file_data)) + assessment_truncated_msg = json.loads(truncated_substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) + installation_truncated_msg = json.loads(truncated_substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"]) + + # Assert truncated status file size < 128 n 126kb and length of truncated patches < length of complete status file patches + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue( + len(assessment_truncated_msg["patches"]) + len(installation_truncated_msg["patches"]) < patch_count_for_assessment + patch_count_for_installation) + + # Assert assessment truncation + self.__assert_patch_summary_from_status(truncated_substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, + Constants.STATUS_WARNING) + + # Assert assessment truncated error + self.__assert_truncated_error(truncated_substatus_file_data[0]["status"]["substatus"][0], error_count=0) + + # Assert installation truncation + installation_truncated_substatus = truncated_substatus_file_data[0]["status"]["substatus"][1] + self.__assert_patch_summary_from_status(truncated_substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, + Constants.STATUS_WARNING, substatus_index=1) + + # Assert installation truncated error + self.__assert_truncated_error(installation_truncated_substatus, error_count=0) + + # Assert all installation fields in the message json are equal in both status files + self.__assert_installation_truncated_msg_fields(installation_msg, installation_truncated_msg) + + def test_both_assessment_and_installation_truncation_keep_min_5_assessment(self): + """ Perform truncation on assessment packages list. + Expecting: + assessment substatus status: warning, + assessment tombstone records, but keep 5 packages + assessment errors code: 2 (warning), + assessment errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. + + Perform truncation on installation packages list. + Expecting: + installation substatus status: warning, + installation tombstone records, + installation errors code: 2 (warning), + installation errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ + + self.runtime.execution_config.operation = Constants.INSTALLATION + self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) + + patch_count_for_assessment = 7 + test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_assessment) + self.runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions) + self.runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) + + patch_count_for_installation = 1000 + test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_installation) + self.runtime.status_handler.set_package_install_status(test_packages, test_package_versions, Constants.INSTALLED) + self.runtime.status_handler.set_installation_substatus_json(status=Constants.STATUS_SUCCESS) + + # Test Complete status file + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: + substatus_file_data = json.load(file_handle) + + assessment_msg = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) + installation_msg = json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"]) + + # Assert complete status file size > 128kb + self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + # Assert assessment summary + self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, Constants.STATUS_SUCCESS) + self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][0], patch_count_for_assessment, error_count=0) + + # Assert installation summary + self.__assert_patch_summary_from_status(substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_SUCCESS, + substatus_index=1) + self.__asert_message_json_from_status(substatus_file_data[0]["status"]["substatus"][1], patch_count_for_installation, error_count=0) + + # Assert truncated status file + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: + truncated_substatus_file_data = json.load(file_handle) + + truncated_substatus_file_byte_size = len(json.dumps(truncated_substatus_file_data)) + assessment_truncated_msg = json.loads(truncated_substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) + installation_truncated_msg = json.loads(truncated_substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"]) + + # Assert truncated status file size < 128 n 126kb and length of truncated patches < length of complete status file patches + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue( + len(assessment_truncated_msg["patches"]) + len(installation_truncated_msg["patches"]) < patch_count_for_assessment + patch_count_for_installation) + + # Assert assessment truncation, keep min 5 + self.__assert_patch_summary_from_status(truncated_substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, + Constants.STATUS_WARNING) + + # Assert assessment truncated error + self.__assert_truncated_error(truncated_substatus_file_data[0]["status"]["substatus"][0], error_count=0) + + # Assert all assessment fields in the message json are equal in both status files + self.__assert_assessment_truncated_msg_fields(assessment_msg, assessment_truncated_msg) + + # Assert installation truncation + installation_truncated_substatus = truncated_substatus_file_data[0]["status"]["substatus"][1] + self.__assert_patch_summary_from_status(truncated_substatus_file_data, Constants.INSTALLATION, Constants.PATCH_INSTALLATION_SUMMARY, + Constants.STATUS_WARNING, substatus_index=1) + + # Assert installation truncated error + self.__assert_truncated_error(installation_truncated_substatus, error_count=0) + + # Assert all installation fields in the message json are equal in both status files + self.__assert_installation_truncated_msg_fields(installation_msg, installation_truncated_msg) + + def test_both_assessment_and_installation_truncation_with_multi_errors(self): + """ Perform truncation on assessment packages list with multiple errors to ensure __try_add_error is working as expected. + Expecting: + assessment substatus status: error, + assessment tombstone records, + assessment errors code: 1 (error), + assessment errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. + + Perform truncation on installation packages list with multiple errors to ensure __try_add_error is working as expected. + Expecting: + installation substatus status: error, + installation tombstone records, + installation errors code: 1 (error), + installation errors details code: Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists. """ + + self.runtime.execution_config.operation = Constants.INSTALLATION + self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) + + patch_count_assessment = random.randint(780, 1000) + test_packages, test_package_versions = self.__set_up_packages_func(patch_count_assessment) + self.runtime.status_handler.set_package_assessment_status(test_packages, test_package_versions, "Security") + + patch_count_installation = random.randint(780, 1000) + test_packages, test_package_versions = self.__set_up_packages_func(patch_count_installation) + self.runtime.status_handler.set_package_install_status(test_packages, test_package_versions, Constants.INSTALLED) + self.runtime.status_handler.set_assessment_substatus_json(status=Constants.STATUS_SUCCESS) + + # Set up complete status file before errors + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: + substatus_file_data = json.load(file_handle) + + # Assert complete status file size > 128kb + self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + # Assert no assessment message errors + self.assertEqual(len(json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"])["errors"]["details"]), 0) + # Assert no installation message errors + self.assertEqual(len(json.loads(substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"])["errors"]["details"]), 0) + + # Set up complete status file after errors - installation + self.__add_multiple_exception_errors() + self.runtime.status_handler.set_installation_substatus_json(status=Constants.STATUS_ERROR) + + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: + substatus_file_data = json.load(file_handle) + + assessment_msg = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) + + # Assert installation status file with multi error exceptions + self.__assert_status_file_data_multi_errors(substatus_file_data, Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_ERROR, patch_count_installation, + count_errors_detail=5, substatus_index=1) + + # Assert assessment truncated status file + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: + truncated_substatus_file_data = json.load(file_handle) + + # Assert truncated status file size < 128kb n 126kb and length of truncated patches < length of completed patches + truncated_substatus_file_byte_size = len(json.dumps(truncated_substatus_file_data)) + assessment_truncated_msg = json.loads(truncated_substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) + installation_truncated_msg = json.loads(truncated_substatus_file_data[0]["status"]["substatus"][1]["formattedMessage"]["message"]) + + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(len(assessment_truncated_msg["patches"]) + len(installation_truncated_msg) < patch_count_assessment + patch_count_installation) + + # Assert assessment truncated + self.__assert_patch_summary_from_status(truncated_substatus_file_data, Constants.INSTALLATION, Constants.PATCH_ASSESSMENT_SUMMARY, + Constants.STATUS_WARNING) + + # Assert truncated error + self.__assert_truncated_error(truncated_substatus_file_data[0]["status"]["substatus"][0], error_count=0) + + # Assert all assessment fields in the message json are equal in both status files + self.__assert_assessment_truncated_msg_fields(assessment_msg, assessment_truncated_msg) + + # Assert installation truncated status file with multi errors + self.__assert_truncated_status_multi_errors(Constants.PATCH_INSTALLATION_SUMMARY, Constants.STATUS_ERROR, patch_count_installation, error_count=5, + substatus_index=1) def test_log_truncated_packages_assert_no_truncation(self): # Set up create temp file for log and set sys.stdout to it @@ -1131,7 +890,7 @@ def test_log_truncated_packages_assert_installation_truncation(self): def test_truncation_method_time_performance(self): self.runtime.execution_config.operation = Constants.INSTALLATION self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) - self.__create_temp_file_and_set_stdout() # set tmp file for storing sys.stout() + self.__create_temp_file_and_set_stdout() # set tmp file for storing sys.stout() # Start no truncation performance test Constants.StatusTruncationConfig.TURN_ON_TRUNCATION = False @@ -1165,44 +924,50 @@ def test_truncation_method_time_performance(self): # Setup functions for testing def __assert_packages_map(self, substatus_file_data, patch_summary, patch_count, expected_patch_id, expected_patch_value, classification): - formatted_message = json.loads(substatus_file_data['formattedMessage']['message']) - self.assertTrue(substatus_file_data["name"] == patch_summary) - self.assertEqual(len(formatted_message['patches']), patch_count) - self.assertEqual(formatted_message['patches'][0]['patchId'], expected_patch_id) - self.assertEqual(formatted_message['patches'][0]['name'], 'python-samba0') - self.assertEqual(formatted_message['patches'][0], expected_patch_value) - self.assertEqual(formatted_message['patches'][0]['classifications'], [classification]) + message = json.loads(substatus_file_data['formattedMessage']['message']) + self.assertEqual(substatus_file_data["name"], patch_summary) + self.assertEqual(len(message['patches']), patch_count) + self.assertEqual(message['patches'][0]['patchId'], expected_patch_id) + self.assertEqual(message['patches'][0]['name'], 'python-samba0') + self.assertTrue(message['patches'][0], expected_patch_value) + self.assertEqual(message['patches'][0]['classifications'], [classification]) def __assert_installation_set_packages_methods(self, substatus_file_data, patch_summary, patch_count): + message = json.loads(substatus_file_data["formattedMessage"]["message"]) self.assertEqual(substatus_file_data["name"], patch_summary) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), patch_count) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["name"], "python-samba0") - self.assertTrue('Critical' in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["classifications"])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["name"], "python-samba1") - self.assertEqual('Critical', str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["classifications"][0])) - self.assertEqual('Installed', str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["patchInstallationState"])) - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["name"], "python-samba2") - self.assertEqual('python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04', str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["patchId"])) - self.assertTrue('Critical' in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["classifications"])) + self.assertEqual(len(message["patches"]), patch_count) + self.assertEqual(message["patches"][0]["name"], "python-samba0") + self.assertTrue('Critical' in str(message["patches"][0]["classifications"])) + self.assertEqual(message["patches"][1]["name"], "python-samba1") + self.assertEqual('Critical', str(message["patches"][1]["classifications"][0])) + self.assertEqual('Installed', str(message["patches"][1]["patchInstallationState"])) + self.assertEqual(message["patches"][2]["name"], "python-samba2") + self.assertEqual('python-samba0_2:4.4.5+dfsg-2ubuntu5.4_Ubuntu_16.04', str(message["patches"][0]["patchId"])) + self.assertTrue('Critical' in str(message["patches"][2]["classifications"])) self.runtime.env_layer.file_system.delete_files_from_dir(self.runtime.status_handler.status_file_path, '*.complete.status') - def __assert_patch_summary_from_status(self, substatus_file_data, operation, patch_summary, status): + def __assert_patch_summary_from_status(self, substatus_file_data, operation, patch_summary, status, substatus_index=0): self.assertEqual(substatus_file_data[0]["status"]["operation"], operation) - substatus_file_data = substatus_file_data[0]["status"]["substatus"][0] + + if patch_summary == Constants.PATCH_ASSESSMENT_SUMMARY: + substatus_file_data = substatus_file_data[0]["status"]["substatus"][substatus_index] + else: + substatus_file_data = substatus_file_data[0]["status"]["substatus"][substatus_index] + self.assertEqual(substatus_file_data["name"], patch_summary) self.assertEqual(substatus_file_data["status"], status.lower()) def __asert_message_json_from_status(self, substatus_file_data, patch_count, error_count=0): message = json.loads(substatus_file_data["formattedMessage"]["message"]) - self.assertEqual(len(message["patches"]), patch_count) - self.assertEqual(message["errors"]["code"], Constants.PatchOperationTopLevelErrorCode.SUCCESS) - self.assertEqual(len(message["errors"]["details"]), error_count) + self.assertEqual(patch_count, len(message["patches"])) + self.assertEqual(Constants.PatchOperationTopLevelErrorCode.SUCCESS, message["errors"]["code"]) + self.assertEqual(error_count, len(message["errors"]["details"])) def __assert_truncated_error(self, substatus_file_data, error_count): # assert error self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), error_count) - def __assert_complete_status_errors(self, patch_summary, status, patch_count): + def __add_multiple_exception_errors(self): # Adding multiple exceptions self.runtime.status_handler.add_error_to_status("exception0", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) self.runtime.status_handler.add_error_to_status("exception1", Constants.PatchOperationErrorCodes.DEFAULT_ERROR) @@ -1211,33 +976,55 @@ def __assert_complete_status_errors(self, patch_summary, status, patch_count): self.runtime.status_handler.add_error_to_status("exception4", Constants.PatchOperationErrorCodes.PACKAGE_MANAGER_FAILURE) self.runtime.status_handler.add_error_to_status("exception5", Constants.PatchOperationErrorCodes.OPERATION_FAILED) - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) - - self.assertTrue(len(json.dumps(substatus_file_data)) > Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - message = json.loads(substatus_file_data[0]["status"]["substatus"][0]["formattedMessage"]["message"]) - self.assertEqual(substatus_file_data[0]["status"]["substatus"][0]["name"], patch_summary) - self.assertEqual(substatus_file_data[0]["status"]["substatus"][0]["status"], status.lower()) + def __assert_status_file_data_multi_errors(self, substatus_file_data, patch_summary, status, patch_count, count_errors_detail=0, substatus_index=0): + message = json.loads(substatus_file_data[0]["status"]["substatus"][substatus_index]["formattedMessage"]["message"]) + self.assertEqual(substatus_file_data[0]["status"]["substatus"][substatus_index]["name"], patch_summary) + self.assertEqual(substatus_file_data[0]["status"]["substatus"][substatus_index]["status"], status.lower()) self.assertEqual(len(message["patches"]), patch_count) self.assertNotEqual(message["errors"], None) self.assertEqual(message["errors"]["code"], Constants.PatchOperationTopLevelErrorCode.ERROR) - self.assertEqual(len(message["errors"]["details"]), 5) + self.assertEqual(len(message["errors"]["details"]), count_errors_detail) self.assertEqual(message["errors"]["details"][0]["code"], Constants.PatchOperationErrorCodes.OPERATION_FAILED) self.assertEqual(message["errors"]["details"][1]["code"], Constants.PatchOperationErrorCodes.PACKAGE_MANAGER_FAILURE) self.assertEqual(message["errors"]["details"][0]["message"], "exception5") - def __assert_truncated_status_multi_errors(self, patch_summary, status, error_count): + def __assert_truncated_status_multi_errors(self, patch_summary, status, patch_count, error_count, substatus_index=0): with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: - substatus_file_data = json.load(file_handle) + truncated_substatus_file_data = json.load(file_handle) - substatus_file_data_byte_size = len(json.dumps(substatus_file_data)) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) - self.assertTrue(substatus_file_data_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) - self.assertEqual(substatus_file_data[0]["status"]["substatus"][0]["name"], patch_summary) - self.assertEqual(substatus_file_data[0]["status"]["substatus"][0]["status"], status.lower()) + truncated_substatus_file_byte_size = len(json.dumps(truncated_substatus_file_data)) + # Assert truncated status file size < 128kb n 126kb and length of truncated patches < length of complete status file patches + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.AGENT_FACING_STATUS_FILE_SIZE_LIMIT_IN_BYTES) + self.assertTrue(truncated_substatus_file_byte_size < Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES) + self.assertEqual(truncated_substatus_file_data[0]["status"]["substatus"][substatus_index]["name"], patch_summary) + self.assertEqual(truncated_substatus_file_data[0]["status"]["substatus"][substatus_index]["status"], status.lower()) + self.assertTrue( + len(json.loads(truncated_substatus_file_data[0]["status"]["substatus"][substatus_index]["formattedMessage"]["message"])["patches"]) < patch_count) # assert truncated error - self.__assert_truncated_error(substatus_file_data[0]["status"]["substatus"][0], error_count=error_count) + self.__assert_truncated_error(truncated_substatus_file_data[0]["status"]["substatus"][substatus_index], error_count=error_count) + + def __assert_assessment_truncated_msg_fields(self, assessment_msg, truncated_assessment_msg): + self.assertEqual(assessment_msg['assessmentActivityId'], truncated_assessment_msg['assessmentActivityId']) + self.assertEqual(assessment_msg['rebootPending'], truncated_assessment_msg['rebootPending']) + self.assertEqual(assessment_msg['criticalAndSecurityPatchCount'], truncated_assessment_msg['criticalAndSecurityPatchCount']) + self.assertEqual(assessment_msg['otherPatchCount'], truncated_assessment_msg['otherPatchCount']) + self.assertEqual(assessment_msg['startTime'], truncated_assessment_msg['startTime']) + self.assertEqual(assessment_msg['lastModifiedTime'], truncated_assessment_msg['lastModifiedTime']) + self.assertEqual(assessment_msg['startedBy'], truncated_assessment_msg['startedBy']) + + def __assert_installation_truncated_msg_fields(self, installation_msg, truncated_installation_msg): + self.assertEqual(installation_msg['installationActivityId'], truncated_installation_msg['installationActivityId']) + self.assertEqual(installation_msg['rebootStatus'], truncated_installation_msg['rebootStatus']) + self.assertEqual(installation_msg['maintenanceWindowExceeded'], truncated_installation_msg['maintenanceWindowExceeded']) + self.assertEqual(installation_msg['notSelectedPatchCount'], truncated_installation_msg['notSelectedPatchCount']) + self.assertEqual(installation_msg['excludedPatchCount'], truncated_installation_msg['excludedPatchCount']) + self.assertEqual(installation_msg['pendingPatchCount'], truncated_installation_msg['pendingPatchCount']) + self.assertEqual(installation_msg['installedPatchCount'], truncated_installation_msg['installedPatchCount']) + self.assertEqual(installation_msg['failedPatchCount'], truncated_installation_msg['failedPatchCount']) + self.assertEqual(installation_msg['startTime'], truncated_installation_msg['startTime']) + self.assertEqual(installation_msg['lastModifiedTime'], truncated_installation_msg['lastModifiedTime']) + self.assertEqual(installation_msg['maintenanceRunId'], truncated_installation_msg['maintenanceRunId']) def __convert_test_performance_to_date_time(self, performance_time): performance_time = abs(performance_time) @@ -1256,12 +1043,12 @@ def __create_temp_file_and_set_stdout(self): # Set up create temp file for log and set sys.stdout to it self.temp_stdout = tempfile.NamedTemporaryFile(delete=False, mode="w+") self.saved_stdout = sys.stdout # Save the original stdout - sys.stdout = self.temp_stdout # set it to the temporary file + sys.stdout = self.temp_stdout # set it to the temporary file def __remove_temp_file_reset_stdout(self): sys.stdout = self.saved_stdout # redirect to original stdout self.temp_stdout.close() - os.remove(self.temp_stdout.name) # Remove the temporary file + os.remove(self.temp_stdout.name) # Remove the temporary file def __read_temp_log_and_assert(self, expected_string): self.temp_stdout.flush() @@ -1270,15 +1057,20 @@ def __read_temp_log_and_assert(self, expected_string): self.assertIn(expected_string, captured_log_output) # Setup functions to populate packages and versions for truncation - def __set_up_packages_func(self, val): + def __set_up_packages_func(self, val, random_char=None): test_packages = [] test_package_versions = [] for i in range(0, val): test_packages.append('python-samba' + str(i)) - test_package_versions.append('2:4.4.5+dfsg-2ubuntu5.4') + + if random_char is not None: + test_package_versions.append('2:4.4.5+dfsg-2ubuntu5.4' + random_char) + else: + test_package_versions.append('2:4.4.5+dfsg-2ubuntu5.4') return test_packages, test_package_versions + if __name__ == '__main__': unittest.main()