update
This commit is contained in:
+2
-2
@@ -2,5 +2,5 @@
|
||||
#IDRAC_PASS=tksoWkd12#
|
||||
IDRAC_USER=root
|
||||
IDRAC_PASS=calvin
|
||||
OME_USER=OME
|
||||
OME_PASS=epF!@34
|
||||
OME_USER=khsky84
|
||||
OME_PASS=Zes9ro12#$
|
||||
|
||||
@@ -26,7 +26,9 @@ def validate_ip_file(ip_file_path):
|
||||
return ip_file_path
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR = "idrac_info"
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
# data/scripts/ -> data/temp/staging
|
||||
OUTPUT_DIR = os.path.join(BASE_DIR, "..", "temp", "staging")
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from dotenv import load_dotenv
|
||||
import sys
|
||||
from multiprocessing import Pool
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# 환경 변수 로드
|
||||
load_dotenv() # .env 파일에서 환경 변수 로드
|
||||
|
||||
# 사용자 이름 및 비밀번호 설정
|
||||
IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
|
||||
|
||||
# IP 주소 파일 로드 함수
|
||||
def load_ip_file(ip_file_path):
|
||||
if not os.path.isfile(ip_file_path):
|
||||
logging.error(f"IP file {ip_file_path} does not exist.")
|
||||
sys.exit(1)
|
||||
with open(ip_file_path, "r") as file:
|
||||
return [line.strip() for line in file if line.strip()]
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
def fetch_idrac_info(idrac_ip):
|
||||
logging.info(f"Collecting TSR report for iDRAC IP: {idrac_ip}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "techsupreport", "collect"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
logging.info(f"Successfully collected TSR report for {idrac_ip}")
|
||||
else:
|
||||
logging.error(f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||
|
||||
# 메인 함수
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit("Usage: python script.py <ip_file>")
|
||||
|
||||
ip_list = load_ip_file(sys.argv[1])
|
||||
start_time = time.time()
|
||||
|
||||
# 병렬로 iDRAC 정보를 가져오는 작업 수행 (최대 10개 동시 처리)
|
||||
with Pool() as pool:
|
||||
pool.map(fetch_idrac_info, ip_list)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
logging.info(f"설정 완료. 수집 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
|
||||
@@ -1,63 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
from dotenv import load_dotenv
|
||||
import sys
|
||||
from multiprocessing import Pool
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# 환경 변수 로드
|
||||
load_dotenv() # .env 파일에서 환경 변수 로드
|
||||
|
||||
# 사용자 이름 및 비밀번호 설정
|
||||
IDRAC_USER = os.getenv("IDRAC_USER")
|
||||
IDRAC_PASS = os.getenv("IDRAC_PASS")
|
||||
OME_USER = os.getenv("OME_USER")
|
||||
OME_PASS = os.getenv("OME_PASS")
|
||||
|
||||
# IP 주소 파일 로드 및 유효성 검사
|
||||
def load_ip_file(ip_file_path):
|
||||
if not os.path.isfile(ip_file_path):
|
||||
logging.error(f"IP file {ip_file_path} does not exist.")
|
||||
sys.exit(1)
|
||||
with open(ip_file_path, "r") as file:
|
||||
return [line.strip() for line in file if line.strip()]
|
||||
|
||||
# iDRAC 정보를 가져오는 함수
|
||||
def fetch_idrac_info(idrac_ip):
|
||||
logging.info(f"Collecting TSR report for iDRAC IP: {idrac_ip}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
"racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS,
|
||||
"techsupreport", "export", "-l", "//10.10.3.251/share/", "-u", OME_USER, "-p", OME_PASS
|
||||
],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
logging.info(f"Successfully collected TSR report for {idrac_ip}")
|
||||
else:
|
||||
logging.error(f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||
|
||||
# 메인 함수
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit("Usage: python script.py <ip_file>")
|
||||
|
||||
ip_list = load_ip_file(sys.argv[1])
|
||||
|
||||
# 병렬로 iDRAC 정보를 가져오는 작업 수행
|
||||
with Pool() as pool:
|
||||
pool.map(fetch_idrac_info, ip_list)
|
||||
|
||||
logging.info("설정 완료.")
|
||||
@@ -1,51 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from dotenv import load_dotenv
|
||||
import sys
|
||||
from multiprocessing import Pool
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv() # Load variables from .env file
|
||||
|
||||
# Credentials
|
||||
IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
|
||||
|
||||
# Load IP addresses from file
|
||||
def load_ip_file(ip_file_path):
|
||||
if not os.path.isfile(ip_file_path):
|
||||
sys.exit(f"IP file {ip_file_path} does not exist.")
|
||||
with open(ip_file_path, "r") as file:
|
||||
return [line.strip() for line in file if line.strip()]
|
||||
|
||||
# Clear SEL log for given iDRAC IP
|
||||
def clear_idrac_sel_log(idrac_ip):
|
||||
print(f"Clearing SEL log for iDRAC IP: {idrac_ip}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "clrsel"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
print(
|
||||
f"Successfully cleared SEL log for {idrac_ip}"
|
||||
if result.returncode == 0
|
||||
else f"Failed to clear SEL log for {idrac_ip}: {result.stderr.strip()}"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Exception occurred for {idrac_ip}: {e}")
|
||||
|
||||
# Main function
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit("Usage: python script.py <ip_file>")
|
||||
|
||||
ip_list = load_ip_file(sys.argv[1])
|
||||
start_time = time.time()
|
||||
|
||||
# Execute in parallel using Pool (max 10 simultaneous processes)
|
||||
with Pool(processes=10) as pool:
|
||||
pool.map(clear_idrac_sel_log, ip_list)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
print(f"Log clear 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
|
||||
@@ -1,60 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from dotenv import load_dotenv
|
||||
import sys
|
||||
from multiprocessing import Pool
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv() # Load variables from .env file
|
||||
|
||||
# Credentials
|
||||
IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
|
||||
|
||||
# Load IP addresses from file
|
||||
def load_ip_file(ip_file_path):
|
||||
if not os.path.isfile(ip_file_path):
|
||||
logging.error(f"IP file {ip_file_path} does not exist.")
|
||||
sys.exit(1)
|
||||
with open(ip_file_path, "r") as file:
|
||||
return [line.strip() for line in file if line.strip()]
|
||||
|
||||
# Power on the server for given iDRAC IP
|
||||
def poweron_idrac_server(idrac_ip):
|
||||
logging.info(f"Powering on server for iDRAC IP: {idrac_ip}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerup"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
logging.info(f"Successfully powered on server for {idrac_ip}")
|
||||
else:
|
||||
logging.error(f"Failed to power on server for {idrac_ip}: {result.stderr.strip()}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||
|
||||
# Main function
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit("Usage: python script.py <ip_file>")
|
||||
|
||||
ip_list = load_ip_file(sys.argv[1])
|
||||
start_time = time.time()
|
||||
|
||||
# Execute in parallel using Pool (max 10 simultaneous processes)
|
||||
with Pool() as pool:
|
||||
pool.map(poweron_idrac_server, ip_list)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
logging.info(f"Server Power On 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
|
||||
@@ -1,60 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from dotenv import load_dotenv
|
||||
import sys
|
||||
from multiprocessing import Pool
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv() # Load variables from .env file
|
||||
|
||||
# Credentials
|
||||
IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
|
||||
|
||||
# Load IP addresses from file
|
||||
def load_ip_file(ip_file_path):
|
||||
if not os.path.isfile(ip_file_path):
|
||||
logging.error(f"IP file {ip_file_path} does not exist.")
|
||||
sys.exit(1)
|
||||
with open(ip_file_path, "r") as file:
|
||||
return [line.strip() for line in file if line.strip()]
|
||||
|
||||
# Power off the server for given iDRAC IP
|
||||
def poweroff_idrac_server(idrac_ip):
|
||||
logging.info(f"Powering off server for iDRAC IP: {idrac_ip}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerdown"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
logging.info(f"Successfully powered off server for {idrac_ip}")
|
||||
else:
|
||||
logging.error(f"Failed to power off server for {idrac_ip}: {result.stderr.strip()}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||
|
||||
# Main function
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit("Usage: python script.py <ip_file>")
|
||||
|
||||
ip_list = load_ip_file(sys.argv[1])
|
||||
start_time = time.time()
|
||||
|
||||
# Execute in parallel using Pool (max 10 simultaneous processes)
|
||||
with Pool() as pool:
|
||||
pool.map(poweroff_idrac_server, ip_list)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
logging.info(f"Server Power Off 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
|
||||
@@ -1,60 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from dotenv import load_dotenv
|
||||
import sys
|
||||
from multiprocessing import Pool
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv() # Load variables from .env file
|
||||
|
||||
# Credentials
|
||||
IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
|
||||
|
||||
# Load IP addresses from file
|
||||
def load_ip_file(ip_file_path):
|
||||
if not os.path.isfile(ip_file_path):
|
||||
logging.error(f"IP file {ip_file_path} does not exist.")
|
||||
sys.exit(1)
|
||||
with open(ip_file_path, "r") as file:
|
||||
return [line.strip() for line in file if line.strip()]
|
||||
|
||||
# Delete all jobs for given iDRAC IP
|
||||
def delete_all_jobs(idrac_ip):
|
||||
logging.info(f"Deleting all jobs for iDRAC IP: {idrac_ip}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "jobqueue", "delete", "-i", "ALL"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
logging.info(f"Successfully deleted all jobs for {idrac_ip}")
|
||||
else:
|
||||
logging.error(f"Failed to delete jobs for {idrac_ip}: {result.stderr.strip()}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||
|
||||
# Main function
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit("Usage: python script.py <ip_file>")
|
||||
|
||||
ip_list = load_ip_file(sys.argv[1])
|
||||
start_time = time.time()
|
||||
|
||||
# Execute in parallel using Pool (max 10 simultaneous processes)
|
||||
with Pool() as pool:
|
||||
pool.map(delete_all_jobs, ip_list)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
logging.info(f"Job delete 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
|
||||
@@ -27,7 +27,8 @@ def validate_ip_file(ip_file_path):
|
||||
return ip_file_path
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR = "idrac_info"
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
OUTPUT_DIR = os.path.join(BASE_DIR, "..", "temp", "staging")
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
# iDRAC 정보를 가져오는 함수
|
||||
|
||||
@@ -28,7 +28,7 @@ def resolve_output_dir() -> Path:
|
||||
else:
|
||||
base = here.parent
|
||||
|
||||
out = base / "idrac_info"
|
||||
out = base / "temp" / "staging"
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
return out
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ def resolve_output_dir() -> Path:
|
||||
else:
|
||||
base = here.parent
|
||||
|
||||
out = base / "idrac_info"
|
||||
out = base / "temp" / "staging"
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
return out
|
||||
|
||||
|
||||
@@ -1,192 +1,21 @@
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
레거시 호환성 래퍼 - Intel_Server_info.py
|
||||
실제 로직은 unified/collect_server_info.py의 'intel_server' 프로파일 사용
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
script_dir = Path(__file__).parent
|
||||
sys.path.insert(0, str(script_dir))
|
||||
|
||||
# --- [설정] iDRAC 접속 정보 ---
|
||||
IDRAC_USER = "root"
|
||||
IDRAC_PASS = "calvin"
|
||||
|
||||
def resolve_output_dir() -> Path:
|
||||
"""
|
||||
사용자가 지정한 로직에 따라 저장 위치를 결정합니다.
|
||||
스크립트 위치가 /data/scripts/ 일 경우 /data/idrac_info/ 에 저장합니다.
|
||||
"""
|
||||
here = Path(__file__).resolve().parent
|
||||
if here.name.lower() == "scripts" and here.parent.name.lower() == "data":
|
||||
base = here.parent
|
||||
elif here.name.lower() == "scripts":
|
||||
base = here.parent
|
||||
else:
|
||||
base = here.parent
|
||||
|
||||
out = base / "idrac_info"
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
return out
|
||||
|
||||
def run_racadm(ip: str, command: str) -> str:
|
||||
"""racadm 명령어를 실행하고 결과를 문자열로 반환합니다."""
|
||||
full_cmd = f"racadm -r {ip} -u {IDRAC_USER} -p {IDRAC_PASS} {command}"
|
||||
try:
|
||||
# 쉘 명령 실행 (stderr도 포함하여 수집)
|
||||
result = subprocess.check_output(full_cmd, shell=True, stderr=subprocess.STDOUT, universal_newlines=True)
|
||||
return result
|
||||
except subprocess.CalledProcessError as e:
|
||||
# 통신 실패 시 빈 문자열 반환
|
||||
return ""
|
||||
|
||||
def get_val(text: str, pattern: str) -> str:
|
||||
"""텍스트 내에서 특정 키워드를 찾아 해당 줄의 설정값(= 이후의 값)을 추출합니다."""
|
||||
for line in text.splitlines():
|
||||
if re.search(pattern, line, re.IGNORECASE):
|
||||
parts = line.split('=')
|
||||
if len(parts) >= 2:
|
||||
return parts[1].strip()
|
||||
return "N/A"
|
||||
|
||||
def fetch_idrac_info(ip: str, output_dir: Path):
|
||||
logging.info(f">>> {ip} 정보 수집 중...")
|
||||
|
||||
# 1. 원시 데이터 벌크 수집 (네트워크 오버헤드 감소)
|
||||
getsysinfo = run_racadm(ip, "getsysinfo")
|
||||
hwinventory = run_racadm(ip, "hwinventory")
|
||||
sys_profile = run_racadm(ip, "get bios.SysProfileSettings")
|
||||
proc_settings = run_racadm(ip, "get bios.ProcSettings")
|
||||
mem_settings = run_racadm(ip, "get bios.MemSettings")
|
||||
storage_ctrl = run_racadm(ip, "get STORAGE.Controller.1")
|
||||
|
||||
# 서비스 태그 추출 (파일명 결정용)
|
||||
svc_tag = get_val(getsysinfo, "SVC Tag")
|
||||
if svc_tag == "N/A":
|
||||
logging.error(f"[경고] {ip} 접속 실패 혹은 SVC Tag 확인 불가. 건너뜁니다.")
|
||||
return
|
||||
|
||||
report_path = output_dir / f"{svc_tag}.txt"
|
||||
|
||||
with open(report_path, "w", encoding="utf-8") as f:
|
||||
# 헤더 기록
|
||||
f.write(f"Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: {svc_tag})\n\n")
|
||||
|
||||
# --- Section 1: Firmware Version 정보 ---
|
||||
f.write("-" * 42 + " Firmware Version 정보 " + "-" * 42 + "\n")
|
||||
f.write(f"1. SVC Tag : {svc_tag}\n")
|
||||
f.write(f"2. Bios Firmware : {get_val(getsysinfo, 'System BIOS Version')}\n")
|
||||
f.write(f"3. iDRAC Firmware Version : {get_val(getsysinfo, 'Firmware Version')}\n")
|
||||
|
||||
nic1 = run_racadm(ip, "get NIC.FrmwImgMenu.1")
|
||||
f.write(f"4. NIC Integrated Firmware Version : {get_val(nic1, '#FamilyVersion')}\n")
|
||||
|
||||
nic5 = run_racadm(ip, "get NIC.FrmwImgMenu.5")
|
||||
f.write(f"5. OnBoard NIC Firmware Version : {get_val(nic5, '#FamilyVersion')}\n")
|
||||
f.write(f"6. Raid Controller Firmware Version : {get_val(hwinventory, 'ControllerFirmwareVersion')}\n\n")
|
||||
|
||||
# --- Section 2: Bios 설정 정보 ---
|
||||
f.write("-" * 45 + " Bios 설정 정보 " + "-" * 46 + "\n")
|
||||
boot_settings = run_racadm(ip, "get bios.BiosBootSettings")
|
||||
f.write(f"01. Bios Boot Mode : {get_val(boot_settings, 'BootMode')}\n")
|
||||
f.write(f"02. System Profile : {get_val(sys_profile, 'SysProfile=')}\n")
|
||||
f.write(f"03. CPU Power Management : {get_val(sys_profile, 'EnergyPerformanceBias')}\n")
|
||||
f.write(f"04. Memory Frequency : {get_val(sys_profile, 'MemFrequency')}\n")
|
||||
f.write(f"05. Turbo Boost : {get_val(sys_profile, 'ProcTurboMode')}\n")
|
||||
f.write(f"06. C1E : {get_val(sys_profile, 'ProcC1E')}\n")
|
||||
f.write(f"07. C-States : {get_val(sys_profile, 'ProcCStates')}\n")
|
||||
f.write(f"08. Monitor/Mwait : {get_val(sys_profile, 'MonitorMwait')}\n")
|
||||
f.write(f"09. Logical Processor : {get_val(proc_settings, 'LogicalProc')}\n")
|
||||
f.write(f"10. Virtualization Technology : {get_val(proc_settings, 'ProcVirtualization')}\n")
|
||||
f.write(f"11. LLC Prefetch : {get_val(proc_settings, 'LlcPrefetch')}\n")
|
||||
f.write(f"12. x2APIC Mode : {get_val(proc_settings, 'ProcX2Apic')}\n")
|
||||
f.write(f"13. Node Interleaving : {get_val(mem_settings, 'NodeInterleave')}\n")
|
||||
f.write(f"14. DIMM Self Healing : {get_val(mem_settings, 'PPROnUCE')}\n")
|
||||
f.write(f"15. Correctable Error Logging : {get_val(mem_settings, 'CECriticalSEL')}\n")
|
||||
|
||||
thermal = run_racadm(ip, "get System.ThermalSettings")
|
||||
f.write(f"16. Thermal Profile Optimization : {get_val(thermal, 'ThermalProfile')}\n")
|
||||
|
||||
sriov = run_racadm(ip, "get Bios.IntegratedDevices")
|
||||
f.write(f"17. SR-IOV Global Enable : {get_val(sriov, 'SriovGlobalEnable')}\n")
|
||||
|
||||
misc = run_racadm(ip, "get bios.MiscSettings")
|
||||
f.write(f"18. F1/F2 Prompt on Error : {get_val(misc, 'ErrPrompt')}\n\n")
|
||||
|
||||
# --- Section 3: iDRAC 설정 정보 ---
|
||||
f.write("-" * 45 + " iDRAC 설정 정보 " + "-" * 45 + "\n")
|
||||
f.write(f"01. Timezone : {get_val(run_racadm(ip, 'get iDRAC.Time.Timezone'), 'Timezone')}\n")
|
||||
f.write(f"02. IPMI LAN Selection : {get_val(run_racadm(ip, 'get iDRAC.CurrentNIC'), 'ActiveNIC')}\n")
|
||||
f.write(f"03. IPMI IP(IPv4) DHCP : {get_val(run_racadm(ip, 'get iDRAC.CurrentIPv4'), 'DHCPEnable')}\n")
|
||||
f.write(f"04. IPMI IP(IPv6) Enable : {get_val(run_racadm(ip, 'get iDRAC.CurrentIPv6'), 'Enable=')}\n")
|
||||
f.write(f"05. Redfish Support : {get_val(run_racadm(ip, 'get iDRAC.Redfish.Enable'), 'Enable=')}\n")
|
||||
f.write(f"06. SSH Support : {get_val(run_racadm(ip, 'get iDRAC.SSH'), 'Enable=')}\n")
|
||||
f.write(f"07. AD User Domain Name : {get_val(run_racadm(ip, 'get iDRAC.USERDomain.1.Name'), 'Name')}\n")
|
||||
f.write(f"08. SC Server Address : {get_val(run_racadm(ip, 'get iDRAC.ActiveDirectory.DomainController1'), 'DomainController1')}\n")
|
||||
|
||||
# Syslog 관련
|
||||
f.write(f"15. Remote Log (syslog) : {get_val(run_racadm(ip, 'get iDRAC.SysLog.SysLogEnable'), 'SysLogEnable')}\n")
|
||||
f.write(f"16. syslog server 1 : {get_val(run_racadm(ip, 'get iDRAC.SysLog.Server1'), 'Server1')}\n")
|
||||
f.write(f"17. syslog server 2 : {get_val(run_racadm(ip, 'get iDRAC.SysLog.Server2'), 'Server2')}\n")
|
||||
f.write(f"18. syslog server port : {get_val(run_racadm(ip, 'get iDRAC.SysLog.Port'), 'Port')}\n")
|
||||
f.write(f"19. VirtualConsole Port : {get_val(run_racadm(ip, 'get iDRAC.VirtualConsole.Port'), 'Port')}\n\n")
|
||||
|
||||
# --- Section 4: Raid 설정 정보 ---
|
||||
f.write("-" * 45 + " Raid 설정 정보 " + "-" * 46 + "\n")
|
||||
f.write(f"01. Raid ProductName : {get_val(hwinventory, 'ProductName = BOSS')}, {get_val(hwinventory, 'ProductName = PERC')}\n")
|
||||
f.write(f"02. RAID Types : {get_val(hwinventory, 'RAIDTypes')}\n")
|
||||
f.write(f"03. StripeSize : {get_val(hwinventory, 'StripeSize')}\n")
|
||||
f.write(f"04. ReadCachePolicy : {get_val(hwinventory, 'ReadCachePolicy')}\n")
|
||||
f.write(f"05. WriteCachePolicy : {get_val(hwinventory, 'WriteCachePolicy')}\n")
|
||||
f.write(f"06. CheckConsistencyMode : {get_val(storage_ctrl, 'CheckConsistencyMode')}\n")
|
||||
f.write(f"07. PatrolReadRate : {get_val(storage_ctrl, 'PatrolReadRate')}\n")
|
||||
f.write(f"08. period : 168h\n")
|
||||
f.write(f"09. Power Save : No\n")
|
||||
f.write(f"10. JBODMODE : Controller does not support JBOD\n")
|
||||
f.write(f"11. maxconcurrentpd : 240\n")
|
||||
|
||||
logging.info(f" ㄴ 완료: {report_path.name}")
|
||||
|
||||
def main():
|
||||
import sys
|
||||
if len(sys.argv) < 2:
|
||||
logging.error("Usage: python script.py <ip_list_file>")
|
||||
return
|
||||
|
||||
ip_file = sys.argv[1]
|
||||
if not os.path.exists(ip_file):
|
||||
logging.error(f"파일을 찾을 수 없습니다: {ip_file}")
|
||||
return
|
||||
|
||||
# 저장 위치 결정
|
||||
output_dir = resolve_output_dir()
|
||||
logging.info(f"[*] 결과 저장 폴더: {output_dir}")
|
||||
|
||||
# 시간 측정 시작
|
||||
start_time = time.time()
|
||||
|
||||
# IP 파일 읽기
|
||||
with open(ip_file, "r") as f:
|
||||
ips = [line.strip() for line in f if line.strip()]
|
||||
|
||||
# 순차적 정보 수집
|
||||
for ip in ips:
|
||||
fetch_idrac_info(ip, output_dir)
|
||||
|
||||
# 소요 시간 계산
|
||||
elapsed = time.time() - start_time
|
||||
hours = int(elapsed // 3600)
|
||||
minutes = int((elapsed % 3600) // 60)
|
||||
seconds = int(elapsed % 60)
|
||||
|
||||
logging.info("=" * 50)
|
||||
logging.info(f"정보 수집 완료.")
|
||||
logging.info(f"소요 시간: {hours}시간 {minutes}분 {seconds}초")
|
||||
logging.info("=" * 50)
|
||||
from unified.collect_server_info import main as unified_main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python Intel_Server_info.py <ip_file>")
|
||||
sys.exit(1)
|
||||
|
||||
# intel_server 프로파일 사용
|
||||
sys.argv.append("intel_server")
|
||||
unified_main()
|
||||
|
||||
@@ -19,7 +19,7 @@ logging.basicConfig(
|
||||
def resolve_output_dir() -> Path:
|
||||
here = Path(__file__).resolve().parent
|
||||
base = here.parent if here.name.lower() == "scripts" else here
|
||||
out = base / "idrac_info"
|
||||
out = base / "temp" / "staging"
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
return out
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ if [ ! -f "$IP_FILE" ]; then
|
||||
fi
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR="idrac_info"
|
||||
OUTPUT_DIR="$(dirname "$0")/../temp/staging"
|
||||
mkdir -p $OUTPUT_DIR
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
|
||||
@@ -18,7 +18,7 @@ if [ ! -f "$IP_FILE" ]; then
|
||||
fi
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR="idrac_info"
|
||||
OUTPUT_DIR="$(dirname "$0")/../temp/staging"
|
||||
mkdir -p $OUTPUT_DIR
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
|
||||
@@ -18,7 +18,7 @@ if [ ! -f "$IP_FILE" ]; then
|
||||
fi
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR="idrac_info"
|
||||
OUTPUT_DIR="$(dirname "$0")/../temp/staging"
|
||||
mkdir -p $OUTPUT_DIR
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
|
||||
+19
-227
@@ -1,234 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
레거시 호환성 래퍼 - MAC_info.py
|
||||
실제 로직은 unified/collect_mac.py의 'default' 프로파일 사용
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
# unified 스크립트 경로 추가
|
||||
script_dir = Path(__file__).parent
|
||||
sys.path.insert(0, str(script_dir))
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 출력 디렉토리 결정
|
||||
def resolve_output_dir() -> Path:
|
||||
here = Path(__file__).resolve().parent
|
||||
if here.name.lower() == "scripts" and here.parent.name.lower() == "data":
|
||||
base = here.parent
|
||||
else:
|
||||
base = here.parent
|
||||
# unified 스크립트 임포트 및 실행
|
||||
from unified.collect_mac import main as unified_main
|
||||
|
||||
out = base / "idrac_info"
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
return out
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# iDRAC 계정
|
||||
IDRAC_USER = "root"
|
||||
IDRAC_PASS = "calvin"
|
||||
|
||||
|
||||
def run(cmd: list[str]) -> str:
|
||||
"""racadm 명령 실행 (stdout만 반환)"""
|
||||
try:
|
||||
return subprocess.getoutput(" ".join(cmd))
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def parse_single_value(pattern: str, text: str) -> Optional[str]:
|
||||
m = re.search(pattern, text, flags=re.IGNORECASE)
|
||||
return m.group(1).strip() if m else None
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# NIC MAC 수집 (가변 포트 대응)
|
||||
def extract_nic_macs(text: str, nic_type: str) -> List[str]:
|
||||
"""
|
||||
nic_type:
|
||||
- 'Integrated'
|
||||
- 'Embedded'
|
||||
"""
|
||||
pattern = re.compile(
|
||||
rf"NIC\.{nic_type}\.\d+-\d+-\d+.*?([0-9A-Fa-f]{{2}}(?::[0-9A-Fa-f]{{2}}){{5}})",
|
||||
re.IGNORECASE
|
||||
)
|
||||
return sorted(set(m.group(1) for m in pattern.finditer(text)))
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# hwinventory 블록 기반 Manufacturer 추출 (DIMM)
|
||||
def extract_memory_vendors(hwinventory: str) -> List[str]:
|
||||
vendors = set()
|
||||
blocks = re.split(r"\n\s*\n", hwinventory)
|
||||
|
||||
for block in blocks:
|
||||
if "[InstanceID: DIMM." in block:
|
||||
m = re.search(r"Manufacturer\s*=\s*(.+)", block, re.IGNORECASE)
|
||||
if m:
|
||||
vendors.add(m.group(1).strip())
|
||||
|
||||
return sorted(vendors)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# BOSS 디스크 Vendor 추출
|
||||
def extract_boss_vendors(hwinventory: str) -> List[str]:
|
||||
vendors = set()
|
||||
blocks = re.split(r"\n\s*\n", hwinventory)
|
||||
|
||||
for block in blocks:
|
||||
if "BOSS." in block:
|
||||
m = re.search(r"Manufacturer\s*=\s*(.+)", block, re.IGNORECASE)
|
||||
if m:
|
||||
vendors.add(m.group(1).strip())
|
||||
|
||||
return sorted(vendors)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 일반 Disk Vendor (BOSS 제외)
|
||||
DISK_PREFIXES = [
|
||||
"Disk.Bay.",
|
||||
"Disk.M.2.",
|
||||
"Disk.Direct.",
|
||||
"Disk.Slot."
|
||||
]
|
||||
|
||||
|
||||
def extract_disk_vendors_excluding_boss(hwinventory: str) -> List[str]:
|
||||
vendors = set()
|
||||
blocks = re.split(r"\n\s*\n", hwinventory)
|
||||
|
||||
for block in blocks:
|
||||
if "BOSS." in block:
|
||||
continue
|
||||
|
||||
for prefix in DISK_PREFIXES:
|
||||
if f"[InstanceID: {prefix}" in block:
|
||||
m = re.search(r"Manufacturer\s*=\s*(.+)", block, re.IGNORECASE)
|
||||
if m:
|
||||
vendors.add(m.group(1).strip())
|
||||
|
||||
return sorted(vendors)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
def fetch_idrac_info_one(ip: str, output_dir: Path) -> None:
|
||||
getsysinfo = run([
|
||||
"racadm", "-r", ip,
|
||||
"-u", IDRAC_USER,
|
||||
"-p", IDRAC_PASS,
|
||||
"getsysinfo"
|
||||
])
|
||||
|
||||
hwinventory = run([
|
||||
"racadm", "-r", ip,
|
||||
"-u", IDRAC_USER,
|
||||
"-p", IDRAC_PASS,
|
||||
"hwinventory"
|
||||
])
|
||||
|
||||
# ── Service Tag
|
||||
svc_tag = parse_single_value(r"SVC\s*Tag\s*=\s*(\S+)", getsysinfo)
|
||||
if not svc_tag:
|
||||
logging.error(f"[ERROR] SVC Tag 수집 실패: {ip}")
|
||||
return
|
||||
|
||||
# ── iDRAC MAC
|
||||
idrac_mac = parse_single_value(
|
||||
r"MAC Address\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo
|
||||
)
|
||||
|
||||
# ── NIC MAC
|
||||
integrated_macs = extract_nic_macs(getsysinfo, "Integrated")
|
||||
embedded_macs = extract_nic_macs(getsysinfo, "Embedded")
|
||||
|
||||
# ── Vendors
|
||||
memory_vendors = extract_memory_vendors(hwinventory)
|
||||
disk_vendors = extract_disk_vendors_excluding_boss(hwinventory)
|
||||
boss_vendors = extract_boss_vendors(hwinventory)
|
||||
|
||||
# ── 결과 파일 저장
|
||||
out_file = output_dir / f"{svc_tag}.txt"
|
||||
with out_file.open("w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(f"{svc_tag}\n")
|
||||
|
||||
for mac in integrated_macs:
|
||||
f.write(f"{mac}\n")
|
||||
|
||||
for mac in embedded_macs:
|
||||
f.write(f"{mac}\n")
|
||||
|
||||
f.write(f"{idrac_mac or ''}\n")
|
||||
|
||||
# Memory Vendors
|
||||
for v in memory_vendors:
|
||||
f.write(f"{v}\n")
|
||||
|
||||
# Disk Vendors (일반)
|
||||
for v in disk_vendors:
|
||||
f.write(f"{v}\n")
|
||||
|
||||
# BOSS Vendors (있을 때만, 벤더명만)
|
||||
for v in boss_vendors:
|
||||
f.write(f"{v}\n")
|
||||
|
||||
logging.info(f"[OK] {svc_tag} 수집 완료")
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
def main(ip_file: str) -> None:
|
||||
ip_path = Path(ip_file)
|
||||
if not ip_path.is_file():
|
||||
logging.error(f"[ERROR] IP 파일 없음: {ip_file}")
|
||||
return
|
||||
|
||||
output_dir = resolve_output_dir()
|
||||
|
||||
ips = [
|
||||
line.strip()
|
||||
for line in ip_path.read_text(encoding="utf-8").splitlines()
|
||||
if line.strip()
|
||||
]
|
||||
|
||||
if not ips:
|
||||
logging.error("[ERROR] IP 목록이 비어 있습니다.")
|
||||
return
|
||||
|
||||
for ip in ips:
|
||||
try:
|
||||
fetch_idrac_info_one(ip, output_dir)
|
||||
except Exception as e:
|
||||
logging.error(f"[ERROR] {ip} 처리 실패: {e}")
|
||||
|
||||
# Bash 스크립트와 동일하게 입력 IP 파일 삭제
|
||||
try:
|
||||
ip_path.unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logging.info("정보 수집 완료.")
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
import time
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
logging.error("Usage: python script.py <ip_file>")
|
||||
# 기존 인터페이스 유지: python MAC_info.py <ip_file>
|
||||
# 내부적으로 unified 스크립트 호출 (default 프로파일 사용)
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python MAC_info.py <ip_file>")
|
||||
sys.exit(1)
|
||||
|
||||
start = time.time()
|
||||
main(sys.argv[1])
|
||||
elapsed = int(time.time() - start)
|
||||
|
||||
h, m, s = elapsed // 3600, (elapsed % 3600) // 60, elapsed % 60
|
||||
logging.info(f"수집 완료 시간: {h} 시간, {m} 분, {s} 초.")
|
||||
|
||||
# 프로파일 이름 추가
|
||||
sys.argv.append("default")
|
||||
unified_main()
|
||||
|
||||
+16
-130
@@ -1,135 +1,21 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from dotenv import load_dotenv
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
import logging
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
레거시 호환성 래퍼 - PortGUID.py
|
||||
실제 로직은 unified/collect_guid.py의 'default' 프로파일 사용
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
script_dir = Path(__file__).parent
|
||||
sys.path.insert(0, str(script_dir))
|
||||
|
||||
# .env 파일에서 사용자 이름 및 비밀번호 설정
|
||||
load_dotenv()
|
||||
IDRAC_USER = os.getenv("IDRAC_USER")
|
||||
IDRAC_PASS = os.getenv("IDRAC_PASS")
|
||||
|
||||
def fetch_idrac_info(idrac_ip, output_dir):
|
||||
try:
|
||||
# 서비스 태그 가져오기 (get 제외)
|
||||
cmd_getsysinfo = f"racadm -r {idrac_ip} -u {IDRAC_USER} -p {IDRAC_PASS} getsysinfo"
|
||||
getsysinfo = subprocess.getoutput(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 = f"racadm -r {idrac_ip} -u {IDRAC_USER} -p {IDRAC_PASS} get InfiniBand.VndrConfigPage"
|
||||
output_list = subprocess.getoutput(cmd_list)
|
||||
|
||||
# InfiniBand.VndrConfigPage.<숫자> 및 Key 값 추출
|
||||
matches = re.findall(r"InfiniBand\.VndrConfigPage\.(\d+)\s+\[Key=InfiniBand\.Slot\.(\d+)-\d+#VndrConfigPage]", output_list)
|
||||
|
||||
# 결과를 저장할 파일 생성
|
||||
output_file = os.path.join(output_dir, f"{svc_tag}.txt")
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
with open(output_file, "w") as f:
|
||||
# 서비스 태그 저장
|
||||
f.write(f"{svc_tag}\n")
|
||||
|
||||
# 슬롯별 GUID를 딕셔너리로 수집
|
||||
slot_to_guid = {}
|
||||
slots_found = []
|
||||
|
||||
# 각 InfiniBand.VndrConfigPage.<숫자> 처리
|
||||
for number, slot in matches:
|
||||
cmd_detail = f"racadm -r {idrac_ip} -u {IDRAC_USER} -p {IDRAC_PASS} get InfiniBand.VndrConfigPage.{number}"
|
||||
output_detail = subprocess.getoutput(cmd_detail)
|
||||
|
||||
# PortGUID 값 추출
|
||||
match_guid = re.search(r"PortGUID=(\S+)", output_detail)
|
||||
port_guid = match_guid.group(1) if match_guid else "Not Found"
|
||||
|
||||
slot_to_guid[slot] = port_guid
|
||||
slots_found.append(slot)
|
||||
|
||||
# 환경변수에서 슬롯 우선순위 읽기
|
||||
slot_priority_str = os.getenv("GUID_SLOT_PRIORITY", "")
|
||||
|
||||
if slot_priority_str:
|
||||
# 사용자 지정 슬롯 우선순위 사용
|
||||
desired_order = [s.strip() for s in slot_priority_str.split(",") if s.strip()]
|
||||
logging.info(f"사용자 지정 슬롯 우선순위 사용: {desired_order}")
|
||||
else:
|
||||
# 기본: 발견된 순서대로
|
||||
desired_order = slots_found
|
||||
logging.info(f"기본 순서 사용 (발견된 순서): {desired_order}")
|
||||
|
||||
# 지정된 순서대로 파일에 기록 + GUID 합치기
|
||||
hex_guid_list = []
|
||||
for slot in desired_order:
|
||||
port_guid = slot_to_guid.get(slot, "Not Found")
|
||||
# Slot.<숫자>: <PortGUID> 형식으로 저장
|
||||
f.write(f"Slot.{slot}: {port_guid}\n")
|
||||
|
||||
# PortGUID를 0x 형식으로 변환하여 리스트에 추가
|
||||
if port_guid != "Not Found":
|
||||
hex_guid_list.append(f"0x{port_guid.replace(':', '').upper()}")
|
||||
|
||||
# 모든 PortGUID를 "GUID: 0x<GUID1>;0x<GUID2>" 형식으로 저장
|
||||
if hex_guid_list:
|
||||
f.write(f"GUID: {';'.join(hex_guid_list)}\n")
|
||||
logging.info(f"GUID 합치기 완료: {len(hex_guid_list)}개 슬롯")
|
||||
|
||||
logging.info(f"✅ Completed: {idrac_ip}")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing IP {idrac_ip}: {e}")
|
||||
|
||||
def main(ip_file):
|
||||
if not os.path.isfile(ip_file):
|
||||
logging.error(f"IP file {ip_file} does not exist.")
|
||||
return
|
||||
|
||||
# Output directory resolution
|
||||
# Try to find 'data/idrac_info' relative to this script
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# Assuming script is in data/scripts, parent is data
|
||||
data_dir = os.path.dirname(script_dir)
|
||||
output_dir = os.path.join(data_dir, "idrac_info")
|
||||
|
||||
# Fallback to current directory if structure is different
|
||||
if not os.path.basename(data_dir) == "data":
|
||||
output_dir = os.path.join(os.getcwd(), "idrac_info")
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
logging.info(f"Output directory set to: {output_dir}")
|
||||
|
||||
with open(ip_file, "r") as file:
|
||||
ip_addresses = [line.strip() for line in file.readlines()]
|
||||
|
||||
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):
|
||||
try:
|
||||
future.result()
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing task: {e}")
|
||||
from unified.collect_guid import main as unified_main
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
if len(sys.argv) != 2:
|
||||
logging.error("Usage: python script.py <ip_file>")
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python PortGUID.py <ip_file>")
|
||||
sys.exit(1)
|
||||
|
||||
ip_file = sys.argv[1]
|
||||
main(ip_file)
|
||||
|
||||
# default 프로파일 사용
|
||||
sys.argv.append("default")
|
||||
unified_main()
|
||||
|
||||
@@ -39,7 +39,7 @@ def resolve_output_dir() -> Path:
|
||||
else:
|
||||
base = here.parent
|
||||
|
||||
out = base / "idrac_info"
|
||||
out = base / "temp" / "staging"
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
return out
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ logging.basicConfig(
|
||||
def resolve_output_dir() -> Path:
|
||||
here = Path(__file__).resolve().parent
|
||||
base = here.parent if here.name.lower() == "scripts" else here
|
||||
out = base / "idrac_info"
|
||||
out = base / "temp" / "staging"
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
return out
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ if [ ! -f "$IP_FILE" ]; then
|
||||
fi
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR="idrac_info"
|
||||
OUTPUT_DIR="$(dirname "$0")/../temp/staging"
|
||||
mkdir -p $OUTPUT_DIR
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
|
||||
@@ -26,7 +26,7 @@ def resolve_output_dir() -> Path:
|
||||
else:
|
||||
base = here.parent
|
||||
|
||||
out = base / "idrac_info"
|
||||
out = base / "temp" / "staging"
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
return out
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ def resolve_output_dir() -> Path:
|
||||
base = here / "data"
|
||||
else:
|
||||
base = here # 그래도 현재 기준으로 생성
|
||||
out = base / "idrac_info"
|
||||
out = base / "temp" / "staging"
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
return out
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ if [ ! -f "$IP_FILE" ]; then
|
||||
fi
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR="idrac_info"
|
||||
OUTPUT_DIR="$(dirname "$0")/../temp/staging"
|
||||
mkdir -p $OUTPUT_DIR
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
|
||||
@@ -18,7 +18,7 @@ if [ ! -f "$IP_FILE" ]; then
|
||||
fi
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR="idrac_info"
|
||||
OUTPUT_DIR="$(dirname "$0")/../temp/staging"
|
||||
mkdir -p $OUTPUT_DIR
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
|
||||
@@ -18,7 +18,7 @@ if [ ! -f "$IP_FILE" ]; then
|
||||
fi
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR="idrac_info"
|
||||
OUTPUT_DIR="$(dirname "$0")/../temp/staging"
|
||||
mkdir -p $OUTPUT_DIR
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
|
||||
@@ -18,7 +18,7 @@ if [ ! -f "$IP_FILE" ]; then
|
||||
fi
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR="idrac_info"
|
||||
OUTPUT_DIR="$(dirname "$0")/../temp/staging"
|
||||
mkdir -p $OUTPUT_DIR
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
|
||||
@@ -1,244 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import argparse
|
||||
import concurrent.futures as futures
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
"""
|
||||
레거시 호환성 래퍼 - XE9680_H200_IB_10EA_MAC_info.py
|
||||
실제 로직은 unified/collect_mac.py의 'xe9680_h200_10ea' 프로파일 사용
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, Tuple, List
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
script_dir = Path(__file__).parent
|
||||
sys.path.insert(0, str(script_dir))
|
||||
|
||||
# ===== 설정: 기본 계정/비밀번호 (환경변수로 덮어쓰기 가능) =====
|
||||
IDRAC_USER = os.getenv("IDRAC_USER", "root")
|
||||
IDRAC_PASS = os.getenv("IDRAC_PASS", "calvin")
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
OUTPUT_DIR = BASE_DIR / "idrac_info"
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
MAC_RE = re.compile(r"([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}")
|
||||
|
||||
# --- 유틸: 명령 실행 ---
|
||||
def run(cmd: List[str]) -> Tuple[int, str]:
|
||||
try:
|
||||
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, check=False)
|
||||
return p.returncode, p.stdout or ""
|
||||
except Exception as e:
|
||||
return 1, f"__EXEC_ERROR__ {e}"
|
||||
|
||||
# --- 파서: getsysinfo/swinventory/hwinventory 에서 필요한 값 추출 ---
|
||||
def extract_first_mac(text: str) -> Optional[str]:
|
||||
m = MAC_RE.search(text)
|
||||
return m.group(0) if m else None
|
||||
|
||||
def parse_svc_tag(getsysinfo: str) -> Optional[str]:
|
||||
# 예: "SVC Tag = ABCDEF2"
|
||||
m = re.search(r"SVC\s*Tag\s*=\s*([^\s]+)", getsysinfo)
|
||||
return m.group(1).strip() if m else None
|
||||
|
||||
def parse_specific_mac_in_getsysinfo(getsysinfo: str, key: str) -> Optional[str]:
|
||||
"""
|
||||
key 예시:
|
||||
- "NIC.Integrated.1-1-1"
|
||||
- "NIC.Embedded.1-1-1"
|
||||
해당 라인에서 MAC 패턴 추출
|
||||
"""
|
||||
for line in getsysinfo.splitlines():
|
||||
if key in line:
|
||||
mac = extract_first_mac(line)
|
||||
if mac:
|
||||
return mac
|
||||
return None
|
||||
|
||||
def parse_idrac_mac(getsysinfo: str) -> Optional[str]:
|
||||
"""
|
||||
원본 스크립트는 "MAC Address = " 를 grep 했습니다.
|
||||
해당 라인(들) 중 첫 MAC을 사용합니다.
|
||||
"""
|
||||
lines = [ln for ln in getsysinfo.splitlines() if "MAC Address" in ln]
|
||||
for ln in lines:
|
||||
mac = extract_first_mac(ln)
|
||||
if mac:
|
||||
return mac
|
||||
return None
|
||||
|
||||
def parse_infiniband_slot_macs_from_swinventory(swinventory: str, slots: List[int]) -> Dict[int, Optional[str]]:
|
||||
"""
|
||||
bash의 awk 트릭(해당 FQDD의 이전 줄에서 MAC 추출)을 그대로 재현:
|
||||
- swinventory 라인을 순회하면서 직전 라인을 prev로 저장
|
||||
- line이 'FQDD = InfiniBand.Slot.<N>-1' 에 매치되면 prev에서 MAC 추출
|
||||
"""
|
||||
want = {s: None for s in slots}
|
||||
prev = ""
|
||||
for line in swinventory.splitlines():
|
||||
m = re.search(r"FQDD\s*=\s*InfiniBand\.Slot\.(\d+)-1", line)
|
||||
if m:
|
||||
slot = int(m.group(1))
|
||||
if slot in want and want[slot] is None:
|
||||
want[slot] = extract_first_mac(prev) # 이전 줄에서 MAC
|
||||
prev = line
|
||||
return want
|
||||
|
||||
def parse_memory_vendor_initials(hwinventory: str) -> str:
|
||||
"""
|
||||
원본: grep -A5 "DIMM" | grep "Manufacturer" | ... | sort | uniq | cut -c1
|
||||
- 간단화: DIMM 근처 5줄 윈도우에서 Manufacturer 라인 모으기
|
||||
- 제조사 첫 글자만 모아 중복 제거, 정렬 후 이어붙임
|
||||
"""
|
||||
lines = hwinventory.splitlines()
|
||||
idxs = [i for i, ln in enumerate(lines) if "DIMM" in ln]
|
||||
vendors: List[str] = []
|
||||
for i in idxs:
|
||||
window = lines[i : i + 6] # 본문 포함 6줄(= -A5)
|
||||
for ln in window:
|
||||
if "Manufacturer" in ln and "=" in ln:
|
||||
v = ln.split("=", 1)[1].strip()
|
||||
if v:
|
||||
vendors.append(v)
|
||||
initials = sorted({v[0] for v in vendors if v})
|
||||
return "".join(initials)
|
||||
|
||||
def parse_ssd_manufacturers(hwinventory: str) -> str:
|
||||
"""
|
||||
원본: grep -A3 "Disk.Bay" | grep "Manufacturer" | uniq
|
||||
- "Disk.Bay" 라인부터 3줄 윈도우 내 Manufacturer 라인 추출, 고유값 join
|
||||
"""
|
||||
lines = hwinventory.splitlines()
|
||||
vendors: List[str] = []
|
||||
for i, ln in enumerate(lines):
|
||||
if "Disk.Bay" in ln:
|
||||
window = lines[i : i + 4]
|
||||
for w in window:
|
||||
if "Manufacturer" in w and "=" in w:
|
||||
v = w.split("=", 1)[1].strip()
|
||||
if v:
|
||||
vendors.append(v)
|
||||
uniq = sorted({v for v in vendors})
|
||||
return ", ".join(uniq)
|
||||
|
||||
# --- 단일 IP 처리 ---
|
||||
def fetch_idrac_info_for_ip(ip: str) -> Tuple[str, bool, str]:
|
||||
"""
|
||||
returns: (ip, success, message)
|
||||
success=True면 파일 저장 완료
|
||||
"""
|
||||
ip = ip.strip()
|
||||
if not ip:
|
||||
return ip, False, "빈 라인"
|
||||
|
||||
# racadm 호출
|
||||
base = ["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS]
|
||||
|
||||
rc1, getsysinfo = run(base + ["getsysinfo"])
|
||||
rc2, swinventory = run(base + ["swinventory"])
|
||||
rc3, hwinventory = run(base + ["hwinventory"])
|
||||
|
||||
if rc1 != 0 and rc2 != 0 and rc3 != 0:
|
||||
return ip, False, f"모든 racadm 호출 실패:\n{getsysinfo or ''}\n{swinventory or ''}\n{hwinventory or ''}"
|
||||
|
||||
svc_tag = parse_svc_tag(getsysinfo) if getsysinfo else None
|
||||
if not svc_tag:
|
||||
return ip, False, "서비스 태그(SVC Tag) 추출 실패"
|
||||
|
||||
# 개별 필드 파싱
|
||||
integrated_1 = parse_specific_mac_in_getsysinfo(getsysinfo, "NIC.Integrated.1-1-1") if getsysinfo else None
|
||||
integrated_2 = parse_specific_mac_in_getsysinfo(getsysinfo, "NIC.Integrated.1-2-1") if getsysinfo else None
|
||||
integrated_3 = parse_specific_mac_in_getsysinfo(getsysinfo, "NIC.Integrated.1-3-1") if getsysinfo else None
|
||||
integrated_4 = parse_specific_mac_in_getsysinfo(getsysinfo, "NIC.Integrated.1-4-1") if getsysinfo else None
|
||||
|
||||
onboard_1 = parse_specific_mac_in_getsysinfo(getsysinfo, "NIC.Embedded.1-1-1") if getsysinfo else None
|
||||
onboard_2 = parse_specific_mac_in_getsysinfo(getsysinfo, "NIC.Embedded.2-1-1") if getsysinfo else None
|
||||
|
||||
idrac_mac = parse_idrac_mac(getsysinfo) if getsysinfo else None
|
||||
|
||||
# InfiniBand.Slot.<N>-1 MAC (이전 라인에서 MAC 추출) — 출력 순서 유지
|
||||
desired_slots = [38, 39, 37, 36, 32, 33, 34, 35, 31, 40]
|
||||
slot_macs = parse_infiniband_slot_macs_from_swinventory(swinventory, desired_slots) if swinventory else {s: None for s in desired_slots}
|
||||
|
||||
memory_initials = parse_memory_vendor_initials(hwinventory) if hwinventory else ""
|
||||
ssd_vendors = parse_ssd_manufacturers(hwinventory) if hwinventory else ""
|
||||
|
||||
# 저장
|
||||
out_path = OUTPUT_DIR / f"{svc_tag}.txt"
|
||||
with out_path.open("w", encoding="utf-8") as f:
|
||||
# 원본 스크립트와 동일한 출력 순서
|
||||
f.write(f"{svc_tag}\n")
|
||||
f.write(f"{integrated_1 or ''}\n")
|
||||
f.write(f"{integrated_2 or ''}\n")
|
||||
f.write(f"{integrated_3 or ''}\n")
|
||||
f.write(f"{integrated_4 or ''}\n")
|
||||
f.write(f"{onboard_1 or ''}\n")
|
||||
f.write(f"{onboard_2 or ''}\n")
|
||||
# 슬롯 고정 순서
|
||||
for sl in desired_slots:
|
||||
f.write(f"{slot_macs.get(sl) or ''}\n")
|
||||
f.write(f"{idrac_mac or ''}\n")
|
||||
f.write(f"{memory_initials}\n")
|
||||
f.write(f"{ssd_vendors}\n")
|
||||
|
||||
return ip, True, f"저장: {out_path}"
|
||||
|
||||
# --- 메인 ---
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="iDRAC 정보 수집 (Python 포팅)")
|
||||
parser.add_argument("ip_file", help="IP 주소 목록 파일 (줄 단위)")
|
||||
parser.add_argument("--workers", type=int, default=20, help="병렬 스레드 수 (기본 20)")
|
||||
args = parser.parse_args()
|
||||
|
||||
ip_file = Path(args.ip_file)
|
||||
if not ip_file.exists():
|
||||
logging.error(f"IP 파일이 존재하지 않습니다: {ip_file}")
|
||||
sys.exit(1)
|
||||
|
||||
with ip_file.open("r", encoding="utf-8") as f:
|
||||
ips = [ln.strip() for ln in f if ln.strip()]
|
||||
|
||||
if not ips:
|
||||
logging.error("IP 목록이 비어 있습니다.")
|
||||
sys.exit(1)
|
||||
|
||||
logging.info(f"[시작] 총 {len(ips)}대, workers={args.workers}, IDRAC_USER={IDRAC_USER}")
|
||||
|
||||
t0 = time.time()
|
||||
ok = 0
|
||||
fail = 0
|
||||
|
||||
# 병렬 수집
|
||||
with futures.ThreadPoolExecutor(max_workers=args.workers) as ex:
|
||||
futs = {ex.submit(fetch_idrac_info_for_ip, ip): ip for ip in ips}
|
||||
for fut in futures.as_completed(futs):
|
||||
ip = futs[fut]
|
||||
try:
|
||||
_ip, success, msg = fut.result()
|
||||
prefix = "[OK] " if success else "[FAIL] "
|
||||
if success:
|
||||
logging.info(prefix + ip + " - " + msg)
|
||||
else:
|
||||
logging.error(prefix + ip + " - " + msg)
|
||||
ok += int(success)
|
||||
fail += int(not success)
|
||||
except Exception as e:
|
||||
logging.error(f"[EXC] {ip} - {e}")
|
||||
fail += 1
|
||||
|
||||
dt = int(time.time() - t0)
|
||||
h, r = divmod(dt, 3600)
|
||||
m, s = divmod(r, 60)
|
||||
|
||||
logging.info("\n정보 수집 완료.")
|
||||
logging.info(f"성공 {ok}대 / 실패 {fail}대")
|
||||
logging.info(f"수집 완료 시간: {h} 시간, {m} 분, {s} 초.")
|
||||
from unified.collect_mac import main as unified_main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python XE9680_H200_IB_10EA_MAC_info.py <ip_file>")
|
||||
sys.exit(1)
|
||||
|
||||
# xe9680_h200_10ea 프로파일 사용
|
||||
sys.argv.append("xe9680_h200_10ea")
|
||||
unified_main()
|
||||
@@ -18,7 +18,7 @@ if [ ! -f "$IP_FILE" ]; then
|
||||
fi
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR="idrac_info"
|
||||
OUTPUT_DIR="$(dirname "$0")/../temp/staging"
|
||||
mkdir -p $OUTPUT_DIR
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import yaml
|
||||
import argparse
|
||||
import subprocess
|
||||
import logging
|
||||
import fnmatch
|
||||
from pathlib import Path
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
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")
|
||||
|
||||
# 경로 설정
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
PROFILES_FILE = BASE_DIR / "profiles" / "gpu_profiles.yaml"
|
||||
OUTPUT_DIR = BASE_DIR.parent / "temp" / "staging"
|
||||
|
||||
def load_profile(profile_name="default"):
|
||||
"""Load specific profile from gpu_profiles.yaml"""
|
||||
if not PROFILES_FILE.exists():
|
||||
logging.warning(f"Profile file not found at {PROFILES_FILE}. Using defaults.")
|
||||
return {}
|
||||
|
||||
try:
|
||||
with open(PROFILES_FILE, 'r', encoding='utf-8') as f:
|
||||
data = yaml.safe_load(f)
|
||||
profiles = data.get('profiles', {})
|
||||
return profiles.get(profile_name, {})
|
||||
except Exception as e:
|
||||
logging.error(f"Error loading profile: {e}")
|
||||
return {}
|
||||
|
||||
def resolve_output_dir() -> Path:
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
return OUTPUT_DIR
|
||||
|
||||
def parse_gpu_serials_from_hwinventory(hwinv_text: str, target_slots=None) -> dict:
|
||||
"""
|
||||
Parse GPU serials and filter based on target_slots patterns.
|
||||
target_slots: list of glob patterns (e.g. ["Video.Slot.*"])
|
||||
"""
|
||||
results = {}
|
||||
blocks = re.split(r"\n\s*\n", hwinv_text, flags=re.MULTILINE)
|
||||
|
||||
for block in blocks:
|
||||
# Check if it looks like a Video/GPU block
|
||||
if not re.search(r"\[?InstanceID:\s*Video\.Slot\.", block):
|
||||
continue
|
||||
|
||||
# Extract FQDD
|
||||
fqdd = None
|
||||
m_fqdd = re.search(r"^FQDD\s*=\s*([^\r\n]+)", block, flags=re.MULTILINE)
|
||||
if m_fqdd:
|
||||
fqdd = m_fqdd.group(1).strip()
|
||||
else:
|
||||
m_hdr = re.search(r"\[InstanceID:\s*(Video\.Slot\.[^\]\r\n]+)\]", block)
|
||||
if m_hdr:
|
||||
fqdd = m_hdr.group(1).strip()
|
||||
|
||||
if not fqdd:
|
||||
continue
|
||||
|
||||
# Filtering logic
|
||||
if target_slots:
|
||||
matched = False
|
||||
for pattern in target_slots:
|
||||
# fnmatch supports simple unix shell style wildcards (*, ?, [], etc)
|
||||
if fnmatch.fnmatch(fqdd, pattern):
|
||||
matched = True
|
||||
break
|
||||
if not matched:
|
||||
continue
|
||||
|
||||
# Extract SerialNumber
|
||||
m_sn = re.search(r"^SerialNumber\s*=\s*([^\r\n]+)", block, flags=re.MULTILINE)
|
||||
serial = m_sn.group(1).strip() if m_sn else "Not Found"
|
||||
|
||||
results[fqdd] = serial
|
||||
|
||||
return results
|
||||
|
||||
def get_slot_index(fqdd: str):
|
||||
"""
|
||||
Extract logic for sorting.
|
||||
Format usually: Video.Slot.X-Y -> returns (X, Y) tuple
|
||||
"""
|
||||
m = re.search(r"Video\.Slot\.(\d+)-(\d+)", fqdd)
|
||||
if m:
|
||||
return (int(m.group(1)), int(m.group(2)))
|
||||
m_simple = re.search(r"Video\.Slot\.(\d+)", fqdd)
|
||||
if m_simple:
|
||||
return (int(m_simple.group(1)), 0)
|
||||
return (float('inf'), float('inf'))
|
||||
|
||||
def fetch_idrac_info(idrac_ip: str, output_dir: Path, gpu_settings: dict) -> None:
|
||||
try:
|
||||
# 1. Get SVC Tag
|
||||
cmd_getsysinfo = [
|
||||
"racadm", "-r", idrac_ip, "-u", IDRAC_USER or "", "-p", IDRAC_PASS or "", "getsysinfo"
|
||||
]
|
||||
# execute without shell=True for security (if possible), but subprocess.getoutput uses shell
|
||||
# Here we use subprocess.run for better control if needed, but sticking to logic similar to original for now
|
||||
# Ideally: result = subprocess.run(cmd_getsysinfo, capture_output=True, text=True)
|
||||
# But let's stick to getoutput wrapper logic or use run compatible way
|
||||
|
||||
# Using subprocess.run is safer and modern
|
||||
res_sys = subprocess.run(cmd_getsysinfo, capture_output=True, text=True, encoding='utf-8', errors='ignore')
|
||||
getsysinfo = res_sys.stdout
|
||||
|
||||
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
|
||||
|
||||
# 2. Get Hardware Inventory
|
||||
cmd_hwinv = [
|
||||
"racadm", "-r", idrac_ip, "-u", IDRAC_USER or "", "-p", IDRAC_PASS or "", "hwinventory"
|
||||
]
|
||||
res_inv = subprocess.run(cmd_hwinv, capture_output=True, text=True, encoding='utf-8', errors='ignore')
|
||||
hwinv_text = res_inv.stdout
|
||||
|
||||
# 3. Parse and Filter
|
||||
target_slots = gpu_settings.get('target_slots', ["Video.Slot.*"])
|
||||
gpu_map = parse_gpu_serials_from_hwinventory(hwinv_text, target_slots)
|
||||
|
||||
# 4. Sort
|
||||
sort_order = gpu_settings.get('sort_order', 'slot_index')
|
||||
|
||||
if sort_order == 'slot_index':
|
||||
sorted_keys = sorted(gpu_map.keys(), key=lambda k: (get_slot_index(k), k))
|
||||
elif sort_order == 'serial':
|
||||
sorted_keys = sorted(gpu_map.keys(), key=lambda k: gpu_map[k])
|
||||
else:
|
||||
sorted_keys = sorted(gpu_map.keys())
|
||||
|
||||
# 5. Write Output (Text format)
|
||||
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")
|
||||
|
||||
if not gpu_map:
|
||||
f.write("No matching GPU inventory found.\n")
|
||||
return
|
||||
|
||||
for fqdd in sorted_keys:
|
||||
serial = gpu_map[fqdd]
|
||||
f.write(f"{fqdd}: {serial}\n")
|
||||
|
||||
# Serials only line
|
||||
serials_only = [gpu_map[k] for k in sorted_keys if gpu_map[k] != "Not Found"]
|
||||
if serials_only:
|
||||
f.write(f"SERIALS: {';'.join(serials_only)}\n")
|
||||
|
||||
logging.info(f"Saved {output_file}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing IP {idrac_ip}: {e}")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Collect GPU Serial Info")
|
||||
parser.add_argument("ip_file", help="File containing list of IP addresses")
|
||||
parser.add_argument("--profile", default="default", help="Profile name from gpu_profiles.yaml")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
ip_path = Path(args.ip_file)
|
||||
if not ip_path.is_file():
|
||||
logging.error(f"IP file {args.ip_file} does not exist.")
|
||||
return
|
||||
|
||||
# Load Profile
|
||||
profile = load_profile(args.profile)
|
||||
logging.info(f"Loaded profile: {args.profile}")
|
||||
|
||||
gpu_settings = profile.get('gpu_settings', {})
|
||||
|
||||
output_dir = resolve_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=50) as executor:
|
||||
future_to_ip = {
|
||||
executor.submit(fetch_idrac_info, ip, output_dir, gpu_settings): ip
|
||||
for ip in ip_addresses
|
||||
}
|
||||
for future in as_completed(future_to_ip):
|
||||
ip = future_to_ip[future]
|
||||
try:
|
||||
future.result()
|
||||
except Exception as e:
|
||||
logging.error(f"Error on {ip}: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -23,7 +23,14 @@ logging.basicConfig(
|
||||
IDRAC_USER = os.getenv("IDRAC_USER", "root")
|
||||
IDRAC_PASS = os.getenv("IDRAC_PASS", "calvin")
|
||||
|
||||
OUTPUT_DIR = Path("idrac_info")
|
||||
# Resolve output dir relative to script location or CWD
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
if BASE_DIR.name == "scripts":
|
||||
DATA_DIR = BASE_DIR.parent
|
||||
else:
|
||||
DATA_DIR = Path("data").resolve()
|
||||
|
||||
OUTPUT_DIR = DATA_DIR / "temp" / "staging"
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
MAC_RE = re.compile(r"([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}")
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
"""
|
||||
공통 라이브러리 패키지
|
||||
"""
|
||||
__version__ = "1.0.0"
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
iDRAC 클라이언트 - racadm 명령 실행 및 결과 캐싱
|
||||
"""
|
||||
from typing import Optional, Dict, Tuple
|
||||
import subprocess
|
||||
import os
|
||||
import logging
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
|
||||
class IDRACClient:
|
||||
"""
|
||||
racadm 명령 실행 및 결과 캐싱을 담당하는 클라이언트
|
||||
"""
|
||||
|
||||
def __init__(self, ip: str, user: str = None, password: str = None):
|
||||
"""
|
||||
Args:
|
||||
ip: iDRAC IP 주소
|
||||
user: iDRAC 사용자명 (기본값: 환경변수 IDRAC_USER 또는 'root')
|
||||
password: iDRAC 비밀번호 (기본값: 환경변수 IDRAC_PASS 또는 'calvin')
|
||||
"""
|
||||
self.ip = ip
|
||||
self.user = user or os.getenv("IDRAC_USER", "root")
|
||||
self.password = password or os.getenv("IDRAC_PASS", "calvin")
|
||||
self._cache: Dict[str, str] = {} # 중복 호출 방지 캐시
|
||||
|
||||
def run_command(self, command: str) -> Tuple[int, str]:
|
||||
"""
|
||||
racadm 명령 실행
|
||||
|
||||
Args:
|
||||
command: 실행할 racadm 명령 (예: "getsysinfo", "get bios.BiosBootSettings")
|
||||
|
||||
Returns:
|
||||
(return_code, output) 튜플
|
||||
"""
|
||||
cmd = f"racadm -r {self.ip} -u {self.user} -p {self.password} {command}"
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
timeout=30 # 30초 타임아웃
|
||||
)
|
||||
return result.returncode, result.stdout
|
||||
except subprocess.TimeoutExpired:
|
||||
logging.error(f"[{self.ip}] 명령 타임아웃: {command}")
|
||||
return 1, ""
|
||||
except Exception as e:
|
||||
logging.error(f"[{self.ip}] 명령 실행 오류: {e}")
|
||||
return 1, f"Error: {e}"
|
||||
|
||||
def get_sysinfo(self) -> str:
|
||||
"""
|
||||
getsysinfo 명령 실행 (캐싱)
|
||||
|
||||
Returns:
|
||||
getsysinfo 출력 결과
|
||||
"""
|
||||
if "sysinfo" not in self._cache:
|
||||
_, output = self.run_command("getsysinfo")
|
||||
self._cache["sysinfo"] = output
|
||||
return self._cache["sysinfo"]
|
||||
|
||||
def get_hwinventory(self) -> str:
|
||||
"""
|
||||
hwinventory 명령 실행 (캐싱)
|
||||
|
||||
Returns:
|
||||
hwinventory 출력 결과
|
||||
"""
|
||||
if "hwinventory" not in self._cache:
|
||||
_, output = self.run_command("hwinventory")
|
||||
self._cache["hwinventory"] = output
|
||||
return self._cache["hwinventory"]
|
||||
|
||||
def get_swinventory(self) -> str:
|
||||
"""
|
||||
swinventory 명령 실행 (캐싱)
|
||||
|
||||
Returns:
|
||||
swinventory 출력 결과
|
||||
"""
|
||||
if "swinventory" not in self._cache:
|
||||
_, output = self.run_command("swinventory")
|
||||
self._cache["swinventory"] = output
|
||||
return self._cache["swinventory"]
|
||||
|
||||
|
||||
def get_infiniband_pages(self) -> str:
|
||||
"""
|
||||
InfiniBand 설정 페이지 목록 조회 (InfiniBand.VndrConfigPage)
|
||||
|
||||
Returns:
|
||||
racadm get InfiniBand.VndrConfigPage 출력
|
||||
"""
|
||||
if "infiniband_pages" not in self._cache:
|
||||
_, output = self.run_command("get InfiniBand.VndrConfigPage")
|
||||
self._cache["infiniband_pages"] = output
|
||||
return self._cache["infiniband_pages"]
|
||||
|
||||
def get_infiniband_page_detail(self, page_number: str) -> str:
|
||||
"""
|
||||
특정 InfiniBand 설정 페이지 상세 조회
|
||||
|
||||
Args:
|
||||
page_number: 페이지 번호 (예: "1")
|
||||
|
||||
Returns:
|
||||
racadm get InfiniBand.VndrConfigPage.{page_number} 출력
|
||||
"""
|
||||
cache_key = f"infiniband_page_{page_number}"
|
||||
if cache_key not in self._cache:
|
||||
_, output = self.run_command(f"get InfiniBand.VndrConfigPage.{page_number}")
|
||||
self._cache[cache_key] = output
|
||||
return self._cache[cache_key]
|
||||
|
||||
def clear_cache(self):
|
||||
"""캐시 초기화"""
|
||||
self._cache.clear()
|
||||
@@ -0,0 +1,291 @@
|
||||
"""
|
||||
racadm 출력 파싱 유틸리티
|
||||
"""
|
||||
import re
|
||||
from typing import List, Optional, Dict, Tuple
|
||||
|
||||
|
||||
class InfoParser:
|
||||
"""racadm 명령 출력 파싱 유틸리티"""
|
||||
|
||||
# Word boundary to ensure we don't match partial GUIDs (which are longer)
|
||||
MAC_PATTERN = re.compile(r"\b([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}\b")
|
||||
|
||||
@staticmethod
|
||||
def extract_service_tag(sysinfo: str) -> Optional[str]:
|
||||
"""
|
||||
서비스 태그 추출
|
||||
|
||||
Args:
|
||||
sysinfo: getsysinfo 명령 출력
|
||||
|
||||
Returns:
|
||||
서비스 태그 또는 None
|
||||
"""
|
||||
m = re.search(r"SVC\s*Tag\s*=\s*(\S+)", sysinfo, re.IGNORECASE)
|
||||
return m.group(1).strip() if m else None
|
||||
|
||||
@staticmethod
|
||||
def extract_nic_macs(sysinfo: str, nic_patterns: List[str]) -> Dict[str, Optional[str]]:
|
||||
"""
|
||||
NIC MAC 주소 추출
|
||||
|
||||
Args:
|
||||
sysinfo: getsysinfo 명령 출력
|
||||
nic_patterns: NIC 패턴 리스트 (예: ["NIC.Integrated.1-1-1", "NIC.Embedded.1-1-1"])
|
||||
|
||||
Returns:
|
||||
{pattern: mac_address} 딕셔너리
|
||||
"""
|
||||
results = {}
|
||||
for pattern in nic_patterns:
|
||||
for line in sysinfo.splitlines():
|
||||
if pattern in line:
|
||||
mac = InfoParser.MAC_PATTERN.search(line)
|
||||
results[pattern] = mac.group(0) if mac else None
|
||||
break
|
||||
else:
|
||||
results[pattern] = None
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def extract_nic_macs_from_hwinventory(hwinventory: str, nic_patterns: List[str]) -> Dict[str, Optional[str]]:
|
||||
"""
|
||||
hwinventory에서 NIC MAC 주소 추출
|
||||
|
||||
Args:
|
||||
hwinventory: hwinventory 명령 출력
|
||||
nic_patterns: NIC 패턴 리스트 (예: ["NIC.Integrated.1-1-1", "NIC.Slot.1-1-1"])
|
||||
|
||||
Returns:
|
||||
{pattern: mac_address} 딕셔너리
|
||||
"""
|
||||
results = {pattern: None for pattern in nic_patterns}
|
||||
|
||||
# hwinventory를 블록 단위로 분리 (빈 줄로 구분)
|
||||
blocks = re.split(r"\n\s*\n", hwinventory)
|
||||
|
||||
for block in blocks:
|
||||
# 블록 내에서 FQDD 또는 InstanceID 확인
|
||||
fqdd = None
|
||||
|
||||
# FQDD = NIC.Integrated.1-1-1 또는 [InstanceID: NIC.Integrated.1-1-1] 형식 찾기
|
||||
m_fqdd = re.search(r"FQDD\s*=\s*(\S+)", block, re.IGNORECASE)
|
||||
if m_fqdd:
|
||||
fqdd = m_fqdd.group(1).strip()
|
||||
# FQDD 키워드가 없을 경우를 대비해 InstanceID 헤더도 확인
|
||||
elif "[InstanceID:" in block:
|
||||
m_inst = re.search(r"InstanceID:\s*([^\]]+)", block)
|
||||
if m_inst:
|
||||
fqdd = m_inst.group(1).strip()
|
||||
|
||||
if not fqdd:
|
||||
continue
|
||||
|
||||
# 요청된 패턴 중 하나라도 포함하는지 확인 (정확한 매칭을 위해 수정 가능하나, 보통 포함 관계면 충분)
|
||||
# 여기서는 패턴이 FQDD와 정확히 일치하거나 FQDD가 패턴을 포함하는 경우를 찾음
|
||||
matched_pattern = None
|
||||
for pattern in nic_patterns:
|
||||
# 대소문자 무시 비교
|
||||
if pattern.lower() == fqdd.lower():
|
||||
matched_pattern = pattern
|
||||
break
|
||||
|
||||
# 정확히 일치하는 패턴을 못 찾았다면 다음 블록으로
|
||||
if not matched_pattern:
|
||||
continue
|
||||
|
||||
# MAC 주소 찾기
|
||||
# Ethernet Address = AA:BB:CC:DD:EE:FF
|
||||
# CurrentMACAddress = AA:BB:CC:DD:EE:FF
|
||||
# MAC Address = AA:BB:CC:DD:EE:FF
|
||||
mac_match = InfoParser.MAC_PATTERN.search(block)
|
||||
|
||||
if mac_match:
|
||||
results[matched_pattern] = mac_match.group(0)
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def extract_idrac_mac(sysinfo: str) -> Optional[str]:
|
||||
"""
|
||||
iDRAC MAC 주소 추출
|
||||
|
||||
Args:
|
||||
sysinfo: getsysinfo 명령 출력
|
||||
|
||||
Returns:
|
||||
iDRAC MAC 주소 또는 None
|
||||
"""
|
||||
for line in sysinfo.splitlines():
|
||||
if "MAC Address" in line:
|
||||
mac = InfoParser.MAC_PATTERN.search(line)
|
||||
if mac:
|
||||
return mac.group(0)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def extract_vendors(hwinventory: str, component_prefix: str) -> List[str]:
|
||||
"""
|
||||
컴포넌트 벤더 추출
|
||||
|
||||
Args:
|
||||
hwinventory: hwinventory 명령 출력
|
||||
component_prefix: 컴포넌트 접두사 ("DIMM", "Disk.Bay", "BOSS" 등)
|
||||
|
||||
Returns:
|
||||
벤더 리스트 (중복 제거, 정렬됨)
|
||||
"""
|
||||
vendors = set()
|
||||
blocks = re.split(r"\n\s*\n", hwinventory)
|
||||
|
||||
for block in blocks:
|
||||
# Check if this block is for the target component using InstanceID or FQDD
|
||||
is_target_component = False
|
||||
|
||||
# Check InstanceID header: [InstanceID: DIMM.Socket.A1]
|
||||
if f"[InstanceID: {component_prefix}" in block:
|
||||
is_target_component = True
|
||||
|
||||
# Also check FQDD line if InstanceID header check fails (fallback)
|
||||
elif not is_target_component:
|
||||
m_fqdd = re.search(r"^FQDD\s*=\s*(\S+)", block, re.MULTILINE)
|
||||
if m_fqdd and component_prefix in m_fqdd.group(1):
|
||||
is_target_component = True
|
||||
|
||||
if is_target_component:
|
||||
m = re.search(r"Manufacturer\s*=\s*(.+)", block, re.IGNORECASE)
|
||||
if m:
|
||||
vendor = m.group(1).strip()
|
||||
# Filter out "Dell Inc." if user considers it wrong/noise for these components?
|
||||
# The user specifically complained about "Dell Inc." being WRONG.
|
||||
# However, if the DIMM *is* Dell, we should report it.
|
||||
# But the likely cause of "Dell Inc." appearing when it shouldn't is the System block match.
|
||||
# With this stricter check, the System block (InstanceID: System.Embedded.1) will NOT match "DIMM".
|
||||
vendors.add(vendor)
|
||||
|
||||
return sorted(vendors)
|
||||
|
||||
@staticmethod
|
||||
def extract_infiniband_page_map(pages_output: str) -> List[Tuple[str, str]]:
|
||||
"""
|
||||
InfiniBand 페이지 번호와 슬롯 번호 매핑 추출
|
||||
|
||||
Args:
|
||||
pages_output: get InfiniBand.VndrConfigPage 출력
|
||||
|
||||
Returns:
|
||||
[(page_number, slot_number), ...] 리스트
|
||||
"""
|
||||
# InfiniBand.VndrConfigPage.1 [Key=InfiniBand.Slot.38-1#VndrConfigPage]
|
||||
matches = re.findall(
|
||||
r"InfiniBand\.VndrConfigPage\.(\d+)\s+\[Key=InfiniBand\.Slot\.(\d+)-\d+#VndrConfigPage]",
|
||||
pages_output
|
||||
)
|
||||
return matches
|
||||
|
||||
@staticmethod
|
||||
def extract_port_guid(page_detail: str) -> Optional[str]:
|
||||
"""
|
||||
InfiniBand 페이지 상세에서 PortGUID 추출
|
||||
|
||||
Args:
|
||||
page_detail: get InfiniBand.VndrConfigPage.{N} 출력
|
||||
|
||||
Returns:
|
||||
PortGUID 값 또는 None
|
||||
"""
|
||||
m = re.search(r"PortGUID=(\S+)", page_detail)
|
||||
return m.group(1) if m else None
|
||||
|
||||
@staticmethod
|
||||
def extract_value_by_pattern(text: str, pattern: str) -> Optional[str]:
|
||||
"""
|
||||
패턴으로 값 추출 (= 뒤의 값)
|
||||
|
||||
Args:
|
||||
text: 검색할 텍스트
|
||||
pattern: 검색 패턴
|
||||
|
||||
Returns:
|
||||
추출된 값 또는 None
|
||||
"""
|
||||
for line in text.splitlines():
|
||||
if pattern.lower() in line.lower():
|
||||
if '=' in line:
|
||||
return line.split('=', 1)[1].strip()
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def extract_infiniband_macs_from_swinventory(swinventory: str, target_slots: List[int]) -> Dict[int, str]:
|
||||
"""
|
||||
swinventory에서 InfiniBand 슬롯의 MAC 주소 추출
|
||||
(FQDD 라인 바로 윗줄에 MAC 주소가 있다는 가정 - 레거시 스크립트 로직)
|
||||
|
||||
Args:
|
||||
swinventory: swinventory 명령 출력
|
||||
target_slots: 추출할 슬롯 번호 리스트
|
||||
|
||||
Returns:
|
||||
{slot: mac_address} 딕셔너리
|
||||
"""
|
||||
results = {}
|
||||
prev_line = ""
|
||||
|
||||
# 슬롯별 패턴 미리 생성 (예: "FQDD = InfiniBand.Slot.31-1")
|
||||
slot_patterns = {slot: f"FQDD = InfiniBand.Slot.{slot}-1" for slot in target_slots}
|
||||
|
||||
for line in swinventory.splitlines():
|
||||
for slot, pattern in slot_patterns.items():
|
||||
if pattern in line:
|
||||
# 이전 라인에서 MAC 찾기
|
||||
mac = InfoParser.MAC_PATTERN.search(prev_line)
|
||||
if mac:
|
||||
results[slot] = mac.group(0)
|
||||
prev_line = line
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def extract_gpu_serials(hwinventory: str) -> Dict[str, str]:
|
||||
"""
|
||||
iDRAC hwinventory 전체 텍스트에서 GPU(Video.Slot.*) 블록을 찾아
|
||||
{FQDD(or InstanceID): SerialNumber} 딕셔너리로 반환.
|
||||
|
||||
Args:
|
||||
hwinventory: hwinventory 명령 출력
|
||||
|
||||
Returns:
|
||||
{fqdd: serial} 딕셔너리
|
||||
"""
|
||||
results = {}
|
||||
|
||||
# 빈 줄 기준 블록 분할(여러 개의 개행을 하나의 경계로)
|
||||
blocks = re.split(r"\n\s*\n", hwinventory, flags=re.MULTILINE)
|
||||
|
||||
for block in blocks:
|
||||
# GPU(Video) 블록만 처리
|
||||
# Video.Slot.X 또는 유사 패턴
|
||||
if not re.search(r"\[?InstanceID:\s*Video\.Slot\.", block):
|
||||
continue
|
||||
|
||||
# FQDD 우선, 없으면 [InstanceID: ...]에서 추출
|
||||
fqdd = None
|
||||
m_fqdd = re.search(r"^FQDD\s*=\s*([^\r\n]+)", block, flags=re.MULTILINE)
|
||||
if m_fqdd:
|
||||
fqdd = m_fqdd.group(1).strip()
|
||||
else:
|
||||
m_hdr = re.search(r"\[InstanceID:\s*(Video\.Slot\.[^\]\r\n]+)\]", block)
|
||||
if m_hdr:
|
||||
fqdd = m_hdr.group(1).strip()
|
||||
|
||||
if not fqdd:
|
||||
continue
|
||||
|
||||
# SerialNumber 추출
|
||||
m_sn = re.search(r"^SerialNumber\s*=\s*([^\r\n]+)", block, flags=re.MULTILINE)
|
||||
serial = m_sn.group(1).strip() if m_sn else "Not Found"
|
||||
|
||||
results[fqdd] = serial
|
||||
|
||||
return results
|
||||
@@ -0,0 +1,22 @@
|
||||
# GPU 서버 정보 수집 프로파일 (GUID + GPU Serial)
|
||||
profiles:
|
||||
default:
|
||||
description: "기본 GPU 시리얼 수집 설정"
|
||||
# GUID 관련 설정 (제거됨)
|
||||
# slot_priority: [38, 39, 37, 36, 32, 33, 34, 35, 31, 40]
|
||||
|
||||
# GPU Serial 관련 설정
|
||||
gpu_settings:
|
||||
target_slots: ["Video.Slot.*"]
|
||||
sort_order: "slot_index" # slot_index, serial, etc.
|
||||
|
||||
output_format: "combined"
|
||||
|
||||
h100_cluster:
|
||||
description: "H100 클러스터용 설정"
|
||||
slot_priority: [1, 2, 3, 4]
|
||||
gpu_settings:
|
||||
target_slots: ["Video.Slot.0-1", "Video.Slot.1-1"]
|
||||
sort_order: "slot_index"
|
||||
|
||||
output_format: "combined"
|
||||
@@ -0,0 +1,11 @@
|
||||
# GUID 수집 프로파일 정의
|
||||
profiles:
|
||||
default:
|
||||
description: "기본 GUID 수집"
|
||||
slot_priority: [38, 39, 37, 36, 32, 33, 34, 35, 31, 40]
|
||||
output_format: "combined" # combined 또는 individual
|
||||
|
||||
custom_priority:
|
||||
description: "사용자 정의 슬롯 우선순위"
|
||||
slot_priority: [] # 런타임에 환경변수 GUID_SLOT_PRIORITY에서 읽음
|
||||
output_format: "combined"
|
||||
@@ -0,0 +1,107 @@
|
||||
profiles:
|
||||
16G_R760XD2:
|
||||
description: 기본 MAC 수집 (Integrated + Embedded)
|
||||
exclude_boss: true
|
||||
include_disk_vendor: true
|
||||
include_idrac_mac: true
|
||||
include_memory_vendor: true
|
||||
nic_patterns:
|
||||
- NIC.Integrated.1-1-1
|
||||
- NIC.Integrated.1-2-1
|
||||
- NIC.Slot.3-1-1
|
||||
- NIC.Slot.3-2-1
|
||||
- NIC.Embedded.1-1-1
|
||||
- NIC.Embedded.2-1-1
|
||||
16G_T8/T8A:
|
||||
description: 새 프로파일
|
||||
extends: default
|
||||
16G_TYPE4,6:
|
||||
description: 기본 MAC 수집 (Integrated + Embedded)
|
||||
exclude_boss: true
|
||||
include_disk_vendor: true
|
||||
include_idrac_mac: true
|
||||
include_memory_vendor: true
|
||||
nic_patterns:
|
||||
- NIC.Integrated.1-1-1
|
||||
- NIC.Integrated.1-2-1
|
||||
- NIC.Embedded.1-1-1
|
||||
- NIC.Embedded.2-1-1
|
||||
16G_TYPE8,8A:
|
||||
description: 기본 MAC 수집 (Integrated + Embedded)
|
||||
exclude_boss: true
|
||||
include_disk_vendor: true
|
||||
include_idrac_mac: true
|
||||
include_memory_vendor: true
|
||||
nic_patterns:
|
||||
- NIC.Integrated.1-1-1
|
||||
- NIC.Integrated.1-2-1
|
||||
- NIC.Embedded.1-1-1
|
||||
- NIC.Embedded.2-1-1
|
||||
TY11_60ea:
|
||||
description: 기본 MAC 수집 (Integrated + Embedded)
|
||||
exclude_boss: true
|
||||
include_disk_vendor: true
|
||||
include_idrac_mac: true
|
||||
include_memory_vendor: true
|
||||
nic_patterns:
|
||||
- NIC.Integrated.1-1-1
|
||||
- NIC.Integrated.1-2-1
|
||||
- NIC.Slot.1-1-1
|
||||
- NIC.Slot.1-2-1
|
||||
- NIC.Slot.2-1-1
|
||||
- NIC.Slot.2-2-1
|
||||
- NIC.Embedded.1-1-1
|
||||
- NIC.Embedded.2-1-1
|
||||
default:
|
||||
description: 기본 MAC 수집 (Integrated + Embedded)
|
||||
exclude_boss: true
|
||||
include_disk_vendor: true
|
||||
include_idrac_mac: true
|
||||
include_memory_vendor: true
|
||||
nic_patterns:
|
||||
- NIC.Integrated.1-1-1
|
||||
- NIC.Integrated.1-2-1
|
||||
- NIC.Embedded.1-1-1
|
||||
- NIC.Embedded.2-1-1
|
||||
jp_54ea:
|
||||
description: JP 54EA 서버
|
||||
extends: default
|
||||
jp_type11a:
|
||||
description: JP_TYPE11A
|
||||
extends: default
|
||||
nic_patterns:
|
||||
- NIC.Integrated.1-1-1
|
||||
- NIC.Integrated.1-2-1
|
||||
- NIC.Embedded.1-1-1
|
||||
- NIC.Embedded.2-1-1
|
||||
- NIC.Slot.1-1-1
|
||||
- NIC.Slot.1-2-1
|
||||
- NIC.Slot.2-1-1
|
||||
- NIC.Slot.2-2-1
|
||||
sp_18ea:
|
||||
description: SP 18EA 서버
|
||||
extends: default
|
||||
type11:
|
||||
description: TYPE11 서버
|
||||
extends: default
|
||||
xe9680_h200_10ea:
|
||||
description: XE9680 H200 10EA InfiniBand
|
||||
extends: default
|
||||
infiniband_slots:
|
||||
- 38
|
||||
- 39
|
||||
- 37
|
||||
- 36
|
||||
- 32
|
||||
- 33
|
||||
- 34
|
||||
- 35
|
||||
- 31
|
||||
- 40
|
||||
nic_patterns:
|
||||
- NIC.Integrated.1-1-1
|
||||
- NIC.Integrated.1-2-1
|
||||
- NIC.Integrated.1-3-1
|
||||
- NIC.Integrated.1-4-1
|
||||
- NIC.Embedded.1-1-1
|
||||
- NIC.Embedded.2-1-1
|
||||
@@ -0,0 +1,676 @@
|
||||
profiles:
|
||||
JYH_AMD:
|
||||
bios_settings:
|
||||
- command: get bios.BiosBootSettings
|
||||
name: BIOS Boot Mode
|
||||
pattern: BootMode
|
||||
- command: get bios.SysProfileSettings
|
||||
name: System Profile
|
||||
pattern: SysProfile=
|
||||
- command: get bios.SysProfileSettings
|
||||
name: Memory Frequency
|
||||
pattern: MemFrequency
|
||||
- command: get bios.SysProfileSettings
|
||||
name: Turbo Boost
|
||||
pattern: ProcTurboMode
|
||||
- command: get bios.SysProfileSettings
|
||||
name: C-States
|
||||
pattern: ProcCStates
|
||||
- command: get bios.ProcSettings
|
||||
name: Logical Processor
|
||||
pattern: LogicalProc
|
||||
- command: get bios.ProcSettings
|
||||
name: Virtualization Technology
|
||||
pattern: ProcVirtualization
|
||||
- command: get bios.MemSettings
|
||||
name: DramRefreshDelay
|
||||
pattern: DramRefreshDelay
|
||||
- command: get bios.ProcSettings
|
||||
name: x2APIC Mode
|
||||
pattern: ProcX2Apic
|
||||
- command: get bios.MemSettings
|
||||
name: DIMM Self Healing
|
||||
pattern: PPROnUCE
|
||||
- command: get bios.MemSettings
|
||||
name: Correctable Error Logging
|
||||
pattern: CECriticalSEL
|
||||
- command: get System.ThermalSettings
|
||||
name: Thermal Profile Optimization
|
||||
pattern: ThermalProfile
|
||||
- command: get Bios.IntegratedDevices
|
||||
name: SR-IOV Global Enable
|
||||
pattern: SriovGlobalEnable
|
||||
- command: get bios.MiscSettings
|
||||
name: F1/F2 Prompt on Error
|
||||
pattern: ErrPrompt
|
||||
- command: get iDRAC.ASRConfig
|
||||
name: ASR Config
|
||||
pattern: Enable=
|
||||
- command: get Bios.IntegratedDevices.OsWatchdogTimer
|
||||
name: Os watchdof timer
|
||||
pattern: OswatchdogTimer=
|
||||
description: 기본 서버 정보 수집 (BIOS, iDRAC, RAID)
|
||||
extends: minimal
|
||||
firmware:
|
||||
- command: get NIC.FrmwImgMenu.1
|
||||
name: NIC(XE810) Integrated Firmware
|
||||
pattern: '#FamilyVersion'
|
||||
- command: get NIC.FrmwImgMenu.3
|
||||
name: NIC(BCM5720) Integrated Firmware
|
||||
pattern: '#FamilyVersion'
|
||||
- command: hwinventory
|
||||
name: RAID Controller Firmware
|
||||
pattern: ControllerFirmwareVersion
|
||||
idrac_settings:
|
||||
- command: get iDRAC.Time.Timezone
|
||||
name: Timezone
|
||||
pattern: Timezone
|
||||
- command: get iDRAC.CurrentNIC
|
||||
name: IPMI LAN Selection
|
||||
pattern: ActiveNIC
|
||||
- command: get iDRAC.CurrentIPv4
|
||||
name: IPMI IPv4 DHCP
|
||||
pattern: DHCPEnable
|
||||
- command: get iDRAC.CurrentIPv6
|
||||
name: IPMI IPv6 Enable
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.Redfish.Enable
|
||||
name: Redfish Support
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.SSH
|
||||
name: SSH Support
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.USERDomain.1.Name
|
||||
name: AD User Domain Name
|
||||
pattern: Name
|
||||
- command: get iDRAC.ActiveDirectory.DomainController1
|
||||
name: SC Server Address
|
||||
pattern: DomainController1
|
||||
- command: get iDRAC.SysLog.SysLogEnable
|
||||
name: Remote Log (syslog)
|
||||
pattern: SysLogEnable
|
||||
- command: get iDRAC.SysLog.Server1
|
||||
name: Syslog Server 1
|
||||
pattern: Server1
|
||||
- command: get iDRAC.SysLog.Server2
|
||||
name: Syslog Server 2
|
||||
pattern: Server2
|
||||
- command: get iDRAC.SysLog.Port
|
||||
name: Syslog Port
|
||||
pattern: Port
|
||||
- command: get iDRAC.VirtualConsole.Port
|
||||
name: VirtualConsole Port
|
||||
pattern: Port
|
||||
raid_settings:
|
||||
- command: hwinventory
|
||||
name: BOSS ProductName
|
||||
pattern: ProductName = BOSS
|
||||
- command: hwinventory
|
||||
name: RAID Types
|
||||
pattern: RAIDTypes
|
||||
- command: hwinventory
|
||||
name: StripeSize
|
||||
pattern: StripeSize
|
||||
- command: hwinventory
|
||||
name: ReadCachePolicy
|
||||
pattern: ReadCachePolicy
|
||||
- command: hwinventory
|
||||
name: WriteCachePolicy
|
||||
pattern: WriteCachePolicy
|
||||
- command: get STORAGE.Controller.1
|
||||
name: CheckConsistencyMode
|
||||
pattern: CheckConsistencyMode
|
||||
R760XD2_Server_info:
|
||||
bios_settings:
|
||||
- command: get bios.BiosBootSettings
|
||||
name: BIOS Boot Mode
|
||||
pattern: BootMode
|
||||
- command: get bios.SysProfileSettings
|
||||
name: System Profile
|
||||
pattern: SysProfile=
|
||||
- command: get bios.SysProfileSettings
|
||||
name: CPU Power Management
|
||||
pattern: EnergyPerformanceBias
|
||||
- command: get bios.SysProfileSettings
|
||||
name: Memory Frequency
|
||||
pattern: MemFrequency
|
||||
- command: get bios.SysProfileSettings
|
||||
name: Turbo Boost
|
||||
pattern: ProcTurboMode
|
||||
- command: get bios.SysProfileSettings
|
||||
name: C1E
|
||||
pattern: ProcC1E
|
||||
- command: get bios.SysProfileSettings
|
||||
name: C-States
|
||||
pattern: ProcCStates
|
||||
- command: get bios.SysProfileSettings
|
||||
name: Monitor/Mwait
|
||||
pattern: MonitorMwait
|
||||
- command: get bios.ProcSettings
|
||||
name: Logical Processor
|
||||
pattern: LogicalProc
|
||||
- command: get bios.ProcSettings
|
||||
name: Virtualization Technology
|
||||
pattern: ProcVirtualization
|
||||
- command: get bios.ProcSettings
|
||||
name: LLC Prefetch
|
||||
pattern: LlcPrefetch
|
||||
- command: get bios.ProcSettings
|
||||
name: x2APIC Mode
|
||||
pattern: ProcX2Apic
|
||||
- command: get bios.MemSettings
|
||||
name: Node Interleaving
|
||||
pattern: NodeInterleave
|
||||
- command: get bios.MemSettings
|
||||
name: DIMM Self Healing
|
||||
pattern: PPROnUCE
|
||||
- command: get bios.MemSettings
|
||||
name: Correctable Error Logging
|
||||
pattern: CECriticalSEL
|
||||
- command: get System.ThermalSettings
|
||||
name: Thermal Profile Optimization
|
||||
pattern: ThermalProfile
|
||||
- command: get Bios.IntegratedDevices
|
||||
name: SR-IOV Global Enable
|
||||
pattern: SriovGlobalEnable
|
||||
- command: get bios.MiscSettings
|
||||
name: F1/F2 Prompt on Error
|
||||
pattern: ErrPrompt
|
||||
- command: get iDRAC.ASRConfig
|
||||
name: ASR Config
|
||||
pattern: Enable=
|
||||
- command: get Bios.IntegratedDevices.OsWatchdogTimer
|
||||
name: Os watchdof timer
|
||||
pattern: OswatchdogTimer=
|
||||
description: 기본 서버 정보 수집 (BIOS, iDRAC, RAID)
|
||||
extends: minimal
|
||||
firmware:
|
||||
- command: get NIC.FrmwImgMenu.1
|
||||
name: NIC Integrated Firmware
|
||||
pattern: '#FamilyVersion'
|
||||
- command: hwinventory
|
||||
name: RAID Controller Firmware
|
||||
pattern: ControllerFirmwareVersion
|
||||
- command: hwinventory
|
||||
name: BOSS Controller Firmware
|
||||
pattern: ControllerFirmwareVersion = 2
|
||||
idrac_settings:
|
||||
- command: get iDRAC.Time.Timezone
|
||||
name: Timezone
|
||||
pattern: Timezone
|
||||
- command: get iDRAC.CurrentNIC
|
||||
name: IPMI LAN Selection
|
||||
pattern: ActiveNIC
|
||||
- command: get iDRAC.CurrentIPv4
|
||||
name: IPMI IPv4 DHCP
|
||||
pattern: DHCPEnable
|
||||
- command: get iDRAC.CurrentIPv6
|
||||
name: IPMI IPv6 Enable
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.Redfish.Enable
|
||||
name: Redfish Support
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.SSH
|
||||
name: SSH Support
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.USERDomain.1.Name
|
||||
name: AD User Domain Name
|
||||
pattern: Name
|
||||
- command: get iDRAC.ActiveDirectory.DomainController1
|
||||
name: SC Server Address
|
||||
pattern: DomainController1
|
||||
- command: get iDRAC.SysLog.SysLogEnable
|
||||
name: Remote Log (syslog)
|
||||
pattern: SysLogEnable
|
||||
- command: get iDRAC.SysLog.Server1
|
||||
name: Syslog Server 1
|
||||
pattern: Server1
|
||||
- command: get iDRAC.SysLog.Server2
|
||||
name: Syslog Server 2
|
||||
pattern: Server2
|
||||
- command: get iDRAC.SysLog.Port
|
||||
name: Syslog Port
|
||||
pattern: Port
|
||||
- command: get iDRAC.VirtualConsole.Port
|
||||
name: VirtualConsole Port
|
||||
pattern: Port
|
||||
raid_settings:
|
||||
- command: hwinventory
|
||||
name: RAID ProductName
|
||||
pattern: ProductName = PERC
|
||||
- command: hwinventory
|
||||
name: BOSS ProductName
|
||||
pattern: ProductName = BOSS
|
||||
- command: hwinventory
|
||||
name: RAID Types
|
||||
pattern: RAIDTypes
|
||||
- command: hwinventory
|
||||
name: StripeSize
|
||||
pattern: StripeSize
|
||||
- command: hwinventory
|
||||
name: ReadCachePolicy
|
||||
pattern: ReadCachePolicy
|
||||
- command: hwinventory
|
||||
name: WriteCachePolicy
|
||||
pattern: WriteCachePolicy
|
||||
- command: get STORAGE.Controller.1
|
||||
name: CheckConsistencyMode
|
||||
pattern: CheckConsistencyMode
|
||||
- command: get STORAGE.Controller.1
|
||||
name: PatrolReadRate
|
||||
pattern: PatrolReadRate
|
||||
amd_server:
|
||||
bios_settings:
|
||||
- command: get bios.ProcSettings
|
||||
name: AMD SMT Mode
|
||||
pattern: SmtMode
|
||||
- command: get bios.ProcSettings
|
||||
name: AMD IOMMU
|
||||
pattern: Iommu
|
||||
description: AMD 서버 전용 프로파일
|
||||
extends: default
|
||||
default:
|
||||
bios_settings:
|
||||
- command: get bios.BiosBootSettings
|
||||
name: BIOS Boot Mode
|
||||
pattern: BootMode
|
||||
- command: get bios.SysProfileSettings
|
||||
name: System Profile
|
||||
pattern: SysProfile=
|
||||
- command: get bios.SysProfileSettings
|
||||
name: CPU Power Management
|
||||
pattern: EnergyPerformanceBias
|
||||
- command: get bios.SysProfileSettings
|
||||
name: Memory Frequency
|
||||
pattern: MemFrequency
|
||||
- command: get bios.SysProfileSettings
|
||||
name: Turbo Boost
|
||||
pattern: ProcTurboMode
|
||||
- command: get bios.SysProfileSettings
|
||||
name: C1E
|
||||
pattern: ProcC1E
|
||||
- command: get bios.SysProfileSettings
|
||||
name: C-States
|
||||
pattern: ProcCStates
|
||||
- command: get bios.SysProfileSettings
|
||||
name: Monitor/Mwait
|
||||
pattern: MonitorMwait
|
||||
- command: get bios.ProcSettings
|
||||
name: Logical Processor
|
||||
pattern: LogicalProc
|
||||
- command: get bios.ProcSettings
|
||||
name: Virtualization Technology
|
||||
pattern: ProcVirtualization
|
||||
- command: get bios.ProcSettings
|
||||
name: LLC Prefetch
|
||||
pattern: LlcPrefetch
|
||||
- command: get bios.ProcSettings
|
||||
name: x2APIC Mode
|
||||
pattern: ProcX2Apic
|
||||
- command: get bios.MemSettings
|
||||
name: Node Interleaving
|
||||
pattern: NodeInterleave
|
||||
- command: get bios.MemSettings
|
||||
name: DIMM Self Healing
|
||||
pattern: PPROnUCE
|
||||
- command: get bios.MemSettings
|
||||
name: Correctable Error Logging
|
||||
pattern: CECriticalSEL
|
||||
- command: get System.ThermalSettings
|
||||
name: Thermal Profile Optimization
|
||||
pattern: ThermalProfile
|
||||
- command: get Bios.IntegratedDevices
|
||||
name: SR-IOV Global Enable
|
||||
pattern: SriovGlobalEnable
|
||||
- command: get bios.MiscSettings
|
||||
name: F1/F2 Prompt on Error
|
||||
pattern: ErrPrompt
|
||||
description: 기본 서버 정보 수집 (BIOS, iDRAC, RAID)
|
||||
extends: minimal
|
||||
firmware:
|
||||
- command: get NIC.FrmwImgMenu.1
|
||||
name: NIC Integrated Firmware
|
||||
pattern: '#FamilyVersion'
|
||||
- command: get NIC.FrmwImgMenu.5
|
||||
name: OnBoard NIC Firmware
|
||||
pattern: '#FamilyVersion'
|
||||
- command: hwinventory
|
||||
name: RAID Controller Firmware
|
||||
pattern: ControllerFirmwareVersion
|
||||
idrac_settings:
|
||||
- command: get iDRAC.Time.Timezone
|
||||
name: Timezone
|
||||
pattern: Timezone
|
||||
- command: get iDRAC.CurrentNIC
|
||||
name: IPMI LAN Selection
|
||||
pattern: ActiveNIC
|
||||
- command: get iDRAC.CurrentIPv4
|
||||
name: IPMI IPv4 DHCP
|
||||
pattern: DHCPEnable
|
||||
- command: get iDRAC.CurrentIPv6
|
||||
name: IPMI IPv6 Enable
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.Redfish.Enable
|
||||
name: Redfish Support
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.SSH
|
||||
name: SSH Support
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.USERDomain.1.Name
|
||||
name: AD User Domain Name
|
||||
pattern: Name
|
||||
- command: get iDRAC.ActiveDirectory.DomainController1
|
||||
name: SC Server Address
|
||||
pattern: DomainController1
|
||||
- command: get iDRAC.SysLog.SysLogEnable
|
||||
name: Remote Log (syslog)
|
||||
pattern: SysLogEnable
|
||||
- command: get iDRAC.SysLog.Server1
|
||||
name: Syslog Server 1
|
||||
pattern: Server1
|
||||
- command: get iDRAC.SysLog.Server2
|
||||
name: Syslog Server 2
|
||||
pattern: Server2
|
||||
- command: get iDRAC.SysLog.Port
|
||||
name: Syslog Port
|
||||
pattern: Port
|
||||
- command: get iDRAC.VirtualConsole.Port
|
||||
name: VirtualConsole Port
|
||||
pattern: Port
|
||||
raid_settings:
|
||||
- command: hwinventory
|
||||
name: RAID ProductName
|
||||
pattern: ProductName = PERC
|
||||
- command: hwinventory
|
||||
name: RAID Types
|
||||
pattern: RAIDTypes
|
||||
- command: hwinventory
|
||||
name: StripeSize
|
||||
pattern: StripeSize
|
||||
- command: hwinventory
|
||||
name: ReadCachePolicy
|
||||
pattern: ReadCachePolicy
|
||||
- command: hwinventory
|
||||
name: WriteCachePolicy
|
||||
pattern: WriteCachePolicy
|
||||
- command: get STORAGE.Controller.1
|
||||
name: CheckConsistencyMode
|
||||
pattern: CheckConsistencyMode
|
||||
- command: get STORAGE.Controller.1
|
||||
name: PatrolReadRate
|
||||
pattern: PatrolReadRate
|
||||
intel_server:
|
||||
description: Intel 서버 전용 프로파일
|
||||
extends: default
|
||||
minimal:
|
||||
description: 최소 정보만 수집 (펌웨어 버전만)
|
||||
firmware:
|
||||
- command: getsysinfo
|
||||
name: SVC Tag
|
||||
pattern: SVC Tag
|
||||
- command: getsysinfo
|
||||
name: BIOS Firmware
|
||||
pattern: System BIOS Version
|
||||
- command: getsysinfo
|
||||
name: iDRAC Firmware
|
||||
pattern: Firmware Version
|
||||
문병철매니저님_AMD_T8A:
|
||||
bios_settings:
|
||||
- command: get bios.BiosBootSettings
|
||||
name: BIOS Boot Mode
|
||||
pattern: BootMode
|
||||
- command: get bios.SysProfileSettings
|
||||
name: System Profile
|
||||
pattern: SysProfile=
|
||||
- command: get bios.SysProfileSettings
|
||||
name: Memory Frequency
|
||||
pattern: MemFrequency
|
||||
- command: get bios.SysProfileSettings
|
||||
name: Turbo Boost
|
||||
pattern: ProcTurboMode
|
||||
- command: get bios.SysProfileSettings
|
||||
name: C-States
|
||||
pattern: ProcCStates
|
||||
- command: get bios.ProcSettings
|
||||
name: Logical Processor
|
||||
pattern: LogicalProc
|
||||
- command: get bios.ProcSettings
|
||||
name: Virtualization Technology
|
||||
pattern: ProcVirtualization
|
||||
- command: get bios.MemSettings
|
||||
name: DramRefreshDelay
|
||||
pattern: DramRefreshDelay
|
||||
- command: get bios.ProcSettings
|
||||
name: x2APIC Mode
|
||||
pattern: ProcX2Apic
|
||||
- command: get bios.MemSettings
|
||||
name: DIMM Self Healing
|
||||
pattern: PPROnUCE
|
||||
- command: get bios.MemSettings
|
||||
name: Correctable Error Logging
|
||||
pattern: CECriticalSEL
|
||||
- command: get System.ThermalSettings
|
||||
name: Thermal Profile Optimization
|
||||
pattern: ThermalProfile
|
||||
- command: get Bios.IntegratedDevices
|
||||
name: SR-IOV Global Enable
|
||||
pattern: SriovGlobalEnable
|
||||
- command: get bios.MiscSettings
|
||||
name: F1/F2 Prompt on Error
|
||||
pattern: ErrPrompt
|
||||
- command: get iDRAC.ASRConfig
|
||||
name: ASR Config
|
||||
pattern: Enable=
|
||||
- command: get Bios.IntegratedDevices.OsWatchdogTimer
|
||||
name: Os watchdof timer
|
||||
pattern: OswatchdogTimer=
|
||||
description: 기본 서버 정보 수집 (BIOS, iDRAC, RAID)
|
||||
extends: minimal
|
||||
firmware:
|
||||
- command: get NIC.FrmwImgMenu.1
|
||||
name: NIC(XE810) Integrated Firmware
|
||||
pattern: '#FamilyVersion'
|
||||
- command: get NIC.FrmwImgMenu.3
|
||||
name: NIC(BCM5720) Integrated Firmware
|
||||
pattern: '#FamilyVersion'
|
||||
- command: hwinventory
|
||||
name: RAID Controller Firmware
|
||||
pattern: ControllerFirmwareVersion
|
||||
idrac_settings:
|
||||
- command: get iDRAC.Time.Timezone
|
||||
name: Timezone
|
||||
pattern: Timezone
|
||||
- command: get iDRAC.CurrentNIC
|
||||
name: IPMI LAN Selection
|
||||
pattern: ActiveNIC
|
||||
- command: get iDRAC.CurrentIPv4
|
||||
name: IPMI IPv4 DHCP
|
||||
pattern: DHCPEnable
|
||||
- command: get iDRAC.CurrentIPv6
|
||||
name: IPMI IPv6 Enable
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.Redfish.Enable
|
||||
name: Redfish Support
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.SSH
|
||||
name: SSH Support
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.USERDomain.1.Name
|
||||
name: AD User Domain Name
|
||||
pattern: Name
|
||||
- command: get iDRAC.ActiveDirectory.DomainController1
|
||||
name: SC Server Address
|
||||
pattern: DomainController1
|
||||
- command: get iDRAC.SysLog.SysLogEnable
|
||||
name: Remote Log (syslog)
|
||||
pattern: SysLogEnable
|
||||
- command: get iDRAC.SysLog.Server1
|
||||
name: Syslog Server 1
|
||||
pattern: Server1
|
||||
- command: get iDRAC.SysLog.Server2
|
||||
name: Syslog Server 2
|
||||
pattern: Server2
|
||||
- command: get iDRAC.SysLog.Port
|
||||
name: Syslog Port
|
||||
pattern: Port
|
||||
- command: get iDRAC.VirtualConsole.Port
|
||||
name: VirtualConsole Port
|
||||
pattern: Port
|
||||
raid_settings:
|
||||
- command: hwinventory
|
||||
name: BOSS ProductName
|
||||
pattern: ProductName = BOSS
|
||||
- command: hwinventory
|
||||
name: RAID Types
|
||||
pattern: RAIDTypes
|
||||
- command: hwinventory
|
||||
name: StripeSize
|
||||
pattern: StripeSize
|
||||
- command: hwinventory
|
||||
name: ReadCachePolicy
|
||||
pattern: ReadCachePolicy
|
||||
- command: hwinventory
|
||||
name: WriteCachePolicy
|
||||
pattern: WriteCachePolicy
|
||||
- command: get STORAGE.Controller.1
|
||||
name: CheckConsistencyMode
|
||||
pattern: CheckConsistencyMode
|
||||
문병철매니저님_INTEL_SERVER:
|
||||
bios_settings:
|
||||
- command: get bios.BiosBootSettings
|
||||
name: BIOS Boot Mode
|
||||
pattern: BootMode
|
||||
- command: get bios.SysProfileSettings
|
||||
name: System Profile
|
||||
pattern: SysProfile=
|
||||
- command: get bios.SysProfileSettings
|
||||
name: CPU Power Management
|
||||
pattern: EnergyPerformanceBias
|
||||
- command: get bios.SysProfileSettings
|
||||
name: Memory Frequency
|
||||
pattern: MemFrequency
|
||||
- command: get bios.SysProfileSettings
|
||||
name: Turbo Boost
|
||||
pattern: ProcTurboMode
|
||||
- command: get bios.SysProfileSettings
|
||||
name: C1E
|
||||
pattern: ProcC1E
|
||||
- command: get bios.SysProfileSettings
|
||||
name: C-States
|
||||
pattern: ProcCStates
|
||||
- command: get bios.SysProfileSettings
|
||||
name: Monitor/Mwait
|
||||
pattern: MonitorMwait
|
||||
- command: get bios.ProcSettings
|
||||
name: Logical Processor
|
||||
pattern: LogicalProc
|
||||
- command: get bios.ProcSettings
|
||||
name: Virtualization Technology
|
||||
pattern: ProcVirtualization
|
||||
- command: get bios.ProcSettings
|
||||
name: LLC Prefetch
|
||||
pattern: LlcPrefetch
|
||||
- command: get bios.ProcSettings
|
||||
name: x2APIC Mode
|
||||
pattern: ProcX2Apic
|
||||
- command: get bios.MemSettings
|
||||
name: Node Interleaving
|
||||
pattern: NodeInterleave
|
||||
- command: get bios.MemSettings
|
||||
name: DIMM Self Healing
|
||||
pattern: PPROnUCE
|
||||
- command: get bios.MemSettings
|
||||
name: Correctable Error Logging
|
||||
pattern: CECriticalSEL
|
||||
- command: get System.ThermalSettings
|
||||
name: Thermal Profile Optimization
|
||||
pattern: ThermalProfile
|
||||
- command: get Bios.IntegratedDevices
|
||||
name: SR-IOV Global Enable
|
||||
pattern: SriovGlobalEnable
|
||||
- command: get bios.MiscSettings
|
||||
name: F1/F2 Prompt on Error
|
||||
pattern: ErrPrompt
|
||||
- command: get iDRAC.ASRConfig
|
||||
name: ASR Config
|
||||
pattern: Enable=
|
||||
- command: get Bios.IntegratedDevices.OsWatchdogTimer
|
||||
name: Os watchdof timer
|
||||
pattern: OswatchdogTimer=
|
||||
description: 기본 서버 정보 수집 (BIOS, iDRAC, RAID)
|
||||
extends: minimal
|
||||
firmware:
|
||||
- command: get NIC.FrmwImgMenu.1
|
||||
name: NIC Integrated Firmware
|
||||
pattern: '#FamilyVersion'
|
||||
- command: hwinventory
|
||||
name: RAID Controller Firmware
|
||||
pattern: ControllerFirmwareVersion
|
||||
- command: hwinventory
|
||||
name: BOSS Controller Firmware
|
||||
pattern: ControllerFirmwareVersion = 2
|
||||
idrac_settings:
|
||||
- command: get iDRAC.Time.Timezone
|
||||
name: Timezone
|
||||
pattern: Timezone
|
||||
- command: get iDRAC.CurrentNIC
|
||||
name: IPMI LAN Selection
|
||||
pattern: ActiveNIC
|
||||
- command: get iDRAC.CurrentIPv4
|
||||
name: IPMI IPv4 DHCP
|
||||
pattern: DHCPEnable
|
||||
- command: get iDRAC.CurrentIPv6
|
||||
name: IPMI IPv6 Enable
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.Redfish.Enable
|
||||
name: Redfish Support
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.SSH
|
||||
name: SSH Support
|
||||
pattern: Enable=
|
||||
- command: get iDRAC.USERDomain.1.Name
|
||||
name: AD User Domain Name
|
||||
pattern: Name
|
||||
- command: get iDRAC.ActiveDirectory.DomainController1
|
||||
name: SC Server Address
|
||||
pattern: DomainController1
|
||||
- command: get iDRAC.SysLog.SysLogEnable
|
||||
name: Remote Log (syslog)
|
||||
pattern: SysLogEnable
|
||||
- command: get iDRAC.SysLog.Server1
|
||||
name: Syslog Server 1
|
||||
pattern: Server1
|
||||
- command: get iDRAC.SysLog.Server2
|
||||
name: Syslog Server 2
|
||||
pattern: Server2
|
||||
- command: get iDRAC.SysLog.Port
|
||||
name: Syslog Port
|
||||
pattern: Port
|
||||
- command: get iDRAC.VirtualConsole.Port
|
||||
name: VirtualConsole Port
|
||||
pattern: Port
|
||||
raid_settings:
|
||||
- command: hwinventory
|
||||
name: RAID ProductName
|
||||
pattern: ProductName = PERC
|
||||
- command: hwinventory
|
||||
name: BOSS ProductName
|
||||
pattern: ProductName = BOSS
|
||||
- command: hwinventory
|
||||
name: RAID Types
|
||||
pattern: RAIDTypes
|
||||
- command: hwinventory
|
||||
name: StripeSize
|
||||
pattern: StripeSize
|
||||
- command: hwinventory
|
||||
name: ReadCachePolicy
|
||||
pattern: ReadCachePolicy
|
||||
- command: hwinventory
|
||||
name: WriteCachePolicy
|
||||
pattern: WriteCachePolicy
|
||||
- command: get STORAGE.Controller.1
|
||||
name: CheckConsistencyMode
|
||||
pattern: CheckConsistencyMode
|
||||
- command: get STORAGE.Controller.1
|
||||
name: PatrolReadRate
|
||||
pattern: PatrolReadRate
|
||||
@@ -18,7 +18,7 @@ if [ ! -f "$IP_FILE" ]; then
|
||||
fi
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR="idrac_info"
|
||||
OUTPUT_DIR="$(dirname "$0")/../temp/staging"
|
||||
mkdir -p $OUTPUT_DIR
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
|
||||
@@ -18,7 +18,7 @@ if [ ! -f "$IP_FILE" ]; then
|
||||
fi
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR="idrac_info"
|
||||
OUTPUT_DIR="$(dirname "$0")/../temp/staging"
|
||||
mkdir -p $OUTPUT_DIR
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
|
||||
@@ -18,7 +18,7 @@ if [ ! -f "$IP_FILE" ]; then
|
||||
fi
|
||||
|
||||
# 정보 저장 디렉터리 설정
|
||||
OUTPUT_DIR="idrac_info"
|
||||
OUTPUT_DIR="$(dirname "$0")/../temp/staging"
|
||||
mkdir -p $OUTPUT_DIR
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
통합 GPU 시리얼 수집 스크립트
|
||||
hwinventory에서 Video.Slot 정보 수집
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
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 collect_gpu_for_ip(ip: str, output_dir: Path) -> tuple:
|
||||
"""단일 IP에 대한 GPU Serial 정보 수집"""
|
||||
try:
|
||||
logging.info(f"[{ip}] GPU 정보 수집 시작")
|
||||
|
||||
client = IDRACClient(ip)
|
||||
|
||||
# 1. Get Service Tag
|
||||
sysinfo = client.get_sysinfo()
|
||||
svc_tag = InfoParser.extract_service_tag(sysinfo)
|
||||
|
||||
if not svc_tag:
|
||||
return ip, False, "서비스 태그 추출 실패"
|
||||
|
||||
# 2. Get Hardware Inventory
|
||||
hwinventory = client.get_hwinventory()
|
||||
|
||||
# 3. Parse GPU Serials
|
||||
gpu_map = InfoParser.extract_gpu_serials(hwinventory)
|
||||
|
||||
# 4. Write File
|
||||
output_file = output_dir / f"{svc_tag}.txt"
|
||||
|
||||
# 정렬 로직 (Legacy GPU_Serial_v1.py 유지)
|
||||
def slot_key(k: str):
|
||||
m = re.search(r"Video\.Slot\.(\d+)-(\d+)", k)
|
||||
if m:
|
||||
return (int(m.group(1)), int(m.group(2)))
|
||||
return (1_000_000, 1_000_000, k)
|
||||
|
||||
with output_file.open('w', encoding='utf-8', newline='\n') as f:
|
||||
f.write(f"{svc_tag}\n")
|
||||
|
||||
if not gpu_map:
|
||||
f.write("No GPU(Video) inventory found or SerialNumber not present.\n")
|
||||
return ip, True, f"저장: {output_file.name} (GPU 없음)"
|
||||
|
||||
# Write individual slots
|
||||
sorted_keys = sorted(gpu_map.keys(), key=slot_key)
|
||||
for fqdd in sorted_keys:
|
||||
serial = gpu_map[fqdd]
|
||||
f.write(f"{fqdd}: {serial}\n")
|
||||
|
||||
# Write summary line
|
||||
serials_only = [gpu_map[k] for k in sorted_keys if gpu_map[k] != "Not Found"]
|
||||
if serials_only:
|
||||
f.write(f"SERIALS: {';'.join(serials_only)}\n")
|
||||
|
||||
return ip, True, f"저장: {output_file.name} ({len(serials_only)}개 발견)"
|
||||
|
||||
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_gpu.py <ip_file>")
|
||||
sys.exit(1)
|
||||
|
||||
ip_file = Path(sys.argv[1])
|
||||
|
||||
if not ip_file.exists():
|
||||
logging.error(f"IP 파일 없음: {ip_file}")
|
||||
sys.exit(1)
|
||||
|
||||
# 출력 디렉토리
|
||||
# 출력 디렉토리 (스크립트 위치 기준)
|
||||
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)}대 GPU 정보 수집 시작")
|
||||
|
||||
# 병렬 처리
|
||||
ok, fail = 0, 0
|
||||
with ThreadPoolExecutor(max_workers=20) as executor:
|
||||
futures = {executor.submit(collect_gpu_for_ip, ip, 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()
|
||||
@@ -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()
|
||||
@@ -0,0 +1,186 @@
|
||||
#!/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 <ip_file> [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()
|
||||
@@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
통합 서버 정보 수집 스크립트
|
||||
프로파일 기반으로 BIOS/iDRAC/RAID 설정 수집
|
||||
"""
|
||||
import sys
|
||||
import yaml
|
||||
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" / "server_info_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()
|
||||
# 리스트 병합
|
||||
for key in ['firmware', 'bios_settings', 'idrac_settings', 'raid_settings', 'custom_items']:
|
||||
if key in profile:
|
||||
base_items = base.get(key, [])
|
||||
profile_items = profile[key]
|
||||
# 중복 제거하면서 병합
|
||||
base[key] = base_items + profile_items
|
||||
base.update({k: v for k, v in profile.items() if k not in ['firmware', 'bios_settings', 'idrac_settings', 'raid_settings', 'custom_items']})
|
||||
profile = base
|
||||
|
||||
return profile
|
||||
|
||||
|
||||
def collect_server_info_for_ip(ip: str, profile: dict, output_dir: Path) -> tuple:
|
||||
"""단일 IP에 대한 서버 정보 수집"""
|
||||
try:
|
||||
client = IDRACClient(ip)
|
||||
parser = InfoParser()
|
||||
|
||||
# 서비스 태그 추출
|
||||
sysinfo = client.get_sysinfo()
|
||||
svc_tag = parser.extract_service_tag(sysinfo)
|
||||
if not svc_tag:
|
||||
return ip, False, "서비스 태그 추출 실패"
|
||||
|
||||
# 결과 파일 생성
|
||||
output_file = output_dir / f"{svc_tag}.txt"
|
||||
|
||||
with output_file.open('w', encoding='utf-8', newline='\n') as f:
|
||||
f.write(f"Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: {svc_tag})\n\n")
|
||||
|
||||
# 펌웨어 버전 정보
|
||||
if 'firmware' in profile:
|
||||
f.write("-" * 42 + " Firmware Version 정보 " + "-" * 42 + "\n")
|
||||
for idx, item in enumerate(profile['firmware'], 1):
|
||||
value = _get_value_from_command(client, item['command'], item['pattern'])
|
||||
f.write(f"{idx}. {item['name']} : {value}\n")
|
||||
f.write("\n")
|
||||
|
||||
# BIOS 설정 정보
|
||||
if 'bios_settings' in profile:
|
||||
f.write("-" * 45 + " Bios 설정 정보 " + "-" * 46 + "\n")
|
||||
for idx, item in enumerate(profile['bios_settings'], 1):
|
||||
value = _get_value_from_command(client, item['command'], item['pattern'])
|
||||
f.write(f"{idx:02d}. {item['name']} : {value}\n")
|
||||
f.write("\n")
|
||||
|
||||
# iDRAC 설정 정보
|
||||
if 'idrac_settings' in profile:
|
||||
f.write("-" * 45 + " iDRAC 설정 정보 " + "-" * 45 + "\n")
|
||||
for idx, item in enumerate(profile['idrac_settings'], 1):
|
||||
value = _get_value_from_command(client, item['command'], item['pattern'])
|
||||
f.write(f"{idx:02d}. {item['name']} : {value}\n")
|
||||
f.write("\n")
|
||||
|
||||
# RAID 설정 정보
|
||||
if 'raid_settings' in profile:
|
||||
f.write("-" * 45 + " Raid 설정 정보 " + "-" * 46 + "\n")
|
||||
for idx, item in enumerate(profile['raid_settings'], 1):
|
||||
value = _get_value_from_command(client, item['command'], item['pattern'])
|
||||
f.write(f"{idx:02d}. {item['name']} : {value}\n")
|
||||
f.write("\n")
|
||||
|
||||
# 커스텀 항목 (있을 경우)
|
||||
if 'custom_items' in profile and profile['custom_items']:
|
||||
f.write("-" * 45 + " 커스텀 항목 " + "-" * 46 + "\n")
|
||||
for idx, item in enumerate(profile['custom_items'], 1):
|
||||
value = _get_value_from_command(client, item['command'], item['pattern'])
|
||||
f.write(f"{idx:02d}. {item['name']} : {value}\n")
|
||||
f.write("\n")
|
||||
|
||||
return ip, True, f"저장: {output_file.name}"
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"[{ip}] 오류: {e}")
|
||||
return ip, False, f"오류: {e}"
|
||||
|
||||
|
||||
def _get_value_from_command(client: IDRACClient, command: str, pattern: str) -> str:
|
||||
"""racadm 명령 실행 및 값 추출"""
|
||||
# 캐싱된 데이터 사용
|
||||
if command == "getsysinfo":
|
||||
output = client.get_sysinfo()
|
||||
elif command == "hwinventory":
|
||||
output = client.get_hwinventory()
|
||||
else:
|
||||
_, output = client.run_command(command)
|
||||
|
||||
# 패턴 매칭
|
||||
value = InfoParser.extract_value_by_pattern(output, pattern)
|
||||
return value if value else "N/A"
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python collect_server_info.py <ip_file> [profile_name]")
|
||||
print("Available profiles: default, intel_server, amd_server, minimal")
|
||||
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', '')}")
|
||||
|
||||
# 출력 디렉토리 (data/temp/staging)
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
# unified 디렉토리에 있으므로, data 디렉토리는 3단계 상위 (unified -> scripts -> data)
|
||||
data_dir = script_dir.parent.parent
|
||||
|
||||
# 만약 구조가 다를 경우를 대비한 안전장치 (scripts가 아닐 경우 등)
|
||||
if data_dir.name != "data":
|
||||
# fallback: 현재 작업 디렉토리 기준 data 찾기 시도
|
||||
data_dir = Path("data").resolve()
|
||||
|
||||
output_dir = data_dir / "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)}대 처리 시작")
|
||||
|
||||
# 병렬 처리 (서버 정보 수집은 racadm 호출이 많아 worker 수 줄임)
|
||||
ok, fail = 0, 0
|
||||
with ThreadPoolExecutor(max_workers=10) as executor:
|
||||
futures = {executor.submit(collect_server_info_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()
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
통합 시스템 제어 스크립트 (Unified System Control Script)
|
||||
기존의 개별 제어 스크립트들을 하나로 통합하였습니다.
|
||||
|
||||
지원 기능:
|
||||
- power_on (06-PowerON.py)
|
||||
- power_off (07-PowerOFF.py)
|
||||
- log_clear (05-clrsel.py)
|
||||
- job_delete (08-job_delete_all.py)
|
||||
- tsr_collect (03-tsr_log.py)
|
||||
- tsr_save (04-tsr_save.py)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import subprocess
|
||||
import time
|
||||
import logging
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Load .env relative to script location or current directory
|
||||
load_dotenv()
|
||||
|
||||
IDRAC_USER = os.getenv("IDRAC_USER", "root")
|
||||
IDRAC_PASS = os.getenv("IDRAC_PASS", "calvin")
|
||||
OME_USER = os.getenv("OME_USER", "")
|
||||
OME_PASS = os.getenv("OME_PASS", "")
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Command Definitions
|
||||
# -----------------------------------------------------------------------------
|
||||
# Each action maps to a function that returns the racadm command list
|
||||
def cmd_power_on(ip):
|
||||
return ["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerup"]
|
||||
|
||||
def cmd_power_off(ip):
|
||||
return ["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerdown"]
|
||||
|
||||
def cmd_log_clear(ip):
|
||||
return ["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "clrsel"]
|
||||
|
||||
def cmd_job_delete(ip):
|
||||
return ["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "jobqueue", "delete", "-i", "ALL"]
|
||||
|
||||
def cmd_tsr_collect(ip):
|
||||
return ["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "techsupreport", "collect"]
|
||||
|
||||
def cmd_tsr_save(ip):
|
||||
# Note: This command assumes a specific share path (legacy behavior)
|
||||
# Ideally this path should also be configurable via env
|
||||
share_path = "//10.10.3.15/share/"
|
||||
return [
|
||||
"racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS,
|
||||
"techsupreport", "export", "-l", share_path, "-u", OME_USER, "-p", OME_PASS
|
||||
]
|
||||
|
||||
ACTION_MAP = {
|
||||
"power_on": cmd_power_on,
|
||||
"power_off": cmd_power_off,
|
||||
"log_clear": cmd_log_clear,
|
||||
"job_delete": cmd_job_delete,
|
||||
"tsr_collect": cmd_tsr_collect,
|
||||
"tsr_save": cmd_tsr_save,
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Execution Logic
|
||||
# -----------------------------------------------------------------------------
|
||||
def execute_action(ip: str, action_name: str) -> None:
|
||||
"""Run the racadm command for a single IP."""
|
||||
try:
|
||||
cmd_func = ACTION_MAP.get(action_name)
|
||||
if not cmd_func:
|
||||
logger.error(f"[{ip}] Unknown action: {action_name}")
|
||||
return
|
||||
|
||||
cmd = cmd_func(ip)
|
||||
logger.info(f"[{ip}] Executing {action_name}...")
|
||||
|
||||
# Run command
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=600 # Generous timeout for TSR operations
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info(f"[{ip}] Success: {result.stdout.strip()}")
|
||||
else:
|
||||
logger.error(f"[{ip}] Failed: {result.stderr.strip()}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[{ip}] Exception: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Unified System Control Script")
|
||||
parser.add_argument("action", choices=ACTION_MAP.keys(), help="Action to perform")
|
||||
parser.add_argument("ip_file", type=Path, help="Path to the IP list file")
|
||||
|
||||
# Optional: Allow passing IP directly via args instead of file (future extension)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
ip_file = args.ip_file
|
||||
action = args.action
|
||||
|
||||
if not ip_file.is_file():
|
||||
logger.error(f"IP file not found: {ip_file}")
|
||||
sys.exit(1)
|
||||
|
||||
# Read IPs
|
||||
try:
|
||||
with ip_file.open("r", encoding="utf-8") as f:
|
||||
ips = [line.strip() for line in f if line.strip()]
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to read IP file: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if not ips:
|
||||
logger.warning("No IPs found in file.")
|
||||
sys.exit(0)
|
||||
|
||||
logger.info(f"Starting '{action}' for {len(ips)} servers...")
|
||||
start_time = time.time()
|
||||
|
||||
# Parallel Execution
|
||||
# Adjust max_workers as needed. 20 is a safe defaults for I/O bound network tasks.
|
||||
with ThreadPoolExecutor(max_workers=20) as executor:
|
||||
futures = [executor.submit(execute_action, ip, action) for ip in ips]
|
||||
for future in futures:
|
||||
future.result() # Wait for all to complete
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
logger.info(f"Completed '{action}' in {elapsed:.2f} seconds.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user