Files
orca/internal/emitter/socket.go
T
Jon Chery ae6eb5a27b feat(P03,P04,P08): update stanza + lifecycle hooks + socket plumbing
P03 — Update stanza (rolling/canary/blue-green):
- internal/spec/schema/update.go: UpdateValidator (strategy enum, max_parallel
  1..count, duration parsing, canary int/% forms, auto_promote). 98.2% cov.
- internal/emitter/update.go: RenderUpdatePlan computes the step sequence
  (rolling batches, canary 1+promote+rest, blue-green all+cutover). Pure plan,
  no execution (v0.10-P10 is transactional). 73.7-100% cov.

P04 — Lifecycle hooks (systemd ExecStop semantics):
- Extended internal/emitter/systemd.go: post_start -> ExecStartPost=,
  pre_stop -> ExecStop=. Order: ExecStart -> ExecStartPost -> ExecStop ->
  socket lines. 8 lifecycle tests. 100% cov on systemd.go.

P08 — Socket plumbing (R-007):
- internal/emitter/socket.go: SocketEmitter renders RuntimeDirectory=orca/
  alloc-<id> per port (mode 0750, orca:orca). ExecStartPre TCP-bind marker
  when service.bind=127.0.0.1. SocketPath(allocID,portName) helper. 100% cov.
- Alloc-id is spec.Name placeholder; real id assigned by scheduler at submit.

22 packages pass, 20 bats pass, gofmt clean, verify-reqs 90 consistent.

---ci---
project: orca
phase: P03/P04/P08
milestone: v0.9
status: execute
---/ci---
2026-08-05 17:55:11 +00:00

129 lines
5.7 KiB
Go

package emitter
import (
"fmt"
"strings"
"git.cloudinit.dev/coreci/orca/internal/jobspec"
)
// SocketEmitter renders the systemd directives that implement the
// R-007 socket-plumbing contract: workloads bind to
// /run/orca/alloc-<id>/port-<name>.sock unless overridden via
// service.bind = "127.0.0.1" (the only documented opt-in).
//
// The systemd side of the contract uses two directives:
//
// - RuntimeDirectory=orca/alloc-<alloc-id> — systemd creates
// /run/orca/alloc-<alloc-id>/ owned by the service user (orca:orca)
// with mode 0750. The directory is removed when the unit stops
// (RuntimeDirectory= semantics). P08 emits one RuntimeDirectory=
// line per port so each port's socket directory is created; the
// alloc-id placeholder is spec.Name (the real alloc-id is assigned
// by the scheduler at submit time — see allocIDFor).
//
// - ExecStartPre= — only when service.bind is "127.0.0.1" (the TCP
// opt-in). In that case the workload binds a TCP port directly
// (no socket), and the ExecStartPre is a placeholder that records
// the bind (the actual bind happens in the process; the directive
// is a no-op marker so operators can see the bind mode in the unit
// file). When service.bind is empty (the default), the workload
// binds the socket and no ExecStartPre is emitted for sockets.
//
// The socket path format is /run/orca/alloc-<alloc-id>/port-<port-name>.sock
// where alloc-id is a PLACEHOLDER (spec.Name) — the real alloc-id is
// assigned at submit time by the scheduler. The placeholder is
// documented in the rendered unit via a comment so operators reading
// the unit file understand the substitution.
//
// P08 is a PLAN/plumbing layer — the actual socket activation (socket
// unit files, systemd socket-activation passing the pre-bound socket
// fd to the process) lands in v0.10. P08 just renders the
// RuntimeDirectory= lines and the optional TCP-bind ExecStartPre so
// the directory exists at runtime.
type SocketEmitter struct{}
// runtimeDirectoryRoot is the systemd RuntimeDirectory path root.
// systemd joins this with the RuntimeDirectory= value to create
// /run/orca/alloc-<id>. The leading slash is implicit in systemd
// (RuntimeDirectory= is relative to /run).
const runtimeDirectoryRoot = "orca"
// SocketPath returns the R-007 socket path for a port on the given
// alloc-id. The alloc-id is the placeholder spec.Name when the real
// alloc-id is not yet known (the scheduler assigns the real alloc-id
// at submit time).
func SocketPath(allocID, portName string) string {
return fmt.Sprintf("/run/orca/alloc-%s/port-%s.sock", allocID, portName)
}
// RenderSocketLines renders the systemd directives that implement
// the R-007 socket plumbing for the given spec. The lines are returned
// WITHOUT a trailing newline so the caller (the systemd emitter) can
// append them to the [Service] block with consistent formatting.
//
// The returned lines are:
//
// - one RuntimeDirectory= line per port (so each port's socket
// directory is created by systemd at unit start).
// - a comment documenting the alloc-id placeholder.
// - when service.bind is "127.0.0.1", an ExecStartPre= marker that
// records the TCP opt-in (the actual bind is in the process).
//
// Returns an empty slice when the spec has no ports (no socket
// plumbing needed — e.g. a Job or a port-less DaemonSet).
func (SocketEmitter) RenderSocketLines(spec *jobspec.WorkloadSpec) []string {
if spec == nil || len(spec.Ports) == 0 {
return nil
}
allocID := allocIDForSocket(spec)
var lines []string
// One RuntimeDirectory= per port. systemd dedupes identical
// values, but we emit one per port so the unit file is
// self-documenting (each port maps to a directory entry).
for _, p := range spec.Ports {
lines = append(lines, fmt.Sprintf("RuntimeDirectory=%s/alloc-%s", runtimeDirectoryRoot, allocID))
// Document the socket path this directory serves. systemd
// ignores comment lines (lines starting with '#').
lines = append(lines, fmt.Sprintf("# socket: %s", SocketPath(allocID, p.Name)))
}
// TCP opt-in: when service.bind is 127.0.0.1, the workload binds
// a TCP port directly instead of the socket. We emit an
// ExecStartPre marker so the bind mode is visible in the unit
// file. The actual bind is in the process; the marker is a
// no-op (echo to journald).
if spec.Service != nil && strings.TrimSpace(spec.Service.Bind) != "" {
if isTCPOptIn(spec.Service.Bind) {
for _, p := range spec.Ports {
lines = append(lines, fmt.Sprintf("ExecStartPre=/bin/echo orca: bind %s port %s (tcp, R-007 opt-in)", spec.Service.Bind, p.Name))
}
}
}
return lines
}
// allocIDForSocket returns the alloc-id placeholder for the spec. The
// real alloc-id is assigned by the scheduler at submit time; P08 uses
// spec.Name as a deterministic placeholder so the rendered unit is
// stable across re-renders. This mirrors the Traefik emitter's
// allocIDFor (which uses the node hostname for the Traefik
// dynamic-config server URL); the systemd unit is per-alloc, so
// spec.Name is the right placeholder here.
func allocIDForSocket(spec *jobspec.WorkloadSpec) string {
if spec == nil || strings.TrimSpace(spec.Name) == "" {
return "<allocID>"
}
return spec.Name
}
// isTCPOptIn returns true when the bind value is the documented
// 127.0.0.1 TCP opt-in (R-007). Other valid IPs (::1, etc.) are also
// TCP opt-ins (any non-empty bind opts out of the socket default); we
// only emit the marker for 127.0.0.1 because that is the only
// documented opt-in per the PRD — other IPs are accepted by the
// schema validator but are operator-specific and we do not
// second-guess them.
func isTCPOptIn(bind string) bool {
return strings.TrimSpace(bind) == "127.0.0.1"
}