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 && rootrunner
2
3package host
4
5import (
6 "context"
7 "encoding/json"
8 "os"
9 "os/exec"
10 "path/filepath"
11 "strings"
12 "testing"
13)
14
15const rebootPredecessorUnit = "plan14-e51-predecessor.service"
16
17func rebootPredecessorCtl(ctx context.Context, args ...string) (string, error) {
18 raw, err := exec.CommandContext(ctx, "systemctl", args...).CombinedOutput()
19 return strings.TrimSpace(string(raw)), err
20}
21
22func claimRebootPredecessorPhase1(t *testing.T) {
23 path := "/etc/systemd/system/" + rebootPredecessorUnit
24 file, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
25 if err != nil {
26 t.Fatal(err)
27 }
28 _, err = file.WriteString("[Service]\nType=simple\nExecStart=/bin/sleep infinity\nKillMode=control-group\n")
29 file.Close()
30 if err != nil {
31 t.Fatal(err)
32 }
33 if out, err := rebootPredecessorCtl(t.Context(), "daemon-reload"); err != nil {
34 t.Fatal(out, err)
35 }
36 if out, err := rebootPredecessorCtl(t.Context(), "start", rebootPredecessorUnit); err != nil {
37 t.Fatal(out, err)
38 }
39 _, in, shape := plan14UnitBindingFixture(t, rebootPredecessorUnit, "", "")
40 binding := claimUnitBinding{Schema: 1, Owner: unitBindingOwner(in), Shape: shape}
41 kernel := claimPredecessorKernel{ctl: rebootPredecessorCtl, group: claimPredecessorGroup, boot: claimUnitBoot}
42 predecessor, err := kernel.capture(t.Context(), binding)
43 if err != nil {
44 t.Fatal(err)
45 }
46 raw, _ := json.Marshal(predecessor)
47 if err := os.WriteFile(filepath.Join(claimUnitRebootDir, "predecessor.json"), raw, 0600); err != nil {
48 t.Fatal(err)
49 }
50 t.Logf("predecessor before boot=%s device=%d inode=%d", predecessor.Boot, predecessor.GroupIdentity.Device, predecessor.GroupIdentity.Inode)
51}
52
53func claimRebootPredecessorPhase2(t *testing.T) {
54 t.Cleanup(func() {
55 rebootPredecessorCtl(context.Background(), "stop", rebootPredecessorUnit)
56 os.Remove("/etc/systemd/system/" + rebootPredecessorUnit)
57 rebootPredecessorCtl(context.Background(), "daemon-reload")
58 })
59 var predecessor claimUnitPredecessor
60 raw, err := os.ReadFile(filepath.Join(claimUnitRebootDir, "predecessor.json"))
61 if err != nil || json.Unmarshal(raw, &predecessor) != nil {
62 t.Fatal(err)
63 }
64 boot, err := claimUnitBoot()
65 if err != nil || boot == predecessor.Boot {
66 t.Fatal("actual reboot required", err)
67 }
68 stops := 0
69 kernel := claimPredecessorKernel{group: claimPredecessorGroup, boot: claimUnitBoot, ctl: func(ctx context.Context, args ...string) (string, error) {
70 if args[0] == "stop" {
71 stops++
72 }
73 return rebootPredecessorCtl(ctx, args...)
74 }}
75 if err = kernel.stop(t.Context(), predecessor); err != nil {
76 t.Fatal("old boot empty predecessor", err)
77 }
78 if out, err := rebootPredecessorCtl(t.Context(), "start", rebootPredecessorUnit); err != nil {
79 t.Fatal(out, err)
80 }
81 invocation, err := rebootPredecessorCtl(t.Context(), "show", "-p", "InvocationID", "--value", rebootPredecessorUnit)
82 if err != nil {
83 t.Fatal(err)
84 }
85 if err = kernel.stop(t.Context(), predecessor); err == nil {
86 t.Fatal("old boot stopped replacement")
87 }
88 after, err := rebootPredecessorCtl(t.Context(), "show", "-p", "InvocationID", "--value", rebootPredecessorUnit)
89 if err != nil || invocation == "" || invocation != after || stops != 0 {
90 t.Fatal("replacement touched", invocation, after, stops, err)
91 }
92}
93