#!/usr/bin/env python3 """ 통합 MAC 수집 스크립트 프로파일 기반으로 다양한 서버 타입 지원 """ import sys import yaml from pathlib import Path from concurrent.futures import ThreadPoolExecutor import logging # core 모듈 임포트 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" / "mac_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) if not profile: logging.error(f"프로파일 '{profile_name}' 없음. 사용 가능: {list(profiles.keys())}") sys.exit(1) # extends 처리 if 'extends' in profile: base = profiles[profile['extends']].copy() base.update(profile) profile = base return profile def collect_mac_for_ip(ip: str, profile: dict, output_dir: Path) -> tuple: """단일 IP에 대한 MAC 정보 수집""" try: client = IDRACClient(ip) parser = InfoParser() # 데이터 수집 sysinfo = client.get_sysinfo() hwinventory = client.get_hwinventory() # 서비스 태그 svc_tag = parser.extract_service_tag(sysinfo) if not svc_tag: return ip, False, "서비스 태그 추출 실패" # NIC MAC 주소 # NIC MAC 주소 # sysinfo보다 hwinventory가 더 상세한 정보(Slot NIC 등)를 포함하므로 변경 nic_macs = parser.extract_nic_macs_from_hwinventory(hwinventory, profile['nic_patterns']) # iDRAC MAC idrac_mac = None if profile.get('include_idrac_mac'): idrac_mac = parser.extract_idrac_mac(sysinfo) # InfiniBand (선택적) ib_guids = {} if 'infiniband_slots' in profile: # Legacy logic: Fetch from swinventory (checking previous line of FQDD) swinventory = client.get_swinventory() ib_guids = parser.extract_infiniband_macs_from_swinventory(swinventory, profile['infiniband_slots']) # 벤더 정보 memory_vendors = [] disk_vendors = [] if profile.get('include_memory_vendor'): memory_vendors = parser.extract_vendors(hwinventory, "DIMM") if profile.get('include_disk_vendor'): if profile.get('exclude_boss'): # BOSS 제외한 디스크 벤더 all_vendors = set() for prefix in ["Disk.Bay.", "Disk.M.2.", "Disk.Direct.", "Disk.Slot."]: all_vendors.update(parser.extract_vendors(hwinventory, prefix)) disk_vendors = sorted(all_vendors) else: disk_vendors = parser.extract_vendors(hwinventory, "Disk.") # 파일 저장 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") # NIC MAC (순서 유지) for pattern in profile['nic_patterns']: f.write(f"{nic_macs.get(pattern) or ''}\n") # InfiniBand (있을 경우) if ib_guids: for slot in profile['infiniband_slots']: f.write(f"{ib_guids.get(slot) or ''}\n") # iDRAC MAC if idrac_mac: f.write(f"{idrac_mac}\n") # 벤더 정보 for vendor in memory_vendors: f.write(f"{vendor}\n") for vendor in disk_vendors: f.write(f"{vendor}\n") return ip, True, f"저장: {output_file.name}" 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_mac.py [profile_name]") print("Available profiles: default, xe9680_h200_10ea, type11, jp_54ea, sp_18ea") 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', '')}") # 출력 디렉토리 (스크립트 위치 기준) # script: data/scripts/unified/collect_mac.py -> data_root: data/ 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_mac_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}대") # IP 파일 삭제 (기존 스크립트와 동일) try: ip_file.unlink(missing_ok=True) except Exception: pass if __name__ == "__main__": main()