forked from jakub/ansible
.
This commit is contained in:
+108
-39
@@ -1,22 +1,51 @@
|
|||||||
# check_raid.yml
|
---
|
||||||
|
- name: Check Linux MD RAID health on pve1
|
||||||
- name: Check Linux MD RAID health
|
|
||||||
hosts: pve1_vm
|
hosts: pve1_vm
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
|
|
||||||
vars:
|
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 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/', '') }}"
|
raid_md_device: "{{ raid_md_raw | regex_replace('^/dev/', '') }}"
|
||||||
|
|
||||||
# Allow check, repair, resync, recovery, or reshape on an otherwise healthy array
|
# Allow a healthy array to be checking, repairing, recovering,
|
||||||
raid_allow_sync: "{{ lookup('env', 'RAID_ALLOW_SYNC') | default('1', true) | int }}"
|
# 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
|
# A server without Linux MD RAID is considered unhealthy by default
|
||||||
raid_allow_no_array: "{{ lookup('env', 'RAID_ALLOW_NO_ARRAY') | default('0', true) | int }}"
|
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
|
# Enable raw diagnostic output
|
||||||
raid_debug: "{{ lookup('env', 'DEBUG') | default('0', true) | int }}"
|
raid_debug: >-
|
||||||
|
{{
|
||||||
|
DEBUG
|
||||||
|
| default(lookup('env', 'DEBUG'), true)
|
||||||
|
| default('0', true)
|
||||||
|
| int
|
||||||
|
}}
|
||||||
|
|
||||||
raid_check_script: |
|
raid_check_script: |
|
||||||
import re
|
import re
|
||||||
@@ -37,7 +66,8 @@
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(
|
print(
|
||||||
"ERROR RAID "
|
"ERROR RAID "
|
||||||
f"read_mdstat_failed error={type(exc).__name__}:{exc}"
|
f"read_mdstat_failed="
|
||||||
|
f"{type(exc).__name__}:{exc}"
|
||||||
)
|
)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
@@ -45,6 +75,7 @@
|
|||||||
r"^(md\d+)\s*:\s*(.*)$",
|
r"^(md\d+)\s*:\s*(.*)$",
|
||||||
re.MULTILINE,
|
re.MULTILINE,
|
||||||
)
|
)
|
||||||
|
|
||||||
header_matches = list(header_pattern.finditer(mdstat))
|
header_matches = list(header_pattern.finditer(mdstat))
|
||||||
array_blocks = {}
|
array_blocks = {}
|
||||||
|
|
||||||
@@ -60,24 +91,29 @@
|
|||||||
]
|
]
|
||||||
|
|
||||||
def md_sort_key(name):
|
def md_sort_key(name):
|
||||||
return int(name[2:])
|
try:
|
||||||
|
return int(name[2:])
|
||||||
|
except ValueError:
|
||||||
|
return name
|
||||||
|
|
||||||
def parse_array(name, block):
|
def parse_array(name, block):
|
||||||
header = block.splitlines()[0]
|
lines = block.splitlines()
|
||||||
|
header = lines[0]
|
||||||
header_data = header.split(":", 1)[1].strip()
|
header_data = header.split(":", 1)[1].strip()
|
||||||
|
|
||||||
state_match = re.match(
|
state_match = re.match(
|
||||||
r"^(active|inactive)\b",
|
r"^(active|inactive)\b",
|
||||||
header_data,
|
header_data,
|
||||||
|
re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
|
||||||
state = (
|
state = (
|
||||||
state_match.group(1)
|
state_match.group(1).lower()
|
||||||
if state_match
|
if state_match
|
||||||
else "unknown"
|
else "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
remaining = (
|
remaining_header = (
|
||||||
header_data[state_match.end():].strip()
|
header_data[state_match.end():].strip()
|
||||||
if state_match
|
if state_match
|
||||||
else header_data
|
else header_data
|
||||||
@@ -85,23 +121,28 @@
|
|||||||
|
|
||||||
mode = "rw"
|
mode = "rw"
|
||||||
|
|
||||||
if remaining.startswith("("):
|
# Handle states such as "active (auto-read-only)"
|
||||||
closing_parenthesis = remaining.find(")")
|
if remaining_header.startswith("("):
|
||||||
|
closing_parenthesis = remaining_header.find(")")
|
||||||
|
|
||||||
if closing_parenthesis >= 0:
|
if closing_parenthesis >= 0:
|
||||||
mode = remaining[1:closing_parenthesis]
|
mode = remaining_header[
|
||||||
remaining = remaining[
|
1:closing_parenthesis
|
||||||
|
]
|
||||||
|
|
||||||
|
remaining_header = remaining_header[
|
||||||
closing_parenthesis + 1:
|
closing_parenthesis + 1:
|
||||||
].strip()
|
].strip()
|
||||||
|
|
||||||
level = (
|
level = (
|
||||||
remaining.split()[0]
|
remaining_header.split()[0].lower()
|
||||||
if remaining
|
if remaining_header
|
||||||
else "unknown"
|
else "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
status_match = re.search(
|
status_match = re.search(
|
||||||
r"\[(\d+)\s*/\s*(\d+)\]\s*\[([U_]+)\]",
|
r"\[(\d+)\s*/\s*(\d+)\]"
|
||||||
|
r"\s*\[([U_]+)\]",
|
||||||
block,
|
block,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -115,7 +156,13 @@
|
|||||||
member_status = status_match.group(3)
|
member_status = status_match.group(3)
|
||||||
|
|
||||||
operation_match = re.search(
|
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,
|
block,
|
||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
)
|
)
|
||||||
@@ -127,8 +174,13 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
progress_match = re.search(
|
progress_match = re.search(
|
||||||
r"(?:resync|recovery|reshape|check|repair)"
|
r"(?:"
|
||||||
r"\s*=\s*([0-9.]+%)",
|
r"resync|"
|
||||||
|
r"recovery|"
|
||||||
|
r"reshape|"
|
||||||
|
r"check|"
|
||||||
|
r"repair"
|
||||||
|
r")\s*=\s*([0-9.]+%)",
|
||||||
block,
|
block,
|
||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
)
|
)
|
||||||
@@ -150,20 +202,25 @@
|
|||||||
"operation": operation,
|
"operation": operation,
|
||||||
"progress": progress,
|
"progress": progress,
|
||||||
"faulty_member": bool(
|
"faulty_member": bool(
|
||||||
re.search(r"\(F\)", block)
|
re.search(
|
||||||
|
r"\(F\)",
|
||||||
|
block,
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def format_array(array):
|
def format_array(array):
|
||||||
if array["member_status"] is None:
|
if array["member_status"] is None:
|
||||||
members = "n/a"
|
members = "n/a"
|
||||||
status = "n/a"
|
member_status = "n/a"
|
||||||
else:
|
else:
|
||||||
members = (
|
members = (
|
||||||
f'{array["active_members"]}/'
|
f'{array["active_members"]}/'
|
||||||
f'{array["total_members"]}'
|
f'{array["total_members"]}'
|
||||||
)
|
)
|
||||||
status = array["member_status"]
|
|
||||||
|
member_status = array["member_status"]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
f'{array["name"]}'
|
f'{array["name"]}'
|
||||||
@@ -172,7 +229,7 @@
|
|||||||
f'mode={array["mode"]},'
|
f'mode={array["mode"]},'
|
||||||
f'level={array["level"]},'
|
f'level={array["level"]},'
|
||||||
f'members={members},'
|
f'members={members},'
|
||||||
f'status={status},'
|
f'status={member_status},'
|
||||||
f'operation={array["operation"]},'
|
f'operation={array["operation"]},'
|
||||||
f'progress={array["progress"]}'
|
f'progress={array["progress"]}'
|
||||||
"}"
|
"}"
|
||||||
@@ -187,7 +244,7 @@
|
|||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
available_arrays = sorted(
|
available_arrays = sorted(
|
||||||
array_blocks,
|
array_blocks.keys(),
|
||||||
key=md_sort_key,
|
key=md_sort_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -198,20 +255,23 @@
|
|||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
"ERROR RAID "
|
"ERROR RAID "
|
||||||
f"target_not_found target={target} "
|
f"target_not_found={target} "
|
||||||
f'found={",".join(available_arrays)}'
|
f'found={",".join(available_arrays)}'
|
||||||
)
|
)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
selected_arrays = [
|
selected_arrays = [
|
||||||
parse_array(name, array_blocks[name])
|
parse_array(
|
||||||
|
name,
|
||||||
|
array_blocks[name],
|
||||||
|
)
|
||||||
for name in selected_names
|
for name in selected_names
|
||||||
]
|
]
|
||||||
|
|
||||||
failures = []
|
failures = []
|
||||||
errors = []
|
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 = {
|
tokenless_levels = {
|
||||||
"raid0",
|
"raid0",
|
||||||
"linear",
|
"linear",
|
||||||
@@ -257,7 +317,8 @@
|
|||||||
and not allow_sync
|
and not allow_sync
|
||||||
):
|
):
|
||||||
failures.append(
|
failures.append(
|
||||||
f'{name}:operation={array["operation"]}'
|
f'{name}:operation='
|
||||||
|
f'{array["operation"]}'
|
||||||
)
|
)
|
||||||
|
|
||||||
details = " ".join(
|
details = " ".join(
|
||||||
@@ -290,12 +351,15 @@
|
|||||||
- (raid_allow_no_array | int) in [0, 1]
|
- (raid_allow_no_array | int) in [0, 1]
|
||||||
- (raid_debug | int) in [0, 1]
|
- (raid_debug | int) in [0, 1]
|
||||||
- >-
|
- >-
|
||||||
(raid_md_device |
|
(
|
||||||
regex_search('^(auto|md[0-9]+)$'))
|
raid_md_device
|
||||||
is not none
|
| regex_search('^(auto|md[0-9]+)$')
|
||||||
|
) is not none
|
||||||
fail_msg: >-
|
fail_msg: >-
|
||||||
ERROR RAID invalid-configuration.
|
ERROR RAID invalid-configuration.
|
||||||
RAID_MD must be auto, md0, md1, or /dev/md0.
|
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
|
quiet: true
|
||||||
|
|
||||||
- name: Run Linux MD RAID health check
|
- name: Run Linux MD RAID health check
|
||||||
@@ -329,7 +393,8 @@
|
|||||||
{%- if stdout | length > 0 -%}
|
{%- if stdout | length > 0 -%}
|
||||||
{{ stdout }}
|
{{ stdout }}
|
||||||
{%- elif stderr | length > 0 -%}
|
{%- 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 -%}
|
{%- else -%}
|
||||||
ERROR RAID no-output rc={{ raid_cmd.rc }}
|
ERROR RAID no-output rc={{ raid_cmd.rc }}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
@@ -350,11 +415,15 @@
|
|||||||
- /proc/mdstat
|
- /proc/mdstat
|
||||||
register: mdstat_debug
|
register: mdstat_debug
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
when: (raid_debug | int) == 1
|
when: (raid_debug | int) == 1
|
||||||
|
|
||||||
- name: Show raw mdstat in debug mode
|
- name: Show raw mdstat in debug mode
|
||||||
ansible.builtin.debug:
|
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
|
when: (raid_debug | int) == 1
|
||||||
|
|
||||||
- name: Show successful RAID result
|
- name: Show successful RAID result
|
||||||
|
|||||||
Reference in New Issue
Block a user