package inventory import ( "fmt" "sort" "time" ) // A machine has applied a send of a provisioned module only once its files are placed (novox/hq ADR 0362, design // 57 P6, issue 672): for a module the send handed to the provisioner (SentDeclaration.Provisioned), the machine's // own report that it applied the send is half of it, and its provisioner client's `prov.applied` for the build's // templates the other half. // // - both reported, applied: the send is applied, at the later of the two (ADR 0362 §5: delivery time runs to it); // - the client reported it failed on that build: a failed apply, named with its reason (§3); // - the client has not reported it applied on that build (not yet, waiting for a value, an older build): not yet // applied, and once ApplySilentAfter has passed since the send the walk names the machine silent (§4); // - a send that handed nothing: the machine's own report alone, as before. // ProvReport is a provisioner client's newest report of one module on its machine, as `prov.applied` says it // (mesh-catalog mesh-provisioner-client Report): applied, waiting, failed with the reason, or removed; the digest // of the templates it filled from; and when it said it. type ProvReport struct { Node string `json:"node"` Module string `json:"module"` Status string `json:"status"` Reason string `json:"reason,omitempty"` WaitsFor []string `json:"waits-for,omitempty"` Templates string `json:"templates,omitempty"` At time.Time `json:"at"` } // The words a client's report says. const ( ProvApplied = "applied" ProvWaiting = "waiting" ProvFailed = "failed" ProvRemoved = "removed" ) // ProvLookup answers a client's newest report of a module on a machine; false when it has said nothing of it. type ProvLookup func(node, module string) (ProvReport, bool) // OutcomeProvisioningFailed begins the outcome of a send whose module's files the provisioner failed to place. const OutcomeProvisioningFailed = "provisioning failed" // ProvisionedSend is a machine's report of a send, with its provisioner client's reports of every module the send // handed to it: what a walk counts as that machine's report. engine and reported are the machine's own report and // whether it said one. A failed client report is the send's outcome, whatever the machine said; none, or one not // yet applied on the build sent, leaves the send unreported. func ProvisionedSend(node string, send SentDeclaration, engine AppliedReport, reported bool, prov ProvLookup) (AppliedReport, bool) { if len(send.Provisioned) == 0 { return engine, reported } // The machine's own failure, or its wait for a busy container, is its report whatever the client says. if reported && (engine.Outcome != OutcomeApplied || engine.Waiting) { return engine, true } modules := make([]string, 0, len(send.Provisioned)) for m := range send.Provisioned { modules = append(modules, m) } sort.Strings(modules) at, placed := engine.At, true for _, m := range modules { r, said := ProvReport{}, false if prov != nil { r, said = prov(node, m) } switch { case said && r.Status == ProvFailed && aboutTheSend(r, send, send.Provisioned[m]): return AppliedReport{At: r.At, Outcome: fmt.Sprintf("%s (%s: %s)", OutcomeProvisioningFailed, m, orNoReason(r.Reason))}, true case said && r.Status == ProvApplied && r.Templates == send.Provisioned[m]: if r.At.After(at) { at = r.At } default: placed = false } } if !reported || !placed { return AppliedReport{}, false } engine.At = at return engine, true } // Only is the send narrowed to what it handed to the provisioner for the named modules: a delivery waits on the // modules it carries, never on another module the same machine's declaration hands (ADR 0362 §1-2, issue 840). // A send handing nothing is returned as it is. func (s SentDeclaration) Only(modules ...string) SentDeclaration { if len(s.Provisioned) == 0 { return s } kept := map[string]string{} for _, m := range modules { if d, ok := s.Provisioned[m]; ok { kept[m] = d } } s.Provisioned = kept return s } // aboutTheSend says a client's failure is about what the send handed: on the build's templates, or, said before it // had any to name (its templates could not be fetched), no earlier than the send. func aboutTheSend(r ProvReport, send SentDeclaration, digest string) bool { if r.Templates != "" { return r.Templates == digest } return send.At.IsZero() || !r.At.Before(send.At) } func orNoReason(s string) string { if s == "" { return "no reason given" } return s }