diff --git a/modules/mesh-delivery-assigner/internal/assign/asks.go b/modules/mesh-delivery-assigner/internal/assign/asks.go index 59e1c8fd..aee5b660 100644 --- a/modules/mesh-delivery-assigner/internal/assign/asks.go +++ b/modules/mesh-delivery-assigner/internal/assign/asks.go @@ -200,6 +200,54 @@ const ( OptionBackupSkip = mwc.BackupSkip ) +// The options of the revert ask (design 47 G §710): keep the revert held, or start the reverted build on the live data. +const ( + OptionRevertHold = "hold" + OptionRevertLive = mwc.RevertLive +) + +// KindRevertHeld is the finding of a held revert (design 47 G §710), scope delivery, id `.`. +const KindRevertHeld = "revert-held" + +// RevertAsk is the ask about a revert of module on node to the build fp that the node holds with no restore point (design 47 G +// §710, ADR 0539 §16): keep it held, or start the reverted build on the live data as it is. kept names the kept data whose form +// the revert crosses, form its form, and words are the node's reason in its own words, from its have; asking is the number of +// the asking, 0 the first, as the backup ask's. +func RevertAsk(key, node, module, fp, kept, form, words string, asking int, now time.Time) asks.Ask { + return assignerAsk(asking, oc.Ask{ + ID: meshdeliveryclient.AskingID(key, asking), + Headline: fmt.Sprintf("The revert of %s on %s has no restore point", module, node), + Explanation: fmt.Sprintf("The revert of %s on %s to %s takes its kept data %s back across form %s, and the node holds it: %s. "+ + "%s keeps running its newer build until you answer. Started on the live data, the older build reads data written in the newer form.", + module, node, short(fp), kept, form, words, module), + Options: []oc.Option{ + {ID: OptionRevertHold, Label: "Keep it held", Does: "the node keeps running the newer build, and the revert waits", Level: occ.Acknowledge}, + {ID: OptionRevertLive, Label: "Start on the live data", Does: "the node starts the older build on the live data as it is, with nothing restored", Level: occ.Acknowledge}, + }, + Who: occ.Operator, + Expires: occ.FormatTime(now.Add(AskLasts)), + OnExpiry: "the module keeps running its newer build", + About: key, + }) +} + +// RevertAnswerWords are the words a want written again on the operator's answer to the revert ask carries (design 47 G §710). +func RevertAnswerWords(option string, asking int) string { + return fmt.Sprintf("the operator answered %q to the revert ask (asking %d)", option, asking+1) +} + +// RevertHeldFinding is the finding `revert-held` of a revert the node holds with no restore point (design 47 G §710): scope +// delivery, id `.`, a warning naming the reason, the kept data and its form, and needing nothing, so the +// operator's one message is the ask. +func RevertHeldFinding(node, module, kept, form, reason string) Observed { + return Observed{Scope: "delivery", ID: node + "." + module, Kind: KindRevertHeld, Machine: node, Severity: "warning", + Summary: fmt.Sprintf("The revert of %s on %s is held: %s (%s, form %s); the ask about it waits for your answer", module, node, reason, kept, form), + Headline: fmt.Sprintf("The revert of %s on %s is held", module, node), + Explanation: fmt.Sprintf("The node holds the revert of %s: %s (%s, form %s). %s keeps running its newer build until the ask is answered.", + module, reason, kept, form, module), + Needs: "Nothing for you to do.", Source: "assigner." + KindRevertHeld} +} + // RecordAsk is the ask about a move of a module of policy record on node to the composition with the digest, judged by the // controller to recreate a container, to move data, or not to be judgeable (ADR 0539 §14). The want names the composition only // after the answer yes. The way the move was judged is in the ask. asking is the writing's number (AskingSeconds), so a key taken diff --git a/modules/mesh-delivery-assigner/internal/assign/asks_test.go b/modules/mesh-delivery-assigner/internal/assign/asks_test.go index dd3ecd79..ab9a26b9 100644 --- a/modules/mesh-delivery-assigner/internal/assign/asks_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/asks_test.go @@ -337,7 +337,12 @@ func TestTheRecordAndBackupAsksAreValid(t *testing.T) { } dgst := "sha256:" + strings.Repeat("b", 64) b := BackupAsk(bkey, "ace", "mail", fNew, dgst, "no space left", 0, now) - for key, a := range map[string]asks.Ask{rkey: r, bkey: b} { + vkey, err := asks.RevertAskKey("ace", "mail", fNew) + if err != nil { + t.Fatal(err) + } + v := RevertAsk(vkey, "ace", "mail", fNew, "records", "3", "restore point missing: none of form 3", 0, now) + for key, a := range map[string]asks.Ask{rkey: r, bkey: b, vkey: v} { if err := occ.Check(mustAsk(t, a), now); err != nil { t.Errorf("%s: the router refuses the ask: %v", key, err) } @@ -367,6 +372,22 @@ func TestTheRecordAndBackupAsksAreValid(t *testing.T) { if BackupAnswerWords("again", 0) == BackupAnswerWords("again", 1) { t.Errorf("two answers of the same option are one want value") } + // design 47 G §710: the revert ask names the build, the kept data, its form and the node's words, and offers hold and live; + // its finding is delivery...revert-held, a warning that needs nothing, the ask being the one message. + if va := mustAsk(t, v); va.Headline != "The revert of mail on ace has no restore point" || va.Options[0].ID != OptionRevertHold || + va.Options[1].ID != mwc.RevertLive || len(va.Options) != 2 || va.OnExpiry != "the module keeps running its newer build" || + !strings.Contains(va.Explanation, "records") || !strings.Contains(va.Explanation, "form 3") || !strings.Contains(va.Explanation, short(fNew)) || + !strings.Contains(va.Explanation, "Started on the live data, the older build reads data written in the newer form.") { + t.Errorf("the revert ask = %+v", va) + } + if f := RevertHeldFinding("ace", "mail", "records", "3", "restore point missing").Finding(now); views.FindingKey(f.Scope, f.ID, f.Kind) != "delivery.ace.mail.revert-held" || + f.Severity != "warning" || f.Needs != "Nothing for you to do." || + f.Summary != "The revert of mail on ace is held: restore point missing (records, form 3); the ask about it waits for your answer" { + t.Errorf("the revert finding = %+v", f) + } + if RevertAnswerWords(OptionRevertLive, 1) != `the operator answered "live" to the revert ask (asking 2)` { + t.Errorf("the words of a live answer: %q", RevertAnswerWords(OptionRevertLive, 1)) + } c := BackupCondition("ace", "mail", fNew, "no space left", true) if _, err := occ.EncodeCondition(c); err != nil { t.Errorf("the backup condition is refused: %v", err)