""" 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