130 lines
4.4 KiB
Python
130 lines
4.4 KiB
Python
import sys
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import time
|
|
import logging
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 설정 (필요하면 .env 등에서 읽어와도 됨)
|
|
IDRAC_USER = os.getenv("IDRAC_USER", "root")
|
|
IDRAC_PASS = os.getenv("IDRAC_PASS", "calvin")
|
|
RACADM = os.getenv("RACADM_PATH", "racadm") # PATH에 있으면 'racadm'
|
|
|
|
# 로깅
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s [INFO] root: %(message)s',
|
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
)
|
|
|
|
def read_ip_list(ip_file: Path):
|
|
ips = []
|
|
for line in ip_file.read_text(encoding="utf-8").splitlines():
|
|
s = line.strip()
|
|
if s:
|
|
ips.append(s)
|
|
return ips
|
|
|
|
def preflight(ip: str) -> tuple[bool, str]:
|
|
"""접속/인증/권한 간단 점검: getsysinfo 호출."""
|
|
cmd = [RACADM, "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "getsysinfo"]
|
|
try:
|
|
p = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", shell=False, timeout=30)
|
|
if p.returncode != 0:
|
|
return False, p.stderr.strip() or p.stdout.strip()
|
|
return True, ""
|
|
except Exception as e:
|
|
return False, str(e)
|
|
|
|
def safe_xml_path(src: Path) -> Path:
|
|
"""
|
|
racadm이 경로의 공백/한글을 싫어하는 문제 회피:
|
|
임시 폴더에 ASCII 파일명으로 복사해서 사용.
|
|
"""
|
|
tmp_dir = Path(tempfile.gettempdir()) / "idrac_xml"
|
|
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
# ASCII, 공백 제거 파일명
|
|
dst_name = "config_" + str(int(time.time())) + ".xml"
|
|
dst = tmp_dir / dst_name
|
|
shutil.copy2(src, dst)
|
|
return dst
|
|
|
|
def apply_xml(ip: str, xml_path: Path) -> tuple[bool, str]:
|
|
"""
|
|
racadm XML 적용. 벤더/세대에 따라
|
|
'config -f' 또는 'set -t xml -f' 를 씁니다.
|
|
일반적으로 최신 iDRAC은 config -f 가 잘 동작합니다.
|
|
"""
|
|
# 1) 공백/한글 제거된 임시 경로 준비
|
|
safe_path = safe_xml_path(xml_path)
|
|
|
|
# 2) 명령 조립(리스트 인자, shell=False)
|
|
# 필요 시 아래 둘 중 하나만 사용하세요.
|
|
cmd = [RACADM, "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "set", "-t", "xml", "-f", str(safe_path)]
|
|
# 대안:
|
|
# cmd = [RACADM, "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "set", "-t", "xml", "-f", str(safe_path)]
|
|
|
|
logging.info(f"실행 명령(리스트) → {' '.join(cmd)}")
|
|
try:
|
|
p = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", shell=False, timeout=180)
|
|
stdout = (p.stdout or "").strip()
|
|
stderr = (p.stderr or "").strip()
|
|
|
|
if p.returncode != 0:
|
|
msg = f"racadm 실패 (rc={p.returncode})\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}"
|
|
return False, msg
|
|
# 일부 버전은 성공해도 stdout만 출력하고 rc=0
|
|
return True, stdout or "성공"
|
|
except Exception as e:
|
|
return False, f"예외: {e}"
|
|
finally:
|
|
# 임시 XML 제거(필요 시 보관하려면 주석처리)
|
|
try:
|
|
safe_path.unlink(missing_ok=True)
|
|
except Exception:
|
|
pass
|
|
|
|
def main():
|
|
if len(sys.argv) != 3:
|
|
logging.error("Usage: python 02-set_config.py <ip_file> <xml_file>")
|
|
sys.exit(1)
|
|
|
|
ip_file = Path(sys.argv[1])
|
|
xml_file = Path(sys.argv[2])
|
|
|
|
if not ip_file.is_file():
|
|
logging.error("IP 파일을 찾을 수 없습니다: %s", ip_file)
|
|
sys.exit(2)
|
|
if not xml_file.is_file():
|
|
logging.error("XML 파일을 찾을 수 없습니다: %s", xml_file)
|
|
sys.exit(3)
|
|
|
|
ips = read_ip_list(ip_file)
|
|
if not ips:
|
|
logging.error("IP 목록이 비어있습니다.")
|
|
sys.exit(4)
|
|
|
|
start = time.time()
|
|
for ip in ips:
|
|
logging.info("%s에 XML 파일 '%s' 설정 적용 중...", ip, xml_file)
|
|
|
|
ok, why = preflight(ip)
|
|
if not ok:
|
|
logging.error("%s 사전 점검(getsysinfo) 실패: %s", ip, why)
|
|
continue
|
|
|
|
ok, msg = apply_xml(ip, xml_file)
|
|
if ok:
|
|
logging.info("%s 설정 성공: %s", ip, msg)
|
|
else:
|
|
logging.error("%s 설정 실패\n%s", ip, msg)
|
|
|
|
el = int(time.time() - start)
|
|
logging.info("전체 설정 소요 시간: %d 시간, %d 분, %d 초.", el // 3600, (el % 3600) // 60, el % 60)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|