package masterdb import ( "database/sql" "fmt" "time" ) // Placement records which node hosts which service. type Placement struct { ServiceName string Node string Tier string DeployedAt time.Time } // CreatePlacement inserts or replaces a placement record. func CreatePlacement(db *sql.DB, serviceName, node, tier string) error { _, err := db.Exec(` INSERT INTO placements (service_name, node, tier, deployed_at) VALUES (?, ?, ?, datetime('now')) ON CONFLICT(service_name) DO UPDATE SET node = excluded.node, tier = excluded.tier, deployed_at = datetime('now') `, serviceName, node, tier) if err != nil { return fmt.Errorf("create placement %s: %w", serviceName, err) } return nil } // GetPlacement returns the placement for a service. func GetPlacement(db *sql.DB, serviceName string) (*Placement, error) { var p Placement var deployedAt string err := db.QueryRow(` SELECT service_name, node, tier, deployed_at FROM placements WHERE service_name = ? `, serviceName).Scan(&p.ServiceName, &p.Node, &p.Tier, &deployedAt) if err == sql.ErrNoRows { return nil, nil } if err != nil { return nil, fmt.Errorf("get placement %s: %w", serviceName, err) } p.DeployedAt, _ = time.Parse("2006-01-02 15:04:05", deployedAt) return &p, nil } // ListPlacements returns all placements. func ListPlacements(db *sql.DB) ([]*Placement, error) { rows, err := db.Query(`SELECT service_name, node, tier, deployed_at FROM placements ORDER BY service_name`) if err != nil { return nil, fmt.Errorf("list placements: %w", err) } defer func() { _ = rows.Close() }() var placements []*Placement for rows.Next() { var p Placement var deployedAt string if err := rows.Scan(&p.ServiceName, &p.Node, &p.Tier, &deployedAt); err != nil { return nil, fmt.Errorf("scan placement: %w", err) } p.DeployedAt, _ = time.Parse("2006-01-02 15:04:05", deployedAt) placements = append(placements, &p) } return placements, rows.Err() } // DeletePlacement removes a placement record. func DeletePlacement(db *sql.DB, serviceName string) error { _, err := db.Exec(`DELETE FROM placements WHERE service_name = ?`, serviceName) if err != nil { return fmt.Errorf("delete placement %s: %w", serviceName, err) } return nil } // CountPlacementsPerNode returns a map of node name → number of placed services. func CountPlacementsPerNode(db *sql.DB) (map[string]int, error) { rows, err := db.Query(`SELECT node, COUNT(*) FROM placements GROUP BY node`) if err != nil { return nil, fmt.Errorf("count placements: %w", err) } defer func() { _ = rows.Close() }() counts := make(map[string]int) for rows.Next() { var node string var count int if err := rows.Scan(&node, &count); err != nil { return nil, fmt.Errorf("scan count: %w", err) } counts[node] = count } return counts, rows.Err() }