This repository has no description
1package executor
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "io"
8 "log/slog"
9 "testing"
10
11 "tangled.org/core/api/tangled"
12 millv1 "tangled.org/core/spindle/mill/proto/gen"
13 "tangled.org/core/spindle/models"
14)
15
16type placementEngine struct {
17 *fakeEngine
18 validationErr error
19 validated bool
20}
21
22func (e *placementEngine) ValidateWorkflowPlacement(*models.Workflow) error {
23 e.validated = true
24 return e.validationErr
25}
26
27func TestHandleReserveRejectsMissingTriggerMetadata(t *testing.T) {
28 enc := newCaptureEncoder()
29 e := &Executor{
30 l: slog.New(slog.NewTextHandler(io.Discard, nil)),
31 enc: enc,
32 seats: 1,
33 engines: map[string]models.Engine{"microvm": &fakeEngine{}},
34 active: make(map[string]*reservation),
35 }
36 twf, err := json.Marshal(tangled.Pipeline_Workflow{Name: "build"})
37 if err != nil {
38 t.Fatal(err)
39 }
40 tpl, err := json.Marshal(tangled.Pipeline{})
41 if err != nil {
42 t.Fatal(err)
43 }
44
45 // engines deref TriggerMetadata unconditionally. this must be a reject,
46 // not a panic that takes the whole executor down
47 e.handleReserve(context.Background(), &millv1.ReserveSeat{
48 LeaseId: "lease-1",
49 TargetEngine: "microvm",
50 RawWorkflowJson: string(twf),
51 RawPipelineJson: string(tpl),
52 Knot: "k",
53 Rkey: "r",
54 })
55
56 result := (<-enc.messages).GetReserveResult()
57 if result == nil {
58 t.Fatal("handleReserve() did not send ReserveResult")
59 }
60 if result.GetAccepted() {
61 t.Fatal("handleReserve() accepted a pipeline without trigger metadata")
62 }
63 if result.GetRejectClass() != millv1.RejectClass_REJECT_CLASS_INCOMPATIBLE {
64 t.Fatalf("reject class = %v, want incompatible", result.GetRejectClass())
65 }
66 if len(e.active) != 0 {
67 t.Fatalf("active reservations = %d, want 0", len(e.active))
68 }
69}
70
71func TestHandleReserveValidatesPlacementBeforeAcquiringSlot(t *testing.T) {
72 enc := newCaptureEncoder()
73 validationErr := errors.New("image architecture is not native")
74 eng := &placementEngine{fakeEngine: &fakeEngine{}, validationErr: validationErr}
75 e := &Executor{
76 l: slog.New(slog.NewTextHandler(io.Discard, nil)),
77 enc: enc,
78 seats: 1,
79 engines: map[string]models.Engine{"microvm": eng},
80 active: make(map[string]*reservation),
81 }
82 twf, err := json.Marshal(tangled.Pipeline_Workflow{Name: "build"})
83 if err != nil {
84 t.Fatal(err)
85 }
86 tpl, err := json.Marshal(tangled.Pipeline{TriggerMetadata: &tangled.Pipeline_TriggerMetadata{}})
87 if err != nil {
88 t.Fatal(err)
89 }
90
91 e.handleReserve(context.Background(), &millv1.ReserveSeat{
92 LeaseId: "lease-1",
93 TargetEngine: "microvm",
94 RawWorkflowJson: string(twf),
95 RawPipelineJson: string(tpl),
96 Knot: "k",
97 Rkey: "r",
98 })
99
100 result := (<-enc.messages).GetReserveResult()
101 if result == nil {
102 t.Fatal("handleReserve() did not send ReserveResult")
103 }
104 if result.GetAccepted() {
105 t.Fatal("handleReserve() accepted placement validation failure")
106 }
107 if result.GetRejectClass() != millv1.RejectClass_REJECT_CLASS_INCOMPATIBLE {
108 t.Fatalf("reject class = %v, want incompatible", result.GetRejectClass())
109 }
110 if !eng.validated {
111 t.Fatal("placement validator was not called")
112 }
113 if eng.acquireCalled {
114 t.Fatal("slot acquisition ran after placement validation failed")
115 }
116 if len(e.active) != 0 {
117 t.Fatalf("active reservations = %d, want 0", len(e.active))
118 }
119}