update
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user