mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Prefer route-aware Proxmox host URLs
This commit is contained in:
parent
3dd2ecd198
commit
022f170dff
3 changed files with 38 additions and 23 deletions
|
|
@ -1376,7 +1376,7 @@ surface and no new `internal/api/` lifecycle handler.
|
|||
instance-scoped under `internal/hostagent/agent.go`; lifecycle-owned
|
||||
registration and update paths must not depend on package-global mutable test
|
||||
seams that can leak between concurrent agent sessions or tests.
|
||||
5. Preserve canonical /api/auto-register node identity continuity when canonical hosts shift between hostname and IP forms for the same node.
|
||||
5. Preserve canonical /api/auto-register node identity continuity when canonical hosts shift between hostname and IP forms for the same node, and keep runtime-side Proxmox setup host ordering aligned with the contract: explicit report-IP override, route-aware local IP, resolvable hostname fallback, then heuristic local IP fallback.
|
||||
That same lifecycle-adjacent shared-API boundary now covers relay and
|
||||
command-target hostname resolution too. When lifecycle flows reuse
|
||||
`internal/api/router_routes_ai_relay.go`, `internal/agentexec/server.go`,
|
||||
|
|
@ -2614,13 +2614,17 @@ That canonical /api/auto-register behavior now also includes hostname/IP continu
|
|||
reruns that arrive through a different canonical host form must reuse the same
|
||||
Pulse-managed node record and token instead of forking duplicate fleet entries.
|
||||
That same lifecycle contract also governs the runtime-side Proxmox setup host
|
||||
selection in `internal/hostagent/proxmox_setup.go`: when the system hostname
|
||||
resolves to a non-loopback, non-link-local address, the generated Proxmox
|
||||
registration host must stay on that canonical hostname instead of downgrading
|
||||
to a route-inferred interface IP. Route-aware IP detection remains the fallback
|
||||
only when hostname resolution is unusable, so multi-NIC and internal-CA
|
||||
deployments preserve canonical hostname continuity without losing an IP escape
|
||||
hatch for non-DNS installs.
|
||||
selection in `internal/hostagent/proxmox_setup.go`: an explicit report-IP
|
||||
override remains the highest-priority operator-provided endpoint, and otherwise
|
||||
the route-aware local IP used to reach Pulse is the preferred registration
|
||||
`host`. A resolvable system hostname remains an ordered fallback candidate, not
|
||||
the primary generated host, so Pulse can still select it when the server-side
|
||||
fingerprint probe proves that hostname is the correct reachable endpoint. The
|
||||
candidate list plus hostname/IP continuity is the canonical place for DNS
|
||||
continuity; runtime setup must not prefer a short hostname over the
|
||||
route-aware interface merely because local resolution succeeds. Heuristic local
|
||||
IP selection is only a fallback after the explicit override, route-aware IP,
|
||||
and resolvable-hostname candidate paths.
|
||||
That same Proxmox registration boundary must now also let Pulse choose from the
|
||||
agent's ordered candidate host list instead of blindly persisting the agent's
|
||||
first preference. Unified Agent setup must send canonical `candidateHosts`
|
||||
|
|
|
|||
|
|
@ -924,26 +924,27 @@ func (p *ProxmoxSetup) candidateHostURLs(ctx context.Context, ptype proxmoxProdu
|
|||
return append(candidates, buildURL(hostname))
|
||||
}
|
||||
|
||||
// Priority 2: Prefer the system hostname when it resolves to a non-loopback,
|
||||
// non-link-local address. This preserves admin-managed DNS names and matching
|
||||
// TLS certificates instead of silently downgrading to an inferred IP address.
|
||||
// Priority 2: Prefer the route-aware local IP used to reach Pulse. This keeps
|
||||
// generated Proxmox config aligned with the reachable endpoint shown to users,
|
||||
// while preserving hostname as a fallback candidate below.
|
||||
if reachableIP := p.getIPThatReachesPulse(); reachableIP != "" {
|
||||
p.logger.Debug().Str("ip", reachableIP).Msg("Using IP that can reach Pulse server")
|
||||
candidates = appendUniqueHostCandidate(candidates, seen, buildURL(reachableIP))
|
||||
}
|
||||
|
||||
// Priority 3: Keep the system hostname as a fallback when it resolves to a
|
||||
// non-loopback, non-link-local address. The server receives candidateHosts
|
||||
// and may still choose it if it is the correct stable endpoint.
|
||||
if hostname := strings.TrimSpace(p.hostname); hostname != "" {
|
||||
if hostnameIP := p.getIPForHostname(); hostnameIP != "" && !isLoopbackOrLinkLocalIP(hostnameIP) {
|
||||
p.logger.Debug().
|
||||
Str("hostname", hostname).
|
||||
Str("ip", hostnameIP).
|
||||
Msg("Using resolvable hostname for Proxmox registration")
|
||||
Msg("Adding resolvable hostname fallback for Proxmox registration")
|
||||
candidates = appendUniqueHostCandidate(candidates, seen, buildURL(hostname))
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 3: Try to determine which local IP is used to connect to Pulse.
|
||||
// This remains the best fallback when the hostname is not usable.
|
||||
if reachableIP := p.getIPThatReachesPulse(); reachableIP != "" {
|
||||
p.logger.Debug().Str("ip", reachableIP).Msg("Using IP that can reach Pulse server")
|
||||
candidates = appendUniqueHostCandidate(candidates, seen, buildURL(reachableIP))
|
||||
}
|
||||
|
||||
// Priority 4: Get all IPs and select the best one based on heuristics.
|
||||
hostnameCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
|
|
@ -974,8 +975,8 @@ func (p *ProxmoxSetup) candidateHostURLs(ctx context.Context, ptype proxmoxProdu
|
|||
}
|
||||
|
||||
// getHostURL constructs the host URL for this Proxmox node.
|
||||
// Prefers canonical hostname continuity when it resolves to a real local address,
|
||||
// then falls back to route-aware IP detection and heuristic local IP selection.
|
||||
// Prefers explicit reportIP, then route-aware IP detection, then hostname and
|
||||
// heuristic local IP fallbacks.
|
||||
func (p *ProxmoxSetup) getHostURL(ctx context.Context, ptype proxmoxProductType) string {
|
||||
candidates := p.candidateHostURLs(ctx, ptype)
|
||||
if len(candidates) == 0 {
|
||||
|
|
|
|||
|
|
@ -441,7 +441,7 @@ func TestGetHostURL(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("prefers resolvable hostname before inferred route IP", func(t *testing.T) {
|
||||
t.Run("prefers route-aware IP before resolvable hostname fallback", func(t *testing.T) {
|
||||
p.reportIP = ""
|
||||
p.pulseURL = "https://pulse:7655"
|
||||
mc.lookupIPFn = func(h string) ([]net.IP, error) {
|
||||
|
|
@ -453,9 +453,19 @@ func TestGetHostURL(t *testing.T) {
|
|||
mc.dialTimeoutFn = func(network, address string, timeout time.Duration) (net.Conn, error) {
|
||||
return &mockConn{localAddr: &net.UDPAddr{IP: net.ParseIP("10.1.1.1")}}, nil
|
||||
}
|
||||
if got := p.getHostURL(context.Background(), "pve"); got != "https://test-host:8006" {
|
||||
if got := p.getHostURL(context.Background(), "pve"); got != "https://10.1.1.1:8006" {
|
||||
t.Errorf("got %s", got)
|
||||
}
|
||||
candidates := p.candidateHostURLs(context.Background(), "pve")
|
||||
if len(candidates) < 2 {
|
||||
t.Fatalf("candidateHostURLs() = %#v, want route IP then hostname fallback", candidates)
|
||||
}
|
||||
if candidates[0] != "https://10.1.1.1:8006" {
|
||||
t.Fatalf("candidateHostURLs()[0] = %q, want route-aware IP", candidates[0])
|
||||
}
|
||||
if candidates[1] != "https://test-host:8006" {
|
||||
t.Fatalf("candidateHostURLs()[1] = %q, want hostname fallback", candidates[1])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("uses IP that reaches pulse when hostname is not resolvable", func(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue