A build machine serves the seat its credential claims (hq ADR 0190, the handover)
After the build role moved to node-build-agent, nothing would hold it until build-agent is registered — and registering build-agent needs a build outcome that only the running builder could produce, bound as it was to the old seat by name. One binary, two roles: the seat a machine serves is the first its credential claims, as the mesh writes the claims beside the credential it issues (ADR 0159); the old builder keeps draining mesh-build-machine, a build-agent takes node-build-agent, and what each says about a build goes out as that seat's events, so an outcome is heard where the asker of that seat listens. A credential naming no claim serves the current role.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
package link
|
||||
|
||||
import "testing"
|
||||
|
||||
// A build machine serves the seat its credential claims (novox/hq ADR 0190 handover): the old
|
||||
// `builder` keeps the old role, a `build-agent` takes the new, from one binary and no flag.
|
||||
func TestABuildMachineServesTheSeatItsCredentialClaims(t *testing.T) {
|
||||
if got := BuildSeatClaimed([]string{"mesh-build-machine"}); got != "mesh-build-machine" {
|
||||
t.Errorf("a credential claiming the old role serves %q", got)
|
||||
}
|
||||
if got := BuildSeatClaimed([]string{"node-build-agent"}); got != TheBuildMachine {
|
||||
t.Errorf("a credential claiming the new role serves %q", got)
|
||||
}
|
||||
if got := BuildSeatClaimed(nil); got != TheBuildMachine {
|
||||
t.Errorf("a credential claiming nothing serves %q, want the current role", got)
|
||||
}
|
||||
if got := BuildSeatClaimed([]string{"", "node-build-agent"}); got != TheBuildMachine {
|
||||
t.Errorf("an empty claim is skipped; got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// What a machine says about a build is the event of the seat it took the build from, so an outcome
|
||||
// is heard where the asker of that seat listens.
|
||||
func TestABuildsEventsAreItsSeats(t *testing.T) {
|
||||
if BuildOutcomeOf(TheBuildMachineBefore) != "mesh.seat.mesh-build-machine.event.built" {
|
||||
t.Error(BuildOutcomeOf(TheBuildMachineBefore))
|
||||
}
|
||||
if BuildWorkOf(TheBuildMachine) != BuildWork() || BuildOutcomeOf(TheBuildMachine) != BuildOutcome() ||
|
||||
BuildStartedOf(TheBuildMachine) != BuildStarted() || BuildLogOf(TheBuildMachine, "b1") != BuildLog("b1") {
|
||||
t.Error("the no-argument forms must name the current role")
|
||||
}
|
||||
}
|
||||
+33
-5
@@ -31,10 +31,36 @@ import (
|
||||
// build-agent to the machines that build; unassign builder and forget it and its seat's stream.
|
||||
const TheBuildMachine = "node-build-agent"
|
||||
|
||||
// TheBuildMachineBefore is the role a build was submitted to until ADR 0190: the mesh's one build
|
||||
// machine, mesh-scoped. Kept named while the handover runs — a machine whose credential claims it
|
||||
// still serves it, and the controller still hears its outcomes — and dropped with the retired seat
|
||||
// row once nothing claims it.
|
||||
const TheBuildMachineBefore = "mesh-build-machine"
|
||||
|
||||
// BuildSeatClaimed is the build role a machine serves: the first seat its credential claims, or the
|
||||
// current role when the credential names none (a credential from before claims travelled in it, or
|
||||
// one written by hand). **The credential decides, not the binary** (ADR 0190 handover): one build
|
||||
// machine binary runs as the old `builder` on the old seat and as a `build-agent` on the new one,
|
||||
// each taking the work the mesh issued it a credential for, so neither drains the other's queue
|
||||
// and the switch needs no flag day.
|
||||
func BuildSeatClaimed(claimed []string) string {
|
||||
for _, seat := range claimed {
|
||||
if seat != "" {
|
||||
return seat
|
||||
}
|
||||
}
|
||||
return TheBuildMachine
|
||||
}
|
||||
|
||||
// BuildWork is where a build request lands, and BuildOutcome is where its result does. Derived from
|
||||
// the seat, so both sides name the role and neither names the other.
|
||||
func BuildWork() string { return "mesh.seat." + TheBuildMachine + ".accept.build" }
|
||||
func BuildOutcome() string { return "mesh.seat." + TheBuildMachine + ".event.built" }
|
||||
// the seat, so both sides name the role and neither names the other. The no-argument forms name the
|
||||
// current role; the `Of` forms take the seat, for the handover during which two roles exist.
|
||||
func BuildWork() string { return BuildWorkOf(TheBuildMachine) }
|
||||
func BuildOutcome() string { return BuildOutcomeOf(TheBuildMachine) }
|
||||
func BuildWorkOf(seat string) string { return "mesh.seat." + seat + ".accept.build" }
|
||||
func BuildOutcomeOf(seat string) string {
|
||||
return "mesh.seat." + seat + ".event.built"
|
||||
}
|
||||
|
||||
// BuildStarted is where a build machine says it has taken a build, and BuildLog is where it says
|
||||
// what it is doing, one line per message, under the build's own id (novox/hq ADR 0157).
|
||||
@@ -44,8 +70,10 @@ func BuildOutcome() string { return "mesh.seat." + TheBuildMachine + ".event.bui
|
||||
// lived in one container's stderr on one machine. Every line is now an event of the role, retained
|
||||
// with the rest of the mesh's events, so a reader follows a build live by subscribing its subject,
|
||||
// or reads it back afterwards from the stream, and a viewer is a subscriber and nothing more.
|
||||
func BuildStarted() string { return "mesh.seat." + TheBuildMachine + ".event.started" }
|
||||
func BuildLog(id string) string { return "mesh.seat." + TheBuildMachine + ".event.log." + id }
|
||||
func BuildStarted() string { return BuildStartedOf(TheBuildMachine) }
|
||||
func BuildLog(id string) string { return BuildLogOf(TheBuildMachine, id) }
|
||||
func BuildStartedOf(seat string) string { return "mesh.seat." + seat + ".event.started" }
|
||||
func BuildLogOf(seat, id string) string { return "mesh.seat." + seat + ".event.log." + id }
|
||||
|
||||
// BuildStart is what a build machine says the moment it takes a build.
|
||||
type BuildStart struct {
|
||||
|
||||
@@ -115,9 +115,16 @@ type natsMachine struct {
|
||||
sub *nats.Subscription
|
||||
}
|
||||
|
||||
// MachineOverNATS takes build work from the role this machine holds.
|
||||
// MachineOverNATS takes build work from the current build role.
|
||||
func MachineOverNATS(js *broker.JetStream, on string) BuildMachine {
|
||||
return &natsMachine{js: js, on: on, seat: TheBuildMachine}
|
||||
return MachineOverNATSOn(js, on, TheBuildMachine)
|
||||
}
|
||||
|
||||
// MachineOverNATSOn takes build work from the role named — the one this machine's credential claims
|
||||
// (ADR 0190 handover): its asks come from that seat's worker, and what it says about a build goes
|
||||
// out as that seat's events, so an outcome is heard where the asker listens.
|
||||
func MachineOverNATSOn(js *broker.JetStream, on, seat string) BuildMachine {
|
||||
return &natsMachine{js: js, on: on, seat: seat}
|
||||
}
|
||||
|
||||
func (m *natsMachine) Close() {
|
||||
@@ -194,7 +201,7 @@ func (m *natsMachine) Take(ctx context.Context, do func(context.Context, Build))
|
||||
// the ask to a second machine nor counts the wait against its deliveries.
|
||||
working := make(chan struct{})
|
||||
go stillWorking(msg, working)
|
||||
do(ctx, &natsBuild{request: request, msg: msg, on: m.on, js: m.js})
|
||||
do(ctx, &natsBuild{request: request, msg: msg, on: m.on, js: m.js, seat: m.seat})
|
||||
close(working)
|
||||
}
|
||||
}
|
||||
@@ -205,7 +212,9 @@ type natsBuild struct {
|
||||
msg *nats.Msg
|
||||
on string
|
||||
js *broker.JetStream
|
||||
seq int
|
||||
// seat is the role this build was taken from; what the machine says about it is that role's.
|
||||
seat string
|
||||
seq int
|
||||
}
|
||||
|
||||
func (b *natsBuild) Request() BuildRequest { return b.request }
|
||||
@@ -232,7 +241,7 @@ func (b *natsBuild) Announce(ctx context.Context, result BuildResult) error {
|
||||
}
|
||||
publish, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
if _, err := b.js.Context().Publish(BuildOutcome(), body, nats.Context(publish)); err != nil {
|
||||
if _, err := b.js.Context().Publish(BuildOutcomeOf(b.seat), body, nats.Context(publish)); err != nil {
|
||||
return fmt.Errorf("cannot announce a build's outcome: %w", err)
|
||||
}
|
||||
return nil
|
||||
@@ -252,7 +261,7 @@ func (b *natsBuild) Began(ctx context.Context) error {
|
||||
}
|
||||
publish, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
if _, err := b.js.Context().Publish(BuildStarted(), body, nats.Context(publish)); err != nil {
|
||||
if _, err := b.js.Context().Publish(BuildStartedOf(b.seat), body, nats.Context(publish)); err != nil {
|
||||
return fmt.Errorf("cannot say a build started: %w", err)
|
||||
}
|
||||
return nil
|
||||
@@ -270,7 +279,7 @@ func (b *natsBuild) Say(step, message string) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = b.js.Conn().Publish(BuildLog(b.request.ID), body)
|
||||
_ = b.js.Conn().Publish(BuildLogOf(b.seat, b.request.ID), body)
|
||||
}
|
||||
|
||||
func (b *natsBuild) Hold(after time.Duration) error { return b.msg.NakWithDelay(after) }
|
||||
|
||||
Reference in New Issue
Block a user