From 9b49a0835b53705f27b4e8aa22e14552db48878a Mon Sep 17 00:00:00 2001 From: "martin.fencl" Date: Wed, 29 Jul 2026 15:10:09 +0200 Subject: [PATCH] . --- check_raid.yml | 149 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 40 deletions(-) diff --git a/check_raid.yml b/check_raid.yml index f374f29..72ee98f 100644 --- a/check_raid.yml +++ b/check_raid.yml @@ -1,22 +1,51 @@ -# check_raid.yml - -- name: Check Linux MD RAID health +--- +- name: Check Linux MD RAID health on pve1 hosts: pve1_vm gather_facts: false vars: + # Semaphore Survey Variables are preferred. + # Environment variables are used only as a fallback. + raid_md_raw: >- + {{ + RAID_MD + | default(lookup('env', 'RAID_MD'), true) + | default('auto', true) + | string + | trim + | lower + }} + # RAID_MD can be md0, md1, /dev/md0, or auto - raid_md_raw: "{{ lookup('env', 'RAID_MD') | default('auto', true) | trim }}" raid_md_device: "{{ raid_md_raw | regex_replace('^/dev/', '') }}" - # Allow check, repair, resync, recovery, or reshape on an otherwise healthy array - raid_allow_sync: "{{ lookup('env', 'RAID_ALLOW_SYNC') | default('1', true) | int }}" + # Allow a healthy array to be checking, repairing, recovering, + # resyncing, or reshaping + raid_allow_sync: >- + {{ + RAID_ALLOW_SYNC + | default(lookup('env', 'RAID_ALLOW_SYNC'), true) + | default('1', true) + | int + }} - # Set to 1 only when a server without MD RAID should be considered healthy - raid_allow_no_array: "{{ lookup('env', 'RAID_ALLOW_NO_ARRAY') | default('0', true) | int }}" + # A server without Linux MD RAID is considered unhealthy by default + raid_allow_no_array: >- + {{ + RAID_ALLOW_NO_ARRAY + | default(lookup('env', 'RAID_ALLOW_NO_ARRAY'), true) + | default('0', true) + | int + }} - # Print raw command data and /proc/mdstat when enabled - raid_debug: "{{ lookup('env', 'DEBUG') | default('0', true) | int }}" + # Enable raw diagnostic output + raid_debug: >- + {{ + DEBUG + | default(lookup('env', 'DEBUG'), true) + | default('0', true) + | int + }} raid_check_script: | import re @@ -37,7 +66,8 @@ except Exception as exc: print( "ERROR RAID " - f"read_mdstat_failed error={type(exc).__name__}:{exc}" + f"read_mdstat_failed=" + f"{type(exc).__name__}:{exc}" ) sys.exit(2) @@ -45,6 +75,7 @@ r"^(md\d+)\s*:\s*(.*)$", re.MULTILINE, ) + header_matches = list(header_pattern.finditer(mdstat)) array_blocks = {} @@ -60,24 +91,29 @@ ] def md_sort_key(name): - return int(name[2:]) + try: + return int(name[2:]) + except ValueError: + return name def parse_array(name, block): - header = block.splitlines()[0] + lines = block.splitlines() + header = lines[0] header_data = header.split(":", 1)[1].strip() state_match = re.match( r"^(active|inactive)\b", header_data, + re.IGNORECASE, ) state = ( - state_match.group(1) + state_match.group(1).lower() if state_match else "unknown" ) - remaining = ( + remaining_header = ( header_data[state_match.end():].strip() if state_match else header_data @@ -85,23 +121,28 @@ mode = "rw" - if remaining.startswith("("): - closing_parenthesis = remaining.find(")") + # Handle states such as "active (auto-read-only)" + if remaining_header.startswith("("): + closing_parenthesis = remaining_header.find(")") if closing_parenthesis >= 0: - mode = remaining[1:closing_parenthesis] - remaining = remaining[ + mode = remaining_header[ + 1:closing_parenthesis + ] + + remaining_header = remaining_header[ closing_parenthesis + 1: ].strip() level = ( - remaining.split()[0] - if remaining + remaining_header.split()[0].lower() + if remaining_header else "unknown" ) status_match = re.search( - r"\[(\d+)\s*/\s*(\d+)\]\s*\[([U_]+)\]", + r"\[(\d+)\s*/\s*(\d+)\]" + r"\s*\[([U_]+)\]", block, ) @@ -115,7 +156,13 @@ member_status = status_match.group(3) operation_match = re.search( - r"\b(resync|recovery|reshape|check|repair)\b", + r"\b(" + r"resync|" + r"recovery|" + r"reshape|" + r"check|" + r"repair" + r")\b", block, re.IGNORECASE, ) @@ -127,8 +174,13 @@ ) progress_match = re.search( - r"(?:resync|recovery|reshape|check|repair)" - r"\s*=\s*([0-9.]+%)", + r"(?:" + r"resync|" + r"recovery|" + r"reshape|" + r"check|" + r"repair" + r")\s*=\s*([0-9.]+%)", block, re.IGNORECASE, ) @@ -150,20 +202,25 @@ "operation": operation, "progress": progress, "faulty_member": bool( - re.search(r"\(F\)", block) + re.search( + r"\(F\)", + block, + re.IGNORECASE, + ) ), } def format_array(array): if array["member_status"] is None: members = "n/a" - status = "n/a" + member_status = "n/a" else: members = ( f'{array["active_members"]}/' f'{array["total_members"]}' ) - status = array["member_status"] + + member_status = array["member_status"] return ( f'{array["name"]}' @@ -172,7 +229,7 @@ f'mode={array["mode"]},' f'level={array["level"]},' f'members={members},' - f'status={status},' + f'status={member_status},' f'operation={array["operation"]},' f'progress={array["progress"]}' "}" @@ -187,7 +244,7 @@ sys.exit(2) available_arrays = sorted( - array_blocks, + array_blocks.keys(), key=md_sort_key, ) @@ -198,20 +255,23 @@ else: print( "ERROR RAID " - f"target_not_found target={target} " + f"target_not_found={target} " f'found={",".join(available_arrays)}' ) sys.exit(2) selected_arrays = [ - parse_array(name, array_blocks[name]) + parse_array( + name, + array_blocks[name], + ) for name in selected_names ] failures = [] errors = [] - # RAID0 and linear arrays do not normally contain [UU] status tokens + # These RAID levels may not always provide a redundant-member token tokenless_levels = { "raid0", "linear", @@ -257,7 +317,8 @@ and not allow_sync ): failures.append( - f'{name}:operation={array["operation"]}' + f'{name}:operation=' + f'{array["operation"]}' ) details = " ".join( @@ -290,12 +351,15 @@ - (raid_allow_no_array | int) in [0, 1] - (raid_debug | int) in [0, 1] - >- - (raid_md_device | - regex_search('^(auto|md[0-9]+)$')) - is not none + ( + raid_md_device + | regex_search('^(auto|md[0-9]+)$') + ) is not none fail_msg: >- ERROR RAID invalid-configuration. RAID_MD must be auto, md0, md1, or /dev/md0. + DEBUG, RAID_ALLOW_SYNC, and RAID_ALLOW_NO_ARRAY + must be 0 or 1. quiet: true - name: Run Linux MD RAID health check @@ -329,7 +393,8 @@ {%- if stdout | length > 0 -%} {{ stdout }} {%- elif stderr | length > 0 -%} - ERROR RAID command_failed rc={{ raid_cmd.rc }} stderr={{ stderr[:500] }} + ERROR RAID command_failed rc={{ raid_cmd.rc }} + stderr={{ stderr[:500] }} {%- else -%} ERROR RAID no-output rc={{ raid_cmd.rc }} {%- endif -%} @@ -350,11 +415,15 @@ - /proc/mdstat register: mdstat_debug changed_when: false + failed_when: false when: (raid_debug | int) == 1 - name: Show raw mdstat in debug mode ansible.builtin.debug: - msg: "{{ mdstat_debug.stdout | default('') }}" + msg: + rc: "{{ mdstat_debug.rc | default('undefined') }}" + stdout: "{{ mdstat_debug.stdout | default('') }}" + stderr: "{{ mdstat_debug.stderr | default('') }}" when: (raid_debug | int) == 1 - name: Show successful RAID result @@ -365,4 +434,4 @@ - name: Fail when RAID is unhealthy ansible.builtin.fail: msg: "{{ raid_line }}" - when: (raid_cmd.rc | int) != 0 + when: (raid_cmd.rc | int) != 0 \ No newline at end of file