feat(device-inventory): hardware discovery pipeline natural key deduplication
- protocol.h: add K_LOCATOR, K_SOCKET, K_DEVICE_NAME natural key constants - models.h: add locator/socket_designation/device_name fields to structs - database.cpp: apply_*, serialize_*, save_nolock, load all updated; upsert_part gains natural key fallback for memory/cpu/disk - discovery.cpp: emit K_LOCATOR for memory sticks/slots; fix CPU filter to skip phantom iLO entries; emit K_SOCKET for cpu/cpu_slot; emit K_DEVICE_NAME for disks with serial collision fix; replace NIC blocklist with /sys/class/net/<if>/device symlink check + lspci enrichment Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
12b62bea00
commit
6e1e4b4134
4 changed files with 173 additions and 20 deletions
|
|
@ -420,6 +420,9 @@ std::vector<DiscoveredPart> Discovery::discover_memory_sticks() {
|
||||||
}
|
}
|
||||||
if (!other.empty()) p.kv[K_OTHER_INFO] = other;
|
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));
|
result.push_back(std::move(p));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -500,6 +503,8 @@ std::vector<DiscoveredPart> Discovery::discover_memory_slots() {
|
||||||
max_size_mb / static_cast<uint64_t>(blocks17.size()));
|
max_size_mb / static_cast<uint64_t>(blocks17.size()));
|
||||||
|
|
||||||
p.kv[K_INSTALLED_STICK] = "NULL";
|
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));
|
result.push_back(std::move(p));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -567,9 +572,9 @@ std::vector<DiscoveredPart> Discovery::discover_cpus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& b : blocks) {
|
for (auto& b : blocks) {
|
||||||
// Skip unpopulated sockets
|
// Skip phantom entries (iLO processor, "Other" type, absent Status)
|
||||||
if (b.count("Status") && b["Status"].find("Unpopulated") != std::string::npos)
|
if (!b.count("Status") || b["Status"].find("Populated") == std::string::npos) continue;
|
||||||
continue;
|
if (b.count("Type") && b["Type"].find("Central Processor") == std::string::npos) continue;
|
||||||
|
|
||||||
DiscoveredPart p;
|
DiscoveredPart p;
|
||||||
p.type_name = PTYPE_CPU;
|
p.type_name = PTYPE_CPU;
|
||||||
|
|
@ -592,6 +597,9 @@ std::vector<DiscoveredPart> Discovery::discover_cpus() {
|
||||||
if (b.count("Core Count")) p.kv[K_CORES] = b["Core Count"];
|
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("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));
|
result.push_back(std::move(p));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -625,11 +633,22 @@ std::vector<DiscoveredPart> Discovery::discover_cpu_slots() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& b : blocks) {
|
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;
|
DiscoveredPart p;
|
||||||
p.type_name = PTYPE_CPU_SLOT;
|
p.type_name = PTYPE_CPU_SLOT;
|
||||||
if (b.count("Upgrade")) p.kv[K_FORM_FACTOR] = b["Upgrade"];
|
if (b.count("Upgrade")) p.kv[K_FORM_FACTOR] = b["Upgrade"];
|
||||||
else p.kv[K_FORM_FACTOR] = "unknown";
|
else p.kv[K_FORM_FACTOR] = "unknown";
|
||||||
p.kv[K_INSTALLED_CPU] = "NULL";
|
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));
|
result.push_back(std::move(p));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -819,6 +838,8 @@ std::vector<DiscoveredPart> Discovery::discover_disks() {
|
||||||
std::string type = json_str(dev, "type");
|
std::string type = json_str(dev, "type");
|
||||||
if (type != "disk") continue;
|
if (type != "disk") continue;
|
||||||
|
|
||||||
|
std::string dev_name = json_str(dev, "name");
|
||||||
|
|
||||||
DiscoveredPart p;
|
DiscoveredPart p;
|
||||||
p.type_name = PTYPE_DISK;
|
p.type_name = PTYPE_DISK;
|
||||||
|
|
||||||
|
|
@ -877,15 +898,33 @@ std::vector<DiscoveredPart> Discovery::discover_disks() {
|
||||||
p.kv[K_VM_HOSTNAMES] = "";
|
p.kv[K_VM_HOSTNAMES] = "";
|
||||||
p.kv[K_VM_SERVER_IDS] = "";
|
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));
|
result.push_back(std::move(p));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═════════════════════════════════════════════════════════════════════════════
|
|
||||||
// NICs
|
|
||||||
// ═════════════════════════════════════════════════════════════════════════════
|
// ═════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
std::vector<DiscoveredPart> Discovery::discover_nics() {
|
std::vector<DiscoveredPart> Discovery::discover_nics() {
|
||||||
|
|
@ -1042,19 +1081,49 @@ std::vector<DiscoveredPart> Discovery::discover_nics() {
|
||||||
std::string flags_str= json_str(iface, "flags"); // may be array
|
std::string flags_str= json_str(iface, "flags"); // may be array
|
||||||
|
|
||||||
if (ifname.empty()) continue;
|
if (ifname.empty()) continue;
|
||||||
// Skip loopback and virtual interfaces
|
// Only include physical NICs: those with a /device symlink pointing to a PCI entry.
|
||||||
if (ifname == "lo") continue;
|
// Virtual interfaces (bridges, veth, flannel, fwbr, vmbr, tap, etc.) have no device symlink.
|
||||||
if (ifname.rfind("docker", 0) == 0 ||
|
{
|
||||||
ifname.rfind("veth", 0) == 0 ||
|
std::string dev_path = "/sys/class/net/" + ifname + "/device";
|
||||||
ifname.rfind("virbr", 0) == 0 ||
|
struct stat st;
|
||||||
ifname.rfind("br-", 0) == 0 ||
|
if (lstat(dev_path.c_str(), &st) != 0 || !S_ISLNK(st.st_mode)) continue;
|
||||||
ifname.rfind("tun", 0) == 0 ||
|
}
|
||||||
ifname.rfind("tap", 0) == 0) 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;
|
DiscoveredPart p;
|
||||||
p.type_name = PTYPE_NIC;
|
p.type_name = PTYPE_NIC;
|
||||||
p.kv[K_MAC] = mac;
|
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
|
// connection type
|
||||||
std::string lower_ifname = ifname;
|
std::string lower_ifname = ifname;
|
||||||
|
|
|
||||||
|
|
@ -48,12 +48,14 @@ struct MemoryStick : PartBase {
|
||||||
std::string manufacturer;
|
std::string manufacturer;
|
||||||
std::string channel_config;
|
std::string channel_config;
|
||||||
std::string other_info;
|
std::string other_info;
|
||||||
|
std::string locator;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MemorySlot : PartBase {
|
struct MemorySlot : PartBase {
|
||||||
uint32_t allowed_speed_mhz = 0;
|
uint32_t allowed_speed_mhz = 0;
|
||||||
uint64_t allowed_size_mb = 0;
|
uint64_t allowed_size_mb = 0;
|
||||||
std::optional<uint32_t> installed_stick_id; // ref to MemoryStick.part_id
|
std::optional<uint32_t> installed_stick_id; // ref to MemoryStick.part_id
|
||||||
|
std::string locator;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CPU : PartBase {
|
struct CPU : PartBase {
|
||||||
|
|
@ -62,11 +64,13 @@ struct CPU : PartBase {
|
||||||
double speed_ghz = 0.0;
|
double speed_ghz = 0.0;
|
||||||
uint32_t cores = 0;
|
uint32_t cores = 0;
|
||||||
uint32_t threads = 0;
|
uint32_t threads = 0;
|
||||||
|
std::string socket_designation;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CPUSlot : PartBase {
|
struct CPUSlot : PartBase {
|
||||||
std::string form_factor; // "LGA1700", "AM5", …
|
std::string form_factor; // "LGA1700", "AM5", …
|
||||||
std::optional<uint32_t> installed_cpu_id; // ref to CPU.part_id
|
std::optional<uint32_t> installed_cpu_id; // ref to CPU.part_id
|
||||||
|
std::string socket_designation;
|
||||||
};
|
};
|
||||||
|
|
||||||
// connection_type: "SATA" | "NVMe" | "SAS" | "USB" | "virtual" | …
|
// connection_type: "SATA" | "NVMe" | "SAS" | "USB" | "virtual" | …
|
||||||
|
|
@ -85,6 +89,7 @@ struct Disk : PartBase {
|
||||||
std::vector<uint64_t> partition_sizes_gb;
|
std::vector<uint64_t> partition_sizes_gb;
|
||||||
std::vector<std::string> vm_hostnames; // hosts/VMs with disk access
|
std::vector<std::string> vm_hostnames; // hosts/VMs with disk access
|
||||||
std::vector<uint32_t> vm_server_ids; // inventory server ids
|
std::vector<uint32_t> vm_server_ids; // inventory server ids
|
||||||
|
std::string device_name;
|
||||||
};
|
};
|
||||||
|
|
||||||
// connection_type: "ethernet" | "wifi" | "infiniband" | …
|
// connection_type: "ethernet" | "wifi" | "infiniband" | …
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,11 @@ constexpr const char* RESP_END = "END";
|
||||||
constexpr const char* K_SERIAL = "serial";
|
constexpr const char* K_SERIAL = "serial";
|
||||||
constexpr const char* K_LAST_UPDATED = "last_updated";
|
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
|
// MemoryStick
|
||||||
constexpr const char* K_SPEED_MHZ = "speed_mhz";
|
constexpr const char* K_SPEED_MHZ = "speed_mhz";
|
||||||
constexpr const char* K_SIZE_MB = "size_mb";
|
constexpr const char* K_SIZE_MB = "size_mb";
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ static void apply_memory_stick(MemoryStick& m, const std::map<std::string,std::s
|
||||||
if (auto it = kv.find(K_MANUFACTURER); it != kv.end()) m.manufacturer = it->second;
|
if (auto it = kv.find(K_MANUFACTURER); it != kv.end()) m.manufacturer = it->second;
|
||||||
if (auto it = kv.find(K_CHANNEL_CONFIG); it != kv.end()) m.channel_config = it->second;
|
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_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<std::string,std::string>& kv) {
|
static void apply_memory_slot(MemorySlot& m, const std::map<std::string,std::string>& kv) {
|
||||||
|
|
@ -119,6 +120,7 @@ static void apply_memory_slot(MemorySlot& m, const std::map<std::string,std::str
|
||||||
if (it->second == "NULL") m.installed_stick_id = std::nullopt;
|
if (it->second == "NULL") m.installed_stick_id = std::nullopt;
|
||||||
else m.installed_stick_id = static_cast<uint32_t>(std::stoul(it->second));
|
else m.installed_stick_id = static_cast<uint32_t>(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<std::string,std::string>& kv) {
|
static void apply_cpu(CPU& c, const std::map<std::string,std::string>& kv) {
|
||||||
|
|
@ -128,6 +130,7 @@ static void apply_cpu(CPU& c, const std::map<std::string,std::string>& 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_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<uint32_t>(std::stoul(it->second));
|
if (auto it = kv.find(K_CORES); it != kv.end()) c.cores = static_cast<uint32_t>(std::stoul(it->second));
|
||||||
if (auto it = kv.find(K_THREADS); it != kv.end()) c.threads = static_cast<uint32_t>(std::stoul(it->second));
|
if (auto it = kv.find(K_THREADS); it != kv.end()) c.threads = static_cast<uint32_t>(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<std::string,std::string>& kv) {
|
static void apply_cpu_slot(CPUSlot& c, const std::map<std::string,std::string>& kv) {
|
||||||
|
|
@ -137,6 +140,7 @@ static void apply_cpu_slot(CPUSlot& c, const std::map<std::string,std::string>&
|
||||||
if (it->second == "NULL") c.installed_cpu_id = std::nullopt;
|
if (it->second == "NULL") c.installed_cpu_id = std::nullopt;
|
||||||
else c.installed_cpu_id = static_cast<uint32_t>(std::stoul(it->second));
|
else c.installed_cpu_id = static_cast<uint32_t>(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<std::string,std::string>& kv) {
|
static void apply_disk(Disk& d, const std::map<std::string,std::string>& kv) {
|
||||||
|
|
@ -154,6 +158,7 @@ static void apply_disk(Disk& d, const std::map<std::string,std::string>& 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_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_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_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<std::string,std::string>& kv) {
|
static void apply_nic(NetworkCard& n, const std::map<std::string,std::string>& kv) {
|
||||||
|
|
@ -187,6 +192,7 @@ std::string Database::serialize_memory_stick(const MemoryStick& m) const {
|
||||||
+ FS + K_MANUFACTURER + FS + m.manufacturer
|
+ FS + K_MANUFACTURER + FS + m.manufacturer
|
||||||
+ FS + K_CHANNEL_CONFIG + FS + m.channel_config
|
+ FS + K_CHANNEL_CONFIG + FS + m.channel_config
|
||||||
+ FS + K_OTHER_INFO + FS + m.other_info
|
+ FS + K_OTHER_INFO + FS + m.other_info
|
||||||
|
+ FS + K_LOCATOR + FS + m.locator
|
||||||
+ "\n";
|
+ "\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_SPEED + FS + std::to_string(m.allowed_speed_mhz)
|
||||||
+ FS + K_ALLOWED_SIZE + FS + std::to_string(m.allowed_size_mb)
|
+ FS + K_ALLOWED_SIZE + FS + std::to_string(m.allowed_size_mb)
|
||||||
+ FS + K_INSTALLED_STICK + FS + installed
|
+ FS + K_INSTALLED_STICK + FS + installed
|
||||||
|
+ FS + K_LOCATOR + FS + m.locator
|
||||||
+ "\n";
|
+ "\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_SPEED_GHZ + FS + std::to_string(c.speed_ghz)
|
||||||
+ FS + K_CORES + FS + std::to_string(c.cores)
|
+ FS + K_CORES + FS + std::to_string(c.cores)
|
||||||
+ FS + K_THREADS + FS + std::to_string(c.threads)
|
+ FS + K_THREADS + FS + std::to_string(c.threads)
|
||||||
|
+ FS + K_SOCKET + FS + c.socket_designation
|
||||||
+ "\n";
|
+ "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,6 +224,7 @@ std::string Database::serialize_cpu_slot(const CPUSlot& c) const {
|
||||||
return base_prefix(PTYPE_CPU_SLOT, c)
|
return base_prefix(PTYPE_CPU_SLOT, c)
|
||||||
+ FS + K_FORM_FACTOR + FS + c.form_factor
|
+ FS + K_FORM_FACTOR + FS + c.form_factor
|
||||||
+ FS + K_INSTALLED_CPU + FS + installed
|
+ FS + K_INSTALLED_CPU + FS + installed
|
||||||
|
+ FS + K_SOCKET + FS + c.socket_designation
|
||||||
+ "\n";
|
+ "\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_PARTITION_SIZES + FS + ls_join_u64(d.partition_sizes_gb)
|
||||||
+ FS + K_VM_HOSTNAMES + FS + esc_join(d.vm_hostnames)
|
+ 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_VM_SERVER_IDS + FS + ls_join_u32(d.vm_server_ids)
|
||||||
|
+ FS + K_DEVICE_NAME + FS + d.device_name
|
||||||
+ "\n";
|
+ "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -336,7 +346,8 @@ bool Database::save_nolock() {
|
||||||
<< "|" << m.size_mb
|
<< "|" << m.size_mb
|
||||||
<< "|" << escape(m.manufacturer)
|
<< "|" << escape(m.manufacturer)
|
||||||
<< "|" << escape(m.channel_config)
|
<< "|" << escape(m.channel_config)
|
||||||
<< "|" << escape(m.other_info) << "\n";
|
<< "|" << escape(m.other_info)
|
||||||
|
<< "|" << escape(m.locator) << "\n";
|
||||||
}
|
}
|
||||||
for (const auto& m : inv_.memory_slots) {
|
for (const auto& m : inv_.memory_slots) {
|
||||||
std::string inst = m.installed_stick_id.has_value()
|
std::string inst = m.installed_stick_id.has_value()
|
||||||
|
|
@ -346,7 +357,8 @@ bool Database::save_nolock() {
|
||||||
<< "|" << m.last_updated
|
<< "|" << m.last_updated
|
||||||
<< "|" << m.allowed_speed_mhz
|
<< "|" << m.allowed_speed_mhz
|
||||||
<< "|" << m.allowed_size_mb
|
<< "|" << m.allowed_size_mb
|
||||||
<< "|" << inst << "\n";
|
<< "|" << inst
|
||||||
|
<< "|" << escape(m.locator) << "\n";
|
||||||
}
|
}
|
||||||
for (const auto& c : inv_.cpus) {
|
for (const auto& c : inv_.cpus) {
|
||||||
f << "CPU|" << c.part_id << "|" << c.server_id
|
f << "CPU|" << c.part_id << "|" << c.server_id
|
||||||
|
|
@ -356,7 +368,8 @@ bool Database::save_nolock() {
|
||||||
<< "|" << escape(c.manufacturer)
|
<< "|" << escape(c.manufacturer)
|
||||||
<< "|" << c.speed_ghz
|
<< "|" << c.speed_ghz
|
||||||
<< "|" << c.cores
|
<< "|" << c.cores
|
||||||
<< "|" << c.threads << "\n";
|
<< "|" << c.threads
|
||||||
|
<< "|" << escape(c.socket_designation) << "\n";
|
||||||
}
|
}
|
||||||
for (const auto& c : inv_.cpu_slots) {
|
for (const auto& c : inv_.cpu_slots) {
|
||||||
std::string inst = c.installed_cpu_id.has_value()
|
std::string inst = c.installed_cpu_id.has_value()
|
||||||
|
|
@ -365,7 +378,8 @@ bool Database::save_nolock() {
|
||||||
<< "|" << escape(base_serial(c))
|
<< "|" << escape(base_serial(c))
|
||||||
<< "|" << c.last_updated
|
<< "|" << c.last_updated
|
||||||
<< "|" << escape(c.form_factor)
|
<< "|" << escape(c.form_factor)
|
||||||
<< "|" << inst << "\n";
|
<< "|" << inst
|
||||||
|
<< "|" << escape(c.socket_designation) << "\n";
|
||||||
}
|
}
|
||||||
for (const auto& d : inv_.disks) {
|
for (const auto& d : inv_.disks) {
|
||||||
f << "DISK|" << d.part_id << "|" << d.server_id
|
f << "DISK|" << d.part_id << "|" << d.server_id
|
||||||
|
|
@ -394,7 +408,7 @@ bool Database::save_nolock() {
|
||||||
if (i) f << static_cast<char>(LS);
|
if (i) f << static_cast<char>(LS);
|
||||||
f << d.vm_server_ids[i];
|
f << d.vm_server_ids[i];
|
||||||
}
|
}
|
||||||
f << "\n";
|
f << "|" << escape(d.device_name) << "\n";
|
||||||
}
|
}
|
||||||
for (const auto& n : inv_.network_cards) {
|
for (const auto& n : inv_.network_cards) {
|
||||||
f << "NIC|" << n.part_id << "|" << n.server_id
|
f << "NIC|" << n.part_id << "|" << n.server_id
|
||||||
|
|
@ -487,6 +501,7 @@ bool Database::load() {
|
||||||
m.manufacturer = get(7);
|
m.manufacturer = get(7);
|
||||||
m.channel_config = get(8);
|
m.channel_config = get(8);
|
||||||
m.other_info = get(9);
|
m.other_info = get(9);
|
||||||
|
m.locator = get(10);
|
||||||
fresh.memory_sticks.push_back(std::move(m));
|
fresh.memory_sticks.push_back(std::move(m));
|
||||||
} else if (rec == "MSLOT" && flds.size() >= 8) {
|
} else if (rec == "MSLOT" && flds.size() >= 8) {
|
||||||
MemorySlot m;
|
MemorySlot m;
|
||||||
|
|
@ -497,6 +512,7 @@ bool Database::load() {
|
||||||
m.allowed_speed_mhz = getu32(5);
|
m.allowed_speed_mhz = getu32(5);
|
||||||
m.allowed_size_mb = getu64(6);
|
m.allowed_size_mb = getu64(6);
|
||||||
m.installed_stick_id = get_opt_u32(7);
|
m.installed_stick_id = get_opt_u32(7);
|
||||||
|
m.locator = get(8);
|
||||||
fresh.memory_slots.push_back(std::move(m));
|
fresh.memory_slots.push_back(std::move(m));
|
||||||
} else if (rec == "CPU" && flds.size() >= 10) {
|
} else if (rec == "CPU" && flds.size() >= 10) {
|
||||||
CPU c;
|
CPU c;
|
||||||
|
|
@ -509,6 +525,7 @@ bool Database::load() {
|
||||||
c.speed_ghz = flds.size() > 7 ? std::stod(flds[7]) : 0.0;
|
c.speed_ghz = flds.size() > 7 ? std::stod(flds[7]) : 0.0;
|
||||||
c.cores = getu32(8);
|
c.cores = getu32(8);
|
||||||
c.threads = getu32(9);
|
c.threads = getu32(9);
|
||||||
|
c.socket_designation = get(10);
|
||||||
fresh.cpus.push_back(std::move(c));
|
fresh.cpus.push_back(std::move(c));
|
||||||
} else if (rec == "CPUSLOT" && flds.size() >= 7) {
|
} else if (rec == "CPUSLOT" && flds.size() >= 7) {
|
||||||
CPUSlot c;
|
CPUSlot c;
|
||||||
|
|
@ -518,6 +535,7 @@ bool Database::load() {
|
||||||
c.last_updated = geti64(4);
|
c.last_updated = geti64(4);
|
||||||
c.form_factor = get(5);
|
c.form_factor = get(5);
|
||||||
c.installed_cpu_id = get_opt_u32(6);
|
c.installed_cpu_id = get_opt_u32(6);
|
||||||
|
c.socket_designation = get(7);
|
||||||
fresh.cpu_slots.push_back(std::move(c));
|
fresh.cpu_slots.push_back(std::move(c));
|
||||||
} else if (rec == "DISK" && flds.size() >= 18) {
|
} else if (rec == "DISK" && flds.size() >= 18) {
|
||||||
Disk d;
|
Disk d;
|
||||||
|
|
@ -558,6 +576,7 @@ bool Database::load() {
|
||||||
d.partition_sizes_gb = ls_split_u64(flds.size() > 15 ? flds[15] : "");
|
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_hostnames = uesc_split_raw(flds.size() > 16 ? flds[16] : "");
|
||||||
d.vm_server_ids = ls_split_u32(flds.size() > 17 ? flds[17] : "");
|
d.vm_server_ids = ls_split_u32(flds.size() > 17 ? flds[17] : "");
|
||||||
|
d.device_name = get(18);
|
||||||
fresh.disks.push_back(std::move(d));
|
fresh.disks.push_back(std::move(d));
|
||||||
} else if (rec == "NIC" && flds.size() >= 13) {
|
} else if (rec == "NIC" && flds.size() >= 13) {
|
||||||
NetworkCard n;
|
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);
|
else if (type_name == PTYPE_DISK) found = try_update(inv_.disks);
|
||||||
if (found) return found;
|
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
|
// Not found — insert
|
||||||
return add_part_nolock(type_name, server_id, kv);
|
return add_part_nolock(type_name, server_id, kv);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue