From 3610e6f621f05b4282f6185f71f44a80d70b36ff Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Tue, 15 Oct 2024 14:25:46 +0530 Subject: [PATCH 01/12] Added boot_sources_result post processing --- boot_sources_result.py | 222 ++++++++++++++++++++++++++++++++ boot_sources_result.yaml | 37 ++++++ boot_sources_result_scehma.yaml | 38 ++++++ 3 files changed, 297 insertions(+) create mode 100755 boot_sources_result.py create mode 100644 boot_sources_result.yaml create mode 100644 boot_sources_result_scehma.yaml diff --git a/boot_sources_result.py b/boot_sources_result.py new file mode 100755 index 0000000..e20b8f7 --- /dev/null +++ b/boot_sources_result.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 + +import argparse +import re +import logging +import os +import sys +from typing import Any, Dict, List +import yaml +import jsonschema +from jsonschema import validate + +DbType = Dict[str, Any] +ResType = List[Dict[str, Any]] # Now each device result is a dictionary + +# Load YAML diagnostics database and validate against schema +def load_diagnostics_db(config_filename: str, schema_filename: str) -> DbType: + logging.debug(f"Loading configuration file `{config_filename}`") + logging.debug(f"Loading schema file `{schema_filename}`") + + # Check if the schema file exists + if not os.path.isfile(schema_filename): + logging.error(f'Schema file `{schema_filename}` does not exist') + sys.exit(1) + + # Load the schema from the schema file + with open(schema_filename, 'r') as schemafile: + try: + schema = yaml.load(schemafile, Loader=yaml.FullLoader) + except yaml.YAMLError as err: + logging.error(f"Error parsing schema file `{schema_filename}`: {err}") + sys.exit(1) + + # Check if the configuration file exists + if not os.path.isfile(config_filename): + logging.error(f'Configuration file `{config_filename}` does not exist') + sys.exit(1) + + # Load the configuration file + with open(config_filename, 'r') as yamlfile: + try: + db = yaml.load(yamlfile, Loader=yaml.FullLoader) + except yaml.YAMLError as err: + logging.error(f"Error parsing configuration file `{config_filename}`: {err}") + sys.exit(1) + + # Validate the configuration against the schema + try: + jsonschema.validate(instance=db, schema=schema) + except jsonschema.exceptions.ValidationError as err: + logging.error(f"YAML file validation error: {err.message}") + sys.exit(1) + + logging.debug(f"YAML contents: {db}") + return db + +# Detect block devices based on the log file +def detect_block_devices(log_path: str) -> int: + device_pattern = re.compile(r'INFO: Block device : /dev/\w+') + device_count = 0 + with open(log_path, 'r') as log_file: + for line in log_file: + if device_pattern.search(line): + device_count += 1 + logging.debug(f'Block devices detected: {device_count}') + return device_count + +# Parse the log file to extract diagnostics results +def parse_diagnostics_log(log_path: str, device_results: ResType) -> None: + partition_pattern = re.compile(r'INFO: Partition table type : GPT|MBR') + read_pattern_success = re.compile(r'INFO: Block read on /dev/\w+.*successful') + read_pattern_fail = re.compile(r'INFO: Block read on /dev/\w+.*failed') + write_pattern_pass = re.compile(r'INFO: write check passed on /dev/\w+') + write_pattern_fail = re.compile(r'INFO: write check failed on /dev/\w+') + write_prompt_pattern = re.compile(r'Do you want to perform a write check on /dev/\w+\?') + + current_device_results = {} + awaiting_write_check = False + + with open(log_path, 'r') as log_file: + for line in log_file: + # Detect block device and process results for the previous device + if re.search(r'INFO: Block device : /dev/\w+', line): + logging.debug(f"Detected device: {line.strip()}") + if current_device_results: + # Ensure 'read' is only set to 'FAIL' if not explicitly set to 'PASS' + if 'read' not in current_device_results: + current_device_results['read'] = 'FAIL' + if awaiting_write_check: + current_device_results['write'] = 'SKIPPED' + # Add the results for the previous device + device_results.append(current_device_results) + current_device_results = {} # Reset for the next device + awaiting_write_check = False + + # Check partition table + if partition_pattern.search(line): + logging.debug(f"Partition table found: {line.strip()}") + current_device_results['partition_table'] = 'PASS' + elif re.search(r'INFO: Invalid partition table', line): + logging.debug(f"Invalid partition table: {line.strip()}") + current_device_results['partition_table'] = 'FAIL' + + # Check block read success/failure + if read_pattern_success.search(line): + logging.debug(f"Block read success: {line.strip()}") + current_device_results['read'] = 'PASS' # Correctly set read status to PASS + elif read_pattern_fail.search(line): + logging.debug(f"Block read failure: {line.strip()}") + current_device_results['read'] = 'FAIL' + + # Detect prompt for write check + if write_prompt_pattern.search(line): + logging.debug(f"Write check prompt detected: {line.strip()}") + awaiting_write_check = True + + # Check write check success/failure + if write_pattern_pass.search(line): + logging.debug(f"Write check success: {line.strip()}") + current_device_results['write'] = 'PASS' + awaiting_write_check = False + elif write_pattern_fail.search(line): + logging.debug(f"Write check failure: {line.strip()}") + current_device_results['write'] = 'FAIL' + awaiting_write_check = False + + # Handle the last device in the log if not already processed + if current_device_results: + if awaiting_write_check: + current_device_results['write'] = 'SKIPPED' + if 'read' not in current_device_results: + current_device_results['read'] = 'FAIL' + device_results.append(current_device_results) + + logging.debug(f"Parsed device results: {device_results}") + +# Apply criteria from the YAML to the parsed log results +def apply_criteria(db: DbType, num_devices: int, device_results: ResType) -> str: + logging.debug('Applying criterias from the database') + num_pass_devices = 0 + num_fail_devices = 0 + + for ir in device_results: + if not ir: + logging.error(f"Skipping incomplete result: {ir}") + continue + + found_match = False + + # Iterate over the YAML criteria and match results + for ii in db['criterias']: + yaml_criteria = ii['results'][0] + + # Check all fields in yaml_criteria against ir (ignore extra fields in ir) + if all(key in ir and ir[key] == yaml_criteria.get(key) for key in yaml_criteria): + logging.debug(f"Match found: {ir}") + ir['result'] = ii['criteria'] + ir['quality'] = ii['quality'] + ir['recommendation'] = ii['recommendation'] + found_match = True + + if ii['criteria'] == 'PASS': + num_pass_devices += 1 + else: + num_fail_devices += 1 + + break # Stop checking other criteria once a match is found + + if not found_match: + logging.error(f"No validating match found for: {ir}") + + # Final result: If at least num_devices passed, result is PASS + if num_pass_devices >= num_devices: + result = 'PASS' + if num_pass_devices > num_devices: + logging.info(f"More devices passed ({num_pass_devices}) than expected ({num_devices}).") + else: + result = 'FAIL' + + logging.info(f"Block-device-diagnostics passed for {num_pass_devices} and requested {num_devices}") + return result + +if __name__ == "__main__": + me = os.path.realpath(__file__) + here = os.path.dirname(me) + + parser = argparse.ArgumentParser( + description='Parse Block Device Diagnostics logs.', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument( + '--config', help='Configuration filename', + default=f"{here}/boot_sources_result.yaml") + parser.add_argument( + '--schema', help='Schema filename', + default=f"{here}/boot_sources_result_scehma.yaml") + parser.add_argument( + '--debug', action='store_true', help='Turn on debug messages') + parser.add_argument( + 'log', help="Input log filename") + parser.add_argument( + 'num_devices', type=int, + help='Number of block devices expected to pass') + args = parser.parse_args() + + logging.basicConfig( + format='%(levelname)s %(funcName)s: %(message)s', + level=logging.DEBUG if args.debug else logging.INFO) + + db = load_diagnostics_db(args.config, args.schema) + num_actual_devices = detect_block_devices(args.log) + num_expected_devices = args.num_devices + + device_results: ResType = [] + parse_diagnostics_log(args.log, device_results) + + result = apply_criteria(db, num_expected_devices, device_results) + + logging.info(f'Block device diagnostics result is: {result}') + if result == "PASS": + sys.exit(0) + else: + sys.exit(1) diff --git a/boot_sources_result.yaml b/boot_sources_result.yaml new file mode 100644 index 0000000..2e89f53 --- /dev/null +++ b/boot_sources_result.yaml @@ -0,0 +1,37 @@ +############################################################################### +# boot_sources_result.py configuration file # +############################################################################### + +criterias: + - results: + - partition_table: 'PASS' + read: 'PASS' + write: 'PASS' + criteria: 'PASS' + quality: 'BEST' + recommendation: 'All tests passed. Block device is in excellent condition.' + - results: + - partition_table: 'PASS' + read: 'PASS' + write: 'FAIL' + criteria: 'FAIL' + quality: 'BAD' + recommendation: 'Write check failed. Check block device.' + - results: + - partition_table: 'PASS' + read: 'PASS' + write: 'SKIPPED' + criteria: 'PASS' + quality: 'OK' + recommendation: 'Write check was skipped. Device appears fine but needs further review.' + - results: + - partition_table: 'PASS' + read: 'FAIL' + criteria: 'FAIL' + quality: 'POOR' + recommendation: 'Read check failed. Check block device.' + - results: + - partition_table: 'FAIL' + criteria: 'FAIL' + quality: 'BAD' + recommendation: 'Partition table is invalid or not found.' diff --git a/boot_sources_result_scehma.yaml b/boot_sources_result_scehma.yaml new file mode 100644 index 0000000..2ebceec --- /dev/null +++ b/boot_sources_result_scehma.yaml @@ -0,0 +1,38 @@ +############################################################################### +# boot_sources_result.py configuration file schema # +############################################################################### + +type: object +properties: + criterias: + type: array + items: + type: object + properties: + results: + type: array + items: + type: object + properties: + partition_table: + type: string + read: + type: string + write: + type: string + additionalProperties: false + criteria: + type: string + quality: + type: string + recommendation: + type: string + required: + - results + - criteria + - quality + - recommendation + additionalProperties: false +required: + - criterias +additionalProperties: false -- GitLab From 9bfd88d2543d7121f1198c5df116dd053c4d821c Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Tue, 15 Oct 2024 15:11:51 +0530 Subject: [PATCH 02/12] minor changes as shifted schema for the boot sources processing in schema folder --- boot_sources_result.py | 24 +++++- .../boot_sources_result_schema.yaml | 85 ++++++++++--------- 2 files changed, 69 insertions(+), 40 deletions(-) rename boot_sources_result_scehma.yaml => schemas/boot_sources_result_schema.yaml (54%) diff --git a/boot_sources_result.py b/boot_sources_result.py index e20b8f7..77f669e 100755 --- a/boot_sources_result.py +++ b/boot_sources_result.py @@ -180,6 +180,20 @@ def apply_criteria(db: DbType, num_devices: int, device_results: ResType) -> str logging.info(f"Block-device-diagnostics passed for {num_pass_devices} and requested {num_devices}") return result +# Function to find the schema file +def find_schema_file(filename): + me = os.path.realpath(__file__) + directories = [ + os.path.join(os.path.dirname(me), 'schemas'), + os.path.join(os.path.dirname(os.path.dirname(me)), 'schemas'), + os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(me))), 'schemas'), + ] + for schema_dir in directories: + schema_file = os.path.join(schema_dir, filename) + if os.path.isfile(schema_file): + return schema_file + return None + if __name__ == "__main__": me = os.path.realpath(__file__) here = os.path.dirname(me) @@ -191,8 +205,8 @@ if __name__ == "__main__": '--config', help='Configuration filename', default=f"{here}/boot_sources_result.yaml") parser.add_argument( - '--schema', help='Schema filename', - default=f"{here}/boot_sources_result_scehma.yaml") + '--schema', help='Schema filename. If not provided, the script will search for it automatically.', + default=None) parser.add_argument( '--debug', action='store_true', help='Turn on debug messages') parser.add_argument( @@ -206,6 +220,12 @@ if __name__ == "__main__": format='%(levelname)s %(funcName)s: %(message)s', level=logging.DEBUG if args.debug else logging.INFO) + if args.schema is None: + args.schema = find_schema_file('boot_sources_result_schema.yaml') + if args.schema is None: + logging.error('Schema file boot_sources_result_schema.yaml not found in schema directories') + sys.exit(1) + db = load_diagnostics_db(args.config, args.schema) num_actual_devices = detect_block_devices(args.log) num_expected_devices = args.num_devices diff --git a/boot_sources_result_scehma.yaml b/schemas/boot_sources_result_schema.yaml similarity index 54% rename from boot_sources_result_scehma.yaml rename to schemas/boot_sources_result_schema.yaml index 2ebceec..d47bd1a 100644 --- a/boot_sources_result_scehma.yaml +++ b/schemas/boot_sources_result_schema.yaml @@ -1,38 +1,47 @@ -############################################################################### -# boot_sources_result.py configuration file schema # -############################################################################### - -type: object -properties: - criterias: - type: array - items: - type: object - properties: - results: - type: array - items: - type: object - properties: - partition_table: - type: string - read: - type: string - write: - type: string - additionalProperties: false - criteria: - type: string - quality: - type: string - recommendation: - type: string - required: - - results - - criteria - - quality - - recommendation - additionalProperties: false -required: - - criterias -additionalProperties: false +--- +$id: "https://gitlab.arm.com/systemready/systemready-scripts/-/raw/master/\ + schemas/boot_sources_result_schema.yaml" +$schema: https://json-schema.org/draft/2020-12/schema +title: Boot sources results post processing schema +description: | + This schema is used by script which processes read_write_block_devices logs in systemready and give pass and fail on basis of boot_sources_result.py + + This schema describes requirements on the configuration file. It can be used + by the valid.py script. + + See the README for details. + +type: object +properties: + criterias: + type: array + items: + type: object + properties: + results: + type: array + items: + type: object + properties: + partition_table: + type: string + read: + type: string + write: + type: string + additionalProperties: false + criteria: + type: string + quality: + type: string + recommendation: + type: string + required: + - results + - criteria + - quality + - recommendation + additionalProperties: false +required: + - criterias +additionalProperties: false -- GitLab From 346994c0843ef6fcd83b923404d46b31bb8c5082 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Tue, 22 Oct 2024 13:43:40 +0530 Subject: [PATCH 03/12] changes for makefile and unit test boot processing --- Makefile | 4 +- boot_sources_result.py | 484 +++++++++--------- .../read_write_check_blk_devices.log | 136 +++++ .../read_write_check_blk_devices_FAIL.log | 136 +++++ tests/test-boot-sources | 46 ++ 5 files changed, 562 insertions(+), 244 deletions(-) create mode 100644 tests/data/test-boot-sources/read_write_check_blk_devices.log create mode 100644 tests/data/test-boot-sources/read_write_check_blk_devices_FAIL.log create mode 100755 tests/test-boot-sources diff --git a/Makefile b/Makefile index bd3fdc3..8f766c5 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,8 @@ VALIDS = \ format-sr-results-schema.yaml__format-sr-results.yaml \ guid-tool-schema.yaml__guid-tool.yaml \ identify-schema.yaml__identify.yaml \ - identify-schema.yaml__tests/data/test-check-sr-results/identify.yaml + identify-schema.yaml__tests/data/test-check-sr-results/identify.yaml \ + boot_sources_result_schema.yaml__boot_sources_result.yaml VALID_TARGETS = $(addsuffix .valid,$(VALIDS)) @@ -48,7 +49,6 @@ test: $(TEST_TARGETS) ./validate.py --schema schemas/$(word 1,$(subst __, ,$@)) \ $(subst .valid,,$(word 2,$(subst __, ,$@))) - valid: $(VALID_TARGETS) # Sequential (small) tests. diff --git a/boot_sources_result.py b/boot_sources_result.py index 77f669e..d071999 100755 --- a/boot_sources_result.py +++ b/boot_sources_result.py @@ -1,242 +1,242 @@ -#!/usr/bin/env python3 - -import argparse -import re -import logging -import os -import sys -from typing import Any, Dict, List -import yaml -import jsonschema -from jsonschema import validate - -DbType = Dict[str, Any] -ResType = List[Dict[str, Any]] # Now each device result is a dictionary - -# Load YAML diagnostics database and validate against schema -def load_diagnostics_db(config_filename: str, schema_filename: str) -> DbType: - logging.debug(f"Loading configuration file `{config_filename}`") - logging.debug(f"Loading schema file `{schema_filename}`") - - # Check if the schema file exists - if not os.path.isfile(schema_filename): - logging.error(f'Schema file `{schema_filename}` does not exist') - sys.exit(1) - - # Load the schema from the schema file - with open(schema_filename, 'r') as schemafile: - try: - schema = yaml.load(schemafile, Loader=yaml.FullLoader) - except yaml.YAMLError as err: - logging.error(f"Error parsing schema file `{schema_filename}`: {err}") - sys.exit(1) - - # Check if the configuration file exists - if not os.path.isfile(config_filename): - logging.error(f'Configuration file `{config_filename}` does not exist') - sys.exit(1) - - # Load the configuration file - with open(config_filename, 'r') as yamlfile: - try: - db = yaml.load(yamlfile, Loader=yaml.FullLoader) - except yaml.YAMLError as err: - logging.error(f"Error parsing configuration file `{config_filename}`: {err}") - sys.exit(1) - - # Validate the configuration against the schema - try: - jsonschema.validate(instance=db, schema=schema) - except jsonschema.exceptions.ValidationError as err: - logging.error(f"YAML file validation error: {err.message}") - sys.exit(1) - - logging.debug(f"YAML contents: {db}") - return db - -# Detect block devices based on the log file -def detect_block_devices(log_path: str) -> int: - device_pattern = re.compile(r'INFO: Block device : /dev/\w+') - device_count = 0 - with open(log_path, 'r') as log_file: - for line in log_file: - if device_pattern.search(line): - device_count += 1 - logging.debug(f'Block devices detected: {device_count}') - return device_count - -# Parse the log file to extract diagnostics results -def parse_diagnostics_log(log_path: str, device_results: ResType) -> None: - partition_pattern = re.compile(r'INFO: Partition table type : GPT|MBR') - read_pattern_success = re.compile(r'INFO: Block read on /dev/\w+.*successful') - read_pattern_fail = re.compile(r'INFO: Block read on /dev/\w+.*failed') - write_pattern_pass = re.compile(r'INFO: write check passed on /dev/\w+') - write_pattern_fail = re.compile(r'INFO: write check failed on /dev/\w+') - write_prompt_pattern = re.compile(r'Do you want to perform a write check on /dev/\w+\?') - - current_device_results = {} - awaiting_write_check = False - - with open(log_path, 'r') as log_file: - for line in log_file: - # Detect block device and process results for the previous device - if re.search(r'INFO: Block device : /dev/\w+', line): - logging.debug(f"Detected device: {line.strip()}") - if current_device_results: - # Ensure 'read' is only set to 'FAIL' if not explicitly set to 'PASS' - if 'read' not in current_device_results: - current_device_results['read'] = 'FAIL' - if awaiting_write_check: - current_device_results['write'] = 'SKIPPED' - # Add the results for the previous device - device_results.append(current_device_results) - current_device_results = {} # Reset for the next device - awaiting_write_check = False - - # Check partition table - if partition_pattern.search(line): - logging.debug(f"Partition table found: {line.strip()}") - current_device_results['partition_table'] = 'PASS' - elif re.search(r'INFO: Invalid partition table', line): - logging.debug(f"Invalid partition table: {line.strip()}") - current_device_results['partition_table'] = 'FAIL' - - # Check block read success/failure - if read_pattern_success.search(line): - logging.debug(f"Block read success: {line.strip()}") - current_device_results['read'] = 'PASS' # Correctly set read status to PASS - elif read_pattern_fail.search(line): - logging.debug(f"Block read failure: {line.strip()}") - current_device_results['read'] = 'FAIL' - - # Detect prompt for write check - if write_prompt_pattern.search(line): - logging.debug(f"Write check prompt detected: {line.strip()}") - awaiting_write_check = True - - # Check write check success/failure - if write_pattern_pass.search(line): - logging.debug(f"Write check success: {line.strip()}") - current_device_results['write'] = 'PASS' - awaiting_write_check = False - elif write_pattern_fail.search(line): - logging.debug(f"Write check failure: {line.strip()}") - current_device_results['write'] = 'FAIL' - awaiting_write_check = False - - # Handle the last device in the log if not already processed - if current_device_results: - if awaiting_write_check: - current_device_results['write'] = 'SKIPPED' - if 'read' not in current_device_results: - current_device_results['read'] = 'FAIL' - device_results.append(current_device_results) - - logging.debug(f"Parsed device results: {device_results}") - -# Apply criteria from the YAML to the parsed log results -def apply_criteria(db: DbType, num_devices: int, device_results: ResType) -> str: - logging.debug('Applying criterias from the database') - num_pass_devices = 0 - num_fail_devices = 0 - - for ir in device_results: - if not ir: - logging.error(f"Skipping incomplete result: {ir}") - continue - - found_match = False - - # Iterate over the YAML criteria and match results - for ii in db['criterias']: - yaml_criteria = ii['results'][0] - - # Check all fields in yaml_criteria against ir (ignore extra fields in ir) - if all(key in ir and ir[key] == yaml_criteria.get(key) for key in yaml_criteria): - logging.debug(f"Match found: {ir}") - ir['result'] = ii['criteria'] - ir['quality'] = ii['quality'] - ir['recommendation'] = ii['recommendation'] - found_match = True - - if ii['criteria'] == 'PASS': - num_pass_devices += 1 - else: - num_fail_devices += 1 - - break # Stop checking other criteria once a match is found - - if not found_match: - logging.error(f"No validating match found for: {ir}") - - # Final result: If at least num_devices passed, result is PASS - if num_pass_devices >= num_devices: - result = 'PASS' - if num_pass_devices > num_devices: - logging.info(f"More devices passed ({num_pass_devices}) than expected ({num_devices}).") - else: - result = 'FAIL' - - logging.info(f"Block-device-diagnostics passed for {num_pass_devices} and requested {num_devices}") - return result - -# Function to find the schema file -def find_schema_file(filename): - me = os.path.realpath(__file__) - directories = [ - os.path.join(os.path.dirname(me), 'schemas'), - os.path.join(os.path.dirname(os.path.dirname(me)), 'schemas'), - os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(me))), 'schemas'), - ] - for schema_dir in directories: - schema_file = os.path.join(schema_dir, filename) - if os.path.isfile(schema_file): - return schema_file - return None - -if __name__ == "__main__": - me = os.path.realpath(__file__) - here = os.path.dirname(me) - - parser = argparse.ArgumentParser( - description='Parse Block Device Diagnostics logs.', - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument( - '--config', help='Configuration filename', - default=f"{here}/boot_sources_result.yaml") - parser.add_argument( - '--schema', help='Schema filename. If not provided, the script will search for it automatically.', - default=None) - parser.add_argument( - '--debug', action='store_true', help='Turn on debug messages') - parser.add_argument( - 'log', help="Input log filename") - parser.add_argument( - 'num_devices', type=int, - help='Number of block devices expected to pass') - args = parser.parse_args() - - logging.basicConfig( - format='%(levelname)s %(funcName)s: %(message)s', - level=logging.DEBUG if args.debug else logging.INFO) - - if args.schema is None: - args.schema = find_schema_file('boot_sources_result_schema.yaml') - if args.schema is None: - logging.error('Schema file boot_sources_result_schema.yaml not found in schema directories') - sys.exit(1) - - db = load_diagnostics_db(args.config, args.schema) - num_actual_devices = detect_block_devices(args.log) - num_expected_devices = args.num_devices - - device_results: ResType = [] - parse_diagnostics_log(args.log, device_results) - - result = apply_criteria(db, num_expected_devices, device_results) - - logging.info(f'Block device diagnostics result is: {result}') - if result == "PASS": - sys.exit(0) - else: - sys.exit(1) +#!/usr/bin/env python3 + +import argparse +import re +import logging +import os +import sys +from typing import Any, Dict, List +import yaml +import jsonschema +from jsonschema import validate + +DbType = Dict[str, Any] +ResType = List[Dict[str, Any]] # Now each device result is a dictionary + +# Load YAML diagnostics database and validate against schema +def load_diagnostics_db(config_filename: str, schema_filename: str) -> DbType: + logging.debug(f"Loading configuration file `{config_filename}`") + logging.debug(f"Loading schema file `{schema_filename}`") + + # Check if the schema file exists + if not os.path.isfile(schema_filename): + logging.error(f'Schema file `{schema_filename}` does not exist') + sys.exit(1) + + # Load the schema from the schema file + with open(schema_filename, 'r') as schemafile: + try: + schema = yaml.load(schemafile, Loader=yaml.FullLoader) + except yaml.YAMLError as err: + logging.error(f"Error parsing schema file `{schema_filename}`: {err}") + sys.exit(1) + + # Check if the configuration file exists + if not os.path.isfile(config_filename): + logging.error(f'Configuration file `{config_filename}` does not exist') + sys.exit(1) + + # Load the configuration file + with open(config_filename, 'r') as yamlfile: + try: + db = yaml.load(yamlfile, Loader=yaml.FullLoader) + except yaml.YAMLError as err: + logging.error(f"Error parsing configuration file `{config_filename}`: {err}") + sys.exit(1) + + # Validate the configuration against the schema + try: + jsonschema.validate(instance=db, schema=schema) + except jsonschema.exceptions.ValidationError as err: + logging.error(f"YAML file validation error: {err.message}") + sys.exit(1) + + logging.debug(f"YAML contents: {db}") + return db + +# Detect block devices based on the log file +def detect_block_devices(log_path: str) -> int: + device_pattern = re.compile(r'INFO: Block device : /dev/\w+') + device_count = 0 + with open(log_path, 'r') as log_file: + for line in log_file: + if device_pattern.search(line): + device_count += 1 + logging.debug(f'Block devices detected: {device_count}') + return device_count + +# Parse the log file to extract diagnostics results +def parse_diagnostics_log(log_path: str, device_results: ResType) -> None: + partition_pattern = re.compile(r'INFO: Partition table type : GPT|MBR') + read_pattern_success = re.compile(r'INFO: Block read on /dev/\w+.*successful') + read_pattern_fail = re.compile(r'INFO: Block read on /dev/\w+.*failed') + write_pattern_pass = re.compile(r'INFO: write check passed on /dev/\w+') + write_pattern_fail = re.compile(r'INFO: write check failed on /dev/\w+') + write_prompt_pattern = re.compile(r'Do you want to perform a write check on /dev/\w+\?') + + current_device_results = {} + awaiting_write_check = False + + with open(log_path, 'r') as log_file: + for line in log_file: + # Detect block device and process results for the previous device + if re.search(r'INFO: Block device : /dev/\w+', line): + logging.debug(f"Detected device: {line.strip()}") + if current_device_results: + # Ensure 'read' is only set to 'FAIL' if not explicitly set to 'PASS' + if 'read' not in current_device_results: + current_device_results['read'] = 'FAIL' + if awaiting_write_check: + current_device_results['write'] = 'SKIPPED' + # Add the results for the previous device + device_results.append(current_device_results) + current_device_results = {} # Reset for the next device + awaiting_write_check = False + + # Check partition table + if partition_pattern.search(line): + logging.debug(f"Partition table found: {line.strip()}") + current_device_results['partition_table'] = 'PASS' + elif re.search(r'INFO: Invalid partition table', line): + logging.debug(f"Invalid partition table: {line.strip()}") + current_device_results['partition_table'] = 'FAIL' + + # Check block read success/failure + if read_pattern_success.search(line): + logging.debug(f"Block read success: {line.strip()}") + current_device_results['read'] = 'PASS' # Correctly set read status to PASS + elif read_pattern_fail.search(line): + logging.debug(f"Block read failure: {line.strip()}") + current_device_results['read'] = 'FAIL' + + # Detect prompt for write check + if write_prompt_pattern.search(line): + logging.debug(f"Write check prompt detected: {line.strip()}") + awaiting_write_check = True + + # Check write check success/failure + if write_pattern_pass.search(line): + logging.debug(f"Write check success: {line.strip()}") + current_device_results['write'] = 'PASS' + awaiting_write_check = False + elif write_pattern_fail.search(line): + logging.debug(f"Write check failure: {line.strip()}") + current_device_results['write'] = 'FAIL' + awaiting_write_check = False + + # Handle the last device in the log if not already processed + if current_device_results: + if awaiting_write_check: + current_device_results['write'] = 'SKIPPED' + if 'read' not in current_device_results: + current_device_results['read'] = 'FAIL' + device_results.append(current_device_results) + + logging.debug(f"Parsed device results: {device_results}") + +# Apply criteria from the YAML to the parsed log results +def apply_criteria(db: DbType, num_devices: int, device_results: ResType) -> str: + logging.debug('Applying criterias from the database') + num_pass_devices = 0 + num_fail_devices = 0 + + for ir in device_results: + if not ir: + logging.error(f"Skipping incomplete result: {ir}") + continue + + found_match = False + + # Iterate over the YAML criteria and match results + for ii in db['criterias']: + yaml_criteria = ii['results'][0] + + # Check all fields in yaml_criteria against ir (ignore extra fields in ir) + if all(key in ir and ir[key] == yaml_criteria.get(key) for key in yaml_criteria): + logging.debug(f"Match found: {ir}") + ir['result'] = ii['criteria'] + ir['quality'] = ii['quality'] + ir['recommendation'] = ii['recommendation'] + found_match = True + + if ii['criteria'] == 'PASS': + num_pass_devices += 1 + else: + num_fail_devices += 1 + + break # Stop checking other criteria once a match is found + + if not found_match: + logging.error(f"No validating match found for: {ir}") + + # Final result: If at least num_devices passed, result is PASS + if num_pass_devices >= num_devices: + result = 'PASS' + if num_pass_devices > num_devices: + logging.info(f"More devices passed ({num_pass_devices}) than expected ({num_devices}).") + else: + result = 'FAIL' + + logging.info(f"Block-device-diagnostics passed for {num_pass_devices} and requested {num_devices}") + return result + +# Function to find the schema file +def find_schema_file(filename): + me = os.path.realpath(__file__) + directories = [ + os.path.join(os.path.dirname(me), 'schemas'), + os.path.join(os.path.dirname(os.path.dirname(me)), 'schemas'), + os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(me))), 'schemas'), + ] + for schema_dir in directories: + schema_file = os.path.join(schema_dir, filename) + if os.path.isfile(schema_file): + return schema_file + return None + +if __name__ == "__main__": + me = os.path.realpath(__file__) + here = os.path.dirname(me) + + parser = argparse.ArgumentParser( + description='Parse Block Device Diagnostics logs.', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument( + '--config', help='Configuration filename', + default=f"{here}/boot_sources_result.yaml") + parser.add_argument( + '--schema', help='Schema filename. If not provided, the script will search for it automatically.', + default=None) + parser.add_argument( + '--debug', action='store_true', help='Turn on debug messages') + parser.add_argument( + 'log', help="Input log filename") + parser.add_argument( + 'num_devices', type=int, + help='Number of block devices expected to pass') + args = parser.parse_args() + + logging.basicConfig( + format='%(levelname)s %(funcName)s: %(message)s', + level=logging.DEBUG if args.debug else logging.INFO) + + if args.schema is None: + args.schema = find_schema_file('boot_sources_result_schema.yaml') + if args.schema is None: + logging.error('Schema file boot_sources_result_schema.yaml not found in schema directories') + sys.exit(1) + + db = load_diagnostics_db(args.config, args.schema) + num_actual_devices = detect_block_devices(args.log) + num_expected_devices = args.num_devices + + device_results: ResType = [] + parse_diagnostics_log(args.log, device_results) + + result = apply_criteria(db, num_expected_devices, device_results) + + logging.info(f'Block device diagnostics result is: {result}') + if result == "PASS": + sys.exit(0) + else: + sys.exit(1) diff --git a/tests/data/test-boot-sources/read_write_check_blk_devices.log b/tests/data/test-boot-sources/read_write_check_blk_devices.log new file mode 100644 index 0000000..a83c569 --- /dev/null +++ b/tests/data/test-boot-sources/read_write_check_blk_devices.log @@ -0,0 +1,136 @@ +******************************************************************************************************************************** + + Read block devices tool + +******************************************************************************************************************************** +INFO: Detected following block devices with lsblk command : +0: ram0 +1: ram1 +2: ram2 +3: ram3 +4: ram4 +5: ram5 +6: ram6 +7: ram7 +8: ram8 +9: ram9 +10: ram10 +11: ram11 +12: ram12 +13: ram13 +14: ram14 +15: ram15 +16: mtdblock0 +17: vda +18: vdb + +******************************************************************************************************************************** + +INFO: Block device : /dev/ram0 +INFO: Invalid partition table or not found for ram0 +INFO: Block device : /dev/ram1 +INFO: Invalid partition table or not found for ram1 +INFO: Block device : /dev/ram2 +INFO: Invalid partition table or not found for ram2 +INFO: Block device : /dev/ram3 +INFO: Invalid partition table or not found for ram3 +INFO: Block device : /dev/ram4 +INFO: Invalid partition table or not found for ram4 +INFO: Block device : /dev/ram5 +INFO: Invalid partition table or not found for ram5 +INFO: Block device : /dev/ram6 +INFO: Invalid partition table or not found for ram6 +INFO: Block device : /dev/ram7 +INFO: Invalid partition table or not found for ram7 +INFO: Block device : /dev/ram8 +INFO: Invalid partition table or not found for ram8 +INFO: Block device : /dev/ram9 +INFO: Invalid partition table or not found for ram9 +INFO: Block device : /dev/ram10 +INFO: Invalid partition table or not found for ram10 +INFO: Block device : /dev/ram11 +INFO: Invalid partition table or not found for ram11 +INFO: Block device : /dev/ram12 +INFO: Invalid partition table or not found for ram12 +INFO: Block device : /dev/ram13 +INFO: Invalid partition table or not found for ram13 +INFO: Block device : /dev/ram14 +INFO: Invalid partition table or not found for ram14 +INFO: Block device : /dev/ram15 +INFO: Invalid partition table or not found for ram15 +INFO: Block device : /dev/mtdblock0 +INFO: Invalid partition table or not found for mtdblock0 +INFO: Block device : /dev/vda +INFO: Partition table type : GPT + + +INFO: Partition : /dev/vda1 Partition type GUID : C12A7328-F81F-11D2-BA4B-00A0C93EC93B "Platform required bit" : 0 +INFO: vda1 partition is PRECIOUS. +INFO: Number of 512B blocks used on /dev/vda1: (132552, 174304) + EFI System partition : C12A7328-F81F-11D2-BA4B-00A0C93EC93B + Skipping block read/write... + +INFO: Partition : /dev/vda2 Partition type GUID : 0FC63DAF-8483-4772-8E79-3D69D8477DE4 "Platform required bit" : 0 +INFO: Performing block read on /dev/vda2 part_guid = 0FC63DAF-8483-4772-8E79-3D69D8477DE4 +INFO: Block read on /dev/vda2 part_guid = 0FC63DAF-8483-4772-8E79-3D69D8477DE4 successful +Do you want to perform a write check on /dev/vda2? (yes/no): yes +INFO: Creating backup of the current block before write check... +1+0 records in +1+0 records out +512 bytes copied, 0.000566366 s, 904 kB/s +INFO: Writing test data to the device for write check... +1+0 records in +1+0 records out +512 bytes copied, 0.00111092 s, 461 kB/s +INFO: Reading back the test data for verification... +1+0 records in +1+0 records out +512 bytes copied, 0.000566845 s, 903 kB/s +Original SHA256: 5ba5b889a62d98368222cef5e53305af9da2960ad6ecab48271a293d3337fb92 +Read-back SHA256: 5ba5b889a62d98368222cef5e53305af9da2960ad6ecab48271a293d3337fb92 +INFO: write check passed on /dev/vda2. +INFO: Restoring the backup to the device after write check... +1+0 records in +1+0 records out +512 bytes copied, 0.000530221 s, 966 kB/s +INFO: Backup restored for /dev/vda2. + +******************************************************************************************************************************** + +INFO: Block device : /dev/vdb +[ 336.395849] vdb: vdb1 vdb2 +INFO: Partition table type : GPT + + +INFO: Partition : /dev/vdb1 Partition type GUID : C12A7328-F81F-11D2-BA4B-00A0C93EC93B "Platform required bit" : 0 +INFO: vdb1 partition is PRECIOUS. +INFO: Number of 512B blocks used on /dev/vdb1: (0, 869816) + EFI System partition : C12A7328-F81F-11D2-BA4B-00A0C93EC93B + Skipping block read/write... + +INFO: Partition : /dev/vdb2 Partition type GUID : B921B045-1DF0-41C3-AF44-4C6F280D3FAE "Platform required bit" : 0 +INFO: Performing block read on /dev/vdb2 part_guid = B921B045-1DF0-41C3-AF44-4C6F280D3FAE +INFO: Block read on /dev/vdb2 part_guid = B921B045-1DF0-41C3-AF44-4C6F280D3FAE successful +Do you want to perform a write check on /dev/vdb2? (yes/no): yes +INFO: Creating backup of the current block before write check... +1+0 records in +1+0 records out +512 bytes copied, 0.00120164 s, 426 kB/s +INFO: Writing test data to the device for write check... +1+0 records in +1+0 records out +512 bytes copied, 0.00179417 s, 285 kB/s +INFO: Reading back the test data for verification... +1+0 records in +1+0 records out +512 bytes copied, 0.00102902 s, 498 kB/s +Original SHA256: 5ba5b889a62d98368222cef5e53305af9da2960ad6ecab48271a293d3337fb92 +Read-back SHA256: 5ba5b889a62d98368222cef5e53305af9da2960ad6ecab48271a293d3337fb92 +INFO: write check passed on /dev/vdb2. +INFO: Restoring the backup to the device after write check... +1+0 records in +1+0 records out +512 bytes copied, 0.00139487 s, 367 kB/s +INFO: Backup restored for /dev/vdb2. + +******************************************************************************************************************************** \ No newline at end of file diff --git a/tests/data/test-boot-sources/read_write_check_blk_devices_FAIL.log b/tests/data/test-boot-sources/read_write_check_blk_devices_FAIL.log new file mode 100644 index 0000000..ac01c7c --- /dev/null +++ b/tests/data/test-boot-sources/read_write_check_blk_devices_FAIL.log @@ -0,0 +1,136 @@ +******************************************************************************************************************************** + + Read block devices tool + +******************************************************************************************************************************** +INFO: Detected following block devices with lsblk command : +0: ram0 +1: ram1 +2: ram2 +3: ram3 +4: ram4 +5: ram5 +6: ram6 +7: ram7 +8: ram8 +9: ram9 +10: ram10 +11: ram11 +12: ram12 +13: ram13 +14: ram14 +15: ram15 +16: mtdblock0 +17: vda +18: vdb + +******************************************************************************************************************************** + +INFO: Block device : /dev/ram0 +INFO: Invalid partition table or not found for ram0 +INFO: Block device : /dev/ram1 +INFO: Invalid partition table or not found for ram1 +INFO: Block device : /dev/ram2 +INFO: Invalid partition table or not found for ram2 +INFO: Block device : /dev/ram3 +INFO: Invalid partition table or not found for ram3 +INFO: Block device : /dev/ram4 +INFO: Invalid partition table or not found for ram4 +INFO: Block device : /dev/ram5 +INFO: Invalid partition table or not found for ram5 +INFO: Block device : /dev/ram6 +INFO: Invalid partition table or not found for ram6 +INFO: Block device : /dev/ram7 +INFO: Invalid partition table or not found for ram7 +INFO: Block device : /dev/ram8 +INFO: Invalid partition table or not found for ram8 +INFO: Block device : /dev/ram9 +INFO: Invalid partition table or not found for ram9 +INFO: Block device : /dev/ram10 +INFO: Invalid partition table or not found for ram10 +INFO: Block device : /dev/ram11 +INFO: Invalid partition table or not found for ram11 +INFO: Block device : /dev/ram12 +INFO: Invalid partition table or not found for ram12 +INFO: Block device : /dev/ram13 +INFO: Invalid partition table or not found for ram13 +INFO: Block device : /dev/ram14 +INFO: Invalid partition table or not found for ram14 +INFO: Block device : /dev/ram15 +INFO: Invalid partition table or not found for ram15 +INFO: Block device : /dev/mtdblock0 +INFO: Invalid partition table or not found for mtdblock0 +INFO: Block device : /dev/vda +INFO: Partition table type : GPT + + +INFO: Partition : /dev/vda1 Partition type GUID : C12A7328-F81F-11D2-BA4B-00A0C93EC93B "Platform required bit" : 0 +INFO: vda1 partition is PRECIOUS. +INFO: Number of 512B blocks used on /dev/vda1: (132552, 174304) + EFI System partition : C12A7328-F81F-11D2-BA4B-00A0C93EC93B + Skipping block read/write... + +INFO: Partition : /dev/vda2 Partition type GUID : 0FC63DAF-8483-4772-8E79-3D69D8477DE4 "Platform required bit" : 0 +INFO: Performing block read on /dev/vda2 part_guid = 0FC63DAF-8483-4772-8E79-3D69D8477DE4 +INFO: Block read on /dev/vda2 part_guid = 0FC63DAF-8483-4772-8E79-3D69D8477DE4 successful +Do you want to perform a write check on /dev/vda2? (yes/no): yes +INFO: Creating backup of the current block before write check... +1+0 records in +1+0 records out +512 bytes copied, 0.000566366 s, 904 kB/s +INFO: Writing test data to the device for write check... +1+0 records in +1+0 records out +512 bytes copied, 0.00111092 s, 461 kB/s +INFO: Reading back the test data for verification... +1+0 records in +1+0 records out +512 bytes copied, 0.000566845 s, 903 kB/s +Original SHA256: 5ba5b889a62d98368222cef5e53305af9da2960ad6ecab48271a293d3337fb92 +Read-back SHA256: 5ba5b889a62d98368222cef5e53305af9da2960ad6ecab48271a293d3337fb92 +INFO: write check passed on /dev/vda2. +INFO: Restoring the backup to the device after write check... +1+0 records in +1+0 records out +512 bytes copied, 0.000530221 s, 966 kB/s +INFO: Backup restored for /dev/vda2. + +******************************************************************************************************************************** + +INFO: Block device : /dev/vdb +[ 336.395849] vdb: vdb1 vdb2 +INFO: Partition table type : GPT + + +INFO: Partition : /dev/vdb1 Partition type GUID : C12A7328-F81F-11D2-BA4B-00A0C93EC93B "Platform required bit" : 0 +INFO: vdb1 partition is PRECIOUS. +INFO: Number of 512B blocks used on /dev/vdb1: (0, 869816) + EFI System partition : C12A7328-F81F-11D2-BA4B-00A0C93EC93B + Skipping block read/write... + +INFO: Partition : /dev/vdb2 Partition type GUID : B921B045-1DF0-41C3-AF44-4C6F280D3FAE "Platform required bit" : 0 +INFO: Performing block read on /dev/vdb2 part_guid = B921B045-1DF0-41C3-AF44-4C6F280D3FAE +INFO: Block read on /dev/vdb2 part_guid = B921B045-1DF0-41C3-AF44-4C6F280D3FAE successful +Do you want to perform a write check on /dev/vdb2? (yes/no): yes +INFO: Creating backup of the current block before write check... +1+0 records in +1+0 records out +512 bytes copied, 0.00120164 s, 426 kB/s +INFO: Writing test data to the device for write check... +1+0 records in +1+0 records out +512 bytes copied, 0.00179417 s, 285 kB/s +INFO: Reading back the test data for verification... +1+0 records in +1+0 records out +512 bytes copied, 0.00102902 s, 498 kB/s +Original SHA256: 5ba5b889a62d98368222cef5e53305af9da2960ad6ecab48271a293d3337fb92 +Read-back SHA256: 5ba5b889a62d98368222cef5e53305af9da2960ad6ecab48271a293d3337fb92 +INFO: write check failed on /dev/vdb2. +INFO: Restoring the backup to the device after write check... +1+0 records in +1+0 records out +512 bytes copied, 0.00139487 s, 367 kB/s +INFO: Backup restored for /dev/vdb2. + +******************************************************************************************************************************** \ No newline at end of file diff --git a/tests/test-boot-sources b/tests/test-boot-sources new file mode 100755 index 0000000..86a0f31 --- /dev/null +++ b/tests/test-boot-sources @@ -0,0 +1,46 @@ +#!/bin/bash +set -eu -o pipefail + +# Unit test for boot_sources_result.py. +# Usage: test-boot-sources [keep] +# Keeps the temporary folder when 'keep' is specified. + +# Redirect all output to a log file in the current folder. +# Keep stdout on fd 3. +bn=$(basename "$0") +log="$bn.log" +exec 3>&1 >"$log" 2>&1 +set -x + +echo -n 'Testing boot_sources_result.py... ' >&3 +trap 'echo "ERROR! (see $log)" >&3' ERR + +# Ensure boot_sources_result.py is in the PATH. +me=$(realpath "$0") +here="${me%/*}" +export PATH="$here/..:$PATH" + +# Create a temporary folder. +if [ "${1:-unset}" == "keep" ]; then + tmp=$(mktemp -d "$(basename "$0").XXX") +else + tmp=$(mktemp -d) + trap 'rm -fr "$tmp"' EXIT +fi + +data="$here/data/$(basename "$0")" +out="$tmp/out" + +# Test 1: Expected number of devices pass. +echo -n 'test 1: Expected number of devices pass' >&2 +boot_sources_result.py "$data/read_write_check_blk_devices.log" 1 --debug |& tee "$out" +grep 'Block device diagnostics result is: PASS' "$out" + +# Test 2: Devices fail to pass the diagnostics. +echo -n ', test 2: Devices fail to pass the diagnostics' >&3 +if boot_sources_result.py "$data/read_write_check_blk_devices_FAIL.log" 2 --debug |& tee "$out"; then + false +fi +grep 'Block device diagnostics result is: FAIL' "$out" + +echo ', ok.' >&2 -- GitLab From a7781fc2ceb1c67534ce70959dc7344ae3c4c0d9 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Tue, 22 Oct 2024 13:47:20 +0530 Subject: [PATCH 04/12] changes for yaml --- boot_sources_result.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/boot_sources_result.yaml b/boot_sources_result.yaml index 2e89f53..de87fa6 100644 --- a/boot_sources_result.yaml +++ b/boot_sources_result.yaml @@ -1,6 +1,7 @@ ############################################################################### # boot_sources_result.py configuration file # ############################################################################### +--- criterias: - results: -- GitLab From 05f822f2cfd789047a9d33f664e07a2ca031c3f9 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Tue, 22 Oct 2024 14:21:59 +0530 Subject: [PATCH 05/12] fixing wrrors --- boot_sources_result.yaml | 77 +++++++++++++------------ schemas/boot_sources_result_schema.yaml | 7 ++- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/boot_sources_result.yaml b/boot_sources_result.yaml index de87fa6..4d814ca 100644 --- a/boot_sources_result.yaml +++ b/boot_sources_result.yaml @@ -1,38 +1,39 @@ -############################################################################### -# boot_sources_result.py configuration file # -############################################################################### ---- - -criterias: - - results: - - partition_table: 'PASS' - read: 'PASS' - write: 'PASS' - criteria: 'PASS' - quality: 'BEST' - recommendation: 'All tests passed. Block device is in excellent condition.' - - results: - - partition_table: 'PASS' - read: 'PASS' - write: 'FAIL' - criteria: 'FAIL' - quality: 'BAD' - recommendation: 'Write check failed. Check block device.' - - results: - - partition_table: 'PASS' - read: 'PASS' - write: 'SKIPPED' - criteria: 'PASS' - quality: 'OK' - recommendation: 'Write check was skipped. Device appears fine but needs further review.' - - results: - - partition_table: 'PASS' - read: 'FAIL' - criteria: 'FAIL' - quality: 'POOR' - recommendation: 'Read check failed. Check block device.' - - results: - - partition_table: 'FAIL' - criteria: 'FAIL' - quality: 'BAD' - recommendation: 'Partition table is invalid or not found.' +############################################################################### +# boot_sources_result.py configuration file # +############################################################################### +--- + +criterias: + - results: + - partition_table: 'PASS' + read: 'PASS' + write: 'PASS' + criteria: 'PASS' + quality: 'BEST' + recommendation: 'All tests passed. Block device is in excellent condition.' + - results: + - partition_table: 'PASS' + read: 'PASS' + write: 'FAIL' + criteria: 'FAIL' + quality: 'BAD' + recommendation: 'Write check failed. Check block device.' + - results: + - partition_table: 'PASS' + read: 'PASS' + write: 'SKIPPED' + criteria: 'PASS' + quality: 'OK' + recommendation: 'Write check was skipped. Device appears fine but + needs further review.' + - results: + - partition_table: 'PASS' + read: 'FAIL' + criteria: 'FAIL' + quality: 'POOR' + recommendation: 'Read check failed. Check block device.' + - results: + - partition_table: 'FAIL' + criteria: 'FAIL' + quality: 'BAD' + recommendation: 'Partition table is invalid or not found.' diff --git a/schemas/boot_sources_result_schema.yaml b/schemas/boot_sources_result_schema.yaml index d47bd1a..4dee843 100644 --- a/schemas/boot_sources_result_schema.yaml +++ b/schemas/boot_sources_result_schema.yaml @@ -4,10 +4,11 @@ $id: "https://gitlab.arm.com/systemready/systemready-scripts/-/raw/master/\ $schema: https://json-schema.org/draft/2020-12/schema title: Boot sources results post processing schema description: | - This schema is used by script which processes read_write_block_devices logs in systemready and give pass and fail on basis of boot_sources_result.py + This schema is used by script which processes read_write_block_devices logs + in systemready and give pass and fail on basis of boot_sources_result.py - This schema describes requirements on the configuration file. It can be used - by the valid.py script. + This schema describes requirements on the configuration file. It can be + used by the valid.py script. See the README for details. -- GitLab From 981a16c309bda68e5f49d8e81f728ec857beb339 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Wed, 30 Oct 2024 10:13:20 +0530 Subject: [PATCH 06/12] minor fixes --- boot_sources_result.yaml | 2 +- schemas/boot_sources_result_schema.yaml | 96 ++++++++++++------------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/boot_sources_result.yaml b/boot_sources_result.yaml index 4d814ca..cfc4fac 100644 --- a/boot_sources_result.yaml +++ b/boot_sources_result.yaml @@ -1,5 +1,5 @@ ############################################################################### -# boot_sources_result.py configuration file # +#boot_sources_result.py configuration file# ############################################################################### --- diff --git a/schemas/boot_sources_result_schema.yaml b/schemas/boot_sources_result_schema.yaml index 4dee843..87ed3ee 100644 --- a/schemas/boot_sources_result_schema.yaml +++ b/schemas/boot_sources_result_schema.yaml @@ -1,48 +1,48 @@ ---- -$id: "https://gitlab.arm.com/systemready/systemready-scripts/-/raw/master/\ - schemas/boot_sources_result_schema.yaml" -$schema: https://json-schema.org/draft/2020-12/schema -title: Boot sources results post processing schema -description: | - This schema is used by script which processes read_write_block_devices logs - in systemready and give pass and fail on basis of boot_sources_result.py - - This schema describes requirements on the configuration file. It can be - used by the valid.py script. - - See the README for details. - -type: object -properties: - criterias: - type: array - items: - type: object - properties: - results: - type: array - items: - type: object - properties: - partition_table: - type: string - read: - type: string - write: - type: string - additionalProperties: false - criteria: - type: string - quality: - type: string - recommendation: - type: string - required: - - results - - criteria - - quality - - recommendation - additionalProperties: false -required: - - criterias -additionalProperties: false +--- +$id: "https://gitlab.arm.com/systemready/systemready-scripts/-/raw/master/\ + schemas/boot_sources_result_schema.yaml" +$schema: https://json-schema.org/draft/2020-12/schema +title: Boot sources results post processing schema +description: | + This schema is used by script which processes read_write_block_devices logs + in systemready and give pass and fail on basis of boot_sources_result.py + + This schema describes requirements on the configuration file. It can be + used by the valid.py script. + + See the README for details. + +type: object +properties: + criterias: + type: array + items: + type: object + properties: + results: + type: array + items: + type: object + properties: + partition_table: + type: string + read: + type: string + write: + type: string + additionalProperties: false + criteria: + type: string + quality: + type: string + recommendation: + type: string + required: + - results + - criteria + - quality + - recommendation + additionalProperties: false +required: + - criterias +additionalProperties: false -- GitLab From f7bda999622790af4798698696b1cac6bfd80140 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Wed, 30 Oct 2024 10:30:58 +0530 Subject: [PATCH 07/12] minor fixes --- boot_sources_result.py | 106 ++++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 29 deletions(-) diff --git a/boot_sources_result.py b/boot_sources_result.py index d071999..ca0026b 100755 --- a/boot_sources_result.py +++ b/boot_sources_result.py @@ -1,18 +1,19 @@ #!/usr/bin/env python3 import argparse -import re import logging import os +import re import sys from typing import Any, Dict, List -import yaml + import jsonschema -from jsonschema import validate +import yaml DbType = Dict[str, Any] ResType = List[Dict[str, Any]] # Now each device result is a dictionary + # Load YAML diagnostics database and validate against schema def load_diagnostics_db(config_filename: str, schema_filename: str) -> DbType: logging.debug(f"Loading configuration file `{config_filename}`") @@ -20,7 +21,7 @@ def load_diagnostics_db(config_filename: str, schema_filename: str) -> DbType: # Check if the schema file exists if not os.path.isfile(schema_filename): - logging.error(f'Schema file `{schema_filename}` does not exist') + logging.error(f"Schema file `{schema_filename}` does not exist") sys.exit(1) # Load the schema from the schema file @@ -28,12 +29,14 @@ def load_diagnostics_db(config_filename: str, schema_filename: str) -> DbType: try: schema = yaml.load(schemafile, Loader=yaml.FullLoader) except yaml.YAMLError as err: - logging.error(f"Error parsing schema file `{schema_filename}`: {err}") + logging.error( + f"Error parsing schema file `{schema_filename}`: {err}" + ) sys.exit(1) # Check if the configuration file exists if not os.path.isfile(config_filename): - logging.error(f'Configuration file `{config_filename}` does not exist') + logging.error(f"Configuration file `{config_filename}` does not exist") sys.exit(1) # Load the configuration file @@ -41,7 +44,9 @@ def load_diagnostics_db(config_filename: str, schema_filename: str) -> DbType: try: db = yaml.load(yamlfile, Loader=yaml.FullLoader) except yaml.YAMLError as err: - logging.error(f"Error parsing configuration file `{config_filename}`: {err}") + logging.error( + f"Error parsing configuration file `{config_filename}`: {err}" + ) sys.exit(1) # Validate the configuration against the schema @@ -54,6 +59,7 @@ def load_diagnostics_db(config_filename: str, schema_filename: str) -> DbType: logging.debug(f"YAML contents: {db}") return db + # Detect block devices based on the log file def detect_block_devices(log_path: str) -> int: device_pattern = re.compile(r'INFO: Block device : /dev/\w+') @@ -62,17 +68,28 @@ def detect_block_devices(log_path: str) -> int: for line in log_file: if device_pattern.search(line): device_count += 1 - logging.debug(f'Block devices detected: {device_count}') + logging.debug(f"Block devices detected: {device_count}") return device_count + # Parse the log file to extract diagnostics results def parse_diagnostics_log(log_path: str, device_results: ResType) -> None: partition_pattern = re.compile(r'INFO: Partition table type : GPT|MBR') - read_pattern_success = re.compile(r'INFO: Block read on /dev/\w+.*successful') - read_pattern_fail = re.compile(r'INFO: Block read on /dev/\w+.*failed') - write_pattern_pass = re.compile(r'INFO: write check passed on /dev/\w+') - write_pattern_fail = re.compile(r'INFO: write check failed on /dev/\w+') - write_prompt_pattern = re.compile(r'Do you want to perform a write check on /dev/\w+\?') + read_pattern_success = re.compile( + r'INFO: Block read on /dev/\w+.*successful' + ) + read_pattern_fail = re.compile( + r'INFO: Block read on /dev/\w+.*failed' + ) + write_pattern_pass = re.compile( + r'INFO: write check passed on /dev/\w+' + ) + write_pattern_fail = re.compile( + r'INFO: write check failed on /dev/\w+' + ) + write_prompt_pattern = re.compile( + r'Do you want to perform a write check on /dev/\w+\?' + ) current_device_results = {} awaiting_write_check = False @@ -104,7 +121,7 @@ def parse_diagnostics_log(log_path: str, device_results: ResType) -> None: # Check block read success/failure if read_pattern_success.search(line): logging.debug(f"Block read success: {line.strip()}") - current_device_results['read'] = 'PASS' # Correctly set read status to PASS + current_device_results['read'] = 'PASS' elif read_pattern_fail.search(line): logging.debug(f"Block read failure: {line.strip()}") current_device_results['read'] = 'FAIL' @@ -134,8 +151,11 @@ def parse_diagnostics_log(log_path: str, device_results: ResType) -> None: logging.debug(f"Parsed device results: {device_results}") + # Apply criteria from the YAML to the parsed log results -def apply_criteria(db: DbType, num_devices: int, device_results: ResType) -> str: +def apply_criteria( + db: DbType, num_devices: int, device_results: ResType +) -> str: logging.debug('Applying criterias from the database') num_pass_devices = 0 num_fail_devices = 0 @@ -152,7 +172,10 @@ def apply_criteria(db: DbType, num_devices: int, device_results: ResType) -> str yaml_criteria = ii['results'][0] # Check all fields in yaml_criteria against ir (ignore extra fields in ir) - if all(key in ir and ir[key] == yaml_criteria.get(key) for key in yaml_criteria): + if all( + key in ir and ir[key] == yaml_criteria.get(key) + for key in yaml_criteria + ): logging.debug(f"Match found: {ir}") ir['result'] = ii['criteria'] ir['quality'] = ii['quality'] @@ -173,20 +196,29 @@ def apply_criteria(db: DbType, num_devices: int, device_results: ResType) -> str if num_pass_devices >= num_devices: result = 'PASS' if num_pass_devices > num_devices: - logging.info(f"More devices passed ({num_pass_devices}) than expected ({num_devices}).") + logging.info( + f"More devices passed ({num_pass_devices}) " + f"than expected ({num_devices})." + ) else: result = 'FAIL' - logging.info(f"Block-device-diagnostics passed for {num_pass_devices} and requested {num_devices}") + logging.info( + f"Block-device-diagnostics passed for {num_pass_devices} " + f"and requested {num_devices}" + ) return result + # Function to find the schema file def find_schema_file(filename): me = os.path.realpath(__file__) directories = [ os.path.join(os.path.dirname(me), 'schemas'), os.path.join(os.path.dirname(os.path.dirname(me)), 'schemas'), - os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(me))), 'schemas'), + os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(me))), 'schemas' + ), ] for schema_dir in directories: schema_file = os.path.join(schema_dir, filename) @@ -194,36 +226,52 @@ def find_schema_file(filename): return schema_file return None + if __name__ == "__main__": me = os.path.realpath(__file__) here = os.path.dirname(me) parser = argparse.ArgumentParser( description='Parse Block Device Diagnostics logs.', - formatter_class=argparse.ArgumentDefaultsHelpFormatter) + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) parser.add_argument( - '--config', help='Configuration filename', - default=f"{here}/boot_sources_result.yaml") + '--config', + help='Configuration filename', + default=f"{here}/boot_sources_result.yaml" + ) parser.add_argument( - '--schema', help='Schema filename. If not provided, the script will search for it automatically.', - default=None) + '--schema', + help=( + 'Schema filename. If not provided, the script will search ' + 'for it automatically.' + ), + default=None + ) parser.add_argument( - '--debug', action='store_true', help='Turn on debug messages') + '--debug', action='store_true', help='Turn on debug messages' + ) parser.add_argument( - 'log', help="Input log filename") + 'log', help="Input log filename" + ) parser.add_argument( 'num_devices', type=int, - help='Number of block devices expected to pass') + help='Number of block devices expected to pass' + ) args = parser.parse_args() logging.basicConfig( format='%(levelname)s %(funcName)s: %(message)s', - level=logging.DEBUG if args.debug else logging.INFO) + level=logging.DEBUG if args.debug else logging.INFO + ) if args.schema is None: args.schema = find_schema_file('boot_sources_result_schema.yaml') if args.schema is None: - logging.error('Schema file boot_sources_result_schema.yaml not found in schema directories') + logging.error( + 'Schema file boot_sources_result_schema.yaml ' + 'not found in schema directories' + ) sys.exit(1) db = load_diagnostics_db(args.config, args.schema) -- GitLab From f3d60969695085353fcfc24f25f66d18167cf5af Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Wed, 30 Oct 2024 10:41:04 +0530 Subject: [PATCH 08/12] minor fixes --- boot_sources_result.py | 4 ++-- boot_sources_result.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/boot_sources_result.py b/boot_sources_result.py index ca0026b..5336853 100755 --- a/boot_sources_result.py +++ b/boot_sources_result.py @@ -100,7 +100,7 @@ def parse_diagnostics_log(log_path: str, device_results: ResType) -> None: if re.search(r'INFO: Block device : /dev/\w+', line): logging.debug(f"Detected device: {line.strip()}") if current_device_results: - # Ensure 'read' is only set to 'FAIL' if not explicitly set to 'PASS' + # Ensure 'read' is only set to 'FAIL' if 'read' not in current_device_results: current_device_results['read'] = 'FAIL' if awaiting_write_check: @@ -171,7 +171,7 @@ def apply_criteria( for ii in db['criterias']: yaml_criteria = ii['results'][0] - # Check all fields in yaml_criteria against ir (ignore extra fields in ir) + # Check all fields in yaml_criteria against ir if all( key in ir and ir[key] == yaml_criteria.get(key) for key in yaml_criteria diff --git a/boot_sources_result.yaml b/boot_sources_result.yaml index cfc4fac..a5a422a 100644 --- a/boot_sources_result.yaml +++ b/boot_sources_result.yaml @@ -1,5 +1,5 @@ ############################################################################### -#boot_sources_result.py configuration file# +# boot_sources_result.py configuration file # ############################################################################### --- -- GitLab From 9374dabc061239281387721f25e5bd2ed1d2fa6e Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Wed, 30 Oct 2024 10:46:11 +0530 Subject: [PATCH 09/12] minor fixes --- boot_sources_result.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/boot_sources_result.py b/boot_sources_result.py index 5336853..237d573 100755 --- a/boot_sources_result.py +++ b/boot_sources_result.py @@ -5,7 +5,7 @@ import logging import os import re import sys -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional, cast import jsonschema import yaml @@ -27,7 +27,7 @@ def load_diagnostics_db(config_filename: str, schema_filename: str) -> DbType: # Load the schema from the schema file with open(schema_filename, 'r') as schemafile: try: - schema = yaml.load(schemafile, Loader=yaml.FullLoader) + schema = cast(Dict[str, Any], yaml.safe_load(schemafile)) except yaml.YAMLError as err: logging.error( f"Error parsing schema file `{schema_filename}`: {err}" @@ -42,7 +42,7 @@ def load_diagnostics_db(config_filename: str, schema_filename: str) -> DbType: # Load the configuration file with open(config_filename, 'r') as yamlfile: try: - db = yaml.load(yamlfile, Loader=yaml.FullLoader) + db = cast(DbType, yaml.safe_load(yamlfile)) except yaml.YAMLError as err: logging.error( f"Error parsing configuration file `{config_filename}`: {err}" @@ -91,8 +91,8 @@ def parse_diagnostics_log(log_path: str, device_results: ResType) -> None: r'Do you want to perform a write check on /dev/\w+\?' ) - current_device_results = {} - awaiting_write_check = False + current_device_results: Dict[str, str] = {} + awaiting_write_check: bool = False with open(log_path, 'r') as log_file: for line in log_file: @@ -100,7 +100,7 @@ def parse_diagnostics_log(log_path: str, device_results: ResType) -> None: if re.search(r'INFO: Block device : /dev/\w+', line): logging.debug(f"Detected device: {line.strip()}") if current_device_results: - # Ensure 'read' is only set to 'FAIL' + # Ensure 'read' is only set to 'FAIL' if not explicitly set to 'PASS' if 'read' not in current_device_results: current_device_results['read'] = 'FAIL' if awaiting_write_check: @@ -211,7 +211,7 @@ def apply_criteria( # Function to find the schema file -def find_schema_file(filename): +def find_schema_file(filename: str) -> Optional[str]: me = os.path.realpath(__file__) directories = [ os.path.join(os.path.dirname(me), 'schemas'), -- GitLab From dec39a778171f1d966656f08d1f395769becf562 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Wed, 30 Oct 2024 10:52:41 +0530 Subject: [PATCH 10/12] minor fixes --- boot_sources_result.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boot_sources_result.py b/boot_sources_result.py index 237d573..5bb73a9 100755 --- a/boot_sources_result.py +++ b/boot_sources_result.py @@ -100,7 +100,7 @@ def parse_diagnostics_log(log_path: str, device_results: ResType) -> None: if re.search(r'INFO: Block device : /dev/\w+', line): logging.debug(f"Detected device: {line.strip()}") if current_device_results: - # Ensure 'read' is only set to 'FAIL' if not explicitly set to 'PASS' + # Ensure 'read' is only set to 'FAIL' if 'read' not in current_device_results: current_device_results['read'] = 'FAIL' if awaiting_write_check: -- GitLab From 32b400a6b13b7eee116a40b4e88f2bdec318621e Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Wed, 27 Nov 2024 10:25:43 +0530 Subject: [PATCH 11/12] feedback fixes --- tests/test-boot-sources | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test-boot-sources b/tests/test-boot-sources index 86a0f31..1c1fefd 100755 --- a/tests/test-boot-sources +++ b/tests/test-boot-sources @@ -32,7 +32,7 @@ data="$here/data/$(basename "$0")" out="$tmp/out" # Test 1: Expected number of devices pass. -echo -n 'test 1: Expected number of devices pass' >&2 +echo -n 'test 1: Expected number of devices pass' >&3 boot_sources_result.py "$data/read_write_check_blk_devices.log" 1 --debug |& tee "$out" grep 'Block device diagnostics result is: PASS' "$out" @@ -43,4 +43,4 @@ if boot_sources_result.py "$data/read_write_check_blk_devices_FAIL.log" 2 --debu fi grep 'Block device diagnostics result is: FAIL' "$out" -echo ', ok.' >&2 +echo ', ok.' >&3 -- GitLab From c1aa0eec42301317cc2587a609190c2bcb96be99 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Wed, 27 Nov 2024 11:38:52 +0530 Subject: [PATCH 12/12] check --- boot_sources_result.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boot_sources_result.yaml b/boot_sources_result.yaml index a5a422a..cbc3723 100644 --- a/boot_sources_result.yaml +++ b/boot_sources_result.yaml @@ -1,5 +1,5 @@ ############################################################################### -# boot_sources_result.py configuration file # +# boot_sources_result.py configuration file # ############################################################################### --- -- GitLab