From b8e45aec638f3648ae08bfdb736444bfad27fbe6 Mon Sep 17 00:00:00 2001 From: john feng Date: Thu, 15 Jun 2023 23:50:51 -0700 Subject: [PATCH 01/11] add complete status file logic, and modify every status file points to complete status file --- src/core/src/core_logic/ExecutionConfig.py | 5 +- .../src/service_interfaces/StatusHandler.py | 108 ++++++++++-------- src/core/tests/Test_AptitudePackageManager.py | 4 +- .../tests/Test_ConfigurePatchingProcessor.py | 14 +-- src/core/tests/Test_CoreMain.py | 60 +++++----- src/core/tests/Test_PatchAssessor.py | 2 +- src/core/tests/Test_PatchInstaller.py | 4 +- src/core/tests/Test_StatusHandler.py | 90 +++++++++++---- src/extension/src/Constants.py | 8 ++ 9 files changed, 185 insertions(+), 110 deletions(-) diff --git a/src/core/src/core_logic/ExecutionConfig.py b/src/core/src/core_logic/ExecutionConfig.py index 905e871d..23453c0e 100644 --- a/src/core/src/core_logic/ExecutionConfig.py +++ b/src/core/src/core_logic/ExecutionConfig.py @@ -74,10 +74,11 @@ def __init__(self, env_layer, composite_logger, execution_parameters): # Derived Settings self.log_file_path = os.path.join(self.log_folder, str(self.sequence_number) + ".core.log") + self.complete_status_file_path = os.path.join(self.status_folder, str(self.sequence_number) + ".complete" + ".status") self.status_file_path = os.path.join(self.status_folder, str(self.sequence_number) + ".status") self.include_assessment_with_configure_patching = (self.operation == Constants.CONFIGURE_PATCHING and self.assessment_mode == Constants.AssessmentModes.AUTOMATIC_BY_PLATFORM) - self.composite_logger.log_debug(" - Derived execution-config settings. [CoreLog={0}][StatusFile={1}][IncludeAssessmentWithConfigurePatching={2}]" - .format(str(self.log_file_path), str(self.status_file_path), self.include_assessment_with_configure_patching)) + self.composite_logger.log_debug(" - Derived execution-config settings. [CoreLog={0}][CompleteStatusFile={1}][StatusFile={2}][IncludeAssessmentWithConfigurePatching={2}]" + .format(str(self.log_file_path), str(self.complete_status_file_path), str(self.status_file_path), self.include_assessment_with_configure_patching)) # Auto assessment overrides if self.exec_auto_assess_only: diff --git a/src/core/src/service_interfaces/StatusHandler.py b/src/core/src/service_interfaces/StatusHandler.py index d25233cd..dd2a193d 100644 --- a/src/core/src/service_interfaces/StatusHandler.py +++ b/src/core/src/service_interfaces/StatusHandler.py @@ -31,6 +31,7 @@ def __init__(self, env_layer, execution_config, composite_logger, telemetry_writ self.execution_config = execution_config self.composite_logger = composite_logger self.telemetry_writer = telemetry_writer # not used immediately but need to know if there are issues persisting status + self.complete_status_file_path = self.execution_config.complete_status_file_path self.status_file_path = self.execution_config.status_file_path self.__log_file_path = self.execution_config.log_file_path self.vm_cloud_type = vm_cloud_type @@ -310,8 +311,8 @@ def set_assessment_substatus_json(self, status=Constants.STATUS_TRANSITIONING, c # Wrap assessment summary into assessment substatus self.__assessment_substatus_json = self.__new_substatus_json_for_operation(Constants.PATCH_ASSESSMENT_SUMMARY, status, code, json.dumps(self.__assessment_summary_json)) - # Update status on disk - self.__write_status_file() + # Update complete status on disk + self.__write_complete_status_file() def __new_assessment_summary_json(self, assessment_packages_json, status, code): """ Called by: set_assessment_substatus_json @@ -359,8 +360,8 @@ def set_installation_substatus_json(self, status=Constants.STATUS_TRANSITIONING, # Wrap deployment summary into installation substatus self.__installation_substatus_json = self.__new_substatus_json_for_operation(Constants.PATCH_INSTALLATION_SUMMARY, status, code, json.dumps(self.__installation_summary_json)) - # Update status on disk - self.__write_status_file() + # Update complete status on disk + self.__write_complete_status_file() def __new_installation_summary_json(self, installation_packages_json): """ Called by: set_installation_substatus_json @@ -421,8 +422,8 @@ def set_patch_metadata_for_healthstore_substatus_json(self, status=Constants.STA # Wrap healthstore summary into healthstore substatus self.__metadata_for_healthstore_substatus_json = self.__new_substatus_json_for_operation(Constants.PATCH_METADATA_FOR_HEALTHSTORE, status, code, json.dumps(self.__metadata_for_healthstore_summary_json)) - # Update status on disk - self.__write_status_file() + # Update complete status on disk + self.__write_complete_status_file() # wait period required in cases where we need to ensure HealthStore reads the status from GA if wait_after_update: @@ -454,8 +455,8 @@ def set_configure_patching_substatus_json(self, status=Constants.STATUS_TRANSITI # Wrap configure patching summary into configure patching substatus self.__configure_patching_substatus_json = self.__new_substatus_json_for_operation(Constants.CONFIGURE_PATCHING_SUMMARY, status, code, json.dumps(self.__configure_patching_summary_json)) - # Update status on disk - self.__write_status_file() + # Update complete status on disk + self.__write_complete_status_file() def __new_configure_patching_summary_json(self, automatic_os_patch_state, auto_assessment_state, status, code): """ Called by: set_configure_patching_substatus_json @@ -495,6 +496,10 @@ def __new_substatus_json_for_operation(operation_name, status="Transitioning", c # region - Status generation def __reset_status_file(self): + status_file_reset_content = json.dumps(self.__new_basic_status_json()) + # Create complete status template + self.env_layer.file_system.write_with_retry(self.complete_status_file_path, '[{0}]'.format(status_file_reset_content), mode='w+') + # Create agent-facing status template self.env_layer.file_system.write_with_retry(self.status_file_path, '[{0}]'.format(json.dumps(self.__new_basic_status_json())), mode='w+') def __new_basic_status_json(self): @@ -552,44 +557,32 @@ def load_status_file_components(self, initial_load=False): self.composite_logger.log_debug("Loading status file components [InitialLoad={0}].".format(str(initial_load))) # Verify the status file exists - if not, reset status file - if not os.path.exists(self.status_file_path) and initial_load: + if not os.path.exists(self.complete_status_file_path) and initial_load: self.composite_logger.log_warning("Status file not found at initial load. Resetting status file to defaults.") self.__reset_status_file() return - - # Read the status file - raise exception on persistent failure - for i in range(0, Constants.MAX_FILE_OPERATION_RETRY_COUNT): - try: - with self.env_layer.file_system.open(self.status_file_path, 'r') as file_handle: - status_file_data_raw = json.load(file_handle)[0] # structure is array of 1 - except Exception as error: - if i < Constants.MAX_FILE_OPERATION_RETRY_COUNT - 1: - time.sleep(i + 1) - else: - self.composite_logger.log_error("Unable to read status file (retries exhausted). Error: {0}.".format(repr(error))) - raise - + complete_status_file_data_raw = self.__read_complete_status_raw_data(self.complete_status_file_path) # Load status data and sanity check structure - raise exception if data loss risk is detected on corrupt data try: - status_file_data = status_file_data_raw - if 'status' not in status_file_data or 'substatus' not in status_file_data['status']: + complete_status_file_data = complete_status_file_data_raw + if 'status' not in complete_status_file_data or 'substatus' not in complete_status_file_data['status']: self.composite_logger.log_error("Malformed status file. Resetting status file for safety.") self.__reset_status_file() return except Exception as error: - self.composite_logger.log_error("Unable to load status file json. Error: {0}; Data: {1}".format(repr(error), str(status_file_data_raw))) + self.composite_logger.log_error("Unable to load status file json. Error: {0}; Data: {1}".format(repr(error), str(complete_status_file_data_raw))) raise # Load portions of data that need to be built on for next write - raise exception if corrupt data is encountered # todo: refactor - self.__high_level_status_message = status_file_data['status']['formattedMessage']['message'] - for i in range(0, len(status_file_data['status']['substatus'])): - name = status_file_data['status']['substatus'][i]['name'] + self.__high_level_status_message = complete_status_file_data['status']['formattedMessage']['message'] + for i in range(0, len(complete_status_file_data['status']['substatus'])): + name = complete_status_file_data['status']['substatus'][i]['name'] if name == Constants.PATCH_INSTALLATION_SUMMARY: # if it exists, it must be to spec, or an exception will get thrown if self.execution_config.exec_auto_assess_only: - self.__installation_substatus_json = status_file_data['status']['substatus'][i] + self.__installation_substatus_json = complete_status_file_data['status']['substatus'][i] else: - message = status_file_data['status']['substatus'][i]['formattedMessage']['message'] + message = complete_status_file_data['status']['substatus'][i]['formattedMessage']['message'] self.__installation_summary_json = json.loads(message) self.__installation_packages = self.__installation_summary_json['patches'] self.__maintenance_window_exceeded = bool(self.__installation_summary_json['maintenanceWindowExceeded']) @@ -599,7 +592,7 @@ def load_status_file_components(self, initial_load=False): self.__installation_errors = errors['details'] self.__installation_total_error_count = self.__get_total_error_count_from_prev_status(errors['message']) if name == Constants.PATCH_ASSESSMENT_SUMMARY: # if it exists, it must be to spec, or an exception will get thrown - message = status_file_data['status']['substatus'][i]['formattedMessage']['message'] + message = complete_status_file_data['status']['substatus'][i]['formattedMessage']['message'] self.__assessment_summary_json = json.loads(message) self.__assessment_packages = self.__assessment_summary_json['patches'] errors = self.__assessment_summary_json['errors'] @@ -608,22 +601,22 @@ def load_status_file_components(self, initial_load=False): self.__assessment_total_error_count = self.__get_total_error_count_from_prev_status(errors['message']) if name == Constants.PATCH_METADATA_FOR_HEALTHSTORE: # if it exists, it must be to spec, or an exception will get thrown if self.execution_config.exec_auto_assess_only: - self.__metadata_for_healthstore_substatus_json = status_file_data['status']['substatus'][i] + self.__metadata_for_healthstore_substatus_json = complete_status_file_data['status']['substatus'][i] else: - message = status_file_data['status']['substatus'][i]['formattedMessage']['message'] + message = complete_status_file_data['status']['substatus'][i]['formattedMessage']['message'] self.__metadata_for_healthstore_summary_json = json.loads(message) if name == Constants.CONFIGURE_PATCHING_SUMMARY: # if it exists, it must be to spec, or an exception will get thrown if self.execution_config.exec_auto_assess_only: - self.__configure_patching_substatus_json = status_file_data['status']['substatus'][i] + self.__configure_patching_substatus_json = complete_status_file_data['status']['substatus'][i] else: - message = status_file_data['status']['substatus'][i]['formattedMessage']['message'] + message = complete_status_file_data['status']['substatus'][i]['formattedMessage']['message'] self.__configure_patching_summary_json = json.loads(message) errors = self.__configure_patching_summary_json['errors'] if errors is not None and errors['details'] is not None: self.__configure_patching_errors = errors['details'] self.__configure_patching_top_level_error_count = self.__get_total_error_count_from_prev_status(errors['message']) - def __write_status_file(self): + def __write_complete_status_file(self): """ Composes and writes the status file from **already up-to-date** in-memory data. This is usually the final call to compose and persist after an in-memory data update in a specialized method. @@ -660,22 +653,26 @@ def __write_status_file(self): :return: None """ - status_file_payload = self.__new_basic_status_json() - status_file_payload['status']['formattedMessage']['message'] = str(self.__high_level_status_message) + complete_status_payload = self.__new_basic_status_json() + complete_status_payload['status']['formattedMessage']['message'] = str(self.__high_level_status_message) if self.__assessment_substatus_json is not None: - status_file_payload['status']['substatus'].append(self.__assessment_substatus_json) + complete_status_payload['status']['substatus'].append(self.__assessment_substatus_json) if self.__installation_substatus_json is not None: - status_file_payload['status']['substatus'].append(self.__installation_substatus_json) + complete_status_payload['status']['substatus'].append(self.__installation_substatus_json) if self.__metadata_for_healthstore_substatus_json is not None: - status_file_payload['status']['substatus'].append(self.__metadata_for_healthstore_substatus_json) + complete_status_payload['status']['substatus'].append(self.__metadata_for_healthstore_substatus_json) if self.__configure_patching_substatus_json is not None: - status_file_payload['status']['substatus'].append(self.__configure_patching_substatus_json) - if os.path.isdir(self.status_file_path): + complete_status_payload['status']['substatus'].append(self.__configure_patching_substatus_json) + if os.path.isdir(self.complete_status_file_path): self.composite_logger.log_error("Core state file path returned a directory. Attempting to reset.") - shutil.rmtree(self.status_file_path) + shutil.rmtree(self.complete_status_file_path) + + # Write complete status file .complete.status + self.env_layer.file_system.write_with_retry_using_temp_file(self.complete_status_file_path, '[{0}]'.format(json.dumps(complete_status_payload)), mode='w+') - self.env_layer.file_system.write_with_retry_using_temp_file(self.status_file_path, '[{0}]'.format(json.dumps(status_file_payload)), mode='w+') + # Write agent status file + self.__write_status_file(complete_status_payload) # endregion # region - Error objects @@ -783,3 +780,24 @@ def __set_errors_json(self, error_count_by_operation, errors_by_operation): "message": message } # endregion + + # region - Patch Truncation + def __read_complete_status_raw_data(self, file_path): + # Read the status file - raise exception on persistent failure + for i in range(0, Constants.MAX_FILE_OPERATION_RETRY_COUNT): + try: + with self.env_layer.file_system.open(file_path, 'r') as file_handle: + complete_status_data_raw = json.load(file_handle)[0] # structure is array of 1 + except Exception as error: + if i < Constants.MAX_FILE_OPERATION_RETRY_COUNT - 1: + time.sleep(i + 1) + else: + self.composite_logger.log_error("Unable to read status file (retries exhausted). Error: {0}.".format(repr(error))) + raise + return complete_status_data_raw + + def __write_status_file(self, complete_status_payload): + truncated_status_payload = complete_status_payload + self.env_layer.file_system.write_with_retry_using_temp_file(self.status_file_path, '[{0}]'.format(json.dumps(truncated_status_payload)), mode='w+') + + # endregion diff --git a/src/core/tests/Test_AptitudePackageManager.py b/src/core/tests/Test_AptitudePackageManager.py index f15fb6dd..5b3167b0 100644 --- a/src/core/tests/Test_AptitudePackageManager.py +++ b/src/core/tests/Test_AptitudePackageManager.py @@ -142,7 +142,7 @@ def test_install_package_failure(self): # ensure that error message appears in substatus properly substatus_file_data = [] - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: status = json.load(file_handle) self.assertEqual(status[0]["status"]["status"].lower(), Constants.STATUS_SUCCESS.lower()) substatus_file_data = status[0]["status"]["substatus"][0] @@ -491,7 +491,7 @@ def test_set_security_esm_package_status_assessment(self): runtime.patch_assessor.start_assessment() status = "" error_set = False - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: status = json.load(file_handle) self.assertEqual(status[0]["status"]["status"].lower(), Constants.STATUS_SUCCESS.lower()) self.assertEqual(status[0]["status"]["substatus"][0]["name"], "PatchAssessmentSummary") diff --git a/src/core/tests/Test_ConfigurePatchingProcessor.py b/src/core/tests/Test_ConfigurePatchingProcessor.py index 2aa0a110..52e68fd4 100644 --- a/src/core/tests/Test_ConfigurePatchingProcessor.py +++ b/src/core/tests/Test_ConfigurePatchingProcessor.py @@ -59,7 +59,7 @@ def test_operation_success_for_configure_patching_request_for_apt(self): self.__check_telemetry_events(runtime) # check status file for configure patching patch mode - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] # check status file for configure patching patch state (and including for 'Platform' initiated assessment data) @@ -106,7 +106,7 @@ def test_operation_success_for_installation_request_with_configure_patching(self self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertTrue(runtime.package_manager.image_default_patch_configuration_backup_exists()) image_default_patch_configuration_backup = json.loads(runtime.env_layer.file_system.read_with_retry(runtime.package_manager.image_default_patch_configuration_backup_path)) @@ -149,7 +149,7 @@ def test_operation_fail_for_configure_patching_telemetry_not_supported(self): runtime.configure_patching_processor.start_configure_patching() # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 1) self.assertTrue(substatus_file_data[0]["name"] == Constants.CONFIGURE_PATCHING_SUMMARY) @@ -177,7 +177,7 @@ def test_operation_fail_for_configure_patching_request_for_apt(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) # assessment is now part of the CP flow @@ -206,7 +206,7 @@ def test_patch_mode_set_failure_for_configure_patching(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) # assessment is now part of the CP flow @@ -244,7 +244,7 @@ def test_configure_patching_with_assessment_mode_by_platform(self): self.__check_telemetry_events(runtime) # check status file for configure patching patch mode - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] # check status file for configure patching patch state @@ -288,7 +288,7 @@ def test_configure_patching_with_patch_mode_and_assessment_mode_by_platform(self self.__check_telemetry_events(runtime) # check status file for configure patching patch mode - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] # check status file for configure patching patch state diff --git a/src/core/tests/Test_CoreMain.py b/src/core/tests/Test_CoreMain.py index 6395e6c2..fcc8949f 100644 --- a/src/core/tests/Test_CoreMain.py +++ b/src/core/tests/Test_CoreMain.py @@ -65,7 +65,7 @@ def test_operation_fail_for_non_autopatching_request(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 3) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -88,7 +88,7 @@ def test_operation_fail_for_autopatching_request(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -115,7 +115,7 @@ def test_operation_success_for_non_autopatching_request(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 3) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -139,7 +139,7 @@ def test_operation_success_for_autopatching_request(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -170,7 +170,7 @@ def test_operation_success_for_autopatching_request_with_security_classification self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -206,7 +206,7 @@ def test_invalid_maintenance_run_id(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -234,7 +234,7 @@ def test_invalid_maintenance_run_id(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -261,7 +261,7 @@ def test_assessment_operation_success(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -281,7 +281,7 @@ def test_assessment_operation_fail(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -300,7 +300,7 @@ def test_assessment_operation_fail_due_to_no_telemetry(self): runtime.set_legacy_test_type('HappyPath') CoreMain(composed_arguments) - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -321,7 +321,7 @@ def test_installation_operation_fail_due_to_telemetry_unsupported_no_events_fold runtime.set_legacy_test_type('SuccessInstallPath') CoreMain(argument_composer.get_composed_arguments()) - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -353,7 +353,7 @@ def test_installation_operation_fail_due_to_no_telemetry(self): runtime.patch_assessor.start_assessment = lambda: () CoreMain(argument_composer.get_composed_arguments()) - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -379,7 +379,7 @@ def test_assessment_operation_fail_on_arc_due_to_no_telemetry(self): runtime.set_legacy_test_type('HappyPath') CoreMain(argument_composer.get_composed_arguments()) - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -400,7 +400,7 @@ def test_installation_operation_fail_on_arc_due_to_no_telemetry(self): runtime.set_legacy_test_type('SuccessInstallPath') CoreMain(argument_composer.get_composed_arguments()) - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -440,7 +440,7 @@ def test_install_all_packages_for_centos_autopatching(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -490,7 +490,7 @@ def test_install_all_packages_for_centos_autopatching_as_warning_with_never_rebo self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -539,7 +539,7 @@ def test_install_only_critical_and_security_packages_for_redhat_autopatching(sel self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -595,7 +595,7 @@ def test_install_only_critical_and_security_packages_for_redhat_autopatching_war self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -644,7 +644,7 @@ def test_auto_assessment_success_with_configure_patching_in_prev_operation_on_sa # check telemetry events self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] self.assertTrue(status_file_data["operation"] == Constants.CONFIGURE_PATCHING) substatus_file_data = status_file_data["substatus"] @@ -670,7 +670,7 @@ def test_auto_assessment_success_with_configure_patching_in_prev_operation_on_sa runtime.execution_config.exec_auto_assess_only = True CoreMain(argument_composer.get_composed_arguments()) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] # verifying the original operation name is preserved self.assertTrue(status_file_data["operation"] == Constants.CONFIGURE_PATCHING) @@ -702,7 +702,7 @@ def test_auto_assessment_success_on_arc_with_configure_patching_in_prev_operatio runtime.set_legacy_test_type("SuccessInstallPath") CoreMain(argument_composer.get_composed_arguments()) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] self.assertTrue(status_file_data["operation"] == Constants.CONFIGURE_PATCHING) substatus_file_data = status_file_data["substatus"] @@ -730,7 +730,7 @@ def test_auto_assessment_success_on_arc_with_configure_patching_in_prev_operatio # check telemetry events self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] # verifying the original operation name is preserved self.assertTrue(status_file_data["operation"] == Constants.CONFIGURE_PATCHING) @@ -762,7 +762,7 @@ def test_auto_assessment_success_with_assessment_in_prev_operation_on_same_seque # check telemetry events self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] self.assertTrue(status_file_data["operation"] == Constants.ASSESSMENT) substatus_file_data = status_file_data["substatus"] @@ -794,7 +794,7 @@ def test_auto_assessment_success_with_assessment_in_prev_operation_on_same_seque # check telemetry events self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] # verifying the original operation name is preserved @@ -831,7 +831,7 @@ def test_auto_assessment_success_with_installation_in_prev_operation_on_same_seq # check telemetry events self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] self.assertTrue(status_file_data["operation"] == Constants.INSTALLATION) substatus_file_data = status_file_data["substatus"] @@ -868,7 +868,7 @@ def test_auto_assessment_success_with_installation_in_prev_operation_on_same_seq # check telemetry events self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] # verifying the original operation name is preserved @@ -922,7 +922,7 @@ def test_assessment_operation_fail_after_package_manager_reboot(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 3) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -958,7 +958,7 @@ def test_assessment_operation_success_after_package_manager_reboot(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 3) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -983,7 +983,7 @@ def test_assessment_superseded(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -1022,7 +1022,7 @@ def test_assessment_superseded(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) diff --git a/src/core/tests/Test_PatchAssessor.py b/src/core/tests/Test_PatchAssessor.py index 96c0684c..641fbba0 100644 --- a/src/core/tests/Test_PatchAssessor.py +++ b/src/core/tests/Test_PatchAssessor.py @@ -51,7 +51,7 @@ def test_assessment_fail_with_status_update(self): self.runtime.package_manager.refresh_repo = self.mock_refresh_repo self.runtime.set_legacy_test_type('UnalignedPath') self.assertRaises(Exception, self.runtime.patch_assessor.start_assessment) - with open(self.runtime.execution_config.status_file_path, 'r') as file_handle: + with open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: file_contents = json.loads(file_handle.read()) self.assertTrue('Unexpected return code (100) from package manager on command: LANG=en_US.UTF8 sudo apt-get -s dist-upgrade' in str(file_contents)) diff --git a/src/core/tests/Test_PatchInstaller.py b/src/core/tests/Test_PatchInstaller.py index 12068df6..228521c0 100644 --- a/src/core/tests/Test_PatchInstaller.py +++ b/src/core/tests/Test_PatchInstaller.py @@ -247,7 +247,7 @@ def test_mark_status_completed_esm_required(self): runtime.patch_installer.install_updates(runtime.maintenance_window, runtime.package_manager, simulate=True) runtime.patch_installer.mark_installation_completed() - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] self.assertEqual('warning', substatus_file_data[0]['status']) @@ -551,7 +551,7 @@ def healthstore_writes_helper(self, health_store_id, maintenance_run_id, is_forc runtime.maintenance_window, runtime.package_manager, simulate=True) runtime.patch_installer.mark_installation_completed() - with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: + 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)[0]["status"]["substatus"] if is_force_reboot: diff --git a/src/core/tests/Test_StatusHandler.py b/src/core/tests/Test_StatusHandler.py index 657adb1d..f014db03 100644 --- a/src/core/tests/Test_StatusHandler.py +++ b/src/core/tests/Test_StatusHandler.py @@ -24,6 +24,7 @@ class TestStatusHandler(unittest.TestCase): def setUp(self): + self.test_internal_file_size_limit = Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES self.runtime = RuntimeCompositor(ArgumentComposer().get_composed_arguments(), True) self.container = self.runtime.container @@ -34,7 +35,7 @@ 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: + 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(substatus_file_data["name"], Constants.PATCH_ASSESSMENT_SUMMARY) self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), 3) @@ -49,7 +50,7 @@ def test_set_package_assessment_status_for_auto_assessment(self): 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: + 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(substatus_file_data["name"], Constants.PATCH_ASSESSMENT_SUMMARY) self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), 3) @@ -63,7 +64,7 @@ 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: + 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(substatus_file_data["name"], Constants.PATCH_INSTALLATION_SUMMARY) self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), 3) @@ -76,13 +77,13 @@ def test_set_package_install_status(self): 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: + 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(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: + 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(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) @@ -93,7 +94,7 @@ def test_set_package_install_status_classification(self): 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: + 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(substatus_file_data["name"], Constants.PATCH_INSTALLATION_SUMMARY) self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), 3) @@ -111,7 +112,7 @@ def test_set_package_install_status_classification_not_set(self): 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: + 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(substatus_file_data["name"], Constants.PATCH_INSTALLATION_SUMMARY) self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), 3) @@ -129,19 +130,19 @@ def test_set_installation_reboot_status(self): # 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: + 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(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: + 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(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: + 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(json.loads(substatus_file_data["formattedMessage"]["message"])["maintenanceWindowExceeded"]) @@ -153,7 +154,7 @@ def test_add_error(self): 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: + 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) @@ -174,7 +175,7 @@ def test_add_error(self): 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: + 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("Success".lower(), str(substatus_file_data["status"]).lower()) @@ -191,7 +192,7 @@ def test_add_error(self): 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: + 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"][1] self.assertNotEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"], None) self.assertEqual(substatus_file_data["name"], Constants.PATCH_INSTALLATION_SUMMARY) @@ -206,7 +207,7 @@ def test_add_duplicate_error(self): 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: + 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) @@ -222,7 +223,7 @@ def test_add_duplicate_error(self): 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: + 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("Success".lower(), str(substatus_file_data["status"]).lower()) @@ -261,7 +262,7 @@ def test_status_file_initial_load(self): 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.status_file_path, 'r') as file_handle: + 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) @@ -269,7 +270,7 @@ def test_status_file_initial_load(self): 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.status_file_path, 'r') as file_handle: + 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) @@ -292,7 +293,7 @@ def test_status_file_initial_load(self): 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: + 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(json.loads(substatus_file_data["formattedMessage"]["message"])["shouldReportToHealthStore"], True) self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patchVersion"], "2020-07-08") @@ -300,14 +301,14 @@ def test_set_patch_metadata_for_healthstore_substatus_json(self): # 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: + 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(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: + 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"] return json.loads(substatus_file_data[0]['formattedMessage']['message'])['maintenanceRunId'] @@ -336,7 +337,7 @@ def test_status_file_maintenance_run_id(self): 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: + 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(substatus_file_data["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) formatted_message = json.loads(substatus_file_data['formattedMessage']['message']) @@ -396,6 +397,53 @@ def test_sort_packages_by_classification_and_state(self): 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_write_truncated_status_file_under_size_limit(self): + self.runtime.execution_config.operation = Constants.ASSESSMENT + self.runtime.status_handler.set_current_operation(Constants.ASSESSMENT) + + patch_count_for_test = 500 + test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_test) + 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) + + # 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) + + self.assertEqual(substatus_file_data[0]["status"]["operation"], Constants.ASSESSMENT) + substatus_file_data = substatus_file_data[0]["status"]["substatus"][0] + self.assertEqual(substatus_file_data["name"], Constants.PATCH_ASSESSMENT_SUMMARY) + self.assertEqual(substatus_file_data["status"], Constants.STATUS_SUCCESS.lower()) + #self.assertTrue(len(json.dumps(substatus_file_data)) < self.test_internal_file_size_limit) + self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), patch_count_for_test ) + self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), 0) + + # Test 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)[0]["status"]["substatus"][0] + + self.assertEqual(substatus_file_data["name"], Constants.PATCH_ASSESSMENT_SUMMARY) + #self.assertTrue(len(json.dumps(substatus_file_data)) < self.test_internal_file_size_limit) + self.assertNotEqual(substatus_file_data["status"], Constants.STATUS_WARNING.lower()) + self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), patch_count_for_test ) + status_file_patches = json.loads(substatus_file_data["formattedMessage"]["message"])["patches"] + self.assertNotEqual(status_file_patches[len(status_file_patches) - 1]['patchId'], "Truncated patch list record") + self.assertNotEqual(status_file_patches[len(status_file_patches) - 1]['name'], "Truncated patch list record") + self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["code"], 0) + self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), 0) + self.assertFalse("review this log file on the machine" in json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["message"]) + self.assertEqual(len(self.runtime.status_handler._StatusHandler__assessment_removed_packages), 0) + + # Setup functions to populate packages and versions for truncation + 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() diff --git a/src/extension/src/Constants.py b/src/extension/src/Constants.py index 72031dad..b93da6a7 100644 --- a/src/extension/src/Constants.py +++ b/src/extension/src/Constants.py @@ -118,12 +118,14 @@ class TelemetryEventLevel(EnumBackport): class PatchOperationTopLevelErrorCode(EnumBackport): SUCCESS = 0 ERROR = 1 + WARNING = 2 class PatchOperationErrorCodes(EnumBackport): # todo: finalize these error codes PACKAGE_MANAGER_FAILURE = "PACKAGE_MANAGER_FAILURE" OPERATION_FAILED = "OPERATION_FAILED" DEFAULT_ERROR = "ERROR" # default error code + TRUNCATION = "PACKAGE_LIST_TRUNCATED" ERROR_ADDED_TO_STATUS = "Error_added_to_status" PYTHON_NOT_FOUND = "Python version could not be discovered for core invocation." @@ -148,6 +150,12 @@ class EnvSettingsFields(EnumBackport): PUBLIC_SETTINGS = "publicSettings" AUTO_ASSESS_ONLY = "autoAssessOnly" + class StatusTruncationConfig(EnumBackport): + INTERNAL_FILE_SIZE_LIMIT_IN_BYTES = 126 * 1024 + AGENT_STATUS_FILE_SIZE_LIMIT_IN_BYTES = 128 * 1024 + MIN_TRUNCATED_PACKAGE_COUNT = 5 + TRUNCATION_ERROR_MESSAGE = "Results were truncated because too many patches were present, Check log for truncated packages" + # Public Settings within Config Settings class ConfigPublicSettingsFields(EnumBackport): operation = "operation" From 2a4271b7bb5756e85da640a34b0cff7e2cde418d Mon Sep 17 00:00:00 2001 From: john feng Date: Fri, 16 Jun 2023 00:19:55 -0700 Subject: [PATCH 02/11] add constants, and unit test to status handler --- src/core/src/bootstrap/Constants.py | 9 +++++++++ src/core/tests/Test_StatusHandler.py | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/core/src/bootstrap/Constants.py b/src/core/src/bootstrap/Constants.py index 5db38abe..757a49c1 100644 --- a/src/core/src/bootstrap/Constants.py +++ b/src/core/src/bootstrap/Constants.py @@ -259,6 +259,7 @@ class PatchAssessmentSummaryStartedBy(EnumBackport): class PatchOperationTopLevelErrorCode(EnumBackport): SUCCESS = 0 ERROR = 1 + WARNING = 2 class PatchOperationErrorCodes(EnumBackport): DEFAULT_ERROR = "ERROR" # default error code @@ -266,6 +267,7 @@ class PatchOperationErrorCodes(EnumBackport): PACKAGE_MANAGER_FAILURE = "PACKAGE_MANAGER_FAILURE" NEWER_OPERATION_SUPERSEDED = "NEWER_OPERATION_SUPERSEDED" UA_ESM_REQUIRED = "UA_ESM_REQUIRED" + TRUNCATION = "PACKAGE_LIST_TRUNCATED" ERROR_ADDED_TO_STATUS = "Error_added_to_status" @@ -334,4 +336,11 @@ class UbuntuProClientSettings(EnumBackport): MINIMUM_PYTHON_VERSION_REQUIRED = (3, 5) # using tuple as we can compare this with sys.version_info. The comparison will happen in the same order. Major version checked first. Followed by Minor version. MAX_OS_MAJOR_VERSION_SUPPORTED = 18 MINIMUM_CLIENT_VERSION = "27.14.4" + class StatusTruncationConfig(EnumBackport): + INTERNAL_FILE_SIZE_LIMIT_IN_BYTES = 126 * 1024 + AGENT_STATUS_FILE_SIZE_LIMIT_IN_BYTES = 128 * 1024 + MIN_TRUNCATED_PACKAGE_COUNT = 5 + TRUNCATION_ERROR_MESSAGE = "Results were truncated because too many patches were present, Check log for truncated packages" + + diff --git a/src/core/tests/Test_StatusHandler.py b/src/core/tests/Test_StatusHandler.py index f014db03..334b7cef 100644 --- a/src/core/tests/Test_StatusHandler.py +++ b/src/core/tests/Test_StatusHandler.py @@ -414,7 +414,7 @@ def test_write_truncated_status_file_under_size_limit(self): substatus_file_data = substatus_file_data[0]["status"]["substatus"][0] self.assertEqual(substatus_file_data["name"], Constants.PATCH_ASSESSMENT_SUMMARY) self.assertEqual(substatus_file_data["status"], Constants.STATUS_SUCCESS.lower()) - #self.assertTrue(len(json.dumps(substatus_file_data)) < self.test_internal_file_size_limit) + self.assertTrue(len(json.dumps(substatus_file_data)) < self.test_internal_file_size_limit) self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), patch_count_for_test ) self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), 0) @@ -423,7 +423,7 @@ def test_write_truncated_status_file_under_size_limit(self): substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0] self.assertEqual(substatus_file_data["name"], Constants.PATCH_ASSESSMENT_SUMMARY) - #self.assertTrue(len(json.dumps(substatus_file_data)) < self.test_internal_file_size_limit) + self.assertTrue(len(json.dumps(substatus_file_data)) < self.test_internal_file_size_limit) self.assertNotEqual(substatus_file_data["status"], Constants.STATUS_WARNING.lower()) self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), patch_count_for_test ) status_file_patches = json.loads(substatus_file_data["formattedMessage"]["message"])["patches"] @@ -432,7 +432,7 @@ def test_write_truncated_status_file_under_size_limit(self): self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["code"], 0) self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), 0) self.assertFalse("review this log file on the machine" in json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["message"]) - self.assertEqual(len(self.runtime.status_handler._StatusHandler__assessment_removed_packages), 0) + # self.assertEqual(len(self.runtime.status_handler._StatusHandler__assessment_removed_packages), 0) # Setup functions to populate packages and versions for truncation def __set_up_packages_func(self, val): From dab9f8901207bdd181bd2983d5292f7ccf4080d5 Mon Sep 17 00:00:00 2001 From: john feng Date: Fri, 16 Jun 2023 11:11:09 -0700 Subject: [PATCH 03/11] add unit test for tesitng complete_status path is dir and log --- src/core/src/bootstrap/Constants.py | 1 + .../src/service_interfaces/StatusHandler.py | 6 ++--- src/core/tests/Test_CoreMain.py | 4 +++ src/core/tests/Test_StatusHandler.py | 27 +++++++++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/core/src/bootstrap/Constants.py b/src/core/src/bootstrap/Constants.py index 757a49c1..281cf2d4 100644 --- a/src/core/src/bootstrap/Constants.py +++ b/src/core/src/bootstrap/Constants.py @@ -336,6 +336,7 @@ class UbuntuProClientSettings(EnumBackport): MINIMUM_PYTHON_VERSION_REQUIRED = (3, 5) # using tuple as we can compare this with sys.version_info. The comparison will happen in the same order. Major version checked first. Followed by Minor version. MAX_OS_MAJOR_VERSION_SUPPORTED = 18 MINIMUM_CLIENT_VERSION = "27.14.4" + class StatusTruncationConfig(EnumBackport): INTERNAL_FILE_SIZE_LIMIT_IN_BYTES = 126 * 1024 AGENT_STATUS_FILE_SIZE_LIMIT_IN_BYTES = 128 * 1024 diff --git a/src/core/src/service_interfaces/StatusHandler.py b/src/core/src/service_interfaces/StatusHandler.py index dd2a193d..404f0114 100644 --- a/src/core/src/service_interfaces/StatusHandler.py +++ b/src/core/src/service_interfaces/StatusHandler.py @@ -561,16 +561,16 @@ def load_status_file_components(self, initial_load=False): self.composite_logger.log_warning("Status file not found at initial load. Resetting status file to defaults.") self.__reset_status_file() return - complete_status_file_data_raw = self.__read_complete_status_raw_data(self.complete_status_file_path) + # Load status data and sanity check structure - raise exception if data loss risk is detected on corrupt data try: - complete_status_file_data = complete_status_file_data_raw + complete_status_file_data = self.__read_complete_status_raw_data(self.complete_status_file_path) if 'status' not in complete_status_file_data or 'substatus' not in complete_status_file_data['status']: self.composite_logger.log_error("Malformed status file. Resetting status file for safety.") self.__reset_status_file() return except Exception as error: - self.composite_logger.log_error("Unable to load status file json. Error: {0}; Data: {1}".format(repr(error), str(complete_status_file_data_raw))) + self.composite_logger.log_error("Unable to load status file json. Error: {0}; Data: {1}".format(repr(error), str(complete_status_file_data))) raise # Load portions of data that need to be built on for next write - raise exception if corrupt data is encountered diff --git a/src/core/tests/Test_CoreMain.py b/src/core/tests/Test_CoreMain.py index fcc8949f..b8be2d59 100644 --- a/src/core/tests/Test_CoreMain.py +++ b/src/core/tests/Test_CoreMain.py @@ -267,6 +267,7 @@ def test_assessment_operation_success(self): self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) self.assertTrue(substatus_file_data[0]["status"].lower() == Constants.STATUS_SUCCESS.lower()) self.assertTrue(substatus_file_data[1]["name"] == Constants.CONFIGURE_PATCHING_SUMMARY) + self.assertEqual(json.loads(substatus_file_data[1]['formattedMessage']['message'])['automaticOSPatchState'], 'Disabled') self.assertTrue(substatus_file_data[1]["status"].lower() == Constants.STATUS_SUCCESS.lower()) runtime.stop() @@ -288,6 +289,7 @@ def test_assessment_operation_fail(self): self.assertTrue(substatus_file_data[0]["status"].lower() == Constants.STATUS_ERROR.lower()) self.assertEqual(len(json.loads(substatus_file_data[0]["formattedMessage"]["message"])["errors"]["details"]), 2) self.assertTrue(substatus_file_data[1]["name"] == Constants.CONFIGURE_PATCHING_SUMMARY) + self.assertEqual(json.loads(substatus_file_data[1]['formattedMessage']['message'])['automaticOSPatchState'], 'Disabled') self.assertTrue(substatus_file_data[1]["status"].lower() == Constants.STATUS_SUCCESS.lower()) runtime.stop() @@ -309,6 +311,7 @@ def test_assessment_operation_fail_due_to_no_telemetry(self): self.assertTrue(Constants.TELEMETRY_NOT_COMPATIBLE_ERROR_MSG in json.loads(substatus_file_data[0]["formattedMessage"]["message"])["errors"]["details"][0]["message"]) self.assertTrue(substatus_file_data[1]["name"] == Constants.CONFIGURE_PATCHING_SUMMARY) self.assertTrue(substatus_file_data[1]["status"].lower() == Constants.STATUS_ERROR.lower()) + self.assertEqual(json.loads(substatus_file_data[1]['formattedMessage']['message'])['automaticOSPatchState'], 'Unknown') self.assertTrue(Constants.TELEMETRY_NOT_COMPATIBLE_ERROR_MSG in json.loads(substatus_file_data[1]["formattedMessage"]["message"])["errors"]["details"][0]["message"]) runtime.stop() @@ -337,6 +340,7 @@ def test_installation_operation_fail_due_to_telemetry_unsupported_no_events_fold self.assertTrue(substatus_file_data[2]["status"].lower() == Constants.STATUS_SUCCESS.lower()) self.assertTrue(substatus_file_data[3]["name"] == Constants.CONFIGURE_PATCHING_SUMMARY) self.assertTrue(substatus_file_data[3]["status"].lower() == Constants.STATUS_ERROR.lower()) + self.assertEqual(json.loads(substatus_file_data[3]['formattedMessage']['message'])['automaticOSPatchState'], 'Unknown') self.assertEqual(len(json.loads(substatus_file_data[3]["formattedMessage"]["message"])["errors"]["details"]), 1) self.assertTrue(Constants.TELEMETRY_NOT_COMPATIBLE_ERROR_MSG in json.loads(substatus_file_data[3]["formattedMessage"]["message"])["errors"]["details"][0]["message"]) runtime.stop() diff --git a/src/core/tests/Test_StatusHandler.py b/src/core/tests/Test_StatusHandler.py index 334b7cef..2f6a8ccc 100644 --- a/src/core/tests/Test_StatusHandler.py +++ b/src/core/tests/Test_StatusHandler.py @@ -15,7 +15,12 @@ # Requires Python 2.7+ import datetime import json +import os import unittest +try: + from unittest.mock import patch, MagicMock +except ImportError: + from mock import patch, MagicMock from core.src.bootstrap.Constants import Constants from core.src.service_interfaces.StatusHandler import StatusHandler from core.tests.library.ArgumentComposer import ArgumentComposer @@ -397,6 +402,28 @@ def test_sort_packages_by_classification_and_state(self): 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_complete_status_has_bad_json(self): + string_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": []}}]' + mock_logger = MagicMock() + self.runtime.composite_logger = mock_logger + 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(string_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 path is dir and being called in the load_status_file_components + status_handler.load_status_file_components(initial_load=True) + self.assertEqual(mock_logger.log_error.call_count, 1) + mock_logger.log_error.assert_called_with("Malformed status file. Resetting status file for safety.") + def test_if_complete_status_path_is_dir(self): + self.runtime.execution_config.complete_status_file_path = self.runtime.execution_config.status_folder + self.assertRaises(Exception, self.runtime.status_handler.load_status_file_components(initial_load=True)) + def test_write_truncated_status_file_under_size_limit(self): self.runtime.execution_config.operation = Constants.ASSESSMENT self.runtime.status_handler.set_current_operation(Constants.ASSESSMENT) From 665182af2254fa7e6104d7bfc4c448ba53735ab0 Mon Sep 17 00:00:00 2001 From: john feng Date: Fri, 16 Jun 2023 11:14:43 -0700 Subject: [PATCH 04/11] modify constant truncation warning msg, and reset_status_file() --- src/core/src/bootstrap/Constants.py | 2 +- src/core/src/service_interfaces/StatusHandler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/src/bootstrap/Constants.py b/src/core/src/bootstrap/Constants.py index 281cf2d4..ced3159e 100644 --- a/src/core/src/bootstrap/Constants.py +++ b/src/core/src/bootstrap/Constants.py @@ -341,7 +341,7 @@ class StatusTruncationConfig(EnumBackport): INTERNAL_FILE_SIZE_LIMIT_IN_BYTES = 126 * 1024 AGENT_STATUS_FILE_SIZE_LIMIT_IN_BYTES = 128 * 1024 MIN_TRUNCATED_PACKAGE_COUNT = 5 - TRUNCATION_ERROR_MESSAGE = "Results were truncated because too many patches were present, Check log for truncated packages" + TRUNCATION_WARNING_MESSAGE = "Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists." diff --git a/src/core/src/service_interfaces/StatusHandler.py b/src/core/src/service_interfaces/StatusHandler.py index 404f0114..110d3cb8 100644 --- a/src/core/src/service_interfaces/StatusHandler.py +++ b/src/core/src/service_interfaces/StatusHandler.py @@ -500,7 +500,7 @@ def __reset_status_file(self): # Create complete status template self.env_layer.file_system.write_with_retry(self.complete_status_file_path, '[{0}]'.format(status_file_reset_content), mode='w+') # Create agent-facing status template - self.env_layer.file_system.write_with_retry(self.status_file_path, '[{0}]'.format(json.dumps(self.__new_basic_status_json())), mode='w+') + self.env_layer.file_system.write_with_retry(self.status_file_path, '[{0}]'.format(status_file_reset_content), mode='w+') def __new_basic_status_json(self): return { From df98f0e4afa30ed496763e0ce0f406b458bdcfaa Mon Sep 17 00:00:00 2001 From: john feng Date: Fri, 16 Jun 2023 13:16:17 -0700 Subject: [PATCH 05/11] revert status_file to complete_status_file --- src/core/tests/Test_AptitudePackageManager.py | 4 +- .../tests/Test_ConfigurePatchingProcessor.py | 14 ++--- src/core/tests/Test_CoreMain.py | 60 +++++++++---------- src/core/tests/Test_PatchAssessor.py | 2 +- src/core/tests/Test_PatchInstaller.py | 4 +- src/core/tests/Test_StatusHandler.py | 45 +++++++------- 6 files changed, 65 insertions(+), 64 deletions(-) diff --git a/src/core/tests/Test_AptitudePackageManager.py b/src/core/tests/Test_AptitudePackageManager.py index 5b3167b0..f15fb6dd 100644 --- a/src/core/tests/Test_AptitudePackageManager.py +++ b/src/core/tests/Test_AptitudePackageManager.py @@ -142,7 +142,7 @@ def test_install_package_failure(self): # ensure that error message appears in substatus properly substatus_file_data = [] - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: status = json.load(file_handle) self.assertEqual(status[0]["status"]["status"].lower(), Constants.STATUS_SUCCESS.lower()) substatus_file_data = status[0]["status"]["substatus"][0] @@ -491,7 +491,7 @@ def test_set_security_esm_package_status_assessment(self): runtime.patch_assessor.start_assessment() status = "" error_set = False - with self.runtime.env_layer.file_system.open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle: status = json.load(file_handle) self.assertEqual(status[0]["status"]["status"].lower(), Constants.STATUS_SUCCESS.lower()) self.assertEqual(status[0]["status"]["substatus"][0]["name"], "PatchAssessmentSummary") diff --git a/src/core/tests/Test_ConfigurePatchingProcessor.py b/src/core/tests/Test_ConfigurePatchingProcessor.py index 52e68fd4..2aa0a110 100644 --- a/src/core/tests/Test_ConfigurePatchingProcessor.py +++ b/src/core/tests/Test_ConfigurePatchingProcessor.py @@ -59,7 +59,7 @@ def test_operation_success_for_configure_patching_request_for_apt(self): self.__check_telemetry_events(runtime) # check status file for configure patching patch mode - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] # check status file for configure patching patch state (and including for 'Platform' initiated assessment data) @@ -106,7 +106,7 @@ def test_operation_success_for_installation_request_with_configure_patching(self self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertTrue(runtime.package_manager.image_default_patch_configuration_backup_exists()) image_default_patch_configuration_backup = json.loads(runtime.env_layer.file_system.read_with_retry(runtime.package_manager.image_default_patch_configuration_backup_path)) @@ -149,7 +149,7 @@ def test_operation_fail_for_configure_patching_telemetry_not_supported(self): runtime.configure_patching_processor.start_configure_patching() # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 1) self.assertTrue(substatus_file_data[0]["name"] == Constants.CONFIGURE_PATCHING_SUMMARY) @@ -177,7 +177,7 @@ def test_operation_fail_for_configure_patching_request_for_apt(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) # assessment is now part of the CP flow @@ -206,7 +206,7 @@ def test_patch_mode_set_failure_for_configure_patching(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) # assessment is now part of the CP flow @@ -244,7 +244,7 @@ def test_configure_patching_with_assessment_mode_by_platform(self): self.__check_telemetry_events(runtime) # check status file for configure patching patch mode - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] # check status file for configure patching patch state @@ -288,7 +288,7 @@ def test_configure_patching_with_patch_mode_and_assessment_mode_by_platform(self self.__check_telemetry_events(runtime) # check status file for configure patching patch mode - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] # check status file for configure patching patch state diff --git a/src/core/tests/Test_CoreMain.py b/src/core/tests/Test_CoreMain.py index b8be2d59..879ae2c9 100644 --- a/src/core/tests/Test_CoreMain.py +++ b/src/core/tests/Test_CoreMain.py @@ -65,7 +65,7 @@ def test_operation_fail_for_non_autopatching_request(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 3) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -88,7 +88,7 @@ def test_operation_fail_for_autopatching_request(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -115,7 +115,7 @@ def test_operation_success_for_non_autopatching_request(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 3) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -139,7 +139,7 @@ def test_operation_success_for_autopatching_request(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -170,7 +170,7 @@ def test_operation_success_for_autopatching_request_with_security_classification self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -206,7 +206,7 @@ def test_invalid_maintenance_run_id(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -234,7 +234,7 @@ def test_invalid_maintenance_run_id(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -261,7 +261,7 @@ def test_assessment_operation_success(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -282,7 +282,7 @@ def test_assessment_operation_fail(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -302,7 +302,7 @@ def test_assessment_operation_fail_due_to_no_telemetry(self): runtime.set_legacy_test_type('HappyPath') CoreMain(composed_arguments) - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -324,7 +324,7 @@ def test_installation_operation_fail_due_to_telemetry_unsupported_no_events_fold runtime.set_legacy_test_type('SuccessInstallPath') CoreMain(argument_composer.get_composed_arguments()) - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -357,7 +357,7 @@ def test_installation_operation_fail_due_to_no_telemetry(self): runtime.patch_assessor.start_assessment = lambda: () CoreMain(argument_composer.get_composed_arguments()) - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -383,7 +383,7 @@ def test_assessment_operation_fail_on_arc_due_to_no_telemetry(self): runtime.set_legacy_test_type('HappyPath') CoreMain(argument_composer.get_composed_arguments()) - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -404,7 +404,7 @@ def test_installation_operation_fail_on_arc_due_to_no_telemetry(self): runtime.set_legacy_test_type('SuccessInstallPath') CoreMain(argument_composer.get_composed_arguments()) - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -444,7 +444,7 @@ def test_install_all_packages_for_centos_autopatching(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -494,7 +494,7 @@ def test_install_all_packages_for_centos_autopatching_as_warning_with_never_rebo self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -543,7 +543,7 @@ def test_install_only_critical_and_security_packages_for_redhat_autopatching(sel self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -599,7 +599,7 @@ def test_install_only_critical_and_security_packages_for_redhat_autopatching_war self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 4) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -648,7 +648,7 @@ def test_auto_assessment_success_with_configure_patching_in_prev_operation_on_sa # check telemetry events self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] self.assertTrue(status_file_data["operation"] == Constants.CONFIGURE_PATCHING) substatus_file_data = status_file_data["substatus"] @@ -674,7 +674,7 @@ def test_auto_assessment_success_with_configure_patching_in_prev_operation_on_sa runtime.execution_config.exec_auto_assess_only = True CoreMain(argument_composer.get_composed_arguments()) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] # verifying the original operation name is preserved self.assertTrue(status_file_data["operation"] == Constants.CONFIGURE_PATCHING) @@ -706,7 +706,7 @@ def test_auto_assessment_success_on_arc_with_configure_patching_in_prev_operatio runtime.set_legacy_test_type("SuccessInstallPath") CoreMain(argument_composer.get_composed_arguments()) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] self.assertTrue(status_file_data["operation"] == Constants.CONFIGURE_PATCHING) substatus_file_data = status_file_data["substatus"] @@ -734,7 +734,7 @@ def test_auto_assessment_success_on_arc_with_configure_patching_in_prev_operatio # check telemetry events self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] # verifying the original operation name is preserved self.assertTrue(status_file_data["operation"] == Constants.CONFIGURE_PATCHING) @@ -766,7 +766,7 @@ def test_auto_assessment_success_with_assessment_in_prev_operation_on_same_seque # check telemetry events self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] self.assertTrue(status_file_data["operation"] == Constants.ASSESSMENT) substatus_file_data = status_file_data["substatus"] @@ -798,7 +798,7 @@ def test_auto_assessment_success_with_assessment_in_prev_operation_on_same_seque # check telemetry events self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] # verifying the original operation name is preserved @@ -835,7 +835,7 @@ def test_auto_assessment_success_with_installation_in_prev_operation_on_same_seq # check telemetry events self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] self.assertTrue(status_file_data["operation"] == Constants.INSTALLATION) substatus_file_data = status_file_data["substatus"] @@ -872,7 +872,7 @@ def test_auto_assessment_success_with_installation_in_prev_operation_on_same_seq # check telemetry events self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: status_file_data = json.load(file_handle)[0]["status"] # verifying the original operation name is preserved @@ -926,7 +926,7 @@ def test_assessment_operation_fail_after_package_manager_reboot(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 3) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -962,7 +962,7 @@ def test_assessment_operation_success_after_package_manager_reboot(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 3) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -987,7 +987,7 @@ def test_assessment_superseded(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) @@ -1026,7 +1026,7 @@ def test_assessment_superseded(self): self.__check_telemetry_events(runtime) # check status file - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual(len(substatus_file_data), 2) self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) diff --git a/src/core/tests/Test_PatchAssessor.py b/src/core/tests/Test_PatchAssessor.py index 641fbba0..96c0684c 100644 --- a/src/core/tests/Test_PatchAssessor.py +++ b/src/core/tests/Test_PatchAssessor.py @@ -51,7 +51,7 @@ def test_assessment_fail_with_status_update(self): self.runtime.package_manager.refresh_repo = self.mock_refresh_repo self.runtime.set_legacy_test_type('UnalignedPath') self.assertRaises(Exception, self.runtime.patch_assessor.start_assessment) - with open(self.runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with open(self.runtime.execution_config.status_file_path, 'r') as file_handle: file_contents = json.loads(file_handle.read()) self.assertTrue('Unexpected return code (100) from package manager on command: LANG=en_US.UTF8 sudo apt-get -s dist-upgrade' in str(file_contents)) diff --git a/src/core/tests/Test_PatchInstaller.py b/src/core/tests/Test_PatchInstaller.py index 228521c0..12068df6 100644 --- a/src/core/tests/Test_PatchInstaller.py +++ b/src/core/tests/Test_PatchInstaller.py @@ -247,7 +247,7 @@ def test_mark_status_completed_esm_required(self): runtime.patch_installer.install_updates(runtime.maintenance_window, runtime.package_manager, simulate=True) runtime.patch_installer.mark_installation_completed() - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] self.assertEqual('warning', substatus_file_data[0]['status']) @@ -551,7 +551,7 @@ def healthstore_writes_helper(self, health_store_id, maintenance_run_id, is_forc runtime.maintenance_window, runtime.package_manager, simulate=True) runtime.patch_installer.mark_installation_completed() - with runtime.env_layer.file_system.open(runtime.execution_config.complete_status_file_path, 'r') as file_handle: + with runtime.env_layer.file_system.open(runtime.execution_config.status_file_path, 'r') as file_handle: substatus_file_data = json.load(file_handle)[0]["status"]["substatus"] if is_force_reboot: diff --git a/src/core/tests/Test_StatusHandler.py b/src/core/tests/Test_StatusHandler.py index 2f6a8ccc..e12be873 100644 --- a/src/core/tests/Test_StatusHandler.py +++ b/src/core/tests/Test_StatusHandler.py @@ -40,7 +40,7 @@ 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.complete_status_file_path, 'r') as file_handle: + 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) @@ -55,7 +55,7 @@ def test_set_package_assessment_status_for_auto_assessment(self): 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.complete_status_file_path, 'r') as file_handle: + 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) @@ -69,7 +69,7 @@ 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.complete_status_file_path, 'r') as file_handle: + 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) @@ -82,13 +82,13 @@ def test_set_package_install_status(self): 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.complete_status_file_path, 'r') as file_handle: + 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.complete_status_file_path, 'r') as file_handle: + 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) @@ -99,7 +99,7 @@ def test_set_package_install_status_classification(self): 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.complete_status_file_path, 'r') as file_handle: + 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) @@ -117,7 +117,7 @@ def test_set_package_install_status_classification_not_set(self): 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.complete_status_file_path, 'r') as file_handle: + 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) @@ -135,19 +135,19 @@ def test_set_installation_reboot_status(self): # 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.complete_status_file_path, 'r') as file_handle: + 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.complete_status_file_path, 'r') as file_handle: + 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.complete_status_file_path, 'r') as file_handle: + 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"]) @@ -159,7 +159,7 @@ def test_add_error(self): 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.complete_status_file_path, 'r') as file_handle: + 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) @@ -180,7 +180,7 @@ def test_add_error(self): 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.complete_status_file_path, 'r') as file_handle: + 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()) @@ -197,7 +197,7 @@ def test_add_error(self): 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.complete_status_file_path, 'r') as file_handle: + 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) @@ -212,7 +212,7 @@ def test_add_duplicate_error(self): 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.complete_status_file_path, 'r') as file_handle: + 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) @@ -228,7 +228,7 @@ def test_add_duplicate_error(self): 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.complete_status_file_path, 'r') as file_handle: + 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()) @@ -267,7 +267,7 @@ def test_status_file_initial_load(self): 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: + 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"] self.assertTrue(len(substatus_file_data) == 1) @@ -275,7 +275,7 @@ def test_status_file_initial_load(self): 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: + 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(status_handler is not None) self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["shouldReportToHealthStore"], False) @@ -298,7 +298,7 @@ def test_status_file_initial_load(self): 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.complete_status_file_path, 'r') as file_handle: + 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") @@ -306,14 +306,14 @@ def test_set_patch_metadata_for_healthstore_substatus_json(self): # 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.complete_status_file_path, 'r') as file_handle: + 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.complete_status_file_path, 'r') as file_handle: + 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'] @@ -342,7 +342,7 @@ def test_status_file_maintenance_run_id(self): 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.complete_status_file_path, 'r') as file_handle: + 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']) @@ -420,8 +420,9 @@ def test_if_complete_status_has_bad_json(self): status_handler.load_status_file_components(initial_load=True) self.assertEqual(mock_logger.log_error.call_count, 1) mock_logger.log_error.assert_called_with("Malformed status file. Resetting status file for safety.") + def test_if_complete_status_path_is_dir(self): - self.runtime.execution_config.complete_status_file_path = self.runtime.execution_config.status_folder + self.runtime.execution_config.status_file_path = self.runtime.execution_config.status_folder self.assertRaises(Exception, self.runtime.status_handler.load_status_file_components(initial_load=True)) def test_write_truncated_status_file_under_size_limit(self): From c7c03aedd3f5d50c9a8b539a24f5dc878324df84 Mon Sep 17 00:00:00 2001 From: john feng Date: Fri, 16 Jun 2023 17:24:02 -0700 Subject: [PATCH 06/11] remove changes in constants, and update tests --- src/core/src/bootstrap/Constants.py | 10 ---- .../src/service_interfaces/StatusHandler.py | 50 ++++++++----------- src/core/tests/Test_StatusHandler.py | 17 +++---- src/extension/src/Constants.py | 8 --- 4 files changed, 27 insertions(+), 58 deletions(-) diff --git a/src/core/src/bootstrap/Constants.py b/src/core/src/bootstrap/Constants.py index ced3159e..5db38abe 100644 --- a/src/core/src/bootstrap/Constants.py +++ b/src/core/src/bootstrap/Constants.py @@ -259,7 +259,6 @@ class PatchAssessmentSummaryStartedBy(EnumBackport): class PatchOperationTopLevelErrorCode(EnumBackport): SUCCESS = 0 ERROR = 1 - WARNING = 2 class PatchOperationErrorCodes(EnumBackport): DEFAULT_ERROR = "ERROR" # default error code @@ -267,7 +266,6 @@ class PatchOperationErrorCodes(EnumBackport): PACKAGE_MANAGER_FAILURE = "PACKAGE_MANAGER_FAILURE" NEWER_OPERATION_SUPERSEDED = "NEWER_OPERATION_SUPERSEDED" UA_ESM_REQUIRED = "UA_ESM_REQUIRED" - TRUNCATION = "PACKAGE_LIST_TRUNCATED" ERROR_ADDED_TO_STATUS = "Error_added_to_status" @@ -337,11 +335,3 @@ class UbuntuProClientSettings(EnumBackport): MAX_OS_MAJOR_VERSION_SUPPORTED = 18 MINIMUM_CLIENT_VERSION = "27.14.4" - class StatusTruncationConfig(EnumBackport): - INTERNAL_FILE_SIZE_LIMIT_IN_BYTES = 126 * 1024 - AGENT_STATUS_FILE_SIZE_LIMIT_IN_BYTES = 128 * 1024 - MIN_TRUNCATED_PACKAGE_COUNT = 5 - TRUNCATION_WARNING_MESSAGE = "Package lists were truncated to limit reporting data volume. In-VM logs contain complete lists." - - - diff --git a/src/core/src/service_interfaces/StatusHandler.py b/src/core/src/service_interfaces/StatusHandler.py index 110d3cb8..29986e93 100644 --- a/src/core/src/service_interfaces/StatusHandler.py +++ b/src/core/src/service_interfaces/StatusHandler.py @@ -563,15 +563,11 @@ def load_status_file_components(self, initial_load=False): return # Load status data and sanity check structure - raise exception if data loss risk is detected on corrupt data - try: - complete_status_file_data = self.__read_complete_status_raw_data(self.complete_status_file_path) - if 'status' not in complete_status_file_data or 'substatus' not in complete_status_file_data['status']: - self.composite_logger.log_error("Malformed status file. Resetting status file for safety.") - self.__reset_status_file() - return - except Exception as error: - self.composite_logger.log_error("Unable to load status file json. Error: {0}; Data: {1}".format(repr(error), str(complete_status_file_data))) - raise + complete_status_file_data = self.__load_complete_status_file_data(self.complete_status_file_path) + if 'status' not in complete_status_file_data or 'substatus' not in complete_status_file_data['status']: + self.composite_logger.log_error("Malformed status file. Resetting status file for safety.") + self.__reset_status_file() + return # Load portions of data that need to be built on for next write - raise exception if corrupt data is encountered # todo: refactor @@ -616,6 +612,20 @@ def load_status_file_components(self, initial_load=False): self.__configure_patching_errors = errors['details'] self.__configure_patching_top_level_error_count = self.__get_total_error_count_from_prev_status(errors['message']) + def __load_complete_status_file_data(self, file_path): + # Read the status file - raise exception on persistent failure + for i in range(0, Constants.MAX_FILE_OPERATION_RETRY_COUNT): + try: + with self.env_layer.file_system.open(file_path, 'r') as file_handle: + complete_status_file_data = json.load(file_handle)[0] # structure is array of 1 + except Exception as error: + if i < Constants.MAX_FILE_OPERATION_RETRY_COUNT - 1: + time.sleep(i + 1) + else: + self.composite_logger.log_error("Unable to read status file (retries exhausted). Error: {0}.".format(repr(error))) + raise + return complete_status_file_data + def __write_complete_status_file(self): """ Composes and writes the status file from **already up-to-date** in-memory data. This is usually the final call to compose and persist after an in-memory data update in a specialized method. @@ -672,7 +682,7 @@ def __write_complete_status_file(self): self.env_layer.file_system.write_with_retry_using_temp_file(self.complete_status_file_path, '[{0}]'.format(json.dumps(complete_status_payload)), mode='w+') # Write agent status file - self.__write_status_file(complete_status_payload) + self.env_layer.file_system.write_with_retry_using_temp_file(self.status_file_path, '[{0}]'.format(json.dumps(complete_status_payload)), mode='w+') # endregion # region - Error objects @@ -781,23 +791,3 @@ def __set_errors_json(self, error_count_by_operation, errors_by_operation): } # endregion - # region - Patch Truncation - def __read_complete_status_raw_data(self, file_path): - # Read the status file - raise exception on persistent failure - for i in range(0, Constants.MAX_FILE_OPERATION_RETRY_COUNT): - try: - with self.env_layer.file_system.open(file_path, 'r') as file_handle: - complete_status_data_raw = json.load(file_handle)[0] # structure is array of 1 - except Exception as error: - if i < Constants.MAX_FILE_OPERATION_RETRY_COUNT - 1: - time.sleep(i + 1) - else: - self.composite_logger.log_error("Unable to read status file (retries exhausted). Error: {0}.".format(repr(error))) - raise - return complete_status_data_raw - - def __write_status_file(self, complete_status_payload): - truncated_status_payload = complete_status_payload - self.env_layer.file_system.write_with_retry_using_temp_file(self.status_file_path, '[{0}]'.format(json.dumps(truncated_status_payload)), mode='w+') - - # endregion diff --git a/src/core/tests/Test_StatusHandler.py b/src/core/tests/Test_StatusHandler.py index e12be873..24bd4b7a 100644 --- a/src/core/tests/Test_StatusHandler.py +++ b/src/core/tests/Test_StatusHandler.py @@ -17,10 +17,6 @@ import json import os import unittest -try: - from unittest.mock import patch, MagicMock -except ImportError: - from mock import patch, MagicMock from core.src.bootstrap.Constants import Constants from core.src.service_interfaces.StatusHandler import StatusHandler from core.tests.library.ArgumentComposer import ArgumentComposer @@ -29,7 +25,7 @@ class TestStatusHandler(unittest.TestCase): def setUp(self): - self.test_internal_file_size_limit = Constants.StatusTruncationConfig.INTERNAL_FILE_SIZE_LIMIT_IN_BYTES + self.test_internal_file_size_limit = 126 * 1024 self.runtime = RuntimeCompositor(ArgumentComposer().get_composed_arguments(), True) self.container = self.runtime.container @@ -404,8 +400,6 @@ def test_sort_packages_by_classification_and_state(self): def test_if_complete_status_has_bad_json(self): string_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": []}}]' - mock_logger = MagicMock() - self.runtime.composite_logger = mock_logger 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 @@ -416,10 +410,13 @@ def test_if_complete_status_has_bad_json(self): 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 path is dir and being called in the load_status_file_components + # Mock complete status path is bas json and being called in the load_status_file_components status_handler.load_status_file_components(initial_load=True) - self.assertEqual(mock_logger.log_error.call_count, 1) - mock_logger.log_error.assert_called_with("Malformed status file. Resetting status file for safety.") + 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"] + self.assertIsNotNone(substatus_file_data) + self.assertEqual(len(substatus_file_data), 0) + self.runtime.env_layer.file_system.delete_files_from_dir(example_file1, "*.complete.status") def test_if_complete_status_path_is_dir(self): self.runtime.execution_config.status_file_path = self.runtime.execution_config.status_folder diff --git a/src/extension/src/Constants.py b/src/extension/src/Constants.py index b93da6a7..72031dad 100644 --- a/src/extension/src/Constants.py +++ b/src/extension/src/Constants.py @@ -118,14 +118,12 @@ class TelemetryEventLevel(EnumBackport): class PatchOperationTopLevelErrorCode(EnumBackport): SUCCESS = 0 ERROR = 1 - WARNING = 2 class PatchOperationErrorCodes(EnumBackport): # todo: finalize these error codes PACKAGE_MANAGER_FAILURE = "PACKAGE_MANAGER_FAILURE" OPERATION_FAILED = "OPERATION_FAILED" DEFAULT_ERROR = "ERROR" # default error code - TRUNCATION = "PACKAGE_LIST_TRUNCATED" ERROR_ADDED_TO_STATUS = "Error_added_to_status" PYTHON_NOT_FOUND = "Python version could not be discovered for core invocation." @@ -150,12 +148,6 @@ class EnvSettingsFields(EnumBackport): PUBLIC_SETTINGS = "publicSettings" AUTO_ASSESS_ONLY = "autoAssessOnly" - class StatusTruncationConfig(EnumBackport): - INTERNAL_FILE_SIZE_LIMIT_IN_BYTES = 126 * 1024 - AGENT_STATUS_FILE_SIZE_LIMIT_IN_BYTES = 128 * 1024 - MIN_TRUNCATED_PACKAGE_COUNT = 5 - TRUNCATION_ERROR_MESSAGE = "Results were truncated because too many patches were present, Check log for truncated packages" - # Public Settings within Config Settings class ConfigPublicSettingsFields(EnumBackport): operation = "operation" From b4340b45b0beef791a696fb5b7af3c48cbe2fbc8 Mon Sep 17 00:00:00 2001 From: john feng Date: Tue, 20 Jun 2023 13:46:50 -0700 Subject: [PATCH 07/11] remove unnecessary truncation unit tests --- src/core/tests/Test_CoreMain.py | 4 -- src/core/tests/Test_StatusHandler.py | 58 +++------------------------- 2 files changed, 5 insertions(+), 57 deletions(-) diff --git a/src/core/tests/Test_CoreMain.py b/src/core/tests/Test_CoreMain.py index 879ae2c9..6395e6c2 100644 --- a/src/core/tests/Test_CoreMain.py +++ b/src/core/tests/Test_CoreMain.py @@ -267,7 +267,6 @@ def test_assessment_operation_success(self): self.assertTrue(substatus_file_data[0]["name"] == Constants.PATCH_ASSESSMENT_SUMMARY) self.assertTrue(substatus_file_data[0]["status"].lower() == Constants.STATUS_SUCCESS.lower()) self.assertTrue(substatus_file_data[1]["name"] == Constants.CONFIGURE_PATCHING_SUMMARY) - self.assertEqual(json.loads(substatus_file_data[1]['formattedMessage']['message'])['automaticOSPatchState'], 'Disabled') self.assertTrue(substatus_file_data[1]["status"].lower() == Constants.STATUS_SUCCESS.lower()) runtime.stop() @@ -289,7 +288,6 @@ def test_assessment_operation_fail(self): self.assertTrue(substatus_file_data[0]["status"].lower() == Constants.STATUS_ERROR.lower()) self.assertEqual(len(json.loads(substatus_file_data[0]["formattedMessage"]["message"])["errors"]["details"]), 2) self.assertTrue(substatus_file_data[1]["name"] == Constants.CONFIGURE_PATCHING_SUMMARY) - self.assertEqual(json.loads(substatus_file_data[1]['formattedMessage']['message'])['automaticOSPatchState'], 'Disabled') self.assertTrue(substatus_file_data[1]["status"].lower() == Constants.STATUS_SUCCESS.lower()) runtime.stop() @@ -311,7 +309,6 @@ def test_assessment_operation_fail_due_to_no_telemetry(self): self.assertTrue(Constants.TELEMETRY_NOT_COMPATIBLE_ERROR_MSG in json.loads(substatus_file_data[0]["formattedMessage"]["message"])["errors"]["details"][0]["message"]) self.assertTrue(substatus_file_data[1]["name"] == Constants.CONFIGURE_PATCHING_SUMMARY) self.assertTrue(substatus_file_data[1]["status"].lower() == Constants.STATUS_ERROR.lower()) - self.assertEqual(json.loads(substatus_file_data[1]['formattedMessage']['message'])['automaticOSPatchState'], 'Unknown') self.assertTrue(Constants.TELEMETRY_NOT_COMPATIBLE_ERROR_MSG in json.loads(substatus_file_data[1]["formattedMessage"]["message"])["errors"]["details"][0]["message"]) runtime.stop() @@ -340,7 +337,6 @@ def test_installation_operation_fail_due_to_telemetry_unsupported_no_events_fold self.assertTrue(substatus_file_data[2]["status"].lower() == Constants.STATUS_SUCCESS.lower()) self.assertTrue(substatus_file_data[3]["name"] == Constants.CONFIGURE_PATCHING_SUMMARY) self.assertTrue(substatus_file_data[3]["status"].lower() == Constants.STATUS_ERROR.lower()) - self.assertEqual(json.loads(substatus_file_data[3]['formattedMessage']['message'])['automaticOSPatchState'], 'Unknown') self.assertEqual(len(json.loads(substatus_file_data[3]["formattedMessage"]["message"])["errors"]["details"]), 1) self.assertTrue(Constants.TELEMETRY_NOT_COMPATIBLE_ERROR_MSG in json.loads(substatus_file_data[3]["formattedMessage"]["message"])["errors"]["details"][0]["message"]) runtime.stop() diff --git a/src/core/tests/Test_StatusHandler.py b/src/core/tests/Test_StatusHandler.py index 24bd4b7a..602f8ba0 100644 --- a/src/core/tests/Test_StatusHandler.py +++ b/src/core/tests/Test_StatusHandler.py @@ -25,7 +25,6 @@ class TestStatusHandler(unittest.TestCase): def setUp(self): - self.test_internal_file_size_limit = 126 * 1024 self.runtime = RuntimeCompositor(ArgumentComposer().get_composed_arguments(), True) self.container = self.runtime.container @@ -398,19 +397,20 @@ def test_sort_packages_by_classification_and_state(self): 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_complete_status_has_bad_json(self): - string_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": []}}]' + def test_if_complete_status_is_empty_json_with_bad_name(self): + # Mock complete status file is empty but with bad json name + 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(string_json) + 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 path is bas json and being called in the load_status_file_components + # Mock complete status file is empty but with bad json name and being called in the load_status_file_components, and it will recreate a good complete_status_file status_handler.load_status_file_components(initial_load=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"] @@ -422,53 +422,5 @@ def test_if_complete_status_path_is_dir(self): self.runtime.execution_config.status_file_path = self.runtime.execution_config.status_folder self.assertRaises(Exception, self.runtime.status_handler.load_status_file_components(initial_load=True)) - def test_write_truncated_status_file_under_size_limit(self): - self.runtime.execution_config.operation = Constants.ASSESSMENT - self.runtime.status_handler.set_current_operation(Constants.ASSESSMENT) - - patch_count_for_test = 500 - test_packages, test_package_versions = self.__set_up_packages_func(patch_count_for_test) - 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) - - # 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) - - self.assertEqual(substatus_file_data[0]["status"]["operation"], Constants.ASSESSMENT) - substatus_file_data = substatus_file_data[0]["status"]["substatus"][0] - self.assertEqual(substatus_file_data["name"], Constants.PATCH_ASSESSMENT_SUMMARY) - self.assertEqual(substatus_file_data["status"], Constants.STATUS_SUCCESS.lower()) - self.assertTrue(len(json.dumps(substatus_file_data)) < self.test_internal_file_size_limit) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), patch_count_for_test ) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), 0) - - # Test 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)[0]["status"]["substatus"][0] - - self.assertEqual(substatus_file_data["name"], Constants.PATCH_ASSESSMENT_SUMMARY) - self.assertTrue(len(json.dumps(substatus_file_data)) < self.test_internal_file_size_limit) - self.assertNotEqual(substatus_file_data["status"], Constants.STATUS_WARNING.lower()) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), patch_count_for_test ) - status_file_patches = json.loads(substatus_file_data["formattedMessage"]["message"])["patches"] - self.assertNotEqual(status_file_patches[len(status_file_patches) - 1]['patchId'], "Truncated patch list record") - self.assertNotEqual(status_file_patches[len(status_file_patches) - 1]['name'], "Truncated patch list record") - self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["code"], 0) - self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["details"]), 0) - self.assertFalse("review this log file on the machine" in json.loads(substatus_file_data["formattedMessage"]["message"])["errors"]["message"]) - # self.assertEqual(len(self.runtime.status_handler._StatusHandler__assessment_removed_packages), 0) - - # Setup functions to populate packages and versions for truncation - 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() From 6b0e1826f83b1e6a6eb1b074f6ad3bc5fb5b99d1 Mon Sep 17 00:00:00 2001 From: john feng Date: Mon, 26 Jun 2023 18:57:05 -0700 Subject: [PATCH 08/11] format code to add extra line and spaces, and change [IncludeAssessmentWithConfigurePatching={3}] --- src/core/src/core_logic/ExecutionConfig.py | 2 +- src/core/tests/Test_StatusHandler.py | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/core/src/core_logic/ExecutionConfig.py b/src/core/src/core_logic/ExecutionConfig.py index 23453c0e..9f4ef958 100644 --- a/src/core/src/core_logic/ExecutionConfig.py +++ b/src/core/src/core_logic/ExecutionConfig.py @@ -77,7 +77,7 @@ def __init__(self, env_layer, composite_logger, execution_parameters): self.complete_status_file_path = os.path.join(self.status_folder, str(self.sequence_number) + ".complete" + ".status") self.status_file_path = os.path.join(self.status_folder, str(self.sequence_number) + ".status") self.include_assessment_with_configure_patching = (self.operation == Constants.CONFIGURE_PATCHING and self.assessment_mode == Constants.AssessmentModes.AUTOMATIC_BY_PLATFORM) - self.composite_logger.log_debug(" - Derived execution-config settings. [CoreLog={0}][CompleteStatusFile={1}][StatusFile={2}][IncludeAssessmentWithConfigurePatching={2}]" + self.composite_logger.log_debug(" - Derived execution-config settings. [CoreLog={0}][CompleteStatusFile={1}][StatusFile={2}][IncludeAssessmentWithConfigurePatching={3}]" .format(str(self.log_file_path), str(self.complete_status_file_path), str(self.status_file_path), self.include_assessment_with_configure_patching)) # Auto assessment overrides diff --git a/src/core/tests/Test_StatusHandler.py b/src/core/tests/Test_StatusHandler.py index 0dd48430..80afe0bc 100644 --- a/src/core/tests/Test_StatusHandler.py +++ b/src/core/tests/Test_StatusHandler.py @@ -397,8 +397,8 @@ def test_sort_packages_by_classification_and_state(self): 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_complete_status_is_empty_json_with_bad_name(self): - # Mock complete status file is empty but with bad json name + 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') @@ -407,11 +407,9 @@ def test_if_complete_status_is_empty_json_with_bad_name(self): 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) + 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 is empty but with bad json name and being called in the load_status_file_components, and it will recreate a good complete_status_file - status_handler.load_status_file_components(initial_load=True) + # 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]["status"]["substatus"] self.assertIsNotNone(substatus_file_data) @@ -421,6 +419,7 @@ def test_if_complete_status_is_empty_json_with_bad_name(self): def test_if_complete_status_path_is_dir(self): self.runtime.execution_config.status_file_path = self.runtime.execution_config.status_folder self.assertRaises(Exception, self.runtime.status_handler.load_status_file_components(initial_load=True)) + 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' From 366685985ce54e3b8822b1f7531ed8148b2fb01d Mon Sep 17 00:00:00 2001 From: john feng Date: Mon, 26 Jun 2023 19:03:15 -0700 Subject: [PATCH 09/11] remove status_handler object reference --- src/core/tests/Test_StatusHandler.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/core/tests/Test_StatusHandler.py b/src/core/tests/Test_StatusHandler.py index 80afe0bc..e150c4a3 100644 --- a/src/core/tests/Test_StatusHandler.py +++ b/src/core/tests/Test_StatusHandler.py @@ -449,15 +449,13 @@ def test_installation_packages_map(self): 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'} - - 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.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) - status_handler.set_package_install_status(test_packages, test_package_versions, 'Installed', 'Other') + 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] @@ -469,7 +467,7 @@ def test_installation_packages_map(self): self.assertEqual(formatted_message['patches'][0]['name'], 'python-samba0') # Update the classification from Other to Critical - status_handler.set_package_install_status_classification(test_packages, test_package_versions, '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] @@ -519,7 +517,6 @@ def test_load_status_and_set_package_install_status(self): 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') # Setup functions to populate packages and versions for truncation From cb3ad5c713f40736b327fa1f4875cc9e0aca4b08 Mon Sep 17 00:00:00 2001 From: john feng Date: Mon, 26 Jun 2023 19:12:07 -0700 Subject: [PATCH 10/11] add more verifications to test_if_status_file_resets_on_load_if_malformed --- src/core/tests/Test_StatusHandler.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/tests/Test_StatusHandler.py b/src/core/tests/Test_StatusHandler.py index e150c4a3..700a5669 100644 --- a/src/core/tests/Test_StatusHandler.py +++ b/src/core/tests/Test_StatusHandler.py @@ -411,9 +411,11 @@ def test_if_status_file_resets_on_load_if_malformed(self): # 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]["status"]["substatus"] - self.assertIsNotNone(substatus_file_data) - self.assertEqual(len(substatus_file_data), 0) + 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_status_path_is_dir(self): From f15e3ab20c1fd8d19f517248caf5e16d3f1d4c7b Mon Sep 17 00:00:00 2001 From: john feng Date: Mon, 26 Jun 2023 19:15:57 -0700 Subject: [PATCH 11/11] force push --- src/core/tests/Test_StatusHandler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/tests/Test_StatusHandler.py b/src/core/tests/Test_StatusHandler.py index 700a5669..75a5f7de 100644 --- a/src/core/tests/Test_StatusHandler.py +++ b/src/core/tests/Test_StatusHandler.py @@ -408,7 +408,6 @@ def test_if_status_file_resets_on_load_if_malformed(self): 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]