From c1a4ccff8f6d83f9f4bdacf09f88374c285bfd6f Mon Sep 17 00:00:00 2001 From: Debian Date: Fri, 29 May 2026 19:45:02 +0200 Subject: [PATCH] =?UTF-8?q?fix(cluster):=20Placeholder=20zuerst=20l=C3=B6s?= =?UTF-8?q?chen=20vor=20UpsertSelf=20(UNIQUE-fqdn-Bug)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ha_nodes hat UNIQUE(fqdn). UpsertSelf nutzt ON CONFLICT(id) — schlägt bei fqdn-Konflikten fehl. Wenn Placeholder und echter Node die gleiche FQDN haben, schlug der INSERT der echten Row mit "duplicate key on ha_nodes_fqdn_unique" fehl. Root cause für "joining" stuck forever: der Peer blieb ewig als Placeholder weil AgentRegisterPeer + reconcileJoiningPeers die Echte- ID-Row nie erfolgreich einfügen konnten. Fix: DeletePlaceholdersByFQDN vor UpsertSelf in beiden Code-Paths. Co-Authored-By: Claude Sonnet 4.6 --- VERSION | 2 +- cmd/edgeguard-api/main.go | 2 +- cmd/edgeguard-ctl/main.go | 2 +- cmd/edgeguard-scheduler/main.go | 2 +- internal/handlers/cluster.go | 15 ++++++++++----- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/VERSION b/VERSION index 4743491..38dfb5f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.161 +1.1.162 diff --git a/cmd/edgeguard-api/main.go b/cmd/edgeguard-api/main.go index eb11df1..514f28f 100644 --- a/cmd/edgeguard-api/main.go +++ b/cmd/edgeguard-api/main.go @@ -60,7 +60,7 @@ import ( usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users" ) -var version = "1.1.161" +var version = "1.1.162" func main() { addr := os.Getenv("EDGEGUARD_API_ADDR") diff --git a/cmd/edgeguard-ctl/main.go b/cmd/edgeguard-ctl/main.go index 103a587..dbb6543 100644 --- a/cmd/edgeguard-ctl/main.go +++ b/cmd/edgeguard-ctl/main.go @@ -11,7 +11,7 @@ import ( "git.netcell-it.de/projekte/edgeguard-native/internal/services/setup" ) -var version = "1.1.161" +var version = "1.1.162" const usage = `edgeguard-ctl — EdgeGuard CLI diff --git a/cmd/edgeguard-scheduler/main.go b/cmd/edgeguard-scheduler/main.go index 2fc406f..b1331df 100644 --- a/cmd/edgeguard-scheduler/main.go +++ b/cmd/edgeguard-scheduler/main.go @@ -41,7 +41,7 @@ import ( "git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts" ) -var version = "1.1.161" +var version = "1.1.162" const ( // renewTickInterval — how often we re-evaluate expiring certs. diff --git a/internal/handlers/cluster.go b/internal/handlers/cluster.go index b5527c5..50f396c 100644 --- a/internal/handlers/cluster.go +++ b/internal/handlers/cluster.go @@ -532,13 +532,16 @@ func (h *ClusterHandler) reconcileJoiningPeers(placeholders []models.HANode) { if n.InternalIP == nil { n.InternalIP = placeholder.InternalIP } + // Placeholder zuerst löschen: ha_nodes hat UNIQUE(fqdn). Ohne + // dieses Delete würde UpsertSelf (ON CONFLICT(id)) mit fqdn- + // unique-Violation scheitern. + _ = h.Store.DeletePlaceholdersByFQDN(ctx, n.FQDN, n.ID) out, err := h.Store.UpsertSelf(ctx, n) if err != nil { slog.Warn("cluster: reconcile joining peer: upsert failed", "fqdn", n.FQDN, "real_id", n.ID, "error", err) continue } - _ = h.Store.DeletePlaceholdersByFQDN(ctx, n.FQDN, n.ID) changed = true slog.Info("cluster: joining peer reconciled via identity pull", "id", out.ID, "fqdn", out.FQDN, "placeholder_id", placeholder.ID) @@ -690,16 +693,18 @@ func (h *ClusterHandler) AgentRegisterPeer(c *gin.Context) { v := req.Version n.Version = &v } + // Placeholder zuerst löschen: ha_nodes hat UNIQUE(fqdn). Der INSERT + // in UpsertSelf verwendet ON CONFLICT(id) — greift NICHT bei fqdn- + // Konflikten. Ohne das Delete würde der INSERT mit "duplicate key on + // ha_nodes_fqdn_unique" scheitern und der Peer bliebe ewig "joining". + _ = h.Store.DeletePlaceholdersByFQDN(c.Request.Context(), req.FQDN, req.ID) + out, err := h.Store.UpsertSelf(c.Request.Context(), n) if err != nil { response.Internal(c, err) return } - // Remove ALL placeholder rows for this FQDN (both legacy "pre-{timestamp}" - // and current "prenode-{fqdn}" style) — the real row just took their place. - _ = h.Store.DeletePlaceholdersByFQDN(c.Request.Context(), req.FQDN, req.ID) - // Firewall-Reload damit peer_ipv4-Set die neue IP aufnimmt. Best- // effort: Fehler loggen, Response weiter durchreichen — der Peer // hat seine Identity erfolgreich registriert, Operator kann manuell