diff --git a/services/device-inventory/src/client/discovery.cpp b/services/device-inventory/src/client/discovery.cpp index f083334..8483753 100644 --- a/services/device-inventory/src/client/discovery.cpp +++ b/services/device-inventory/src/client/discovery.cpp @@ -420,6 +420,9 @@ std::vector Discovery::discover_memory_sticks() { } if (!other.empty()) p.kv[K_OTHER_INFO] = other; + if (b.count("Locator") && !b["Locator"].empty()) + p.kv[K_LOCATOR] = b["Locator"]; + result.push_back(std::move(p)); } #endif @@ -500,6 +503,8 @@ std::vector Discovery::discover_memory_slots() { max_size_mb / static_cast(blocks17.size())); p.kv[K_INSTALLED_STICK] = "NULL"; + if (b.count("Locator") && !b["Locator"].empty()) + p.kv[K_LOCATOR] = b["Locator"]; result.push_back(std::move(p)); } #endif @@ -567,9 +572,9 @@ std::vector Discovery::discover_cpus() { } for (auto& b : blocks) { - // Skip unpopulated sockets - if (b.count("Status") && b["Status"].find("Unpopulated") != std::string::npos) - continue; + // Skip phantom entries (iLO processor, "Other" type, absent Status) + if (!b.count("Status") || b["Status"].find("Populated") == std::string::npos) continue; + if (b.count("Type") && b["Type"].find("Central Processor") == std::string::npos) continue; DiscoveredPart p; p.type_name = PTYPE_CPU; @@ -592,6 +597,9 @@ std::vector Discovery::discover_cpus() { if (b.count("Core Count")) p.kv[K_CORES] = b["Core Count"]; if (b.count("Thread Count")) p.kv[K_THREADS] = b["Thread Count"]; + if (b.count("Socket Designation") && b["Socket Designation"] != "Not Specified") + p.kv[K_SOCKET] = b["Socket Designation"]; + result.push_back(std::move(p)); } #endif @@ -625,11 +633,22 @@ std::vector Discovery::discover_cpu_slots() { } for (auto& b : blocks) { + // Only real CPU sockets: must have a recognizable Status (Populated or Unpopulated) + // and must be of type Central Processor + if (!b.count("Status")) continue; + const std::string& status = b["Status"]; + bool is_populated = status.find("Populated") != std::string::npos; + bool is_unpopulated = status.find("Unpopulated") != std::string::npos; + if (!is_populated && !is_unpopulated) continue; // Skip "Other", empty, or phantom entries + if (b.count("Type") && b["Type"].find("Central Processor") == std::string::npos) continue; + DiscoveredPart p; p.type_name = PTYPE_CPU_SLOT; if (b.count("Upgrade")) p.kv[K_FORM_FACTOR] = b["Upgrade"]; else p.kv[K_FORM_FACTOR] = "unknown"; p.kv[K_INSTALLED_CPU] = "NULL"; + if (b.count("Socket Designation") && b["Socket Designation"] != "Not Specified") + p.kv[K_SOCKET] = b["Socket Designation"]; result.push_back(std::move(p)); } #endif @@ -819,6 +838,8 @@ std::vector Discovery::discover_disks() { std::string type = json_str(dev, "type"); if (type != "disk") continue; + std::string dev_name = json_str(dev, "name"); + DiscoveredPart p; p.type_name = PTYPE_DISK; @@ -877,15 +898,33 @@ std::vector Discovery::discover_disks() { p.kv[K_VM_HOSTNAMES] = ""; p.kv[K_VM_SERVER_IDS] = ""; + // Handle serial collisions: if another disk in result already has the same serial, + // differentiate both by appending the device name to the serial. + if (!dev_name.empty()) { + p.kv[K_DEVICE_NAME] = dev_name; + auto ser_it = p.kv.find(K_SERIAL); + if (ser_it != p.kv.end()) { + for (auto& prev : result) { + auto prev_ser = prev.kv.find(K_SERIAL); + if (prev_ser != prev.kv.end() && prev_ser->second == ser_it->second) { + // Collision: suffix both with their device names + auto prev_dev = prev.kv.find(K_DEVICE_NAME); + std::string prev_name = (prev_dev != prev.kv.end()) ? prev_dev->second : ""; + if (!prev_name.empty()) + prev_ser->second += ":" + prev_name; + ser_it->second += ":" + dev_name; + break; + } + } + } + } + result.push_back(std::move(p)); } #endif return result; } - -// ═════════════════════════════════════════════════════════════════════════════ -// NICs // ═════════════════════════════════════════════════════════════════════════════ std::vector Discovery::discover_nics() { @@ -1042,19 +1081,49 @@ std::vector Discovery::discover_nics() { std::string flags_str= json_str(iface, "flags"); // may be array if (ifname.empty()) continue; - // Skip loopback and virtual interfaces - if (ifname == "lo") continue; - if (ifname.rfind("docker", 0) == 0 || - ifname.rfind("veth", 0) == 0 || - ifname.rfind("virbr", 0) == 0 || - ifname.rfind("br-", 0) == 0 || - ifname.rfind("tun", 0) == 0 || - ifname.rfind("tap", 0) == 0) continue; + // Only include physical NICs: those with a /device symlink pointing to a PCI entry. + // Virtual interfaces (bridges, veth, flannel, fwbr, vmbr, tap, etc.) have no device symlink. + { + std::string dev_path = "/sys/class/net/" + ifname + "/device"; + struct stat st; + if (lstat(dev_path.c_str(), &st) != 0 || !S_ISLNK(st.st_mode)) continue; + } + + // Enrich with PCI device description via lspci + std::string pci_model; + std::string pci_vendor; + { + std::string link = run_cmd("readlink /sys/class/net/" + ifname + "/device 2>/dev/null"); + // link is like "../../../0000:01:00.0" — extract last path component + size_t last_slash = link.rfind('/'); + if (last_slash != std::string::npos) link = link.substr(last_slash + 1); + // trim whitespace + while (!link.empty() && std::isspace((unsigned char)link.back())) link.pop_back(); + if (!link.empty()) { + std::string lspci_out = run_cmd("lspci -s " + link + " 2>/dev/null"); + // Format: "0000:01:00.0 Ethernet controller: Broadcom Corporation NetXtreme II..." + // Find first ": " after the address + size_t colon = lspci_out.find(": "); + if (colon != std::string::npos) { + std::string desc = lspci_out.substr(colon + 2); + while (!desc.empty() && std::isspace((unsigned char)desc.back())) desc.pop_back(); + // Try to split "VendorName Description" — look for another ": " for sub-vendor + size_t sub = desc.find(": "); + if (sub != std::string::npos) { + pci_vendor = desc.substr(0, sub); + pci_model = desc.substr(sub + 2); + } else { + pci_model = desc; + } + } + } + } DiscoveredPart p; p.type_name = PTYPE_NIC; p.kv[K_MAC] = mac; - p.kv[K_MODEL] = link_type; + p.kv[K_MODEL] = pci_model.empty() ? link_type : pci_model; + if (!pci_vendor.empty()) p.kv[K_MANUFACTURER] = pci_vendor; // connection type std::string lower_ifname = ifname; diff --git a/services/device-inventory/src/common/models.h b/services/device-inventory/src/common/models.h index e6fd427..28c4833 100644 --- a/services/device-inventory/src/common/models.h +++ b/services/device-inventory/src/common/models.h @@ -48,12 +48,14 @@ struct MemoryStick : PartBase { std::string manufacturer; std::string channel_config; std::string other_info; + std::string locator; }; struct MemorySlot : PartBase { uint32_t allowed_speed_mhz = 0; uint64_t allowed_size_mb = 0; std::optional installed_stick_id; // ref to MemoryStick.part_id + std::string locator; }; struct CPU : PartBase { @@ -62,11 +64,13 @@ struct CPU : PartBase { double speed_ghz = 0.0; uint32_t cores = 0; uint32_t threads = 0; + std::string socket_designation; }; struct CPUSlot : PartBase { std::string form_factor; // "LGA1700", "AM5", … std::optional installed_cpu_id; // ref to CPU.part_id + std::string socket_designation; }; // connection_type: "SATA" | "NVMe" | "SAS" | "USB" | "virtual" | … @@ -85,6 +89,7 @@ struct Disk : PartBase { std::vector partition_sizes_gb; std::vector vm_hostnames; // hosts/VMs with disk access std::vector vm_server_ids; // inventory server ids + std::string device_name; }; // connection_type: "ethernet" | "wifi" | "infiniband" | … diff --git a/services/device-inventory/src/common/protocol.h b/services/device-inventory/src/common/protocol.h index 475052a..7160c68 100644 --- a/services/device-inventory/src/common/protocol.h +++ b/services/device-inventory/src/common/protocol.h @@ -82,6 +82,11 @@ constexpr const char* RESP_END = "END"; constexpr const char* K_SERIAL = "serial"; constexpr const char* K_LAST_UPDATED = "last_updated"; +// Natural keys for deduplication when serial is absent +constexpr const char* K_LOCATOR = "locator"; // DIMM locator for memory +constexpr const char* K_SOCKET = "socket_designation"; // CPU socket label +constexpr const char* K_DEVICE_NAME = "device_name"; // block device name (sda, nvme0n1…) + // MemoryStick constexpr const char* K_SPEED_MHZ = "speed_mhz"; constexpr const char* K_SIZE_MB = "size_mb"; diff --git a/services/device-inventory/src/server/database.cpp b/services/device-inventory/src/server/database.cpp index d944c1c..301c1d8 100644 --- a/services/device-inventory/src/server/database.cpp +++ b/services/device-inventory/src/server/database.cpp @@ -109,6 +109,7 @@ static void apply_memory_stick(MemoryStick& m, const std::mapsecond; if (auto it = kv.find(K_CHANNEL_CONFIG); it != kv.end()) m.channel_config = it->second; if (auto it = kv.find(K_OTHER_INFO); it != kv.end()) m.other_info = it->second; + if (auto it = kv.find(K_LOCATOR); it != kv.end()) m.locator = it->second; } static void apply_memory_slot(MemorySlot& m, const std::map& kv) { @@ -119,6 +120,7 @@ static void apply_memory_slot(MemorySlot& m, const std::mapsecond == "NULL") m.installed_stick_id = std::nullopt; else m.installed_stick_id = static_cast(std::stoul(it->second)); } + if (auto it = kv.find(K_LOCATOR); it != kv.end()) m.locator = it->second; } static void apply_cpu(CPU& c, const std::map& kv) { @@ -128,6 +130,7 @@ static void apply_cpu(CPU& c, const std::map& kv) { if (auto it = kv.find(K_SPEED_GHZ); it != kv.end()) c.speed_ghz = std::stod(it->second); if (auto it = kv.find(K_CORES); it != kv.end()) c.cores = static_cast(std::stoul(it->second)); if (auto it = kv.find(K_THREADS); it != kv.end()) c.threads = static_cast(std::stoul(it->second)); + if (auto it = kv.find(K_SOCKET); it != kv.end()) c.socket_designation = it->second; } static void apply_cpu_slot(CPUSlot& c, const std::map& kv) { @@ -137,6 +140,7 @@ static void apply_cpu_slot(CPUSlot& c, const std::map& if (it->second == "NULL") c.installed_cpu_id = std::nullopt; else c.installed_cpu_id = static_cast(std::stoul(it->second)); } + if (auto it = kv.find(K_SOCKET); it != kv.end()) c.socket_designation = it->second; } static void apply_disk(Disk& d, const std::map& kv) { @@ -154,6 +158,7 @@ static void apply_disk(Disk& d, const std::map& kv) { if (auto it = kv.find(K_PARTITION_SIZES); it != kv.end()) d.partition_sizes_gb = ls_split_u64(it->second); if (auto it = kv.find(K_VM_HOSTNAMES); it != kv.end()) d.vm_hostnames = ls_split(it->second); if (auto it = kv.find(K_VM_SERVER_IDS); it != kv.end()) d.vm_server_ids = ls_split_u32(it->second); + if (auto it = kv.find(K_DEVICE_NAME); it != kv.end()) d.device_name = it->second; } static void apply_nic(NetworkCard& n, const std::map& kv) { @@ -187,6 +192,7 @@ std::string Database::serialize_memory_stick(const MemoryStick& m) const { + FS + K_MANUFACTURER + FS + m.manufacturer + FS + K_CHANNEL_CONFIG + FS + m.channel_config + FS + K_OTHER_INFO + FS + m.other_info + + FS + K_LOCATOR + FS + m.locator + "\n"; } @@ -197,6 +203,7 @@ std::string Database::serialize_memory_slot(const MemorySlot& m) const { + FS + K_ALLOWED_SPEED + FS + std::to_string(m.allowed_speed_mhz) + FS + K_ALLOWED_SIZE + FS + std::to_string(m.allowed_size_mb) + FS + K_INSTALLED_STICK + FS + installed + + FS + K_LOCATOR + FS + m.locator + "\n"; } @@ -207,6 +214,7 @@ std::string Database::serialize_cpu(const CPU& c) const { + FS + K_SPEED_GHZ + FS + std::to_string(c.speed_ghz) + FS + K_CORES + FS + std::to_string(c.cores) + FS + K_THREADS + FS + std::to_string(c.threads) + + FS + K_SOCKET + FS + c.socket_designation + "\n"; } @@ -216,6 +224,7 @@ std::string Database::serialize_cpu_slot(const CPUSlot& c) const { return base_prefix(PTYPE_CPU_SLOT, c) + FS + K_FORM_FACTOR + FS + c.form_factor + FS + K_INSTALLED_CPU + FS + installed + + FS + K_SOCKET + FS + c.socket_designation + "\n"; } @@ -248,6 +257,7 @@ std::string Database::serialize_disk(const Disk& d) const { + FS + K_PARTITION_SIZES + FS + ls_join_u64(d.partition_sizes_gb) + FS + K_VM_HOSTNAMES + FS + esc_join(d.vm_hostnames) + FS + K_VM_SERVER_IDS + FS + ls_join_u32(d.vm_server_ids) + + FS + K_DEVICE_NAME + FS + d.device_name + "\n"; } @@ -336,7 +346,8 @@ bool Database::save_nolock() { << "|" << m.size_mb << "|" << escape(m.manufacturer) << "|" << escape(m.channel_config) - << "|" << escape(m.other_info) << "\n"; + << "|" << escape(m.other_info) + << "|" << escape(m.locator) << "\n"; } for (const auto& m : inv_.memory_slots) { std::string inst = m.installed_stick_id.has_value() @@ -346,7 +357,8 @@ bool Database::save_nolock() { << "|" << m.last_updated << "|" << m.allowed_speed_mhz << "|" << m.allowed_size_mb - << "|" << inst << "\n"; + << "|" << inst + << "|" << escape(m.locator) << "\n"; } for (const auto& c : inv_.cpus) { f << "CPU|" << c.part_id << "|" << c.server_id @@ -356,7 +368,8 @@ bool Database::save_nolock() { << "|" << escape(c.manufacturer) << "|" << c.speed_ghz << "|" << c.cores - << "|" << c.threads << "\n"; + << "|" << c.threads + << "|" << escape(c.socket_designation) << "\n"; } for (const auto& c : inv_.cpu_slots) { std::string inst = c.installed_cpu_id.has_value() @@ -365,7 +378,8 @@ bool Database::save_nolock() { << "|" << escape(base_serial(c)) << "|" << c.last_updated << "|" << escape(c.form_factor) - << "|" << inst << "\n"; + << "|" << inst + << "|" << escape(c.socket_designation) << "\n"; } for (const auto& d : inv_.disks) { f << "DISK|" << d.part_id << "|" << d.server_id @@ -394,7 +408,7 @@ bool Database::save_nolock() { if (i) f << static_cast(LS); f << d.vm_server_ids[i]; } - f << "\n"; + f << "|" << escape(d.device_name) << "\n"; } for (const auto& n : inv_.network_cards) { f << "NIC|" << n.part_id << "|" << n.server_id @@ -487,6 +501,7 @@ bool Database::load() { m.manufacturer = get(7); m.channel_config = get(8); m.other_info = get(9); + m.locator = get(10); fresh.memory_sticks.push_back(std::move(m)); } else if (rec == "MSLOT" && flds.size() >= 8) { MemorySlot m; @@ -497,6 +512,7 @@ bool Database::load() { m.allowed_speed_mhz = getu32(5); m.allowed_size_mb = getu64(6); m.installed_stick_id = get_opt_u32(7); + m.locator = get(8); fresh.memory_slots.push_back(std::move(m)); } else if (rec == "CPU" && flds.size() >= 10) { CPU c; @@ -509,6 +525,7 @@ bool Database::load() { c.speed_ghz = flds.size() > 7 ? std::stod(flds[7]) : 0.0; c.cores = getu32(8); c.threads = getu32(9); + c.socket_designation = get(10); fresh.cpus.push_back(std::move(c)); } else if (rec == "CPUSLOT" && flds.size() >= 7) { CPUSlot c; @@ -518,6 +535,7 @@ bool Database::load() { c.last_updated = geti64(4); c.form_factor = get(5); c.installed_cpu_id = get_opt_u32(6); + c.socket_designation = get(7); fresh.cpu_slots.push_back(std::move(c)); } else if (rec == "DISK" && flds.size() >= 18) { Disk d; @@ -558,6 +576,7 @@ bool Database::load() { d.partition_sizes_gb = ls_split_u64(flds.size() > 15 ? flds[15] : ""); d.vm_hostnames = uesc_split_raw(flds.size() > 16 ? flds[16] : ""); d.vm_server_ids = ls_split_u32(flds.size() > 17 ? flds[17] : ""); + d.device_name = get(18); fresh.disks.push_back(std::move(d)); } else if (rec == "NIC" && flds.size() >= 13) { NetworkCard n; @@ -793,6 +812,61 @@ uint32_t Database::upsert_part(const std::string& type_name, uint32_t server_id, else if (type_name == PTYPE_DISK) found = try_update(inv_.disks); if (found) return found; } + // Natural key fallback when no serial — prevents duplicate inserts on each run + if (type_name == PTYPE_MEMORY_STICK || type_name == PTYPE_MEMORY_SLOT) { + auto loc_it = kv.find(K_LOCATOR); + if (loc_it != kv.end() && !loc_it->second.empty()) { + const std::string& locator = loc_it->second; + auto try_locator = [&](auto& vec, auto apply_fn) -> uint32_t { + for (auto& p : vec) { + if (p.server_id == server_id && p.locator == locator) { + apply_fn(p, kv); + p.last_updated = now; + save_nolock(); + return p.part_id; + } + } + return 0u; + }; + uint32_t found = 0; + if (type_name == PTYPE_MEMORY_STICK) found = try_locator(inv_.memory_sticks, apply_memory_stick); + else if (type_name == PTYPE_MEMORY_SLOT) found = try_locator(inv_.memory_slots, apply_memory_slot); + if (found) return found; + } + } else if (type_name == PTYPE_CPU || type_name == PTYPE_CPU_SLOT) { + auto sock_it = kv.find(K_SOCKET); + if (sock_it != kv.end() && !sock_it->second.empty()) { + const std::string& socket = sock_it->second; + auto try_socket = [&](auto& vec, auto apply_fn) -> uint32_t { + for (auto& p : vec) { + if (p.server_id == server_id && p.socket_designation == socket) { + apply_fn(p, kv); + p.last_updated = now; + save_nolock(); + return p.part_id; + } + } + return 0u; + }; + uint32_t found = 0; + if (type_name == PTYPE_CPU) found = try_socket(inv_.cpus, apply_cpu); + else if (type_name == PTYPE_CPU_SLOT) found = try_socket(inv_.cpu_slots, apply_cpu_slot); + if (found) return found; + } + } else if (type_name == PTYPE_DISK) { + auto dev_it = kv.find(K_DEVICE_NAME); + if (dev_it != kv.end() && !dev_it->second.empty()) { + const std::string& dev_name = dev_it->second; + for (auto& d : inv_.disks) { + if (d.server_id == server_id && d.device_name == dev_name) { + apply_disk(d, kv); + d.last_updated = now; + save_nolock(); + return d.part_id; + } + } + } + } } // Not found — insert return add_part_nolock(type_name, server_id, kv);