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 activator
4
5import (
6 "context"
7 "errors"
8 "os/exec"
9 "slices"
10 "sync"
11 "testing"
12 "time"
13
14 "github.com/carlosframework/platform/internal/hostclaim"
15 "github.com/carlosframework/platform/internal/hostwrite"
16 "github.com/carlosframework/platform/internal/registry"
17)
18
19// Open question 5's premise, corrected (instance-key plan rev 2): on a
20// claim box a session's process retries do not go back to the launch
21// source, which is where the key is minted. Prepare runs once per
22// admitted session and captures the launch inputs, and every attempt
23// calls Materialize (the C8 provider's Establish) before it reserves a
24// start. So no attempt runs with a key minted after the files manifest
25// was established. This pins that shape of startAdmitted: one Prepare,
26// and Materialize before each attempt's start.
27func TestStartAdmittedRetriesReuseTheirInputsAndMaterializeFirst(t *testing.T) {
28 t.Parallel()
29 var (
30 mu sync.Mutex
31 events []string
32 prepares int
33 )
34 note := func(e string) { mu.Lock(); events = append(events, e); mu.Unlock() }
35 obs := hostclaim.Observed{Host: "x.example", ETag: "one", Claim: &hostclaim.Claim{Holder: &hostclaim.Holder{Location: "instances/x.example/inc", Session: 1, Generation: "one"}, Op: hostclaim.Op{Kind: "landing", ID: "op", Phase: "probing", Exec: "box"}}}
36 work, _ := (hostwrite.Config{}).Select(t.Context(), obs.Host, "spawn")
37 a := &Service{cfg: Config{HostWrites: hostwrite.Config{Enabled: true}, Claims: &ClaimRuntime{Box: "box", Read: func(context.Context, string) (hostclaim.Observed, error) { return obs, nil }, Prepare: func(context.Context, registry.Route, hostclaim.Observed, startPolicy) (ClaimStart, error) {
38 mu.Lock()
39 prepares++
40 mu.Unlock()
41 return ClaimStart{RoleLauncher: &RoleLaunch{}, CurrentAttempt: func() hostclaim.WakeRetryAttempt { return hostclaim.WakeRetryAttempt{} }, Host: obs.Host, Work: work, Identity: hostwrite.Identity{Config: "c", Release: "r"},
42 OpenProbe: func(context.Context, time.Time) (time.Time, error) {
43 return time.Time{}, errors.New("unreachable while Start refuses")
44 },
45 Restore: func(context.Context) error { note("restore"); return nil },
46 Materialize: func(context.Context) error {
47 note("materialize")
48 return nil
49 },
50 Build: func() *exec.Cmd { return nil },
51 Ready: func(context.Context, *exec.Cmd) error { return nil },
52 Reserve: func(context.Context, int) error { note("reserve"); return nil },
53 Failed: func(context.Context, int) error { note("failed"); return nil },
54 Healthy: func(context.Context, int, *exec.Cmd) error { return nil },
55 Replicate: func(context.Context, string) (Rep, error) { return nil, nil },
56 Publish: func(context.Context, hostclaim.Observed) (hostclaim.Observed, error) { return obs, nil },
57 Retain: func(Proc, Rep, error) {},
58 }, nil
59 }}}}
60 // Two starts: the first refused (RoleLaunch.Start refuses until claim
61 // Plan 9 Group D), a 1 s backoff, then the second.
62 err := a.startAdmitted(t.Context(), &actEntry{host: obs.Host}, obs, startPolicy{MaxStarts: 2, Window: 30 * time.Second, Deadline: time.Now().Add(time.Minute), Probe: true})
63 if !errors.Is(err, ErrClaimIsolationUnavailable) {
64 t.Fatalf("the loop should end on the last start's refusal: %v", err)
65 }
66 want := []string{"restore", "reserve", "materialize", "failed", "reserve", "materialize", "failed"}
67 mu.Lock()
68 defer mu.Unlock()
69 if prepares != 1 || !slices.Equal(events, want) {
70 t.Fatalf("prepares %d, events %v; want 1 and %v", prepares, events, want)
71 }
72}
73