mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 21:46:57 +00:00
133 lines
3.8 KiB
Go
133 lines
3.8 KiB
Go
package lifecycle
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/rqlite"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
maintenanceFlagPath = "/opt/orama/.orama/maintenance.flag"
|
|
)
|
|
|
|
// HandlePreUpgrade prepares the node for a safe rolling upgrade:
|
|
// 1. Checks quorum safety
|
|
// 2. Writes maintenance flag
|
|
// 3. Transfers leadership on global RQLite (port 5001) if leader
|
|
// 4. Transfers leadership on each namespace RQLite
|
|
// 5. Waits 15s for metadata propagation (H5 fix)
|
|
func HandlePreUpgrade() {
|
|
if os.Geteuid() != 0 {
|
|
fmt.Fprintf(os.Stderr, "Error: pre-upgrade must be run as root (use sudo)\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Printf("Pre-upgrade: preparing node for safe restart...\n")
|
|
|
|
// 1. Check quorum safety
|
|
if warning := checkQuorumSafety(); warning != "" {
|
|
fmt.Fprintf(os.Stderr, " UNSAFE: %s\n", warning)
|
|
fmt.Fprintf(os.Stderr, " Aborting pre-upgrade. Use 'orama stop --force' to override.\n")
|
|
os.Exit(1)
|
|
}
|
|
fmt.Printf(" Quorum check passed\n")
|
|
|
|
// 2. Write maintenance flag
|
|
if err := os.MkdirAll(filepath.Dir(maintenanceFlagPath), 0755); err != nil {
|
|
fmt.Fprintf(os.Stderr, " Warning: failed to create flag directory: %v\n", err)
|
|
}
|
|
if err := os.WriteFile(maintenanceFlagPath, []byte(time.Now().Format(time.RFC3339)), 0644); err != nil {
|
|
fmt.Fprintf(os.Stderr, " Warning: failed to write maintenance flag: %v\n", err)
|
|
} else {
|
|
fmt.Printf(" Maintenance flag written\n")
|
|
}
|
|
|
|
// 3. Transfer leadership on global RQLite (port 5001)
|
|
logger, _ := zap.NewProduction()
|
|
defer logger.Sync()
|
|
|
|
fmt.Printf(" Checking global RQLite leadership (port 5001)...\n")
|
|
if err := rqlite.TransferLeadership(5001, logger); err != nil {
|
|
fmt.Printf(" Warning: global leadership transfer: %v\n", err)
|
|
} else {
|
|
fmt.Printf(" Global RQLite leadership handled\n")
|
|
}
|
|
|
|
// 4. Transfer leadership on each namespace RQLite
|
|
nsPorts := getNamespaceRQLitePorts()
|
|
for ns, port := range nsPorts {
|
|
fmt.Printf(" Checking namespace '%s' RQLite leadership (port %d)...\n", ns, port)
|
|
if err := rqlite.TransferLeadership(port, logger); err != nil {
|
|
fmt.Printf(" Warning: namespace '%s' leadership transfer: %v\n", ns, err)
|
|
} else {
|
|
fmt.Printf(" Namespace '%s' RQLite leadership handled\n", ns)
|
|
}
|
|
}
|
|
|
|
// 5. Wait for metadata propagation (H5 fix: 15s, not 3s)
|
|
// The peer exchange cycle is 30s, but we force-triggered metadata updates
|
|
// via leadership transfer. 15s is sufficient for at least one exchange cycle.
|
|
fmt.Printf(" Waiting 15s for metadata propagation...\n")
|
|
time.Sleep(15 * time.Second)
|
|
|
|
fmt.Printf("Pre-upgrade complete. Node is ready for restart.\n")
|
|
}
|
|
|
|
// getNamespaceRQLitePorts scans namespace env files to find RQLite HTTP ports.
|
|
// Returns map of namespace_name → HTTP port.
|
|
func getNamespaceRQLitePorts() map[string]int {
|
|
namespacesDir := "/opt/orama/.orama/data/namespaces"
|
|
ports := make(map[string]int)
|
|
|
|
entries, err := os.ReadDir(namespacesDir)
|
|
if err != nil {
|
|
return ports
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() {
|
|
continue
|
|
}
|
|
ns := entry.Name()
|
|
envFile := filepath.Join(namespacesDir, ns, "rqlite.env")
|
|
port := parseHTTPPortFromEnv(envFile)
|
|
if port > 0 {
|
|
ports[ns] = port
|
|
}
|
|
}
|
|
|
|
return ports
|
|
}
|
|
|
|
// parseHTTPPortFromEnv reads an env file and extracts the HTTP port from
|
|
// the HTTP_ADDR=0.0.0.0:PORT line.
|
|
func parseHTTPPortFromEnv(envFile string) int {
|
|
f, err := os.Open(envFile)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
defer f.Close()
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if strings.HasPrefix(line, "HTTP_ADDR=") {
|
|
addr := strings.TrimPrefix(line, "HTTP_ADDR=")
|
|
// Format: 0.0.0.0:PORT
|
|
if idx := strings.LastIndex(addr, ":"); idx >= 0 {
|
|
if port, err := strconv.Atoi(addr[idx+1:]); err == nil {
|
|
return port
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0
|
|
}
|