#!/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 [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()