mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
Keep PBS Agent identity fresh and reject host-local IP matches
Direct PBS host correlation could lend Agent History and SMART ownership through loopback, Docker-local or stale address evidence, and a stale Agent could block its fresh replacement. Reuse the provider address filter and the five-minute observation boundary, preserve IPv6 URL hosts, and cover unsafe evidence, private management controls, link replacement and the three-PBS/nine-Agent topology. Change-source: pulse-maintainer
This commit is contained in:
@@ -319,6 +319,8 @@ The backend may establish that explicit link through a running PVE guest's uniqu
|
||||
|
||||
The operator's PBS connection label is never sufficient backend host identity. A label matching an unrelated Agent must not win over the endpoint's corroborated PVE guest-IP/Agent link or project that Agent's SMART disks under PBS. If only the label matches, the backend leaves the host link absent and the PBS service series separate; Backups presentation has its own correlation boundary.
|
||||
|
||||
The direct backend PBS-to-Agent path requires non-zero observations within five minutes and excludes loopback, link-local, non-unicast and recognisable Docker-local interface addresses. Private management networks, custom management bridges and explicit report-IP hints remain eligible, including correctly parsed IPv6 URL endpoints. A stale Agent at a reused endpoint must lose PBS disk membership and its presentation link; a fresh unique replacement may acquire them, while concurrent eligible Agents remain ambiguous. `pbs_pmg_registry_test.go` covers unsafe direct evidence, management-network controls and fresh/stale/replacement/ambiguity transitions through monitor registry replacement. Its three-PBS/nine-Agent fixture preserves distinct source-native host targets and disk/network/I/O telemetry, but does not establish the reporter's VirtualBox identity or installed History acceptance.
|
||||
|
||||
A realtime refresh can briefly omit the correlated host row while the PBS server row remains. Retain the last resolved host per PBS server across that omission instead of falling back to the service target, so the drawer's Identity rows and History series stay on the host key. Reuse the remembered host only while it is still fresh relative to the server and drop it once stale, so a removed or replaced host is not advertised indefinitely; a present but ambiguous host still declines.
|
||||
|
||||
Verification: ProxmoxBackupServersTable.drawer.test.tsx covers standalone and
|
||||
|
||||
@@ -508,6 +508,8 @@ PBS presentation correlation accepts standalone agents and VM/system-container r
|
||||
|
||||
When the registry uniquely corroborates a PBS connection to a host agent by PBS-reported node name, configured endpoint hostname or reported interface IP, the PBS service may expose that source-native agent ID as `pbs.linkedAgentId`. The user-editable connection name alone is not host identity: it must not attach an unrelated Agent or preempt a corroborated VM-IP/Agent link. This is a presentation link, not a replacement for the PBS service's canonical ID or metrics target. Token-auth connections may lack `nodeName`; consumers must use the explicit link in preference to display-name matching and must not infer one from an IP or name themselves. PVE-only nodes without an actual Agent facet are not host-history candidates, even if their name matches the PBS connection. A changed link invalidates any remembered correlation to an old host.
|
||||
|
||||
Direct corroboration also requires a source-native Agent ID and non-zero PBS/Agent observations within five minutes. A current PBS endpoint must not keep a stale Agent's link or disk membership when the address is reused. Interface evidence uses the shared provider-link address filter: loopback, link-local, non-unicast and recognisable Docker-local interfaces cannot prove machine identity; ordinary private management interfaces, custom management bridges and explicit global-unicast report-IP hints remain eligible. URL hostname extraction preserves bracketed IPv6 literals instead of truncating at their first colon. `TestPBSDirectHostLinkRejectsUnsafeObservations`, `TestPBSDirectHostLinkPreservesManagementEvidence` and `TestPBSDirectHostLinkWithdrawsAndReplacesStaleAgent` pin these boundaries. `TestPBSHostHistoryThreeServerTopology` proves source-native host targets and Agent disk/network/I/O telemetry remain distinct for two PVE guests and one independently IP-corroborated non-PVE PBS among nine Agents; it is synthetic, not the reporter's installed VirtualBox evidence.
|
||||
|
||||
For a PBS inside a PVE VM whose in-guest Agent omits interface IPs, the registry may also corroborate a unique Agent-to-VM state link with a unique running VM reporting the PBS endpoint's exact IP. All three observations must be within five minutes, and reused/non-unique IPs, stopped guests or competing Agent links decline to associate. This is an alternate backend evidence chain, not frontend hostname inference; it cannot identify a VirtualBox guest without equivalent observed topology.
|
||||
|
||||
A single agent surfaced as both a VM/system-container guest and a standalone agent row is one identity, not an ambiguous pair. Collapse candidates that share an agent identity before deciding, and prefer the guest's canonical metricsTarget because it names the persisted host series; the PBS service target names the service key and now records only PBS API node-status CPU/memory history, not host-agent disk, network, or I/O. Only distinct agent identities, or candidates whose identity cannot be proven equal, decline to choose. Never use input order or a fallback that discards the PBS row id.
|
||||
@@ -5159,7 +5161,10 @@ to an agent host and its agent-reported SMART disk resources only when one
|
||||
host is uniquely corroborated by the PBS-reported node name, endpoint hostname,
|
||||
endpoint IP against the host report and interface addresses, or a fresh PVE
|
||||
guest-IP/Agent link. The user-editable connection label is not identity evidence.
|
||||
Multiple matches are ambiguous and must fail closed without assigning PBS ownership.
|
||||
All candidate Agent reports must be fresh relative to the PBS observation;
|
||||
direct IP corroboration excludes host-local networks under the shared provider
|
||||
link filter. Multiple eligible matches are ambiguous and must fail closed
|
||||
without assigning PBS ownership.
|
||||
|
||||
The same unique match may set `PBSData.LinkedAgentID` on the PBS service only
|
||||
after the source-native host ID resolves to a canonical resource carrying that
|
||||
|
||||
@@ -3908,11 +3908,7 @@ func extractHostname(raw string) string {
|
||||
}
|
||||
parsed, err := url.Parse(raw)
|
||||
if err == nil && parsed.Host != "" {
|
||||
host := parsed.Host
|
||||
if strings.Contains(host, ":") {
|
||||
host = strings.Split(host, ":")[0]
|
||||
}
|
||||
return host
|
||||
return parsed.Hostname()
|
||||
}
|
||||
|
||||
if strings.Contains(raw, "/") {
|
||||
|
||||
@@ -1980,3 +1980,16 @@ func TestUnraidArrayDiskCountAdapters(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderURLHostnamePreservesIPv6(t *testing.T) {
|
||||
for _, test := range []struct{ endpoint, want string }{
|
||||
{"https://[fd00::13]:8007", "fd00::13"},
|
||||
{"https://[::1]:8007", "::1"},
|
||||
{"https://backup-host.local:8007", "backup-host.local"},
|
||||
{"https://10.2.0.13:8007", "10.2.0.13"},
|
||||
} {
|
||||
if got := extractHostname(test.endpoint); got != test.want {
|
||||
t.Errorf("extractHostname(%q) = %q, want %q", test.endpoint, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,9 @@ func (rr *ResourceRegistry) associatePBSHostAgentResources(
|
||||
func uniquePBSHostAgent(instance models.PBSInstance, hosts []models.Host, vms []models.VM) *models.Host {
|
||||
var match *models.Host
|
||||
for index := range hosts {
|
||||
if !pbsInstanceCorroboratesHost(instance, hosts[index]) {
|
||||
if strings.TrimSpace(hosts[index].ID) == "" ||
|
||||
!pbsHostLinkObservationFresh(instance.LastSeen, hosts[index].LastSeen) ||
|
||||
!pbsInstanceCorroboratesHost(instance, hosts[index]) {
|
||||
continue
|
||||
}
|
||||
if match != nil {
|
||||
@@ -119,7 +121,7 @@ func uniquePBSHostAgent(instance models.PBSInstance, hosts []models.Host, vms []
|
||||
guestID := ""
|
||||
for _, vm := range vms {
|
||||
if !strings.EqualFold(strings.TrimSpace(vm.Status), "running") ||
|
||||
!pbsGuestLinkObservationFresh(instance.LastSeen, vm.LastSeen) {
|
||||
!pbsHostLinkObservationFresh(instance.LastSeen, vm.LastSeen) {
|
||||
continue
|
||||
}
|
||||
for _, address := range vm.IPAddresses {
|
||||
@@ -141,7 +143,8 @@ func uniquePBSHostAgent(instance models.PBSInstance, hosts []models.Host, vms []
|
||||
if strings.TrimSpace(hosts[index].LinkedVMID) != guestID {
|
||||
continue
|
||||
}
|
||||
if !pbsGuestLinkObservationFresh(instance.LastSeen, hosts[index].LastSeen) {
|
||||
if strings.TrimSpace(hosts[index].ID) == "" ||
|
||||
!pbsHostLinkObservationFresh(instance.LastSeen, hosts[index].LastSeen) {
|
||||
continue
|
||||
}
|
||||
if match != nil {
|
||||
@@ -152,7 +155,10 @@ func uniquePBSHostAgent(instance models.PBSInstance, hosts []models.Host, vms []
|
||||
return match
|
||||
}
|
||||
|
||||
func pbsGuestLinkObservationFresh(pbsSeen, peerSeen time.Time) bool {
|
||||
// Endpoint addresses and machine names can be reused after replacement. A
|
||||
// current PBS observation must not borrow identity from an old Agent report,
|
||||
// whether corroboration is direct or passes through a PVE guest.
|
||||
func pbsHostLinkObservationFresh(pbsSeen, peerSeen time.Time) bool {
|
||||
if pbsSeen.IsZero() || peerSeen.IsZero() {
|
||||
return false
|
||||
}
|
||||
@@ -162,9 +168,6 @@ func pbsGuestLinkObservationFresh(pbsSeen, peerSeen time.Time) bool {
|
||||
|
||||
func pbsInstanceCorroboratesHost(instance models.PBSInstance, host models.Host) bool {
|
||||
hostName := NormalizeHostname(host.Hostname)
|
||||
if hostName == "" {
|
||||
return false
|
||||
}
|
||||
// The node hostname the PBS API reports about itself is machine identity.
|
||||
// It is the strongest evidence when the connection is configured by IP or
|
||||
// a DNS alias the agent never reports, which the connection label and the
|
||||
@@ -180,18 +183,14 @@ func pbsInstanceCorroboratesHost(instance models.PBSInstance, host models.Host)
|
||||
return false
|
||||
}
|
||||
if ip := NormalizeIP(endpoint); ip != "" {
|
||||
if NormalizeIP(host.ReportIP) == ip {
|
||||
if providerLinkIP(host.ReportIP) == ip {
|
||||
return true
|
||||
}
|
||||
for _, iface := range host.NetworkInterfaces {
|
||||
for _, address := range iface.Addresses {
|
||||
if NormalizeIP(address) == ip {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if _, ok := providerLinkNetworkIPs(host.NetworkInterfaces)[ip]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return NormalizeHostname(endpoint) == hostName
|
||||
return hostName != "" && NormalizeHostname(endpoint) == hostName
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package unifiedresources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -601,3 +602,244 @@ func containsPlatformScope(values []string, expected string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Direct PBS/Agent correlation must use the same usable machine addresses as
|
||||
// provider-link inference, not host-local addresses repeated on every server.
|
||||
func TestPBSDirectHostLinkRejectsUnsafeObservations(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
endpoint string
|
||||
mutate func(*models.Host)
|
||||
}{
|
||||
{"loopback-report", "127.0.0.1", func(h *models.Host) { h.ReportIP = "127.0.0.1" }},
|
||||
{"ipv6-loopback", "[::1]", func(h *models.Host) { h.ReportIP = "::1" }},
|
||||
{"unspecified-report", "0.0.0.0", func(h *models.Host) { h.ReportIP = "0.0.0.0" }},
|
||||
{"link-local-report", "169.254.2.3", func(h *models.Host) { h.ReportIP = "169.254.2.3" }},
|
||||
{"multicast-report", "224.0.0.2", func(h *models.Host) { h.ReportIP = "224.0.0.2" }},
|
||||
{"loopback-interface", "127.0.0.1", func(h *models.Host) {
|
||||
h.NetworkInterfaces = []models.HostNetworkInterface{{Name: "lo", Addresses: []string{"127.0.0.1/8"}}}
|
||||
}},
|
||||
{"docker-interface", "172.17.0.1", func(h *models.Host) {
|
||||
h.NetworkInterfaces = []models.HostNetworkInterface{{Name: "docker0", Addresses: []string{"172.17.0.1/16"}}}
|
||||
}},
|
||||
{"docker-generated-bridge", "172.22.0.1", func(h *models.Host) {
|
||||
h.NetworkInterfaces = []models.HostNetworkInterface{{Name: "br-abcdef123456", Addresses: []string{"172.22.0.1/16"}}}
|
||||
}},
|
||||
{"stale-report", "10.2.0.13", func(h *models.Host) { h.LastSeen = now.Add(-10 * time.Minute) }},
|
||||
{"missing-report-time", "10.2.0.13", func(h *models.Host) { h.LastSeen = time.Time{} }},
|
||||
{"missing-agent-id", "10.2.0.13", func(h *models.Host) { h.ID = "" }},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
host := models.Host{
|
||||
ID: "unrelated-agent", Hostname: "unrelated-host", ReportIP: "10.2.0.13",
|
||||
Status: "online", LastSeen: now,
|
||||
Sensors: models.HostSensorSummary{SMART: []models.HostDiskSMART{{Device: "/dev/sda", Serial: "UNRELATED-DISK"}}},
|
||||
}
|
||||
test.mutate(&host)
|
||||
registry := NewRegistry(nil)
|
||||
registry.IngestSnapshot(models.StateSnapshot{
|
||||
Hosts: []models.Host{host},
|
||||
PBSInstances: []models.PBSInstance{{
|
||||
ID: "pbs-service", Name: "backup", Host: "https://" + test.endpoint + ":8007",
|
||||
Status: "online", LastSeen: now,
|
||||
}},
|
||||
})
|
||||
for _, resource := range registry.ListForPresentation() {
|
||||
if resource.PBS != nil && resource.PBS.LinkedAgentID != "" {
|
||||
t.Errorf("unsafe observation linked PBS to %q", resource.PBS.LinkedAgentID)
|
||||
}
|
||||
if (resource.Agent != nil || resource.PhysicalDisk != nil) && containsDataSource(resource.Sources, SourcePBS) {
|
||||
t.Errorf("unsafe observation lent PBS ownership to %s (%s)", resource.ID, resource.Type)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Corroboration stays possible for ordinary private management networks,
|
||||
// explicit multi-NIC report-IP hints, and PBS-reported machine hostnames.
|
||||
func TestPBSDirectHostLinkPreservesManagementEvidence(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
endpoint string
|
||||
nodeName string
|
||||
host models.Host
|
||||
}{
|
||||
{"private-report", "10.2.0.13", "", models.Host{Hostname: "different", ReportIP: "10.2.0.13"}},
|
||||
{"interface-without-hostname", "10.2.0.13", "", models.Host{NetworkInterfaces: []models.HostNetworkInterface{{Name: "eth0", Addresses: []string{"10.2.0.13/24"}}}}},
|
||||
{"172-management-report", "172.22.0.1", "", models.Host{Hostname: "different", ReportIP: "172.22.0.1"}},
|
||||
{"management-bridge", "172.22.0.1", "", models.Host{Hostname: "different", NetworkInterfaces: []models.HostNetworkInterface{{Name: "vmbr0", Addresses: []string{"172.22.0.1/24"}}}}},
|
||||
{"custom-management-bridge", "172.22.0.1", "", models.Host{Hostname: "different", NetworkInterfaces: []models.HostNetworkInterface{{Name: "br-management", Addresses: []string{"172.22.0.1/24"}}}}},
|
||||
{"ipv6-management", "[fd00::13]", "", models.Host{Hostname: "different", NetworkInterfaces: []models.HostNetworkInterface{{Name: "eth0", Addresses: []string{"fd00::13/64"}}}}},
|
||||
{"reported-node-name", "10.2.0.13", "backup-host", models.Host{Hostname: "backup-host.local"}},
|
||||
{"endpoint-hostname", "backup-host.local", "", models.Host{Hostname: "backup-host"}},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
host := test.host
|
||||
host.ID, host.LastSeen = "host-agent", now
|
||||
instance := models.PBSInstance{Host: "https://" + test.endpoint + ":8007", NodeName: test.nodeName, LastSeen: now}
|
||||
if got := uniquePBSHostAgent(instance, []models.Host{host}, nil); got == nil || got.ID != host.ID {
|
||||
t.Fatalf("usable management evidence lost PBS Agent link: %+v", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A previously observed address may be reused. Registry replacement must
|
||||
// withdraw the old host link and PBS disk membership, then safely select a
|
||||
// fresh unique replacement without changing the service's History target.
|
||||
func TestPBSDirectHostLinkWithdrawsAndReplacesStaleAgent(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
host := models.Host{
|
||||
ID: "old-agent", Hostname: "backup-host", ReportIP: "10.2.0.13", LastSeen: now,
|
||||
Sensors: models.HostSensorSummary{SMART: []models.HostDiskSMART{{Device: "/dev/sda", Serial: "OLD-DISK"}}},
|
||||
}
|
||||
snapshot := models.StateSnapshot{
|
||||
Hosts: []models.Host{host},
|
||||
PBSInstances: []models.PBSInstance{{ID: "pbs-service", Host: "https://10.2.0.13:8007", LastSeen: now}},
|
||||
}
|
||||
adapter := NewMonitorAdapter(NewRegistry(nil))
|
||||
for _, phase := range []string{"fresh", "stale", "replaced", "ambiguous"} {
|
||||
switch phase {
|
||||
case "stale":
|
||||
snapshot.PBSInstances[0].LastSeen = now.Add(10 * time.Minute)
|
||||
case "replaced":
|
||||
replacement := host
|
||||
replacement.ID, replacement.Hostname = "new-agent", "replacement-host"
|
||||
replacement.LastSeen = snapshot.PBSInstances[0].LastSeen
|
||||
replacement.Sensors = models.HostSensorSummary{SMART: []models.HostDiskSMART{{Device: "/dev/sda", Serial: "NEW-DISK"}}}
|
||||
snapshot.Hosts = append(snapshot.Hosts, replacement)
|
||||
case "ambiguous":
|
||||
snapshot.Hosts[0].LastSeen = snapshot.PBSInstances[0].LastSeen
|
||||
}
|
||||
adapter.replaceRegistry(snapshot, nil)
|
||||
registry := adapter.currentRegistry()
|
||||
want := ""
|
||||
if phase == "fresh" {
|
||||
want = "old-agent"
|
||||
} else if phase == "replaced" {
|
||||
want = "new-agent"
|
||||
}
|
||||
for _, resource := range registry.List() {
|
||||
if resource.PBS != nil {
|
||||
if resource.PBS.LinkedAgentID != want {
|
||||
t.Errorf("%s: PBS link = %q, want %q", phase, resource.PBS.LinkedAgentID, want)
|
||||
}
|
||||
if got := registry.MetricsTarget(resource.ID); got == nil || *got != (MetricsTarget{ResourceType: "agent", ResourceID: "pbs-service"}) {
|
||||
t.Errorf("%s: PBS service target changed: %+v", phase, got)
|
||||
}
|
||||
}
|
||||
if resource.Agent != nil {
|
||||
if got := containsDataSource(resource.Sources, SourcePBS); got != (resource.Agent.AgentID == want) {
|
||||
t.Errorf("%s: Agent %s PBS membership = %t", phase, resource.Agent.AgentID, got)
|
||||
}
|
||||
}
|
||||
if resource.PhysicalDisk != nil {
|
||||
wantMembership := phase == "fresh" && resource.PhysicalDisk.Serial == "OLD-DISK" ||
|
||||
phase == "replaced" && resource.PhysicalDisk.Serial == "NEW-DISK"
|
||||
if got := containsDataSource(resource.Sources, SourcePBS); got != wantMembership {
|
||||
t.Errorf("%s: disk %s PBS membership = %t", phase, resource.PhysicalDisk.Serial, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mirrors #1723's two PVE-hosted PBS machines and one non-PVE machine among
|
||||
// nine Agents. The non-PVE case supplies observed interface-IP evidence: the
|
||||
// test is not a claim that the reporter's VirtualBox Agent supplies that IP.
|
||||
func TestPBSHostHistoryThreeServerTopology(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
snapshot := models.StateSnapshot{}
|
||||
for index := 1; index <= 9; index++ {
|
||||
host := models.Host{
|
||||
ID: fmt.Sprintf("agent-%d", index), Hostname: fmt.Sprintf("machine-%d", index), LastSeen: now,
|
||||
CPUUsage: float64(index * 10), Memory: models.Memory{Total: 1000, Used: int64(index * 100), Usage: float64(index * 10)},
|
||||
Disks: []models.Disk{{Device: "/dev/sda", Total: 1000, Used: int64(index * 100), Usage: float64(index * 10)}},
|
||||
NetworkInterfaces: []models.HostNetworkInterface{{Name: "eth0", RXBytes: uint64(index * 11), TXBytes: uint64(index * 12)}},
|
||||
NetInRate: float64(index * 11), NetOutRate: float64(index * 12),
|
||||
DiskReadRate: float64(index * 13), DiskWriteRate: float64(index * 14),
|
||||
DiskIO: []models.DiskIO{{Device: "sda", ReadBytes: uint64(index * 13), WriteBytes: uint64(index * 14)}},
|
||||
Sensors: models.HostSensorSummary{SMART: []models.HostDiskSMART{{Device: "/dev/sda", Serial: fmt.Sprintf("DISK-%d", index)}}},
|
||||
}
|
||||
if index <= 2 {
|
||||
host.LinkedVMID = fmt.Sprintf("pve:node:%d", index+100)
|
||||
snapshot.VMs = append(snapshot.VMs, models.VM{
|
||||
ID: host.LinkedVMID, VMID: index + 100, Instance: "pve", Node: "node", Name: host.Hostname,
|
||||
Status: "running", IPAddresses: []string{fmt.Sprintf("10.2.0.%d", index)}, LastSeen: now,
|
||||
})
|
||||
} else if index == 3 {
|
||||
host.NetworkInterfaces[0].Addresses = []string{"10.2.0.3/24"}
|
||||
}
|
||||
snapshot.Hosts = append(snapshot.Hosts, host)
|
||||
if index <= 3 {
|
||||
snapshot.PBSInstances = append(snapshot.PBSInstances, models.PBSInstance{
|
||||
ID: fmt.Sprintf("pbs-%d", index), Name: fmt.Sprintf("backup-%d", index), Host: fmt.Sprintf("https://10.2.0.%d:8007", index),
|
||||
Status: "online", LastSeen: now, CPU: float64(index), Memory: float64(index * 2),
|
||||
})
|
||||
}
|
||||
}
|
||||
registry := NewRegistry(nil)
|
||||
registry.IngestSnapshot(snapshot)
|
||||
services, linkedHosts, linkedDisks := 0, 0, 0
|
||||
for _, resource := range registry.ListForPresentation() {
|
||||
if resource.PBS != nil {
|
||||
services++
|
||||
var index int
|
||||
// Registry order is not identity. Extract the fixture's stable ID.
|
||||
if _, err := fmt.Sscanf(resource.PBS.InstanceID, "pbs-%d", &index); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resource.PBS.LinkedAgentID != fmt.Sprintf("agent-%d", index) {
|
||||
t.Errorf("%s linked to %q", resource.PBS.InstanceID, resource.PBS.LinkedAgentID)
|
||||
}
|
||||
if got := registry.MetricsTarget(resource.ID); got == nil || *got != (MetricsTarget{ResourceType: "agent", ResourceID: resource.PBS.InstanceID}) {
|
||||
t.Errorf("service target replaced with a host target: %+v", got)
|
||||
}
|
||||
}
|
||||
if resource.Agent != nil && containsDataSource(resource.Sources, SourcePBS) {
|
||||
linkedHosts++
|
||||
var index int
|
||||
if _, err := fmt.Sscanf(resource.Agent.AgentID, "agent-%d", &index); err != nil || index < 1 || index > 3 {
|
||||
t.Fatalf("unrelated Agent gained PBS membership: %s", resource.Agent.AgentID)
|
||||
}
|
||||
if got := registry.MetricsTarget(resource.ID); got == nil || *got != (MetricsTarget{ResourceType: "agent", ResourceID: resource.Agent.AgentID}) {
|
||||
t.Errorf("host target lost source-native Agent identity: %+v", got)
|
||||
}
|
||||
if resource.Metrics == nil {
|
||||
t.Fatalf("%s lost all Agent metrics", resource.Agent.AgentID)
|
||||
}
|
||||
for name, metric := range map[string]*MetricValue{"memory": resource.Metrics.Memory, "disk": resource.Metrics.Disk} {
|
||||
if metric == nil || metric.Source != SourceAgent || metric.Used == nil || *metric.Used != int64(index*100) {
|
||||
t.Errorf("%s %s lost distinct Agent usage: %+v", resource.Agent.AgentID, name, metric)
|
||||
}
|
||||
}
|
||||
for _, observation := range []struct {
|
||||
name string
|
||||
metric *MetricValue
|
||||
want float64
|
||||
}{
|
||||
{"CPU", resource.Metrics.CPU, float64(index * 10)},
|
||||
{"network in", resource.Metrics.NetIn, float64(index * 11)},
|
||||
{"network out", resource.Metrics.NetOut, float64(index * 12)},
|
||||
{"disk read", resource.Metrics.DiskRead, float64(index * 13)},
|
||||
{"disk write", resource.Metrics.DiskWrite, float64(index * 14)},
|
||||
} {
|
||||
if metric := observation.metric; metric == nil || metric.Source != SourceAgent || metric.Value != observation.want {
|
||||
t.Errorf("%s %s lost distinct Agent telemetry (want %g): %+v", resource.Agent.AgentID, observation.name, observation.want, metric)
|
||||
}
|
||||
}
|
||||
}
|
||||
if resource.PhysicalDisk != nil && containsDataSource(resource.Sources, SourcePBS) {
|
||||
linkedDisks++
|
||||
if resource.PhysicalDisk.Serial != "DISK-1" && resource.PhysicalDisk.Serial != "DISK-2" && resource.PhysicalDisk.Serial != "DISK-3" {
|
||||
t.Errorf("unrelated disk gained PBS membership: %s", resource.PhysicalDisk.Serial)
|
||||
}
|
||||
}
|
||||
}
|
||||
if services != 3 || linkedHosts != 3 || linkedDisks != 3 {
|
||||
t.Fatalf("three-server topology: services=%d linked hosts=%d linked disks=%d", services, linkedHosts, linkedDisks)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user