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>
131 lines
3.2 KiB
Go
131 lines
3.2 KiB
Go
package master
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
|
|
"git.wntrmute.dev/mc/mcp/internal/masterdb"
|
|
)
|
|
|
|
// Status returns the status of services across the fleet.
|
|
func (m *Master) Status(ctx context.Context, req *mcpv1.MasterStatusRequest) (*mcpv1.MasterStatusResponse, error) {
|
|
m.Logger.Debug("Status", "service", req.GetServiceName())
|
|
|
|
resp := &mcpv1.MasterStatusResponse{}
|
|
|
|
// If a specific service is requested, look up its placement.
|
|
if name := req.GetServiceName(); name != "" {
|
|
placement, err := masterdb.GetPlacement(m.DB, name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("lookup placement: %w", err)
|
|
}
|
|
if placement == nil {
|
|
return resp, nil // empty — service not found
|
|
}
|
|
|
|
ss := m.getServiceStatus(ctx, placement)
|
|
resp.Services = append(resp.Services, ss)
|
|
return resp, nil
|
|
}
|
|
|
|
// All services.
|
|
placements, err := masterdb.ListPlacements(m.DB)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list placements: %w", err)
|
|
}
|
|
|
|
for _, p := range placements {
|
|
ss := m.getServiceStatus(ctx, p)
|
|
resp.Services = append(resp.Services, ss)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (m *Master) getServiceStatus(ctx context.Context, p *masterdb.Placement) *mcpv1.ServiceStatus {
|
|
ss := &mcpv1.ServiceStatus{
|
|
Name: p.ServiceName,
|
|
Node: p.Node,
|
|
Tier: p.Tier,
|
|
Status: "unknown",
|
|
}
|
|
|
|
// Query the agent for live status.
|
|
client, err := m.Pool.Get(p.Node)
|
|
if err != nil {
|
|
ss.Status = "unreachable"
|
|
return ss
|
|
}
|
|
|
|
statusCtx, cancel := context.WithTimeout(ctx, m.Config.Timeouts.HealthCheck.Duration)
|
|
defer cancel()
|
|
|
|
agentResp, err := client.GetServiceStatus(statusCtx, &mcpv1.GetServiceStatusRequest{
|
|
Name: p.ServiceName,
|
|
})
|
|
if err != nil {
|
|
ss.Status = "unreachable"
|
|
return ss
|
|
}
|
|
|
|
// Map agent status to master status.
|
|
for _, info := range agentResp.GetServices() {
|
|
if info.GetName() == p.ServiceName {
|
|
if info.GetActive() {
|
|
ss.Status = "running"
|
|
} else {
|
|
ss.Status = "stopped"
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// Attach edge route info.
|
|
edgeRoutes, err := masterdb.ListEdgeRoutesForService(m.DB, p.ServiceName)
|
|
if err == nil {
|
|
for _, er := range edgeRoutes {
|
|
ss.EdgeRoutes = append(ss.EdgeRoutes, &mcpv1.EdgeRouteStatus{
|
|
Hostname: er.Hostname,
|
|
EdgeNode: er.EdgeNode,
|
|
})
|
|
}
|
|
}
|
|
|
|
return ss
|
|
}
|
|
|
|
// ListNodes returns all nodes in the registry with placement counts.
|
|
func (m *Master) ListNodes(_ context.Context, _ *mcpv1.ListNodesRequest) (*mcpv1.ListNodesResponse, error) {
|
|
m.Logger.Debug("ListNodes")
|
|
|
|
nodes, err := masterdb.ListNodes(m.DB)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list nodes: %w", err)
|
|
}
|
|
|
|
counts, err := masterdb.CountPlacementsPerNode(m.DB)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("count placements: %w", err)
|
|
}
|
|
|
|
resp := &mcpv1.ListNodesResponse{}
|
|
for _, n := range nodes {
|
|
ni := &mcpv1.NodeInfo{
|
|
Name: n.Name,
|
|
Role: n.Role,
|
|
Address: n.Address,
|
|
Arch: n.Arch,
|
|
Status: n.Status,
|
|
Containers: int32(n.Containers), //nolint:gosec // small number
|
|
Services: int32(counts[n.Name]), //nolint:gosec // small number
|
|
}
|
|
if n.LastHeartbeat != nil {
|
|
ni.LastHeartbeat = n.LastHeartbeat.Format("2006-01-02T15:04:05Z")
|
|
}
|
|
resp.Nodes = append(resp.Nodes, ni)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|