mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
Index only possible grouping owners and exercise real pass boundaries
Maximum-priority roots cannot be better than any group in a snapshot, so leave them out of the temporary owner indexes while retaining every resource and group. Correct new fixtures to respect existing attachment semantics: differently ranked hosts may already merge, and an agent's first-pass IP attachment must remain ambiguous until its two owners merge. Cover equal-priority Proxmox cost before any timing run. Contract-Neutral: Candidate-only priority pruning and faithful regression fixtures preserve all stored/published resources, identity, strict attachment ambiguity, evidence precedence, public schemas and counted-system admission. Change-source: pulse-maintainer
This commit is contained in:
@@ -5221,8 +5221,9 @@ short hostname and another reports the FQDN, but it must not collapse two
|
||||
distinct fully-qualified hosts that merely share the same short prefix across
|
||||
different domains.
|
||||
Fallback owner buckets are pass-local priority-ordered slices, rebuilt from
|
||||
each complete group snapshot. Queries visit only strictly better-priority
|
||||
owners; a second distinct qualifying root still rejects attachment even when
|
||||
each complete group snapshot. A maximum-priority group cannot be better than
|
||||
any query and is omitted only from the temporary candidate index. Queries visit
|
||||
only strictly better-priority owners; a second distinct qualifying root still rejects attachment even when
|
||||
its priority or evidence kind differs. The unique root keeps lexical exact-host
|
||||
evidence before full short/FQDN evidence before lexical exact-IP evidence.
|
||||
Comparable/short-form keys only prune candidates: the full hostname equivalence
|
||||
|
||||
@@ -697,7 +697,7 @@ func TestTopLevelOwnerIndexPreservesAmbiguityInMonitoredCountAndAdmission(t *tes
|
||||
t.Run(fmt.Sprintf("%d_targets", targetCount), func(t *testing.T) {
|
||||
resources := []Resource{
|
||||
topLevelTestProxmoxNode("platform", "tower.lab", "pve-source", "https://tower.lab:8006"),
|
||||
topLevelTestTrueNAS("nas", "tower.lab"),
|
||||
topLevelTestProxmoxNode("platform-other", "tower.lab", "pve-source-other", "https://tower.lab:8006"),
|
||||
}
|
||||
registry := NewRegistry(nil)
|
||||
registry.IngestResources(resources[:targetCount])
|
||||
|
||||
@@ -168,12 +168,26 @@ func TestTopLevelOwnerIndexBucketsAreOrderedAndDeduplicated(t *testing.T) {
|
||||
}
|
||||
index := buildTopLevelSystemFallbackIndex(groups)
|
||||
for name, bucket := range map[string][]int{"host": index.hosts["alpha"], "ip": index.ips["192.0.2.8"], "form": index.hostForms["alpha"]} {
|
||||
if !reflect.DeepEqual(bucket, []int{9, 3, 4, 1}) {
|
||||
if !reflect.DeepEqual(bucket, []int{9, 3, 4}) {
|
||||
t.Fatalf("%s owners not priority/root ordered and deduplicated: %v", name, bucket)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopLevelOwnerIndexEqualPriorityDoesNotBuildCandidateBuckets(t *testing.T) {
|
||||
groups := map[int]topLevelSystemResolvedGroup{
|
||||
0: ownerTestGroup(3, []string{"alpha", "alpha.lab"}, []string{"192.0.2.8"}),
|
||||
7: ownerTestGroup(3, []string{"alpha"}, []string{"192.0.2.8"}),
|
||||
}
|
||||
index := buildTopLevelSystemFallbackIndex(groups)
|
||||
if len(index.hosts)+len(index.ips)+len(index.hostForms) != 0 {
|
||||
t.Fatal("equal-priority roots cannot be candidates for any query")
|
||||
}
|
||||
for root := range groups {
|
||||
assertOwnerTargetMatchesOracle(t, root, groups, index)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopLevelOwnerIndexEvidenceAndAmbiguity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -243,7 +257,7 @@ type topLevelOwnerIndexFixture struct {
|
||||
|
||||
func topLevelOwnerIndexFixtures() []topLevelOwnerIndexFixture {
|
||||
fixtures := []topLevelOwnerIndexFixture{{name: "empty"}, {name: "one", resources: []Resource{topLevelTestAgent("agent", "tower.lab", "machine", "agent")}}}
|
||||
for _, name := range []string{"spread-1220", "shared-exact-1220", "shared-short-1220", "ambiguous-1220", "different-fqdns-200"} {
|
||||
for _, name := range []string{"spread-1220", "shared-exact-1220", "shared-short-1220", "ambiguous-1220", "different-fqdns-200", "equal-priority-proxmox-1220"} {
|
||||
count, nodes := 1220, 1
|
||||
switch name {
|
||||
case "spread-1220":
|
||||
@@ -274,7 +288,11 @@ func topLevelOwnerIndexFixtures() []topLevelOwnerIndexFixture {
|
||||
case "different-fqdns-200":
|
||||
host = fmt.Sprintf("tower.other-%d", i)
|
||||
}
|
||||
resources = append(resources, topLevelTestAgent(fmt.Sprintf("agent-%d", i), host, fmt.Sprintf("machine-%d", i), fmt.Sprintf("agent-id-%d", i)))
|
||||
if name == "equal-priority-proxmox-1220" {
|
||||
resources = append(resources, Resource{ID: fmt.Sprintf("vm-%d", i), Type: ResourceTypeVM, Name: fmt.Sprintf("workload-%d", i), Proxmox: &ProxmoxData{NodeName: host, VMID: i + 100}})
|
||||
} else {
|
||||
resources = append(resources, topLevelTestAgent(fmt.Sprintf("agent-%d", i), host, fmt.Sprintf("machine-%d", i), fmt.Sprintf("agent-id-%d", i)))
|
||||
}
|
||||
}
|
||||
fixtures = append(fixtures, topLevelOwnerIndexFixture{name: name, resources: resources})
|
||||
}
|
||||
|
||||
@@ -417,14 +417,25 @@ type topLevelSystemFallbackIndex struct {
|
||||
}
|
||||
|
||||
func buildTopLevelSystemFallbackIndex(groups map[int]topLevelSystemResolvedGroup) topLevelSystemFallbackIndex {
|
||||
index := topLevelSystemFallbackIndex{
|
||||
hosts: make(map[string][]int),
|
||||
ips: make(map[string][]int),
|
||||
hostForms: make(map[string][]int),
|
||||
maxPriority := 0
|
||||
havePriority := false
|
||||
for _, group := range groups {
|
||||
if !havePriority || group.priority > maxPriority {
|
||||
maxPriority = group.priority
|
||||
havePriority = true
|
||||
}
|
||||
}
|
||||
roots := make([]int, 0, len(groups))
|
||||
for root := range groups {
|
||||
roots = append(roots, root)
|
||||
var roots []int
|
||||
for root, group := range groups {
|
||||
// A maximum-priority root cannot be better than any query in this
|
||||
// snapshot. Omit it only from the temporary candidate index, never
|
||||
// from the groups, complete inventory or output.
|
||||
if group.priority < maxPriority {
|
||||
roots = append(roots, root)
|
||||
}
|
||||
}
|
||||
if len(roots) == 0 {
|
||||
return topLevelSystemFallbackIndex{}
|
||||
}
|
||||
sort.Slice(roots, func(i, j int) bool {
|
||||
if groups[roots[i]].priority != groups[roots[j]].priority {
|
||||
@@ -432,6 +443,11 @@ func buildTopLevelSystemFallbackIndex(groups map[int]topLevelSystemResolvedGroup
|
||||
}
|
||||
return roots[i] < roots[j]
|
||||
})
|
||||
index := topLevelSystemFallbackIndex{
|
||||
hosts: make(map[string][]int),
|
||||
ips: make(map[string][]int),
|
||||
hostForms: make(map[string][]int),
|
||||
}
|
||||
add := func(owners map[string][]int, key string, root int) {
|
||||
bucket := owners[key]
|
||||
// All aliases of one root are added together, so a last-entry check
|
||||
|
||||
@@ -437,18 +437,17 @@ func topLevelTestK8sCluster(id, clusterID, agentID, server string) Resource {
|
||||
}
|
||||
|
||||
func TestResolveTopLevelSystemsRebuildsOwnerIndexAtEveryPassAndCall(t *testing.T) {
|
||||
bridge := topLevelTestAgent("bridge", "alpha.lab", "bridge-machine", "bridge-agent")
|
||||
platform := topLevelTestProxmoxNode("platform", "alpha.lab", "pve-source", "https://beta.lab:8006")
|
||||
platform.Identity.IPAddresses = []string{"192.0.2.8"}
|
||||
bridge := topLevelTestDockerHost("bridge", "beta.lab", "bridge-runtime", "")
|
||||
bridge.Identity.IPAddresses = []string{"192.0.2.8"}
|
||||
leaf := topLevelTestDockerHost("leaf", "beta.lab", "leaf-runtime", "")
|
||||
leaf := topLevelTestAgent("leaf", "gamma.lab", "leaf-machine", "leaf-agent")
|
||||
leaf.Identity.IPAddresses = []string{"192.0.2.8"}
|
||||
resources := []Resource{
|
||||
topLevelTestProxmoxNode("platform", "alpha.lab", "pve-source", "https://alpha.lab:8006"),
|
||||
bridge, leaf,
|
||||
topLevelTestK8sCluster("cluster", "cluster-id", "", "https://alpha.lab:6443"),
|
||||
}
|
||||
// Docker has better priority than the unmerged agent, so it cannot attach
|
||||
// by their shared IP in the first pass. Once the agent joins the platform,
|
||||
// a fresh pass must admit the IP attachment while keeping the cluster apart.
|
||||
resources := []Resource{platform, bridge, leaf, topLevelTestK8sCluster("cluster", "cluster-id", "", "https://alpha.lab:6443")}
|
||||
// The agent's IP initially matches two distinct better-priority groups.
|
||||
// Docker can attach to the platform, but that does not change the first
|
||||
// pass's candidate snapshot. A fresh pass must then admit the agent's now
|
||||
// unique IP attachment while keeping the Kubernetes cluster apart.
|
||||
resolver := ResolveTopLevelSystems(resources)
|
||||
if resolver.Count() != 2 {
|
||||
t.Fatalf("stale pass index or excluded cluster attachment: %d groups", resolver.Count())
|
||||
|
||||
Reference in New Issue
Block a user