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..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 }