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 "encoding/json"
7 "errors"
8 "os"
9 "path/filepath"
10 "slices"
11 "strings"
12 "testing"
13
14 "github.com/carlosframework/platform/internal/hostbox"
15 "github.com/carlosframework/platform/internal/hostops"
16 "github.com/carlosframework/platform/internal/provision"
17 "github.com/carlosframework/platform/internal/registry"
18)
19
20// step8MovedAside is B2: claimtrial's and wakecheck's instance directories,
21// program files and live links, moved out of leo's census roots. Their
22// certificates stay (the certificate cache is box state).
23func step8MovedAside(r leoRow) bool {
24 for _, h := range []string{leoClaimtrial, leoWakecheck} {
25 switch r.Root {
26 case "/data":
27 for _, dir := range []string{"instances/" + h, ".instances/" + h} {
28 if r.Path == dir || strings.HasPrefix(r.Path, dir+"/") {
29 return true
30 }
31 }
32 if r.Path == "programs/"+h+".json" {
33 return true
34 }
35 case "/opt/carlos":
36 if r.Path == "live/"+h {
37 return true
38 }
39 }
40 }
41 return false
42}
43
44// newEmptyLeoCensusTree is leo's tree for the empty-leo flow (Option B):
45// claimtrial's row gone (B1), the records keep left in the bucket, and, when
46// movedAside, claimtrial's and wakecheck's leftovers moved out (B2). Leo
47// reports zero installations.
48func newEmptyLeoCensusTree(t *testing.T, keep []string, movedAside bool) *leoCensusTree {
49 t.Helper()
50 base := t.TempDir()
51 roots := map[string]string{"data": filepath.Join(base, "data"), "opt": filepath.Join(base, "opt"), "units": filepath.Join(base, "units")}
52 for _, dir := range roots {
53 if err := os.Mkdir(dir, 0o755); err != nil {
54 t.Fatal(err)
55 }
56 }
57 buildLeoTree(t, leoTreeRows(t, true), leoTreeOptions{
58 Roots: map[string]string{"/data": roots["data"], "/opt/carlos": roots["opt"], "/etc/systemd/system": roots["units"]},
59 Skip: func(r leoRow) bool {
60 if movedAside && step8MovedAside(r) {
61 return true
62 }
63 return r.Root == "/etc/systemd/system" && r.Type == "l" && (strings.HasPrefix(r.Target, "/usr/lib/") || strings.HasPrefix(r.Target, "/lib/"))
64 },
65 })
66 reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db"))
67 if err != nil {
68 t.Fatal(err)
69 }
70 t.Cleanup(func() { reg.Close() })
71 c := &leoCensusTree{roots: roots, r: ClaimRuntimeResources{Mode: "instance-control", Registry: reg, DataDir: roots["data"], OptDir: roots["opt"], UnitsDir: roots["units"]}}
72 c.snap.Report.Installations = []hostops.CensusInstallation{}
73 for _, r := range leoRecords(t) {
74 if !slices.Contains(keep, r.Holder.Host) {
75 continue
76 }
77 var rec provision.Record
78 if err := json.Unmarshal(r.Body, &rec); err != nil {
79 t.Fatal(err)
80 }
81 c.snap.Records = append(c.snap.Records, rec)
82 }
83 return c
84}
85
86// The empty-leo flow's census on leo's real tree, under the window's masks:
87// with B1 and B2 done it accepts, whether the bucket keeps no record (the
88// trial's ruling) or rollcheck's and wakecheck's. Without B2, and with
89// wakecheck's record deleted, wakecheck's leftovers refuse, naming them; so
90// do claimtrial's, whose record is gone whatever B1 kept.
91func TestMigrationCensusClassifiesEmptyLeo(t *testing.T) {
92 for name, keep := range map[string][]string{"zero hosts": nil, "two hosts": {leoRollcheck, leoWakecheck}} {
93 t.Run(name, func(t *testing.T) {
94 c := newEmptyLeoCensusTree(t, keep, true)
95 if err := c.check(t, allWindowUnits()); err != nil {
96 t.Fatalf("empty leo under the window's masks: %v", err)
97 }
98 if err := c.check(t, nil); !errors.Is(err, hostbox.ErrTypedInventory) || !strings.Contains(err.Error(), "units entry") {
99 t.Fatalf("masks without --window = %v", err)
100 }
101 c = newEmptyLeoCensusTree(t, keep, false)
102 err := c.check(t, allWindowUnits())
103 if !errors.Is(err, hostbox.ErrTypedInventory) {
104 t.Fatalf("leftovers not moved aside = %v, want a refusal", err)
105 }
106 if !strings.Contains(err.Error(), "claimtrial") && !strings.Contains(err.Error(), "wakecheck") {
107 t.Fatalf("leftovers refusal does not name them: %v", err)
108 }
109 t.Logf("without B2: %v", err)
110 })
111 }
112}
113