#!/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 import re from concurrent.futures import ThreadPoolExecutor from pathlib import Path from typing import Optional 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") TSR_SHARE_URL = os.getenv("TSR_SHARE_URL", "//10.10.3.15/share/") OME_USER = os.getenv("OME_USER", "") OME_PASS = os.getenv("OME_PASS", "") TSR_JOB_TIMEOUT = int(os.getenv("TSR_JOB_TIMEOUT", "900")) TSR_JOB_POLL_INTERVAL = int(os.getenv("TSR_JOB_POLL_INTERVAL", "10")) TSR_SAVE_WORKERS = int(os.getenv("TSR_SAVE_WORKERS", "30")) # ----------------------------------------------------------------------------- # 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): return [ "racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "techsupreport", "export", "-l", TSR_SHARE_URL, "-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 redact_cmd(cmd): redacted = [] hide_next = False for part in cmd: if hide_next: redacted.append("***") hide_next = False continue redacted.append(part) if part == "-p": hide_next = True return redacted def extract_job_id(output: str) -> Optional[str]: match = re.search(r"Job ID\s*=\s*(JID_\d+)", output or "") return match.group(1) if match else None def wait_for_job(ip: str, job_id: str) -> bool: deadline = time.time() + TSR_JOB_TIMEOUT view_cmd = [ "racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "jobqueue", "view", "-i", job_id ] while time.time() < deadline: result = subprocess.run( view_cmd, capture_output=True, text=True, timeout=120 ) output = "\n".join(part for part in [result.stdout.strip(), result.stderr.strip()] if part) logger.info(f"[{ip}] Job {job_id} status:\n{output}") if "Status=Completed" in output: return True if "Status=Failed" in output: return False time.sleep(TSR_JOB_POLL_INTERVAL) logger.error(f"[{ip}] Job {job_id} status check timed out after {TSR_JOB_TIMEOUT} seconds.") return False def execute_action(ip: str, action_name: str) -> bool: """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 False cmd = cmd_func(ip) logger.info(f"[{ip}] Executing {action_name}...") if action_name == "tsr_save": logger.info(f"[{ip}] Command: {redact_cmd(cmd)}") # Run command result = subprocess.run( cmd, capture_output=True, text=True, timeout=600 # Generous timeout for TSR operations ) output = "\n".join(part for part in [result.stdout.strip(), result.stderr.strip()] if part) if result.returncode == 0: logger.info(f"[{ip}] Success: {output}") if action_name == "tsr_save": job_id = extract_job_id(output) if not job_id: logger.error(f"[{ip}] TSR Save did not return a Job ID.") return False return wait_for_job(ip, job_id) return True else: logger.error(f"[{ip}] Failed: {output}") return False except Exception as e: logger.error(f"[{ip}] Exception: {e}") return False 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() success_count = 0 failed_count = 0 if action == "tsr_save": max_workers = max(1, min(TSR_SAVE_WORKERS, len(ips))) logger.info(f"TSR Save parallel workers={max_workers}") with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = [executor.submit(execute_action, ip, action) for ip in ips] for future in futures: if future.result(): success_count += 1 else: failed_count += 1 else: # 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: if future.result(): success_count += 1 else: failed_count += 1 elapsed = time.time() - start_time logger.info( f"Completed '{action}' in {elapsed:.2f} seconds. " f"success={success_count}, failed={failed_count}" ) if failed_count: sys.exit(1) if __name__ == "__main__": main()