package inspur import ( "encoding/json" "fmt" "regexp" "strings" "time" "git.mchus.pro/mchus/logpile/internal/models" ) // ParseComponentLog parses component.log file and extracts detailed hardware info func ParseComponentLog(content []byte, hw *models.HardwareConfig) { if hw == nil { return } text := string(content) // Parse RESTful Memory info (detailed memory data) parseMemoryInfo(text, hw) // Parse RESTful PSU info parsePSUInfo(text, hw) // Parse RESTful HDD info parseHDDInfo(text, hw) // Parse RESTful diskbackplane info parseDiskBackplaneInfo(text, hw) // Parse RESTful Network Adapter info parseNetworkAdapterInfo(text, hw) // Extract firmware from all components extractComponentFirmware(text, hw) } // ParseComponentLogEvents extracts events from component.log (memory errors, etc.) func ParseComponentLogEvents(content []byte) []models.Event { var events []models.Event text := string(content) // Parse RESTful Memory info for Warning/Error status memEvents := parseMemoryEvents(text) events = append(events, memEvents...) return events } // MemoryRESTInfo represents the RESTful Memory info structure type MemoryRESTInfo struct { MemModules []struct { MemModID int `json:"mem_mod_id"` ConfigStatus int `json:"config_status"` MemModSlot string `json:"mem_mod_slot"` MemModStatus int `json:"mem_mod_status"` MemModSize int `json:"mem_mod_size"` MemModType string `json:"mem_mod_type"` MemModTechnology string `json:"mem_mod_technology"` MemModFrequency int `json:"mem_mod_frequency"` MemModCurrentFreq int `json:"mem_mod_current_frequency"` MemModVendor string `json:"mem_mod_vendor"` MemModPartNum string `json:"mem_mod_part_num"` MemModSerial string `json:"mem_mod_serial_num"` MemModRanks int `json:"mem_mod_ranks"` Status string `json:"status"` } `json:"mem_modules"` TotalMemoryCount int `json:"total_memory_count"` PresentMemoryCount int `json:"present_memory_count"` MemTotalMemSize int `json:"mem_total_mem_size"` } func parseMemoryInfo(text string, hw *models.HardwareConfig) { // Find RESTful Memory info section re := regexp.MustCompile(`RESTful Memory info:\s*(\{[\s\S]*?\})\s*RESTful HDD`) match := re.FindStringSubmatch(text) if match == nil { return } jsonStr := match[1] jsonStr = strings.ReplaceAll(jsonStr, "\n", "") var memInfo MemoryRESTInfo if err := json.Unmarshal([]byte(jsonStr), &memInfo); err != nil { return } // Replace memory data with detailed info from component.log hw.Memory = nil for _, mem := range memInfo.MemModules { hw.Memory = append(hw.Memory, models.MemoryDIMM{ Slot: mem.MemModSlot, Location: mem.MemModSlot, Present: mem.MemModStatus == 1 && mem.MemModSize > 0, SizeMB: mem.MemModSize * 1024, // Convert GB to MB Type: mem.MemModType, Technology: strings.TrimSpace(mem.MemModTechnology), MaxSpeedMHz: mem.MemModFrequency, CurrentSpeedMHz: mem.MemModCurrentFreq, Manufacturer: mem.MemModVendor, SerialNumber: mem.MemModSerial, PartNumber: strings.TrimSpace(mem.MemModPartNum), Status: mem.Status, Ranks: mem.MemModRanks, }) } } // PSURESTInfo represents the RESTful PSU info structure type PSURESTInfo struct { PowerSupplies []struct { ID int `json:"id"` Present int `json:"present"` VendorID string `json:"vendor_id"` Model string `json:"model"` SerialNum string `json:"serial_num"` PartNum string `json:"part_num"` FwVer string `json:"fw_ver"` InputType string `json:"input_type"` Status string `json:"status"` RatedPower int `json:"rated_power"` PSInPower int `json:"ps_in_power"` PSOutPower int `json:"ps_out_power"` PSInVolt float64 `json:"ps_in_volt"` PSOutVolt float64 `json:"ps_out_volt"` PSUMaxTemp int `json:"psu_max_temperature"` } `json:"power_supplies"` PresentPowerReading int `json:"present_power_reading"` } func parsePSUInfo(text string, hw *models.HardwareConfig) { // Find RESTful PSU info section re := regexp.MustCompile(`RESTful PSU info:\s*(\{[\s\S]*?\})\s*RESTful Network`) match := re.FindStringSubmatch(text) if match == nil { return } jsonStr := match[1] jsonStr = strings.ReplaceAll(jsonStr, "\n", "") var psuInfo PSURESTInfo if err := json.Unmarshal([]byte(jsonStr), &psuInfo); err != nil { return } // Clear existing PSU data and populate with RESTful data hw.PowerSupply = nil for _, psu := range psuInfo.PowerSupplies { hw.PowerSupply = append(hw.PowerSupply, models.PSU{ Slot: fmt.Sprintf("PSU%d", psu.ID), Present: psu.Present == 1, Model: strings.TrimSpace(psu.Model), Vendor: strings.TrimSpace(psu.VendorID), WattageW: psu.RatedPower, SerialNumber: strings.TrimSpace(psu.SerialNum), PartNumber: strings.TrimSpace(psu.PartNum), Firmware: psu.FwVer, Status: psu.Status, InputType: psu.InputType, InputPowerW: psu.PSInPower, OutputPowerW: psu.PSOutPower, InputVoltage: psu.PSInVolt, OutputVoltage: psu.PSOutVolt, TemperatureC: psu.PSUMaxTemp, }) } } // HDDRESTInfo represents the RESTful HDD info structure type HDDRESTInfo []struct { ID int `json:"id"` Present int `json:"present"` Enable int `json:"enable"` SN string `json:"SN"` Model string `json:"model"` Capacity int `json:"capacity"` Manufacture string `json:"manufacture"` Firmware string `json:"firmware"` LocationString string `json:"locationstring"` CapableSpeed int `json:"capablespeed"` } func parseHDDInfo(text string, hw *models.HardwareConfig) { // Find RESTful HDD info section re := regexp.MustCompile(`RESTful HDD info:\s*(\[[\s\S]*?\])\s*RESTful PSU`) match := re.FindStringSubmatch(text) if match == nil { return } jsonStr := match[1] jsonStr = strings.ReplaceAll(jsonStr, "\n", "") var hddInfo HDDRESTInfo if err := json.Unmarshal([]byte(jsonStr), &hddInfo); err != nil { return } // Update storage with detailed info (merge with existing data from asset.json) hddMap := make(map[string]struct { SN string Model string Firmware string Mfr string }) for _, hdd := range hddInfo { if hdd.Present == 1 { hddMap[hdd.LocationString] = struct { SN string Model string Firmware string Mfr string }{ SN: strings.TrimSpace(hdd.SN), Model: strings.TrimSpace(hdd.Model), Firmware: strings.TrimSpace(hdd.Firmware), Mfr: strings.TrimSpace(hdd.Manufacture), } } } // If storage is empty, populate from HDD info if len(hw.Storage) == 0 { for _, hdd := range hddInfo { if hdd.Present != 1 { continue } storType := "HDD" model := strings.TrimSpace(hdd.Model) if strings.Contains(strings.ToUpper(model), "SSD") || strings.Contains(model, "MZ7") { storType = "SSD" } iface := "SATA" if hdd.CapableSpeed == 12 { iface = "SAS" } hw.Storage = append(hw.Storage, models.Storage{ Slot: hdd.LocationString, Type: storType, Model: model, SizeGB: hdd.Capacity, SerialNumber: strings.TrimSpace(hdd.SN), Manufacturer: extractStorageManufacturer(model), Firmware: strings.TrimSpace(hdd.Firmware), Interface: iface, }) } } } // NetworkAdapterRESTInfo represents the RESTful Network Adapter info structure type NetworkAdapterRESTInfo struct { SysAdapters []struct { ID int `json:"id"` Name string `json:"name"` Location string `json:"Location"` Present int `json:"present"` Slot int `json:"slot"` VendorID int `json:"vendor_id"` DeviceID int `json:"device_id"` Vendor string `json:"vendor"` Model string `json:"model"` FwVer string `json:"fw_ver"` Status string `json:"status"` SN string `json:"sn"` PN string `json:"pn"` PortNum int `json:"port_num"` PortType string `json:"port_type"` Ports []struct { ID int `json:"id"` MacAddr string `json:"mac_addr"` } `json:"ports"` } `json:"sys_adapters"` } func parseNetworkAdapterInfo(text string, hw *models.HardwareConfig) { // Find RESTful Network Adapter info section re := regexp.MustCompile(`RESTful Network Adapter info:\s*(\{[\s\S]*?\})\s*RESTful fan`) match := re.FindStringSubmatch(text) if match == nil { return } jsonStr := match[1] jsonStr = strings.ReplaceAll(jsonStr, "\n", "") var netInfo NetworkAdapterRESTInfo if err := json.Unmarshal([]byte(jsonStr), &netInfo); err != nil { return } hw.NetworkAdapters = nil for _, adapter := range netInfo.SysAdapters { var macs []string for _, port := range adapter.Ports { if port.MacAddr != "" { macs = append(macs, port.MacAddr) } } hw.NetworkAdapters = append(hw.NetworkAdapters, models.NetworkAdapter{ Slot: fmt.Sprintf("Slot %d", adapter.Slot), Location: adapter.Location, Present: adapter.Present == 1, Model: strings.TrimSpace(adapter.Model), Vendor: strings.TrimSpace(adapter.Vendor), VendorID: adapter.VendorID, DeviceID: adapter.DeviceID, SerialNumber: strings.TrimSpace(adapter.SN), PartNumber: strings.TrimSpace(adapter.PN), Firmware: adapter.FwVer, PortCount: adapter.PortNum, PortType: adapter.PortType, MACAddresses: macs, Status: adapter.Status, }) } } func parseMemoryEvents(text string) []models.Event { var events []models.Event // Find RESTful Memory info section re := regexp.MustCompile(`RESTful Memory info:\s*(\{[\s\S]*?\})\s*RESTful HDD`) match := re.FindStringSubmatch(text) if match == nil { return events } jsonStr := match[1] jsonStr = strings.ReplaceAll(jsonStr, "\n", "") var memInfo MemoryRESTInfo if err := json.Unmarshal([]byte(jsonStr), &memInfo); err != nil { return events } // Generate events for memory modules with Warning or Error status for _, mem := range memInfo.MemModules { if mem.Status == "Warning" || mem.Status == "Error" || mem.Status == "Critical" { severity := models.SeverityWarning if mem.Status == "Error" || mem.Status == "Critical" { severity = models.SeverityCritical } description := fmt.Sprintf("Memory module %s: %s", mem.MemModSlot, mem.Status) if mem.MemModSize == 0 { description = fmt.Sprintf("Memory module %s not detected (capacity 0GB)", mem.MemModSlot) } events = append(events, models.Event{ ID: fmt.Sprintf("mem_%d", mem.MemModID), Timestamp: time.Now(), Source: "Memory", SensorType: "memory", SensorName: mem.MemModSlot, EventType: "Memory Status", Severity: severity, Description: description, RawData: fmt.Sprintf("Slot: %s, Vendor: %s, P/N: %s, S/N: %s", mem.MemModSlot, mem.MemModVendor, mem.MemModPartNum, mem.MemModSerial), }) } } return events } // extractComponentFirmware extracts firmware versions from all component data func extractComponentFirmware(text string, hw *models.HardwareConfig) { // Create a map to track existing firmware entries (avoid duplicates) existingFW := make(map[string]bool) for _, fw := range hw.Firmware { existingFW[fw.DeviceName] = true } // HDD firmware is already extracted from asset.json with better names // Skip extracting from component.log to avoid duplicates // Extract PSU firmware from RESTful PSU info rePSU := regexp.MustCompile(`RESTful PSU info:\s*(\{[\s\S]*?\})\s*RESTful Network`) if match := rePSU.FindStringSubmatch(text); match != nil { jsonStr := strings.ReplaceAll(match[1], "\n", "") var psuInfo PSURESTInfo if err := json.Unmarshal([]byte(jsonStr), &psuInfo); err == nil { for _, psu := range psuInfo.PowerSupplies { if psu.Present == 1 && psu.FwVer != "" { fwName := fmt.Sprintf("PSU%d (%s)", psu.ID, psu.Model) if !existingFW[fwName] { hw.Firmware = append(hw.Firmware, models.FirmwareInfo{ DeviceName: fwName, Version: psu.FwVer, }) existingFW[fwName] = true } } } } } // Extract Network Adapter firmware from RESTful Network Adapter info reNet := regexp.MustCompile(`RESTful Network Adapter info:\s*(\{[\s\S]*?\})\s*RESTful fan`) if match := reNet.FindStringSubmatch(text); match != nil { jsonStr := strings.ReplaceAll(match[1], "\n", "") var netInfo NetworkAdapterRESTInfo if err := json.Unmarshal([]byte(jsonStr), &netInfo); err == nil { for _, adapter := range netInfo.SysAdapters { if adapter.Present == 1 && adapter.FwVer != "" && adapter.FwVer != "NA" { fwName := fmt.Sprintf("NIC %s (%s)", adapter.Location, adapter.Model) if !existingFW[fwName] { hw.Firmware = append(hw.Firmware, models.FirmwareInfo{ DeviceName: fwName, Version: adapter.FwVer, }) existingFW[fwName] = true } } } } } } // DiskBackplaneRESTInfo represents the RESTful diskbackplane info structure type DiskBackplaneRESTInfo []struct { PortCount int `json:"port_count"` DriverCount int `json:"driver_count"` Front int `json:"front"` BackplaneIndex int `json:"backplane_index"` Present int `json:"present"` CPLDVersion string `json:"cpld_version"` Temperature int `json:"temperature"` } func parseDiskBackplaneInfo(text string, hw *models.HardwareConfig) { // Find RESTful diskbackplane info section re := regexp.MustCompile(`RESTful diskbackplane info:\s*(\[[\s\S]*?\])\s*BMC`) match := re.FindStringSubmatch(text) if match == nil { return } jsonStr := match[1] jsonStr = strings.ReplaceAll(jsonStr, "\n", "") var backplaneInfo DiskBackplaneRESTInfo if err := json.Unmarshal([]byte(jsonStr), &backplaneInfo); err != nil { return } // Create storage entries based on backplane info for _, bp := range backplaneInfo { if bp.Present != 1 { continue } location := "Rear" if bp.Front == 1 { location = "Front" } // Create entries for each port (disk slot) for i := 0; i < bp.PortCount; i++ { isPresent := i < bp.DriverCount hw.Storage = append(hw.Storage, models.Storage{ Slot: fmt.Sprintf("%d", i), Present: isPresent, Location: location, BackplaneID: bp.BackplaneIndex, Type: "HDD", }) } } }