156 lines
5.7 KiB
Python
156 lines
5.7 KiB
Python
import os
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
import logging
|
|
|
|
from dotenv import load_dotenv, find_dotenv
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s [INFO] root: %(message)s',
|
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
)
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# .env 자동 탐색 로드 (현재 파일 기준 상위 디렉터리까지 검색)
|
|
load_dotenv(find_dotenv())
|
|
|
|
IDRAC_USER = os.getenv("IDRAC_USER")
|
|
IDRAC_PASS = os.getenv("IDRAC_PASS")
|
|
|
|
|
|
def resolve_output_dir() -> Path:
|
|
"""
|
|
실행 위치와 무관하게 결과를 data/idrac_info 밑으로 저장.
|
|
- 스크립트가 data/scripts/ 에 있다면 → data/idrac_info
|
|
- 그 외 위치라도 → (스크립트 상위 폴더)/idrac_info
|
|
"""
|
|
here = Path(__file__).resolve().parent # .../data/scripts 또는 다른 폴더
|
|
# case 1: .../data/scripts → data/idrac_info
|
|
if here.name.lower() == "scripts" and here.parent.name.lower() == "data":
|
|
base = here.parent # data
|
|
# case 2: .../scripts (상위가 data가 아닐 때도 상위 폴더를 base로 사용)
|
|
elif here.name.lower() == "scripts":
|
|
base = here.parent
|
|
# case 3: 일반적인 경우: 현재 파일의 상위 폴더
|
|
else:
|
|
base = here.parent
|
|
|
|
out = base / "idrac_info"
|
|
out.mkdir(parents=True, exist_ok=True)
|
|
return out
|
|
|
|
|
|
def fetch_idrac_info(idrac_ip: str, output_dir: Path) -> None:
|
|
try:
|
|
# 서비스 태그 가져오기 (get 제외)
|
|
cmd_getsysinfo = [
|
|
"racadm", "-r", idrac_ip, "-u", IDRAC_USER or "", "-p", IDRAC_PASS or "", "getsysinfo"
|
|
]
|
|
getsysinfo = subprocess.getoutput(" ".join(cmd_getsysinfo))
|
|
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:
|
|
logging.error(f"Failed to retrieve SVC Tag for IP: {idrac_ip}")
|
|
return
|
|
|
|
# InfiniBand.VndrConfigPage 목록 가져오기
|
|
cmd_list = [
|
|
"racadm", "-r", idrac_ip, "-u", IDRAC_USER or "", "-p", IDRAC_PASS or "", "get", "InfiniBand.VndrConfigPage"
|
|
]
|
|
output_list = subprocess.getoutput(" ".join(cmd_list))
|
|
|
|
# InfiniBand.VndrConfigPage.<숫자> 및 Key 값 추출
|
|
matches = re.findall(
|
|
r"InfiniBand\.VndrConfigPage\.(\d+)\s+\[Key=InfiniBand\.Slot\.(\d+)-\d+#VndrConfigPage]",
|
|
output_list
|
|
)
|
|
|
|
# 결과 저장 파일
|
|
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")
|
|
|
|
# --- 슬롯/GUID 수집 후 원하는 순서로 기록 ---
|
|
slot_to_guid: dict[str, str] = {}
|
|
slots_in_match_order: list[str] = []
|
|
|
|
# 각 페이지 상세 조회
|
|
for number, slot in matches:
|
|
cmd_detail = [
|
|
"racadm", "-r", idrac_ip, "-u", IDRAC_USER or "", "-p", IDRAC_PASS or "",
|
|
"get", f"InfiniBand.VndrConfigPage.{number}"
|
|
]
|
|
output_detail = subprocess.getoutput(" ".join(cmd_detail))
|
|
|
|
# PortGUID 추출
|
|
match_guid = re.search(r"PortGUID=(\S+)", output_detail)
|
|
port_guid = match_guid.group(1) if match_guid else "Not Found"
|
|
|
|
s = str(slot)
|
|
slot_to_guid[s] = port_guid
|
|
slots_in_match_order.append(s)
|
|
|
|
# 검색된 슬롯 개수에 따라 출력 순서 결정
|
|
total_slots = len(slots_in_match_order)
|
|
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 = slots_in_match_order
|
|
|
|
# 지정된 순서대로 파일에 기록 + GUID 요약 생성
|
|
hex_guid_list: list[str] = []
|
|
for s in desired_order:
|
|
guid = slot_to_guid.get(s, "Not Found")
|
|
f.write(f"Slot.{s}: {guid}\n")
|
|
if guid != "Not Found":
|
|
hex_guid_list.append(f"0x{guid.replace(':', '').upper()}")
|
|
|
|
if hex_guid_list:
|
|
f.write(f"GUID: {';'.join(hex_guid_list)}\n")
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error processing IP {idrac_ip}: {e}")
|
|
|
|
|
|
def main(ip_file: str) -> None:
|
|
ip_path = Path(ip_file)
|
|
if not ip_path.is_file():
|
|
logging.error(f"IP file {ip_file} does not exist.")
|
|
return
|
|
|
|
output_dir = resolve_output_dir() # ← 여기서 OS 무관 저장 위치 확정 (data/idrac_info)
|
|
# print(f"[debug] output_dir = {output_dir}") # 필요 시 확인
|
|
|
|
with ip_path.open("r", encoding="utf-8") as file:
|
|
ip_addresses = [line.strip() for line in file if line.strip()]
|
|
|
|
# 스레드풀
|
|
with ThreadPoolExecutor(max_workers=100) as executor:
|
|
future_to_ip = {executor.submit(fetch_idrac_info, ip, output_dir): ip for ip in ip_addresses}
|
|
|
|
for future in as_completed(future_to_ip):
|
|
ip = future_to_ip[future]
|
|
try:
|
|
future.result()
|
|
logging.info(f"Completed: {ip}")
|
|
except Exception as e:
|
|
logging.error(f"Error processing {ip}: {e}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
if len(sys.argv) != 2:
|
|
logging.error("Usage: python script.py <ip_file>")
|
|
sys.exit(1)
|
|
|
|
main(sys.argv[1])
|