mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-27 05:57:27 +00:00
8d00d54740
The linked host agent's SMART merge falls back to matching a Proxmox disk by device path, and a path names a slot, not a disk. When a disk is swapped before the agent reports again, or after the agent went silent and kept its last rows, the row for the old disk matched the replacement. It filled or, for SAS, promoted the old serial onto it, which keyed the replacement to the old disk's canonical resource and metric history, and lent it the old disk's temperature and failed health. For SAS the registry's same-path join did the same at the canonical level even when monitoring kept the rows apart. Both now refuse a row whose identity contradicts the disk's, judged only on what both producers report like for like. WWNs count for every disk. Serials count only where Proxmox's serial is the drive's own, as smartctl's is: NVMe namespaces, and disks presented with SCSI vendor "ATA", whose serial udev's ata_id reads from IDENTIFY. Elsewhere Proxmox's serial may be a SAS address, a SCSI designator or a USB bridge's own serial (#1595). Reporter spellings are not disagreement: udev writes whitespace in serials as underscores, its T10 designator for an ATA drive puts "ATA" and the model before the drive serial, and its ID_WWN keeps only the first 64 bits of an NAA 6 identifier. The WWN match step compares normalized identity too, so the agent's 5-c50-a1b2c3d4 spelling finds the Proxmox 0x5000c500a1b2c3d4 disk. Proxmox's disk inventory reports a serial or WWN udev cannot read as the literal "unknown". The poller recorded it as a collected serial, so the collection state hid the gap and the agent's serial never filled it. It is now recorded as empty, and retained evidence carries only a usable previous serial so an older record cannot restore it. Identity keys already ignored the placeholder.
62 lines
2.7 KiB
Go
62 lines
2.7 KiB
Go
package diskinventory
|
|
|
|
import "strings"
|
|
|
|
// HardwareIdentityConflict reports whether two observations of one disk slot
|
|
// carry hardware identities that name different disks: nothing agrees across
|
|
// serial and WWN (HardwareIdentityMatch), and at least one field reported on
|
|
// both sides disagrees. A field missing or unusable on either side is no
|
|
// evidence, so an observation without identity never conflicts.
|
|
//
|
|
// Reporters spell one identity differently, and spelling is not disagreement:
|
|
// - serials are compared on letters and digits only, because udev rewrites
|
|
// whitespace and unusual characters in a serial as underscores while
|
|
// smartctl keeps them, and a T10 vendor designator for an ATA drive
|
|
// ("ATA", the model, then the drive serial) is no evidence against the
|
|
// serial it ends with, because udev reports one when a SCSI-ATA
|
|
// translation layer hides IDENTIFY from it;
|
|
// - a WWN that begins with the other is no evidence, because udev's ID_WWN
|
|
// carries only the first 64 bits of a 128-bit NAA 6 identifier that sysfs
|
|
// and smartctl report in full.
|
|
//
|
|
// A caller blanks a field its producer may not report as disk identity, such
|
|
// as a SAS disk's serial, where Proxmox may report a SAS address, and checks
|
|
// HardwareIdentityMatch with the field kept if it may still show agreement.
|
|
func HardwareIdentityConflict(leftSerial, leftWWN, rightSerial, rightWWN string) bool {
|
|
if HardwareIdentityMatch(leftSerial, leftWWN, rightSerial, rightWWN) {
|
|
return false
|
|
}
|
|
return serialsDisagree(leftSerial, rightSerial) || wwnsDisagree(leftWWN, rightWWN)
|
|
}
|
|
|
|
func serialsDisagree(left, right string) bool {
|
|
left, right = compactSerial(normalizeHardwareID(left)), compactSerial(normalizeHardwareID(right))
|
|
return left != "" && right != "" && left != right &&
|
|
!isATADesignatorOf(left, right) && !isATADesignatorOf(right, left)
|
|
}
|
|
|
|
// isATADesignatorOf reports whether designator, compacted, is the T10 vendor
|
|
// designator of the ATA drive whose serial is given: the "ata" vendor field,
|
|
// a model, then that serial.
|
|
func isATADesignatorOf(designator, serial string) bool {
|
|
const vendor = "ata"
|
|
return len(designator) > len(vendor)+len(serial) &&
|
|
strings.HasPrefix(designator, vendor) && strings.HasSuffix(designator, serial)
|
|
}
|
|
|
|
func compactSerial(value string) string {
|
|
var builder strings.Builder
|
|
for _, char := range value {
|
|
if (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') {
|
|
builder.WriteRune(char)
|
|
}
|
|
}
|
|
return builder.String()
|
|
}
|
|
|
|
func wwnsDisagree(left, right string) bool {
|
|
left, right = normalizeHardwareWWN(left), normalizeHardwareWWN(right)
|
|
return left != "" && right != "" &&
|
|
!strings.HasPrefix(left, right) && !strings.HasPrefix(right, left)
|
|
}
|