This commit is contained in:
zj
2026-02-02 04:20:00 +08:00
parent a22ea24274
commit be5a0bc2c7
12 changed files with 1162 additions and 119 deletions

View File

@@ -2,9 +2,9 @@
import subprocess
import json
import os
import logging # 导入 logging 模块
import logging
logger = logging.getLogger(__name__) # 获取当前模块的 logger 实例
logger = logging.getLogger(__name__)
class SystemInfoManager:
def __init__(self):
@@ -23,20 +23,17 @@ class SystemInfoManager:
full_cmd.append("sudo")
full_cmd.extend(cmd)
cmd_str = ' '.join(full_cmd) # 用于日志记录的完整命令字符串
cmd_str = ' '.join(full_cmd)
logger.info(f"执行命令: {cmd_str}")
try:
# text=True 自动解码为字符串encoding='utf-8' 确保正确处理中文
# check=check_output 表示如果命令返回非零退出码则抛出CalledProcessError
result = subprocess.run(
full_cmd,
capture_output=True,
text=True,
check=check_output,
encoding='utf-8',
# 设置LANG环境变量确保命令输出使用UTF-8编码避免解析问题
env=dict(os.environ, LANG="en_US.UTF-8")
)
if result.stdout:
@@ -45,13 +42,12 @@ class SystemInfoManager:
logger.warning(f"命令输出 (stderr):\n{result.stderr.strip()}")
return result.stdout.strip(), result.stderr.strip()
except subprocess.CalledProcessError as e:
# 捕获命令执行失败的情况
error_msg = f"命令执行失败: {cmd_str}\n" \
f"退出码: {e.returncode}\n" \
f"标准输出: {e.stdout}\n" \
f"标准错误: {e.stderr}"
logger.error(error_msg) # 使用 logger 记录错误
raise ValueError(error_msg) # 抛出更易于处理的异常
logger.error(error_msg)
raise ValueError(error_msg)
except FileNotFoundError:
error_msg = f"命令未找到: {full_cmd[0]}。请确保已安装。"
logger.error(error_msg)
@@ -72,21 +68,125 @@ class SystemInfoManager:
logger.info("成功获取块设备信息。")
return data.get('blockdevices', [])
except Exception as e:
logger.error(f"获取块设备信息失败: {e}") # 使用 logger 记录错误
logger.error(f"获取块设备信息失败: {e}")
return []
def get_mdadm_arrays(self):
"""
获取所有RAID阵列的详细信息。
使用 mdadm --detail --scan 命令。
首先使用 mdadm --detail --scan 获取阵列列表,
然后对每个阵列使用 mdadm --detail 获取更详细的信息。
"""
all_raid_details = []
try:
stdout, _ = self._run_command(["mdadm", "--detail", "--scan"], root_privilege=False)
# 1. 获取所有RAID设备的路径
scan_stdout, _ = self._run_command(["mdadm", "--detail", "--scan"], root_privilege=False)
array_paths = []
for line in scan_stdout.splitlines():
if line.startswith("ARRAY"):
parts = line.split()
if len(parts) > 1:
array_paths.append(parts[1]) # 例如 /dev/md126
if not array_paths:
logger.info("未找到任何RAID阵列。")
return []
# 2. 对每个RAID设备获取详细信息
for path in array_paths:
try:
detail_stdout, _ = self._run_command(["mdadm", "--detail", path], root_privilege=False)
parsed_detail = self._parse_mdadm_detail_output(detail_stdout)
parsed_detail['device'] = path # 添加设备路径
all_raid_details.append(parsed_detail)
except Exception as e:
logger.error(f"获取RAID阵列 {path} 的详细信息失败: {e}")
logger.info("成功获取RAID阵列信息。")
return stdout
return all_raid_details
except Exception as e:
logger.error(f"获取RAID阵列信息失败: {e}")
return "无法获取RAID阵列信息。"
logger.error(f"获取RAID阵列列表失败: {e}")
return []
def _parse_mdadm_detail_output(self, output_string):
"""
解析 mdadm --detail 命令的输出字符串,提取关键信息。
"""
details = {
'level': 'N/A',
'array_size': 'N/A',
'raid_devices': 'N/A',
'total_devices': 'N/A',
'state': 'N/A',
'active_devices': 'N/A',
'working_devices': 'N/A',
'failed_devices': 'N/A',
'spare_devices': 'N/A',
'chunk_size': 'N/A',
'uuid': 'N/A',
'name': 'N/A',
'member_devices': []
}
member_devices_section = False
lines = output_string.splitlines()
for line in lines:
line = line.strip()
if not line:
continue
if line.startswith("Number Major Minor RaidDevice State"):
member_devices_section = True
continue
if member_devices_section:
parts = line.split()
if len(parts) >= 6: # Ensure enough parts for device info
# State can be multiple words, e.g., "active sync"
device_state_parts = parts[4:-1]
# Handle cases where device path might have spaces or be complex, though usually not for /dev/sdX
device_path = parts[-1]
details['member_devices'].append({
'number': parts[0],
'major': parts[1],
'minor': parts[2],
'raid_device': parts[3],
'state': ' '.join(device_state_parts),
'device_path': device_path
})
else:
if ':' in line:
key, value = line.split(':', 1)
key = key.strip().lower().replace(' ', '_')
value = value.strip()
if key == 'raid_level':
details['level'] = value
elif key == 'array_size':
details['array_size'] = value
elif key == 'raid_devices':
details['raid_devices'] = value
elif key == 'total_devices':
details['total_devices'] = value
elif key == 'state':
details['state'] = value
elif key == 'active_devices':
details['active_devices'] = value
elif key == 'working_devices':
details['working_devices'] = value
elif key == 'failed_devices':
details['failed_devices'] = value
elif key == 'spare_devices':
details['spare_devices'] = value
elif key == 'chunk_size':
details['chunk_size'] = value
elif key == 'uuid':
details['uuid'] = value
elif key == 'name':
details['name'] = value
return details
def get_lvm_info(self):
"""
@@ -122,7 +222,6 @@ class SystemInfoManager:
# 示例用法 (可以在此模块中添加测试代码)
if __name__ == "__main__":
# 为了在单独运行时也能看到日志输出,这里简单配置一下
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
manager = SystemInfoManager()
logger.info("--- 块设备信息 ---")
@@ -132,16 +231,34 @@ if __name__ == "__main__":
logger.info("\n--- RAID 阵列信息 ---")
raid_info = manager.get_mdadm_arrays()
logger.info(raid_info)
if raid_info:
for array in raid_info:
logger.info(f" Device: {array.get('device')}, Level: {array.get('level')}, State: {array.get('state')}, Size: {array.get('array_size')}")
logger.info(f" Active: {array.get('active_devices')}, Failed: {array.get('failed_devices')}, Spare: {array.get('spare_devices')}")
for member in array.get('member_devices', []):
logger.info(f" Member: {member.get('device_path')} (State: {member.get('state')})")
else:
logger.info(" 未找到RAID阵列。")
logger.info("\n--- LVM 信息 ---")
lvm_info = manager.get_lvm_info()
logger.info("物理卷 (PVs):")
for pv in lvm_info['pvs']:
logger.info(f" {pv.get('pv_name')} (VG: {pv.get('vg_name')}, Size: {pv.get('pv_size')})")
if lvm_info['pvs']:
for pv in lvm_info['pvs']:
logger.info(f" {pv.get('pv_name')} (VG: {pv.get('vg_name')}, Size: {pv.get('pv_size')})")
else:
logger.info(" 未找到物理卷。")
logger.info("卷组 (VGs):")
for vg in lvm_info['vgs']:
logger.info(f" {vg.get('vg_name')} (Size: {vg.get('vg_size')}, PVs: {vg.get('pv_count')}, LVs: {vg.get('lv_count')})")
if lvm_info['vgs']:
for vg in lvm_info['vgs']:
logger.info(f" {vg.get('vg_name')} (Size: {vg.get('vg_size')}, PVs: {vg.get('pv_count')}, LVs: {vg.get('lv_count')})")
else:
logger.info(" 未找到卷组。")
logger.info("逻辑卷 (LVs):")
for lv in lvm_info['lvs']:
logger.info(f" {lv.get('lv_name')} (VG: {lv.get('vg_name')}, Size: {lv.get('lv_size')})")
if lvm_info['lvs']:
for lv in lvm_info['lvs']:
logger.info(f" {lv.get('lv_name')} (VG: {lv.get('vg_name')}, Size: {lv.get('lv_size')})")
else:
logger.info(" 未找到逻辑卷。")