Files
mesh-controller/internal/broker/checks.go
T

119 lines
5.2 KiB
Go

package broker
import "strings"
// The pull requests' checks have work queues of their own (novox/hq ADR 0406 §3, issue 974).
//
// A delivery build goes on the build seat's own queue, `mesh.seat.node-build-agent.accept.build`. A check of a pull
// request marked ready goes on a second work-queue stream and every other check on a third. A builder takes from the
// first, then the second, then the third; the others are taken in the order asked. **Each check stream has one subject
// per pull request and keeps only the newest request** (a limit of one message a subject, the older discarded), so a
// newer request replaces one still waiting and a pull request has at most one waiting check: the stream's rule, not a
// pass of the controller that moves asks about. Nothing pauses the worker or deletes and republishes a request.
const (
// ChecksReadyStream holds the checks of pull requests marked ready; ChecksOtherStream every other check.
ChecksReadyStream = "CHECKS_READY"
ChecksOtherStream = "CHECKS_OTHER"
// ChecksReadyWorker and ChecksOtherWorker are the durable consumers the build seat's holders share on them, as
// they share the seat's worker on its own queue (novox/hq ADR 0190).
ChecksReadyWorker = "CHECKS_READY_worker"
ChecksOtherWorker = "CHECKS_OTHER_worker"
// TheBuildSeat is the role whose checks these streams queue. The retired build role's checks stay on its own queue.
TheBuildSeat = "node-build-agent"
)
// PullsBucket is mesh-delivery's key per pull request: its head, the trunk commit its current check is asked against and
// its verdict (ADR 0406 §4). A running check watches its key.
var PullsBucket = BucketName("mesh-delivery", "pulls")
// CheckSubjectPrefix is where a seat's checks of one kind are asked: `mesh.seat.<seat>.check.ready.` or `.other.`. The
// `check` token keeps the subjects out of the seat's own queue, which takes `accept.>`.
func CheckSubjectPrefix(seat string, ready bool) string {
kind := "other"
if ready {
kind = "ready"
}
return "mesh.seat." + seat + ".check." + kind + "."
}
// CheckStreams are the two check queues, in the order a builder takes from them.
func CheckStreams() []Stream {
return []Stream{
{
Name: ChecksReadyStream,
Subjects: []string{CheckSubjectPrefix(TheBuildSeat, true) + ">"},
Retention: RetentionWorkQueue,
MaxAge: 7 * 24 * 60 * 60,
MaxMsgsPerSubject: 1,
Why: "the checks of pull requests marked ready, taken after a delivery build and before any other check " +
"(novox/hq ADR 0406 §3); one subject a pull request, the newest request kept, so a newer one replaces a " +
"request still waiting",
},
{
Name: ChecksOtherStream,
Subjects: []string{CheckSubjectPrefix(TheBuildSeat, false) + ">"},
Retention: RetentionWorkQueue,
MaxAge: 7 * 24 * 60 * 60,
MaxMsgsPerSubject: 1,
Why: "every other check, taken last and in the order asked (novox/hq ADR 0406 §3); one subject a pull " +
"request, the newest request kept; one left behind for a head already judged is dropped when taken",
},
}
}
// CheckWorkers are the consumers the build seat's holders pull the check queues through: as the seat's own worker is,
// one for every holder, one ask at a time, acknowledged when the check is done.
func CheckWorkers() []Consumer {
worker := func(name, stream string, ready bool) Consumer {
return Consumer{
Name: name,
Stream: stream,
Filters: []string{CheckSubjectPrefix(TheBuildSeat, ready) + ">"},
AckWaitSeconds: 60,
MaxDeliver: 5,
Why: "every holder of " + TheBuildSeat + " pulls one check at a time from this worker and acknowledges after " +
"the check is done, so a crash mid-check redelivers rather than loses (novox/hq ADR 0406 §3)",
}
}
return []Consumer{
worker(ChecksReadyWorker, ChecksReadyStream, true),
worker(ChecksOtherWorker, ChecksOtherStream, false),
}
}
// holderCheckGrants are what a holder of the build seat is granted to take checks: ask about the two workers, pull from
// them and acknowledge, and nothing of the subjects themselves.
func holderCheckGrants() []string {
var pub []string
for _, w := range CheckWorkers() {
pub = append(pub,
"$JS.API.CONSUMER.INFO."+w.Stream+"."+w.Name,
"$JS.API.CONSUMER.MSG.NEXT."+w.Stream+"."+w.Name,
"$JS.ACK."+w.Stream+"."+w.Name+".>")
}
// And the bucket of the checks they are running: each writes the key of its own check, reads another's, and the bucket
// is a controller's own bucket the build seat's holders write by design (ADR 0406 §3).
pub = append(pub,
"$KV."+CheckRunningBucket+".>",
"$JS.API.STREAM.INFO.KV_"+CheckRunningBucket,
"$JS.API.DIRECT.GET.KV_"+CheckRunningBucket+".>")
return pub
}
// IsCheckStream says a stream is one of the check queues.
func IsCheckStream(stream string) bool {
return stream == ChecksReadyStream || stream == ChecksOtherStream
}
// isCheckAsk says a message of a check queue is an ask the controller itself makes.
func isCheckAsk(stream, subject string) bool {
switch stream {
case ChecksReadyStream:
return strings.HasPrefix(subject, CheckSubjectPrefix(TheBuildSeat, true))
case ChecksOtherStream:
return strings.HasPrefix(subject, CheckSubjectPrefix(TheBuildSeat, false))
}
return false
}