298 lines
10 KiB
Python
298 lines
10 KiB
Python
"""
|
|
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, instance_must_contain: str = None) -> List[str]:
|
|
"""
|
|
컴포넌트 벤더 추출
|
|
|
|
Args:
|
|
hwinventory: hwinventory 명령 출력
|
|
component_prefix: 컴포넌트 접두사 ("DIMM", "Disk.Bay", "BOSS" 등)
|
|
instance_must_contain: InstanceID/FQDD에 반드시 포함돼야 할 문자열 (예: "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
|
|
matched_id = None
|
|
|
|
# Check InstanceID header: [InstanceID: DIMM.Socket.A1]
|
|
if f"[InstanceID: {component_prefix}" in block:
|
|
is_target_component = True
|
|
m_inst = re.search(r"\[InstanceID:\s*([^\]]+)\]", block)
|
|
if m_inst:
|
|
matched_id = m_inst.group(1).strip()
|
|
|
|
# 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
|
|
matched_id = m_fqdd.group(1).strip()
|
|
|
|
if not is_target_component:
|
|
continue
|
|
|
|
# instance_must_contain 필터 적용
|
|
if instance_must_contain and matched_id and instance_must_contain not in matched_id:
|
|
continue
|
|
|
|
m = re.search(r"Manufacturer\s*=\s*(.+)", block, re.IGNORECASE)
|
|
if m:
|
|
vendors.add(m.group(1).strip())
|
|
|
|
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
|