carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package host
4
5import (
6 "context"
7 "encoding/json"
8 "errors"
9 "fmt"
10 "os"
11 "testing"
12
13 "github.com/carlosframework/platform/internal/hostclaim"
14 "github.com/carlosframework/platform/internal/hostguard"
15 "github.com/carlosframework/platform/internal/registry"
16 "github.com/carlosframework/platform/internal/release"
17)
18
19type plan9D4RetagConflict struct {
20 release.Store
21 armed, failed bool
22}
23
24func (s *plan9D4RetagConflict) Put(ctx context.Context, key string, body []byte, etag string) (string, error) {
25 var c hostclaim.Claim
26 if s.armed && json.Unmarshal(body, &c) == nil && c.Holder != nil && c.Inventory["box-1"].Paths[recoveryHost+".db.gen"] == c.Holder.Stamp {
27 s.armed = false
28 s.failed = true
29 return "", release.ErrCASConflict
30 }
31 return s.Store.Put(ctx, key, body, etag)
32}
33
34func TestPlan9D4RetirementCASFailureReplaysWithoutMarker(t *testing.T) {
35 d := plan9D4Landing(t)
36 st := &plan9D4RetagConflict{Store: d.f.claims}
37 d.f.claims = st
38 d.f.guard.Claims = hostguard.StoreReader{Store: st}
39 c := d.controller("boot-1", "controller-1", 4101, "start-1", true)
40 first := d.mustStart(t, c, plan9D4Probe, false)
41 st.armed = true
42 second, err := d.start(t, c, plan9D4Probe, true)
43 if !errors.Is(err, release.ErrCASConflict) || !st.failed {
44 t.Fatalf("retag CAS was not interrupted: %v", err)
45 }
46 if _, err := os.Stat(d.db + ".gen"); !errors.Is(err, os.ErrNotExist) {
47 t.Fatalf("marker still present after park: %v", err)
48 }
49 if got := d.f.read().Claim.Inventory["box-1"].Paths[recoveryHost+".db.gen"]; got != first.Claim.Holder.Stamp {
50 t.Fatalf("tag changed on failed CAS: %d", got)
51 }
52 // Replay the same admitted start. Its absent marker must not bypass the
53 // unfinished retirement or permit bytes under the predecessor's tag.
54 start, err := c.runtime.Prepare(t.Context(), registry.Route{Host: recoveryHost}, second, plan9D4Probe)
55 if err != nil {
56 t.Fatal(err)
57 }
58 if err := start.Restore(t.Context()); err != nil {
59 t.Fatal(err)
60 }
61 if err := os.WriteFile(d.db, []byte(fmt.Sprintf("written by session %d", second.Claim.Holder.Session)), 0600); err != nil {
62 t.Fatal(err)
63 }
64 d.requireRetired(t, first, second, false)
65 third := d.mustStart(t, c, plan9D4Probe, true)
66 d.requireRetired(t, second, third, false)
67}
68
69func TestPlan9D4RetirementCollectorKeepsItsSelectedChore(t *testing.T) {
70 d := plan9D4Landing(t)
71 c := d.controller("boot-1", "controller-1", 4101, "start-1", true)
72 d.mustStart(t, c, plan9D4Probe, false)
73 d.step(t, "retirement chore", hostclaim.QueueLocalPark("m1", "box-1", func() string { return "retire-this-installation" }))
74 h := d.f.read().Claim.Holder
75 row := registry.Route{Host: recoveryHost, ClaimOwnership: registry.ClaimOwnership{ClaimIncarnation: h.Incarnation, ClaimStamp: h.Stamp}}
76 target, err := c.assembly.collectRetirementStop(t.Context(), row)
77 if err != nil {
78 t.Fatal(err)
79 }
80 if target.ObligationID != "retire-this-installation" || len(d.f.requests) != 1 || d.f.requests[0].ObligationID != target.ObligationID {
81 t.Fatalf("retirement relabelled as a wake stop: %+v", target)
82 }
83}
84