Master struct with Run() lifecycle following the agent pattern exactly: open DB → bootstrap nodes → create agent pool → DNS client → TLS → auth interceptor → gRPC server → signal handler. RPC handlers: - Deploy: place service (tier-aware), forward to agent, register DNS with Tailnet IP, detect public routes, validate against allowed domains, coordinate edge routing via SetupEdgeRoute, record placement and edge routes in master DB, return structured per-step results. - Undeploy: undeploy on worker first, then remove edge routes, DNS, and DB records. Best-effort cleanup on failure. - Status: query agents for service status, aggregate with placements and edge route info from master DB. - ListNodes: return all nodes with placement counts. Placement algorithm: fewest services, ties broken alphabetically. DNS client: extracted from agent's DNSRegistrar with explicit nodeAddr parameter (master registers for different nodes). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
65 lines
1.5 KiB
Go
65 lines
1.5 KiB
Go
package master
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"git.wntrmute.dev/mc/mcp/internal/masterdb"
|
|
)
|
|
|
|
// PickNode selects the best worker node for a new service deployment.
|
|
// Algorithm: fewest placed services, ties broken alphabetically.
|
|
func PickNode(db *sql.DB) (string, error) {
|
|
workers, err := masterdb.ListWorkerNodes(db)
|
|
if err != nil {
|
|
return "", fmt.Errorf("list workers: %w", err)
|
|
}
|
|
if len(workers) == 0 {
|
|
return "", fmt.Errorf("no worker nodes available")
|
|
}
|
|
|
|
counts, err := masterdb.CountPlacementsPerNode(db)
|
|
if err != nil {
|
|
return "", fmt.Errorf("count placements: %w", err)
|
|
}
|
|
|
|
// Sort: fewest placements first, then alphabetically.
|
|
sort.Slice(workers, func(i, j int) bool {
|
|
ci := counts[workers[i].Name]
|
|
cj := counts[workers[j].Name]
|
|
if ci != cj {
|
|
return ci < cj
|
|
}
|
|
return workers[i].Name < workers[j].Name
|
|
})
|
|
|
|
return workers[0].Name, nil
|
|
}
|
|
|
|
// FindMasterNode returns the name of the node with role "master".
|
|
func FindMasterNode(db *sql.DB) (string, error) {
|
|
nodes, err := masterdb.ListNodes(db)
|
|
if err != nil {
|
|
return "", fmt.Errorf("list nodes: %w", err)
|
|
}
|
|
for _, n := range nodes {
|
|
if n.Role == "master" {
|
|
return n.Name, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("no master node found")
|
|
}
|
|
|
|
// FindEdgeNode returns the name of the first edge node.
|
|
func FindEdgeNode(db *sql.DB) (string, error) {
|
|
edges, err := masterdb.ListEdgeNodes(db)
|
|
if err != nil {
|
|
return "", fmt.Errorf("list edge nodes: %w", err)
|
|
}
|
|
if len(edges) == 0 {
|
|
return "", fmt.Errorf("no edge nodes available")
|
|
}
|
|
return edges[0].Name, nil
|
|
}
|