update
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user