Files
2026-05-05 17:14:11 +09:00

217 lines
8.0 KiB
Python

#!/usr/bin/env python3
"""
통합 GUID 수집 스크립트
프로파일 기반으로 InfiniBand GUID 수집
"""
import sys
import yaml
import os
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import logging
sys.path.insert(0, str(Path(__file__).parent.parent))
from core.idrac_client import IDRACClient
from core.parsers import InfoParser
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [INFO] root: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
def load_profile(profile_name: str) -> dict:
"""프로파일 로드"""
profile_file = Path(__file__).parent.parent / "profiles" / "guid_profiles.yaml"
if not profile_file.exists():
logging.error(f"프로파일 파일 없음: {profile_file}")
sys.exit(1)
with open(profile_file, 'r', encoding='utf-8') as f:
profiles = yaml.safe_load(f)['profiles']
profile = profiles.get(profile_name, profiles['default'])
# 환경변수에서 슬롯 우선순위 읽기 (custom_priority 프로파일용)
if not profile.get('slot_priority'):
env_priority = os.getenv("GUID_SLOT_PRIORITY", "")
if env_priority:
profile['slot_priority'] = [int(s.strip()) for s in env_priority.split(",") if s.strip()]
else:
profile['slot_priority'] = profiles['default']['slot_priority']
return profile
def collect_guid_for_ip(ip: str, profile: dict, output_dir: Path) -> tuple:
"""단일 IP에 대한 GUID 정보 수집 (PortGUID_v1.py 로직 기반)"""
try:
logging.info(f"[{ip}] DEBUG: Running Unified GUID Script v2.2 (PortGUID_v1 Logic)")
# IDRAC credentials from env or default
idrac_user = os.getenv("IDRAC_USER", "root")
idrac_pass = os.getenv("IDRAC_PASS", "calvin")
# 1. Get Service Tag (using raw subprocess like v1)
cmd_getsysinfo = [
"racadm", "-r", ip, "-u", idrac_user, "-p", idrac_pass, "getsysinfo"
]
# v1 uses subprocess.getoutput which runs with shell=True/standard shell behavior.
# Here we replicate it or use subprocess.run with capture_output.
# PortGUID_v1 uses: getsysinfo = subprocess.getoutput(" ".join(cmd_getsysinfo))
# Using subprocess.run for better control but mimicking the command structure
import subprocess
import re
try:
# mimic v1: Use the exact same command string style
proc_info = subprocess.run(cmd_getsysinfo, capture_output=True, text=True, timeout=60)
getsysinfo = proc_info.stdout
except Exception as e:
return ip, False, f"SVC Tag 수집 실패: {e}"
svc_tag_match = re.search(r"SVC Tag\s*=\s*(\S+)", getsysinfo)
svc_tag = svc_tag_match.group(1) if svc_tag_match else None
if not svc_tag:
return ip, False, "서비스 태그 추출 실패 (Regex miss)"
# 2. Get InfiniBand Page List
cmd_list = [
"racadm", "-r", ip, "-u", idrac_user, "-p", idrac_pass, "get", "InfiniBand.VndrConfigPage"
]
proc_list = subprocess.run(cmd_list, capture_output=True, text=True, timeout=60)
output_list = proc_list.stdout
# 3. Parse Page Map
matches = re.findall(
r"InfiniBand\.VndrConfigPage\.(\d+)\s+\[Key=InfiniBand\.Slot\.(\d+)-\d+#VndrConfigPage]",
output_list
)
slot_to_guid = {}
# 4. Fetch Detail for each page
for number, slot in matches:
cmd_detail = [
"racadm", "-r", ip, "-u", idrac_user, "-p", idrac_pass,
"get", f"InfiniBand.VndrConfigPage.{number}"
]
proc_detail = subprocess.run(cmd_detail, capture_output=True, text=True, timeout=60)
output_detail = proc_detail.stdout
match_guid = re.search(r"PortGUID=(\S+)", output_detail)
port_guid = match_guid.group(1) if match_guid else None
if port_guid:
slot_to_guid[int(slot)] = port_guid
# 5. Determine Priority (Preference: Profile > Env > Default(v1 logic))
# Logic from PortGUID_v1.py:
# if total_slots == 4: order = ...
# elif total_slots == 10: order = ...
# We will use the profile's logic if available, but fallback to v1 logic if profile is 'default' and matches slot counts
desired_order = profile.get('slot_priority', [])
# If profile didn't specify (or empty), use v1 adaptive logic
if not desired_order:
total_slots = len(matches)
if total_slots == 4:
desired_order = [38, 37, 32, 34]
elif total_slots == 10:
desired_order = [38, 39, 37, 36, 32, 33, 34, 35, 31, 40]
else:
desired_order = [int(slot) for _, slot in matches] # Just collection order
logging.info(f"[{ip}] DEBUG: Using Adaptive Priority (Count={total_slots}): {desired_order}")
else:
logging.info(f"[{ip}] DEBUG: Using Profile Priority: {desired_order}")
# 6. Write File
output_file = output_dir / f"{svc_tag}.txt"
with output_file.open('w', encoding='utf-8', newline='\n') as f:
f.write(f"{svc_tag}\n")
hex_guid_list = []
# Write in desired order
for slot in desired_order:
guid = slot_to_guid.get(slot)
if guid:
f.write(f"Slot.{slot}: {guid}\n")
# Format
fmt_guid = guid.replace(':', '').upper()
if not fmt_guid.startswith("0X"):
fmt_guid = "0x" + fmt_guid
hex_guid_list.append(fmt_guid)
# Remaining slots (if any weren't in priority list)?
# v1 doesn't write them if not in priority list. We will stick to v1 behavior.
if profile.get('output_format', 'combined') == 'combined' and hex_guid_list:
f.write(f"GUID: {';'.join(hex_guid_list)}\n")
return ip, True, f"저장: {output_file.name} ({len(hex_guid_list)}개 GUID/v1_Logic)"
except Exception as e:
logging.error(f"[{ip}] 오류: {e}")
return ip, False, f"오류: {e}"
def main():
if len(sys.argv) < 2:
print("Usage: python collect_guid.py <ip_file> [profile_name]")
print("Available profiles: default, custom_priority")
sys.exit(1)
ip_file = Path(sys.argv[1])
profile_name = sys.argv[2] if len(sys.argv) > 2 else "default"
if not ip_file.exists():
logging.error(f"IP 파일 없음: {ip_file}")
sys.exit(1)
# 프로파일 로드
profile = load_profile(profile_name)
logging.info(f"프로파일 사용: {profile_name} - {profile.get('description', '')}")
logging.info(f"슬롯 우선순위: {profile['slot_priority']}")
# 출력 디렉토리
# 출력 디렉토리 (스크립트 위치 기준)
script_dir = Path(__file__).resolve().parent
data_root = script_dir.parent.parent
output_dir = data_root / "temp" / "staging"
output_dir.mkdir(parents=True, exist_ok=True)
# IP 목록 읽기
ips = [line.strip() for line in ip_file.read_text(encoding='utf-8').splitlines() if line.strip()]
if not ips:
logging.error("IP 목록이 비어 있습니다.")
sys.exit(1)
logging.info(f"{len(ips)}대 처리 시작")
# 병렬 처리
ok, fail = 0, 0
with ThreadPoolExecutor(max_workers=20) as executor:
futures = {executor.submit(collect_guid_for_ip, ip, profile, output_dir): ip for ip in ips}
for future in futures:
ip, success, msg = future.result()
if success:
logging.info(f"[OK] {ip} - {msg}")
ok += 1
else:
logging.error(f"[FAIL] {ip} - {msg}")
fail += 1
logging.info(f"완료: 성공 {ok}대 / 실패 {fail}")
if __name__ == "__main__":
main()