package keeper import ( "context" "encoding/json" "errors" "fmt" "time" "git.novox.be/novox/mesh-sdk/go/asks" ) // A person's request (design 25 ยง 12.3; CAT-N8): the bus module's tools hold no grant of the seat's acting verbs, so // each hands its request to the keeper through the keeper's own state requests, stamped with the caller the tool runner // proved (never a name the request's body gives), and reads the keeper's answer back from the same key. create and // apply act at once; recreate and drop only once the operator's warrant on the ask the tool raised is held, after a // snapshot of the entry's streams. Every act is recorded as a hand act. // The verbs a request names. const ( VerbCreate = "create" VerbApply = "apply" VerbRecreate = "recreate" VerbDrop = "drop" ) // Request is one person's request, the value of its key in StateRequests. type Request struct { ID string `json:"id"` Verb string `json:"verb"` Module string `json:"module"` Local string `json:"local"` Why string `json:"why"` // Caller is who asked, as the tool runner proved it (stdio.CallerOf), written by the tool. Caller string `json:"caller"` At time.Time `json:"at"` // Ask is the id of the operator's ask the tool raised for a recreate or a drop; the keeper acts only on its warrant. Ask string `json:"ask,omitempty"` // Done is when the keeper ended it, Outcome what it did, Error why not. Done time.Time `json:"done,omitempty"` Outcome string `json:"outcome,omitempty"` Error string `json:"error,omitempty"` Snapshot string `json:"snapshot,omitempty"` } // Act is what an option of the operator's ask binds for a recreate or a drop: the verb and the entry (asks.ActDigest). func (r Request) Act() asks.Act { return asks.Act{"verb": r.Verb, "module": r.Module, "local": r.Local} } // ErrNoWarrant is a request that waits for the operator's word: kept, acted on once the warrant is held. var ErrNoWarrant = errors.New("the operator has not answered yet") // ActOnRequests acts on every request not ended yet, oldest first, and writes each one's end back to its key. func (k *Keeper) ActOnRequests() { keys, err := k.Store.Keys(StateRequests) if err != nil { k.logf("the persons' requests cannot be read: %v", err) return } for _, key := range keys { raw, _, found, err := k.Store.Get(StateRequests, key) if err != nil || !found { continue } var r Request if json.Unmarshal(raw, &r) != nil || !r.Done.IsZero() { continue } outcome, err := k.act(&r) if errors.Is(err, ErrNoWarrant) { continue } r.Done, r.Outcome = k.now(), outcome if err != nil { r.Error = err.Error() } data, _ := json.Marshal(r) if _, err := k.Store.Put(StateRequests, key, data); err != nil { k.logf("the end of the request %s cannot be written: %v", key, err) } } } func (k *Keeper) act(r *Request) (string, error) { if !k.Viewed() { return "", fmt.Errorf("%w: the keeper holds no first view yet, so it acts on nothing", ErrNoWarrant) } held, _, found := k.asked(r.Module) if !found { return "nothing done", fmt.Errorf("the bus module holds no entry of %s", r.Module) } m, made := held.Made[r.Local] asked := false for _, e := range held.Entries { asked = asked || e.Name == r.Local } switch r.Verb { case VerbCreate, VerbApply: if !asked { return "nothing done", fmt.Errorf("no accepted manifest of %s asks for %s: the bus module creates only what one asks", r.Module, r.Local) } for _, e := range held.Entries { if e.Name == r.Local { k.makeEntry(&held, e) } } k.write(held) m = held.Made[r.Local] k.record(r, fmt.Sprintf("%s of %s's %s: created %v, drift %q, waiting %q", r.Verb, r.Module, r.Local, m.Created, m.Drift, m.Waiting)) if m.Refused != "" || m.Waiting != "" { return "not made", fmt.Errorf("%s%s", m.Refused, m.Waiting) } return fmt.Sprintf("%s: the bus holds %s as asked%s", r.Verb, BusName(r.Module, r.Local), map[bool]string{true: ", but for a drift a person decides: " + m.Drift}[m.Drift != ""]), nil case VerbRecreate, VerbDrop: default: return "nothing done", fmt.Errorf("%q is no verb a person asks the bus module for: create, apply, recreate or drop", r.Verb) } if !made || !m.Created { return "nothing done", fmt.Errorf("the bus module made no %s of %s", r.Local, r.Module) } if r.Verb == VerbDrop && !m.Orphaned { return "nothing done", fmt.Errorf("%s of %s is asked for by its manifest: drop removes an orphan alone", r.Local, r.Module) } if r.Verb == VerbRecreate && m.Orphaned { return "nothing done", fmt.Errorf("%s of %s is an orphan: drop it, or ask for it in a manifest again", r.Local, r.Module) } if err := k.warranted(r); err != nil { return "nothing done", err } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() var streams []string for _, o := range m.Plan.Objects { streams = append(streams, o.Stream()) } path, err := k.Bus.Snapshot(ctx, streams) if err != nil { return "nothing done", fmt.Errorf("the snapshot before %s failed, so nothing was removed: %w", r.Verb, err) } r.Snapshot = path for _, o := range m.Plan.Objects { if err := k.Bus.Delete(ctx, o); err != nil { return "partly done", fmt.Errorf("%s could not be removed after its snapshot at %s: %w", o.Stream(), path, err) } } if r.Verb == VerbDrop { delete(held.Made, r.Local) k.write(held) k.emit(EventDropped, m.Plan, held.Revision, r.Why, nil, nil) k.clear(conditionOrphaned, r.Module, r.Local) k.record(r, fmt.Sprintf("dropped %s of %s on the operator's warrant (snapshot %s)", r.Local, r.Module, path)) return fmt.Sprintf("dropped %s, its snapshot at %s", BusName(r.Module, r.Local), path), nil } for _, o := range m.Plan.Objects { if err := k.Bus.Create(ctx, o); err != nil { m.Created, m.Refused = false, fmt.Sprintf("%s: %v", o.Stream(), err) held.Made[r.Local] = m k.write(held) return "partly done", fmt.Errorf("%s was removed and could not be made again (its snapshot at %s): %w", o.Stream(), path, err) } } wasDrift := m.Drift != "" m.Drift = "" held.Made[r.Local] = m k.write(held) k.emit(EventRecreated, m.Plan, held.Revision, r.Why, nil, nil) if wasDrift { k.clear(conditionDrifted, r.Module, r.Local) } k.record(r, fmt.Sprintf("recreated %s of %s on the operator's warrant (snapshot %s)", r.Local, r.Module, path)) return fmt.Sprintf("recreated %s as asked, its old data's snapshot at %s", BusName(r.Module, r.Local), path), nil } // warranted holds a recreate or a drop to the operator's warrant on the ask its tool raised: chosen by the operator, // on that ask, binding exactly this act. func (k *Keeper) warranted(r *Request) error { if r.Ask == "" || k.Warrant == nil { return fmt.Errorf("a %s acts only on the operator's warrant, and this request names no ask", r.Verb) } ask, w, err := k.Warrant(r.Ask) if err != nil { return fmt.Errorf("%w: the router's record of the ask %s cannot be read now: %v", ErrNoWarrant, r.Ask, err) } if ask == nil || w == nil { return ErrNoWarrant } o, err := w.For(Module, *ask) if err != nil { return fmt.Errorf("no warrant: %w", err) } if o.Level != asks.Destroy { return fmt.Errorf("the operator chose %s, so nothing is removed", o.Label) } return o.Performs(r.Act()) } func (k *Keeper) record(r *Request, what string) { if k.Act == nil { return } if err := k.Act(what+", asked by "+r.Caller, r.Why, "nats_"+r.Verb); err != nil { k.logf("the hand act %q could not be recorded: %v", what, err) } }