carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "errors"
6 "os"
7 "strings"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/rootstate"
11)
12
13func TestPlan14OperatorUnitMigration(t *testing.T) {
14 for _, attack := range []string{"none", "no consent", "changed bytes", "replacement inode", "replacement invocation", "lost intent", "lost stop", "lost replace", "lost completion"} {
15 t.Run(attack, func(t *testing.T) {
16 b, in, shape := plan14UnitBindingFixture(t, "console.service", "", "")
17 old := []byte("[Service]\nExecStart=/opt/carlos/live/shop.example\n")
18 shape.Program = strings.Repeat("a", 64)
19 shape.PredecessorDigest = claimUnitDigest(old)
20 if attack == "no consent" {
21 shape.PredecessorDigest = ""
22 }
23 b.canonical = func(context.Context, string) (claimUnitShape, error) { return shape, nil }
24 binding, err := b.establish(t.Context(), in.Host)
25 if err != nil {
26 t.Fatal(err)
27 }
28 files := &plan14UnitFiles{body: old, id: plan14UnitIdentity(2)}
29 state := &plan14UnitBindingFault{Store: &rollbackMemory{files: map[string][]byte{}}}
30 stopped, replaced := false, false
31 kernel := claimUnitPredecessor{Unit: shape.Unit, Invocation: "old", Group: "system.slice/console.service", GroupIdentity: rootstate.FileIdentity{Device: 3, Inode: 4}}
32 m := claimUnitMaterializer{state: state, read: files.read, create: files.create, replace: files.replace, idle: func(string) error {
33 if !stopped {
34 return os.ErrPermission
35 }
36 return nil
37 }}
38 m.predecessor = func(context.Context, claimUnitBinding) (claimUnitPredecessor, error) { return kernel, nil }
39 m.stopPredecessor = func(_ context.Context, got claimUnitPredecessor) error {
40 r, e := m.load(in.Host)
41 if e != nil || r.Migration == nil || r.Migration.Predecessor != got {
42 t.Fatal("stop without durable exact intention", e)
43 }
44 if got != kernel {
45 return os.ErrPermission
46 }
47 stopped = true
48 if attack == "lost stop" {
49 return os.ErrInvalid
50 }
51 return nil
52 }
53 m.replace = func(unit string, id rootstate.FileIdentity, body []byte) error {
54 if !stopped {
55 t.Fatal("replace before predecessor stop")
56 }
57 if err := files.replace(unit, id, body); err != nil {
58 return err
59 }
60 replaced = true
61 if attack == "lost completion" {
62 state.fail = true
63 }
64 if attack == "lost replace" {
65 return os.ErrInvalid
66 }
67 return nil
68 }
69 if attack == "changed bytes" {
70 files.body = append(files.body, 'x')
71 }
72 if attack == "lost intent" {
73 state.fail = true
74 }
75 if attack == "replacement inode" || attack == "replacement invocation" {
76 // Persist the initial intention, then lose the reply before any effect.
77 state.fail = true
78 if m.install(t.Context(), binding) == nil {
79 t.Fatal("lost intention accepted")
80 }
81 state.fail = false
82 if attack == "replacement inode" {
83 files.id.Inode++
84 } else {
85 kernel.Invocation = "new"
86 }
87 }
88 err = m.install(t.Context(), binding)
89 if attack == "none" {
90 if err != nil {
91 t.Fatal(err)
92 }
93 } else if err == nil {
94 t.Fatal("attack accepted")
95 }
96 if attack == "no consent" || attack == "changed bytes" || attack == "replacement inode" || attack == "replacement invocation" || attack == "lost intent" {
97 if stopped || replaced {
98 t.Fatal("unowned predecessor changed")
99 }
100 return
101 }
102 state.fail = false
103 m.stopPredecessor = func(_ context.Context, got claimUnitPredecessor) error {
104 if got != kernel {
105 return os.ErrPermission
106 }
107 stopped = true
108 return nil
109 }
110 m.replace = files.replace
111 if err = m.install(t.Context(), binding); err != nil {
112 t.Fatal("resume", err)
113 }
114 if files.retires != 1 {
115 t.Fatal("repeated replacement", files.retires)
116 }
117 if err = m.verify(binding); err != nil {
118 t.Fatal(err)
119 }
120 files.id.Inode++
121 if err = m.install(t.Context(), binding); !errors.Is(err, os.ErrPermission) {
122 t.Fatal("rebound completed identity", err)
123 }
124 })
125 }
126}
127