|
|
|
|
@@ -63,16 +63,16 @@ func (h *ClusterHandler) RepairReplication(c *gin.Context) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
local := findNode(all, h.LocalID)
|
|
|
|
|
standby := findByPGRole(all, "standby")
|
|
|
|
|
|
|
|
|
|
// Primary → an den Standby delegieren.
|
|
|
|
|
if local != nil && local.PGRole == "primary" {
|
|
|
|
|
// Primary → an den Subscriber-Peer (Nicht-Primary) delegieren.
|
|
|
|
|
if isPrimaryNode(local) {
|
|
|
|
|
standby := findSubscriberPeer(all, h.LocalID)
|
|
|
|
|
if h.Aggregator == nil {
|
|
|
|
|
response.BadRequest(c, errors.New("kein mTLS-Aggregator verfügbar — Resync nicht delegierbar"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if standby == nil {
|
|
|
|
|
response.BadRequest(c, errors.New("kein Standby-Node gefunden, an den der Resync delegiert werden könnte"))
|
|
|
|
|
response.BadRequest(c, errors.New("kein Standby-/Subscriber-Node gefunden, an den der Resync delegiert werden könnte"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
res := h.Aggregator.PostPeer(c.Request.Context(), *standby, repairAgentPath)
|
|
|
|
|
@@ -131,13 +131,13 @@ func (h *ClusterHandler) AgentRepairReplication(c *gin.Context) {
|
|
|
|
|
// Primary (kein Subscriber). Gibt den ermittelten Primary-Host zurück.
|
|
|
|
|
func (h *ClusterHandler) runLocalRepair(_ context.Context, all []models.HANode) (string, error) {
|
|
|
|
|
local := findNode(all, h.LocalID)
|
|
|
|
|
primary := findByPGRole(all, "primary")
|
|
|
|
|
primary := findPrimary(all)
|
|
|
|
|
|
|
|
|
|
if local != nil && local.PGRole == "primary" {
|
|
|
|
|
return "", errors.New("dieser Node ist der PostgreSQL-Primary — Resync läuft nur auf einem Standby/Subscriber")
|
|
|
|
|
if isPrimaryNode(local) {
|
|
|
|
|
return "", errors.New("dieser Node ist der Cluster-Primary — Resync läuft nur auf einem Standby/Subscriber")
|
|
|
|
|
}
|
|
|
|
|
if primary == nil {
|
|
|
|
|
return "", errors.New("kein PostgreSQL-Primary im Cluster gefunden — Resync-Quelle unbekannt")
|
|
|
|
|
return "", errors.New("kein Cluster-Primary gefunden — Resync-Quelle unbekannt")
|
|
|
|
|
}
|
|
|
|
|
if primary.ID == h.LocalID {
|
|
|
|
|
return "", errors.New("der lokale Node ist als Primary markiert — Resync nicht möglich")
|
|
|
|
|
@@ -200,8 +200,8 @@ func (h *ClusterHandler) RepairReplicationStatus(c *gin.Context) {
|
|
|
|
|
if h.Store != nil {
|
|
|
|
|
if all, err := h.Store.List(c.Request.Context()); err == nil {
|
|
|
|
|
local := findNode(all, h.LocalID)
|
|
|
|
|
standby := findByPGRole(all, "standby")
|
|
|
|
|
if local != nil && local.PGRole == "primary" && h.Aggregator != nil && standby != nil {
|
|
|
|
|
standby := findSubscriberPeer(all, h.LocalID)
|
|
|
|
|
if isPrimaryNode(local) && h.Aggregator != nil && standby != nil {
|
|
|
|
|
results := h.Aggregator.FanOut(c.Request.Context(),
|
|
|
|
|
[]models.HANode{*standby}, repairAgentPath+"/status", h.LocalID)
|
|
|
|
|
if len(results) == 1 && results[0].OK && len(results[0].Data) > 0 {
|
|
|
|
|
@@ -305,15 +305,44 @@ func findNode(nodes []models.HANode, id string) *models.HANode {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func findByPGRole(nodes []models.HANode, role string) *models.HANode {
|
|
|
|
|
// isPrimaryNode: ein Node gilt als Primary (Publication-Quelle), wenn
|
|
|
|
|
// role ODER pg_role "primary" ist. pg_role bleibt nach cluster-setup-
|
|
|
|
|
// standby auf "standalone" (nur `promote` setzt es), daher ist role das
|
|
|
|
|
// verlässliche Signal — analog zur keepalived-Logik.
|
|
|
|
|
func isPrimaryNode(n *models.HANode) bool {
|
|
|
|
|
return n != nil && (n.Role == "primary" || n.PGRole == "primary")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// findPrimary liefert den Primary-Node (Resync-Quelle).
|
|
|
|
|
func findPrimary(nodes []models.HANode) *models.HANode {
|
|
|
|
|
for i := range nodes {
|
|
|
|
|
if nodes[i].PGRole == role {
|
|
|
|
|
if isPrimaryNode(&nodes[i]) {
|
|
|
|
|
return &nodes[i]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// findSubscriberPeer liefert den Resync-Ziel-Peer: ein anderer Node, der
|
|
|
|
|
// NICHT der Primary ist (in einem 2-Node-Cluster der Standby/Subscriber).
|
|
|
|
|
// Bevorzugt einen online erreichbaren Peer.
|
|
|
|
|
func findSubscriberPeer(nodes []models.HANode, localID string) *models.HANode {
|
|
|
|
|
var fallback *models.HANode
|
|
|
|
|
for i := range nodes {
|
|
|
|
|
n := &nodes[i]
|
|
|
|
|
if n.ID == localID || isPrimaryNode(n) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if n.Status == "online" {
|
|
|
|
|
return n
|
|
|
|
|
}
|
|
|
|
|
if fallback == nil {
|
|
|
|
|
fallback = n
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return fallback
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// pickPrimaryHost wählt die beste erreichbare Adresse des Primary:
|
|
|
|
|
// Mgmt-IP → Internal-IP → Public-IP → FQDN. Strippt eine etwaige
|
|
|
|
|
// CIDR-Maske (inet-Spalten können "10.0.0.5/32" liefern).
|
|
|
|
|
|