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 "strings"
7 "testing"
8)
9
10// selfOK is the agent's own openat2 check passing, which is every box the
11// edge unit is right on.
12func selfOK() error { return nil }
13
14// TestNestedGateRefusesWhenTheAgentCannotOpenat2 pins the gate's first
15// question (#126, the edge unit blocker): when the agent's own process
16// cannot make the openat2 call, the gate refuses with that error before it
17// looks at any mount or asks /ready, so the first layout migration is
18// refused whole rather than failing on its first host's first open.
19func TestNestedGateRefusesWhenTheAgentCannotOpenat2(t *testing.T) {
20 wantErr := errors.New("instancefs: openat2 is refused in this process (ENOSYS)")
21 mountsCalled, readyCalled := false, false
22 mounts := func(string) (bool, error) { mountsCalled = true; return true, nil }
23 ready := func(context.Context) error { readyCalled = true; return nil }
24 err := nestedGate(func() error { return wantErr }, mounts, ready)()
25 if err != wantErr {
26 t.Fatalf("nestedGate() = %v, want %v", err, wantErr)
27 }
28 if mountsCalled || readyCalled {
29 t.Fatal("nestedGate went on to the mounts or /ready after the self check refused")
30 }
31}
32
33// TestNestedGateRefusesOnAMissingMount pins the first half of the isolated
34// wake gate (design §8.5): the three nosymfollow mounts are checked in
35// order, and the first one that is missing (or errors) names itself in the
36// refusal rather than the gate silently moving on to /ready.
37func TestNestedGateRefusesOnAMissingMount(t *testing.T) {
38 mounts := func(mountpoint string) (bool, error) {
39 if mountpoint == "/run/carlos/instances" {
40 return false, nil
41 }
42 return true, nil
43 }
44 readyCalled := false
45 ready := func(context.Context) error { readyCalled = true; return nil }
46 err := nestedGate(selfOK, mounts, ready)()
47 if err == nil || !strings.Contains(err.Error(), "/run/carlos/instances") {
48 t.Fatalf("nestedGate() = %v, want an error naming /run/carlos/instances", err)
49 }
50 if readyCalled {
51 t.Fatal("nestedGate called /ready after a mount already refused the wake")
52 }
53}
54
55// TestNestedGateRefusesOnAMountCheckError pins the same refusal for a
56// mounts func that itself errors (an unreadable /proc/self/mountinfo, in
57// production), rather than nestedGate treating an error as "not mounted"
58// silently or panicking on it.
59func TestNestedGateRefusesOnAMountCheckError(t *testing.T) {
60 wantErr := errors.New("read mountinfo: permission denied")
61 mounts := func(string) (bool, error) { return false, wantErr }
62 err := nestedGate(selfOK, mounts, func(context.Context) error { return nil })()
63 if err == nil || !strings.Contains(err.Error(), wantErr.Error()) {
64 t.Fatalf("nestedGate() = %v, want it to carry %v", err, wantErr)
65 }
66}
67
68// TestNestedGatePassesMountsThenChecksReady pins the second half: once
69// every mount checks out, the gate's answer is exactly whatever /ready
70// says, refused when the control service is not ready, allowed when it is.
71func TestNestedGatePassesMountsThenChecksReady(t *testing.T) {
72 mounts := func(string) (bool, error) { return true, nil }
73
74 wantErr := errors.New("instance control: box not ready")
75 gate := nestedGate(selfOK, mounts, func(context.Context) error { return wantErr })
76 if err := gate(); err != wantErr {
77 t.Fatalf("nestedGate() = %v, want the /ready error %v", err, wantErr)
78 }
79
80 gate = nestedGate(selfOK, mounts, func(context.Context) error { return nil })
81 if err := gate(); err != nil {
82 t.Fatalf("nestedGate() = %v, want nil once every mount and /ready both pass", err)
83 }
84}
85