package main import ( "context" "encoding/json" "fmt" "os" "github.com/nats-io/nats.go" "github.com/novox/mesh-controller/internal/broker" "github.com/novox/mesh-controller/internal/inventory" ) // What a machine's provisioner client said of a module's files (novox/hq ADR 0362, ADR 0349 §15, design 57 P6, // issue 672), read where a walk's end and a gate count a machine's send applied: a module the send handed to the // provisioner is applied there only once the client reported `prov.applied` on that build's templates as well. // provAppliedOn is the client's newest report of a module on a machine, from the stream that keeps it // (broker.ProvAppliedStream); false when it has said nothing of it. A variable so a test can say the client's words. var provAppliedOn = func(ctx context.Context, node, module string) (inventory.ProvReport, bool, error) { var raw []byte var said bool read := func(js nats.JetStreamContext) error { var err error raw, said, err = broker.LastProvApplied(js, node, module) return err } var err error if d := doctorFrom; d != nil && d.js != nil { err = read(d.js.Context()) } else { err = onTheBus(func(conn *nats.Conn) error { js, err := conn.JetStream(nats.Context(ctx)) if err != nil { return err } return read(js) }) } if err != nil || !said { return inventory.ProvReport{}, false, err } var r inventory.ProvReport if err := json.Unmarshal(raw, &r); err != nil { return inventory.ProvReport{}, false, fmt.Errorf("what %s's provisioner client said of %s is not its report: %w", node, module, err) } return r, true, nil } // provLookup answers the clients' reports for one reading; a report that cannot be read is not counted, which // leaves the machine waiting, then silent, never applied — and is said. func provLookup(ctx context.Context) inventory.ProvLookup { return func(node, module string) (inventory.ProvReport, bool) { r, said, err := provAppliedOn(ctx, node, module) if err != nil { fmt.Fprintf(os.Stderr, "what %s's provisioner client said of %s could not be read: %v\n", node, module, err) return inventory.ProvReport{}, false } return r, said } } // provisioningFailed is every module whose files a machine's provisioner client last said it failed to place, from // the stream that keeps their reports. func provisioningFailed(js nats.JetStreamContext) ([]inventory.ProvReport, error) { all, err := broker.ProvAppliedReports(js) if err != nil { return nil, err } var out []inventory.ProvReport for subject, raw := range all { var r inventory.ProvReport if err := json.Unmarshal(raw, &r); err != nil { return nil, fmt.Errorf("what was said on %s is not a provisioner client's report: %w", subject, err) } if r.Status == inventory.ProvFailed { out = append(out, r) } } return out, nil } // provisionedOutcome is a machine's report of one send as a delivery's end counts it (novox/hq ADR 0362 §1-5, issue // 840): the engine's own report, combined with its provisioner client's for a send that handed modules to it. The // outcome of a client's failure begins inventory.OutcomeProvisioningFailed; a report not yet applied on both sides // is none (false). A send handing nothing is the engine's report unchanged. onSend says the report is about the send. func provisionedOutcome(node string, sent inventory.SentDeclaration, r inventory.Reported, onSend bool, prov inventory.ProvLookup) (string, bool) { if len(sent.Provisioned) == 0 { return r.Outcome, onSend } engine := inventory.AppliedReport{Outcome: r.Outcome} if r.At != nil { engine.At = *r.At } got, ok := inventory.ProvisionedSend(node, sent, engine, onSend, prov) return got.Outcome, ok }