carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostops
2
3import (
4 "context"
5 "crypto/sha256"
6 "encoding/hex"
7 "errors"
8 "github.com/carlosframework/platform/internal/alerting"
9 "github.com/carlosframework/platform/internal/hostclaim"
10 "strings"
11 "testing"
12 "time"
13)
14
15func TestPlan12ReportingFailureStillRunsRecovery(t *testing.T) {
16 f := newResolveFixture(t, true)
17 o, err := hostclaim.Update(t.Context(), f.raw, f.moving, func(c *hostclaim.Claim) error {
18 ref := hostclaim.FenceRef{Enrollment: strings.Repeat("a", 32), SHA256: strings.Repeat("b", 64), Key: "control/box-fences/" + strings.Repeat("a", 32) + ".json"}
19 sum := sha256.Sum256([]byte(c.Op.ID + "\x00box-b\x00" + ref.Enrollment + "\x00" + ref.SHA256))
20 id := "auto-" + hex.EncodeToString(sum[:16])
21 c.LossRefs = []string{id}
22 c.EvidenceRefs = []hostclaim.AutomaticLoss{{ID: id, Box: "box-b", Enrollment: ref.Enrollment, Fence: ref, ObservedAt: f.now.Format(time.RFC3339Nano), Op: c.Op.ID, Kind: hostclaim.KindLanding, Phase: hostclaim.PhaseProbing, Role: "target", Incarnation: c.Holder.Incarnation, Stamp: c.Holder.Stamp, Timers: hostclaim.TimersUnknown}}
23 return nil
24 })
25 // The raw fixture below supplies a retained evidence row; validation of the
26 // row belongs to ReportAutomaticLosses, whose failure is the point here.
27 if err != nil {
28 _ = o
29 t.Fatal(err)
30 }
31 noWork := func(context.Context) error { return nil }
32 wrong := func(context.Context, hostclaim.Observed) error { t.Fatal("generic recovery"); return nil }
33 failure := errors.New("loss signer unavailable")
34 calls := 0
35 pages := 0
36 alerting.Arm(&alerting.Sinks{WebhookURL: "http://unused", Post: func(_ string, _ []byte) error { pages++; return nil }})
37 t.Cleanup(func() { alerting.Arm(nil) })
38 r := Recovery{Controller: Controller{Store: f.raw, Control: f.w, Authority: &f.authority, Now: func() time.Time { return f.now }, NewID: func() string { return "recovery" }}, Purge: wrong, Placement: wrong, SweepRecords: noWork, SweepPurges: noWork, SweepAdmissions: noWork}
39 r.ReportAutomaticLosses = func(ctx context.Context, host string) error {
40 before, err := hostclaim.Read(ctx, f.raw, host)
41 if err != nil {
42 return err
43 }
44 _, err = hostclaim.Update(ctx, f.raw, before, func(c *hostclaim.Claim) error {
45 if c.Op.Journal == nil {
46 c.Op.Journal = map[string]string{}
47 }
48 c.Op.Journal["report-attempt"] = "failed"
49 return nil
50 })
51 if err != nil {
52 return err
53 }
54 return failure
55 }
56 r.Moves = func(ctx context.Context, g *MoveRecoveryGrant) error {
57 calls++
58 fresh, err := hostclaim.Read(ctx, f.raw, g.Observation().Host)
59 if err != nil {
60 return err
61 }
62 if fresh.ETag != g.Observation().ClaimETag || fresh.Claim.Op.Journal["report-attempt"] != "failed" {
63 t.Fatal("recovery used observation before report")
64 }
65 return nil
66 }
67 if err := f.w.RunRecovery(t.Context(), r.Pass); !errors.Is(err, failure) || calls != 1 || pages != 1 {
68 t.Fatalf("pass=%v recovery calls=%d", err, calls)
69 }
70}
71