blob: ff382c34fe2b42c03fbbcd881c31787b625b0d67 [file] [log] [blame]
Carlos Amedee528629c2023-09-07 10:52:44 -04001// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//go:build linux || darwin
Carlos Amedee528629c2023-09-07 10:52:44 -04006
7package gomote
8
9import (
10 "context"
Carlos Amedeef44ba662023-11-06 12:41:15 -050011 "errors"
Carlos Amedee07e14b42023-09-21 16:03:58 -040012 "fmt"
13 "io"
14 "log"
15 "net/http"
16 "net/http/httptest"
Carlos Amedee528629c2023-09-07 10:52:44 -040017 "os"
Carlos Amedee07e14b42023-09-21 16:03:58 -040018 "strings"
Carlos Amedee528629c2023-09-07 10:52:44 -040019 "testing"
20 "time"
21
22 "github.com/google/go-cmp/cmp"
Carlos Amedee07e14b42023-09-21 16:03:58 -040023 "github.com/google/uuid"
Carlos Amedeef44ba662023-11-06 12:41:15 -050024 buildbucketpb "go.chromium.org/luci/buildbucket/proto"
Carlos Amedee07e14b42023-09-21 16:03:58 -040025 "go.chromium.org/luci/swarming/client/swarming"
26 "go.chromium.org/luci/swarming/client/swarming/swarmingtest"
27 swarmpb "go.chromium.org/luci/swarming/proto/api_v2"
Carlos Amedee528629c2023-09-07 10:52:44 -040028 "golang.org/x/build/internal/access"
29 "golang.org/x/build/internal/coordinator/remote"
30 "golang.org/x/build/internal/gomote/protos"
Carlos Amedee07e14b42023-09-21 16:03:58 -040031 "golang.org/x/build/internal/rendezvous"
Carlos Amedee528629c2023-09-07 10:52:44 -040032 "golang.org/x/crypto/ssh"
33 "golang.org/x/net/nettest"
34 "google.golang.org/grpc"
35 "google.golang.org/grpc/codes"
36 "google.golang.org/grpc/status"
Carlos Amedeecabf17f2023-10-25 17:47:42 -040037 "google.golang.org/protobuf/testing/protocmp"
Carlos Amedee528629c2023-09-07 10:52:44 -040038)
39
40const testSwarmingBucketName = "unit-testing-bucket-swarming"
41
Carlos Amedeef44ba662023-11-06 12:41:15 -050042func fakeGomoteSwarmingServer(t *testing.T, ctx context.Context, swarmClient swarming.Client, rdv rendezvousClient) protos.GomoteServiceServer {
Carlos Amedee528629c2023-09-07 10:52:44 -040043 signer, err := ssh.ParsePrivateKey([]byte(devCertCAPrivate))
44 if err != nil {
45 t.Fatalf("unable to parse raw certificate authority private key into signer=%s", err)
46 }
47 return &SwarmingServer{
48 bucket: &fakeBucketHandler{bucketName: testSwarmingBucketName},
49 buildlets: remote.NewSessionPool(ctx),
50 gceBucketName: testSwarmingBucketName,
51 sshCertificateAuthority: signer,
Carlos Amedee07e14b42023-09-21 16:03:58 -040052 rendezvous: rdv,
53 swarmingClient: swarmClient,
Carlos Amedeef44ba662023-11-06 12:41:15 -050054 buildersClient: &FakeBuildersClient{},
Carlos Amedee528629c2023-09-07 10:52:44 -040055 }
56}
57
Carlos Amedee07e14b42023-09-21 16:03:58 -040058func setupGomoteSwarmingTest(t *testing.T, ctx context.Context, swarmClient swarming.Client) protos.GomoteServiceClient {
Carlos Amedee528629c2023-09-07 10:52:44 -040059 lis, err := nettest.NewLocalListener("tcp")
60 if err != nil {
61 t.Fatalf("unable to create net listener: %s", err)
62 }
Carlos Amedee07e14b42023-09-21 16:03:58 -040063 rdv := rendezvous.NewFake(context.Background(), func(ctx context.Context, jwt string) bool { return true })
Carlos Amedee528629c2023-09-07 10:52:44 -040064 sopts := access.FakeIAPAuthInterceptorOptions()
65 s := grpc.NewServer(sopts...)
Carlos Amedeef44ba662023-11-06 12:41:15 -050066 protos.RegisterGomoteServiceServer(s, fakeGomoteSwarmingServer(t, ctx, swarmClient, rdv))
Carlos Amedee528629c2023-09-07 10:52:44 -040067 go s.Serve(lis)
68
69 // create GRPC client
70 copts := []grpc.DialOption{
71 grpc.WithInsecure(),
72 grpc.WithBlock(),
73 grpc.WithTimeout(5 * time.Second),
74 }
75 conn, err := grpc.Dial(lis.Addr().String(), copts...)
76 if err != nil {
77 lis.Close()
78 t.Fatalf("unable to create GRPC client: %s", err)
79 }
80 gc := protos.NewGomoteServiceClient(conn)
81 t.Cleanup(func() {
82 conn.Close()
83 s.Stop()
84 lis.Close()
85 })
86 return gc
87}
88
Carlos Amedee387fe0c2023-10-25 17:54:12 -040089func TestSwarmingAuthenticate(t *testing.T) {
90 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
91 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClient())
92 got, err := client.Authenticate(ctx, &protos.AuthenticateRequest{})
93 if err != nil {
94 t.Fatalf("client.Authenticate(ctx, request) = %v, %s; want no error", got, err)
95 }
96}
97
98func TestSwarmingAuthenticateError(t *testing.T) {
99 wantCode := codes.Unauthenticated
100 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClient())
101 _, err := client.Authenticate(context.Background(), &protos.AuthenticateRequest{})
102 if status.Code(err) != wantCode {
103 t.Fatalf("client.Authenticate(ctx, request) = _, %s; want %s", status.Code(err), wantCode)
104 }
105}
106
Carlos Amedee95a56fd2023-11-15 13:32:17 -0500107func TestSwarmingAddBootstrap(t *testing.T) {
108 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
109 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
110 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
111 req := &protos.AddBootstrapRequest{
112 GomoteId: gomoteID,
113 }
114 got, err := client.AddBootstrap(ctx, req)
115 if err != nil {
116 t.Fatalf("client.AddBootstrap(ctx, %v) = %v, %s; want no error", req, got, err)
117 }
118}
119
120func TestSwarmingAddBootstrapError(t *testing.T) {
121 // This test will create a gomote instance and attempt to call AddBootstrap.
122 // If overrideID is set to true, the test will use a different gomoteID than
123 // the one created for the test.
124 testCases := []struct {
125 desc string
126 ctx context.Context
127 overrideID bool
128 gomoteID string // Used iff overrideID is true.
129 wantCode codes.Code
130 }{
131 {
132 desc: "unauthenticated request",
133 ctx: context.Background(),
134 wantCode: codes.Unauthenticated,
135 },
136 {
137 desc: "missing gomote id",
138 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
139 overrideID: true,
140 wantCode: codes.NotFound,
141 },
142 {
143 desc: "gomote does not exist",
144 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
145 overrideID: true,
146 gomoteID: "xyz",
147 wantCode: codes.NotFound,
148 },
149 {
150 desc: "gomote is not owned by caller",
151 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("user-x", "email-y")),
152 wantCode: codes.PermissionDenied,
153 },
154 }
155 for _, tc := range testCases {
156 t.Run(tc.desc, func(t *testing.T) {
157 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
158 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
159 if tc.overrideID {
160 gomoteID = tc.gomoteID
161 }
162 req := &protos.AddBootstrapRequest{
163 GomoteId: gomoteID,
164 }
165 got, err := client.AddBootstrap(tc.ctx, req)
166 if err != nil && status.Code(err) != tc.wantCode {
167 t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
168 }
169 if err == nil {
170 t.Fatalf("client.AddBootstrap(ctx, %v) = %v, nil; want error", req, got)
171 }
172 })
173 }
174}
175
Carlos Amedee528629c2023-09-07 10:52:44 -0400176func TestSwarmingListSwarmingBuilders(t *testing.T) {
Carlos Amedee07e14b42023-09-21 16:03:58 -0400177 log.SetOutput(io.Discard)
178 defer log.SetOutput(os.Stdout)
179
180 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClient())
Carlos Amedee528629c2023-09-07 10:52:44 -0400181 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
182 response, err := client.ListSwarmingBuilders(ctx, &protos.ListSwarmingBuildersRequest{})
183 if err != nil {
184 t.Fatalf("client.ListSwarmingBuilders = nil, %s; want no error", err)
185 }
186 got := response.GetBuilders()
Carlos Amedeed0f6cc12023-12-15 16:35:40 -0500187 if diff := cmp.Diff([]string{"gotip-linux-amd64", "gotip-linux-amd64-boringcrypto", "gotip-linux-arm"}, got); diff != "" {
Carlos Amedee528629c2023-09-07 10:52:44 -0400188 t.Errorf("ListBuilders() mismatch (-want, +got):\n%s", diff)
189 }
190}
191
192func TestSwarmingListSwarmingBuildersError(t *testing.T) {
Carlos Amedee07e14b42023-09-21 16:03:58 -0400193 log.SetOutput(io.Discard)
194 defer log.SetOutput(os.Stdout)
195
196 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClient())
Carlos Amedee528629c2023-09-07 10:52:44 -0400197 req := &protos.ListSwarmingBuildersRequest{}
198 got, err := client.ListSwarmingBuilders(context.Background(), req)
199 if err != nil && status.Code(err) != codes.Unauthenticated {
200 t.Fatalf("unexpected error: %s; want %s", err, codes.Unauthenticated)
201 }
202 if err == nil {
203 t.Fatalf("client.ListSwarmingBuilder(ctx, %v) = %v, nil; want error", req, got)
204 }
205}
Carlos Amedee07e14b42023-09-21 16:03:58 -0400206
207func TestSwarmingCreateInstance(t *testing.T) {
208 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
209 req := &protos.CreateInstanceRequest{BuilderType: "gotip-linux-amd64-boringcrypto"}
210
211 msc := mockSwarmClient()
212 msc.NewTaskMock = func(_ context.Context, req *swarmpb.NewTaskRequest) (*swarmpb.TaskRequestMetadataResponse, error) {
213 taskID := uuid.New().String()
214 return &swarmpb.TaskRequestMetadataResponse{
215 TaskId: taskID,
216 Request: &swarmpb.TaskRequestResponse{
217 TaskId: taskID,
218 Name: req.Name,
219 },
220 }, nil
221 }
222 msc.TaskResultMock = func(_ context.Context, taskID string, _ *swarming.TaskResultFields) (*swarmpb.TaskResultResponse, error) {
223 return &swarmpb.TaskResultResponse{
224 TaskId: taskID,
225 State: swarmpb.TaskState_RUNNING,
226 }, nil
227 }
228
229 gomoteClient := setupGomoteSwarmingTest(t, context.Background(), msc)
230
231 stream, err := gomoteClient.CreateInstance(ctx, req)
232 if err != nil {
233 t.Fatalf("client.CreateInstance(ctx, %v) = %v, %s; want no error", req, stream, err)
234 }
235 var updateComplete bool
236 for {
237 update, err := stream.Recv()
238 if err == io.EOF && !updateComplete {
239 t.Fatal("stream.Recv = stream, io.EOF; want no EOF")
240 }
241 if err == io.EOF {
242 break
243 }
244 if err != nil {
245 t.Fatalf("stream.Recv() = nil, %s; want no error", err)
246 }
247 if update.GetStatus() == protos.CreateInstanceResponse_COMPLETE {
248 updateComplete = true
249 }
250 }
251}
252
253func TestSwarmingCreateInstanceError(t *testing.T) {
254 log.SetOutput(io.Discard)
255 defer log.SetOutput(os.Stdout)
256
257 testCases := []struct {
258 desc string
259 ctx context.Context
260 request *protos.CreateInstanceRequest
261 wantCode codes.Code
262 }{
263 {
264 desc: "unauthenticated request",
265 ctx: context.Background(),
266 request: &protos.CreateInstanceRequest{},
267 wantCode: codes.Unauthenticated,
268 },
269 {
270 desc: "missing builder type",
271 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
272 request: &protos.CreateInstanceRequest{},
273 wantCode: codes.InvalidArgument,
274 },
275 {
276 desc: "invalid builder type",
277 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
278 request: &protos.CreateInstanceRequest{
279 BuilderType: "funky-time-builder",
280 },
281 wantCode: codes.InvalidArgument,
282 },
283 }
284 for _, tc := range testCases {
285 t.Run(tc.desc, func(t *testing.T) {
286 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClient())
287
288 stream, err := client.CreateInstance(tc.ctx, tc.request)
289 if err != nil {
290 t.Fatalf("client.CreateInstance(ctx, %v) = %v, %s; want no error", tc.request, stream, err)
291 }
292 for {
293 _, got := stream.Recv()
294 if got == io.EOF {
295 t.Fatal("stream.Recv = stream, io.EOF; want no EOF")
296 }
297 if got != nil && status.Code(got) != tc.wantCode {
298 t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
299 }
300 return
301 }
302 })
303 }
304}
305
Carlos Amedee08f08c62023-10-25 17:45:33 -0400306func TestSwarmingDestroyInstance(t *testing.T) {
307 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
308 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
309 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
310 if _, err := client.DestroyInstance(ctx, &protos.DestroyInstanceRequest{
311 GomoteId: gomoteID,
312 }); err != nil {
313 t.Fatalf("client.DestroyInstance(ctx, req) = response, %s; want no error", err)
314 }
315}
316
317func TestSwarmingDestroyInstanceError(t *testing.T) {
318 // This test will create a gomote instance and attempt to call DestroyInstance.
319 // If overrideID is set to true, the test will use a different gomoteID than
320 // the one created for the test.
321 testCases := []struct {
322 desc string
323 ctx context.Context
324 overrideID bool
325 gomoteID string // Used iff overrideID is true.
326 wantCode codes.Code
327 }{
328 {
329 desc: "unauthenticated request",
330 ctx: context.Background(),
331 wantCode: codes.Unauthenticated,
332 },
333 {
334 desc: "missing gomote id",
335 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
336 overrideID: true,
337 gomoteID: "",
338 wantCode: codes.InvalidArgument,
339 },
340 {
341 desc: "gomote does not exist",
342 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
343 overrideID: true,
344 gomoteID: "chucky",
345 wantCode: codes.NotFound,
346 },
347 {
348 desc: "wrong gomote id",
349 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
350 overrideID: false,
351 wantCode: codes.PermissionDenied,
352 },
353 }
354 for _, tc := range testCases {
355 t.Run(tc.desc, func(t *testing.T) {
356 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
357 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
358 if tc.overrideID {
359 gomoteID = tc.gomoteID
360 }
361 req := &protos.DestroyInstanceRequest{
362 GomoteId: gomoteID,
363 }
364 got, err := client.DestroyInstance(tc.ctx, req)
365 if err != nil && status.Code(err) != tc.wantCode {
366 t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
367 }
368 if err == nil {
369 t.Fatalf("client.DestroyInstance(ctx, %v) = %v, nil; want error", req, got)
370 }
371 })
372 }
373}
374
Carlos Amedee7f5600b2023-10-25 17:50:37 -0400375func TestSwarmingExecuteCommand(t *testing.T) {
376 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
377 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
378 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
379 stream, err := client.ExecuteCommand(ctx, &protos.ExecuteCommandRequest{
380 GomoteId: gomoteID,
381 Command: "ls",
382 SystemLevel: false,
383 Debug: false,
384 AppendEnvironment: nil,
385 Path: nil,
386 Directory: "/workdir",
387 Args: []string{"-alh"},
388 })
389 if err != nil {
390 t.Fatalf("client.ExecuteCommand(ctx, req) = response, %s; want no error", err)
391 }
392 var out []byte
393 for {
394 res, err := stream.Recv()
395 if err != nil && err == io.EOF {
396 break
397 }
398 if err != nil {
399 t.Fatalf("stream.Recv() = _, %s; want no error", err)
400 }
401 out = append(out, res.GetOutput()...)
402 }
403 if len(out) == 0 {
404 t.Fatalf("output: %q, expected non-empty", out)
405 }
406}
407
408func TestSwarmingExecuteCommandError(t *testing.T) {
409 // This test will create a gomote instance and attempt to call TestExecuteCommand.
410 // If overrideID is set to true, the test will use a different gomoteID than
411 // the one created for the test.
412 testCases := []struct {
413 desc string
414 ctx context.Context
415 overrideID bool
416 gomoteID string // Used iff overrideID is true.
417 cmd string
418 wantCode codes.Code
419 }{
420 {
421 desc: "unauthenticated request",
422 ctx: context.Background(),
423 wantCode: codes.Unauthenticated,
424 },
425 {
426 desc: "missing gomote id",
427 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
428 overrideID: true,
429 gomoteID: "",
430 wantCode: codes.NotFound,
431 },
432 {
433 desc: "missing command",
434 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
435 wantCode: codes.Aborted,
436 },
437 {
438 desc: "gomote does not exist",
439 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
440 overrideID: true,
441 gomoteID: "chucky",
442 cmd: "ls",
443 wantCode: codes.NotFound,
444 },
445 {
446 desc: "wrong gomote id",
447 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
448 overrideID: false,
449 cmd: "ls",
450 wantCode: codes.PermissionDenied,
451 },
452 }
453 for _, tc := range testCases {
454 t.Run(tc.desc, func(t *testing.T) {
455 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
456 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
457 if tc.overrideID {
458 gomoteID = tc.gomoteID
459 }
460 stream, err := client.ExecuteCommand(tc.ctx, &protos.ExecuteCommandRequest{
461 GomoteId: gomoteID,
462 Command: tc.cmd,
463 SystemLevel: false,
464 Debug: false,
465 AppendEnvironment: nil,
466 Path: nil,
467 Directory: "/workdir",
468 Args: []string{"-alh"},
469 })
470 if err != nil {
471 t.Fatalf("unexpected error: %s", err)
472 }
473 res, err := stream.Recv()
474 if err != nil && status.Code(err) != tc.wantCode {
475 t.Fatalf("unexpected error: %s", err)
476 }
477 if err == nil {
478 t.Fatalf("client.ExecuteCommand(ctx, req) = %v, nil; want error", res)
479 }
480 })
481 }
482}
483
Carlos Amedee6fa55242023-10-25 17:56:41 -0400484func TestSwarmingInstanceAlive(t *testing.T) {
485 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
486 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
487 req := &protos.InstanceAliveRequest{
488 GomoteId: gomoteID,
489 }
490 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
491 got, err := client.InstanceAlive(ctx, req)
492 if err != nil {
493 t.Fatalf("client.InstanceAlive(ctx, %v) = %v, %s; want no error", req, got, err)
494 }
495}
496
497func TestSwarmingInstanceAliveError(t *testing.T) {
498 // This test will create a gomote instance and attempt to call InstanceAlive.
499 // If overrideID is set to true, the test will use a different gomoteID than
500 // the one created for the test.
501 testCases := []struct {
502 desc string
503 ctx context.Context
504 overrideID bool
505 gomoteID string // Used iff overrideID is true.
506 wantCode codes.Code
507 }{
508 {
509 desc: "unauthenticated request",
510 ctx: context.Background(),
511 wantCode: codes.Unauthenticated,
512 },
513 {
514 desc: "missing gomote id",
515 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
516 overrideID: true,
517 wantCode: codes.InvalidArgument,
518 },
519 {
520 desc: "gomote does not exist",
521 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
522 overrideID: true,
523 gomoteID: "xyz",
524 wantCode: codes.NotFound,
525 },
526 {
527 desc: "gomote is not owned by caller",
528 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("user-x", "email-y")),
529 wantCode: codes.PermissionDenied,
530 },
531 }
532 for _, tc := range testCases {
533 t.Run(tc.desc, func(t *testing.T) {
534 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
535 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
536 if tc.overrideID {
537 gomoteID = tc.gomoteID
538 }
539 req := &protos.InstanceAliveRequest{
540 GomoteId: gomoteID,
541 }
542 got, err := client.InstanceAlive(tc.ctx, req)
543 if err != nil && status.Code(err) != tc.wantCode {
544 t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
545 }
546 if err == nil {
547 t.Fatalf("client.InstanceAlive(ctx, %v) = %v, nil; want error", req, got)
548 }
549 })
550 }
551}
552
Carlos Amedee89f26ba2023-10-25 17:58:05 -0400553func TestSwarmingListDirectory(t *testing.T) {
554 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
555 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
556 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
557 if _, err := client.ListDirectory(ctx, &protos.ListDirectoryRequest{
558 GomoteId: gomoteID,
559 Directory: "/foo",
560 }); err != nil {
561 t.Fatalf("client.RemoveFiles(ctx, req) = response, %s; want no error", err)
562 }
563}
564
565func TestSwarmingListDirectoryError(t *testing.T) {
566 // This test will create a gomote instance and attempt to call ListDirectory.
567 // If overrideID is set to true, the test will use a different gomoteID than
568 // the one created for the test.
569 testCases := []struct {
570 desc string
571 ctx context.Context
572 overrideID bool
573 gomoteID string // Used iff overrideID is true.
574 directory string
575 recursive bool
576 skipFiles []string
577 digest bool
578 wantCode codes.Code
579 }{
580 {
581 desc: "unauthenticated request",
582 ctx: context.Background(),
583 wantCode: codes.Unauthenticated,
584 },
585 {
586 desc: "missing gomote id",
587 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
588 overrideID: true,
589 gomoteID: "",
590 wantCode: codes.InvalidArgument,
591 },
592 {
593 desc: "missing directory",
594 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
595 wantCode: codes.InvalidArgument,
596 },
597 {
598 desc: "gomote does not exist",
599 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
600 overrideID: true,
601 gomoteID: "chucky",
602 directory: "/foo",
603 wantCode: codes.NotFound,
604 },
605 {
606 desc: "wrong gomote id",
607 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
608 overrideID: false,
609 directory: "/foo",
610 wantCode: codes.PermissionDenied,
611 },
612 }
613 for _, tc := range testCases {
614 t.Run(tc.desc, func(t *testing.T) {
615 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
616 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
617 if tc.overrideID {
618 gomoteID = tc.gomoteID
619 }
620 req := &protos.ListDirectoryRequest{
621 GomoteId: gomoteID,
622 Directory: tc.directory,
623 Recursive: false,
624 SkipFiles: []string{},
625 Digest: false,
626 }
627 got, err := client.ListDirectory(tc.ctx, req)
628 if err != nil && status.Code(err) != tc.wantCode {
629 t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
630 }
631 if err == nil {
632 t.Fatalf("client.RemoveFiles(ctx, %v) = %v, nil; want error", req, got)
633 }
634 })
635 }
636}
637
Carlos Amedeecabf17f2023-10-25 17:47:42 -0400638func TestSwarmingListInstance(t *testing.T) {
639 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
640 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
641 var want []*protos.Instance
642 for i := 0; i < 3; i++ {
643 want = append(want, &protos.Instance{
644 GomoteId: mustCreateSwarmingInstance(t, client, fakeIAP()),
645 BuilderType: "gotip-linux-amd64-boringcrypto",
646 })
647 }
648 mustCreateSwarmingInstance(t, client, fakeIAPWithUser("user-x", "uuid-user-x"))
649 response, err := client.ListInstances(ctx, &protos.ListInstancesRequest{})
650 if err != nil {
651 t.Fatalf("client.ListInstances = nil, %s; want no error", err)
652 }
653 got := response.GetInstances()
654 if diff := cmp.Diff(want, got, protocmp.Transform(), protocmp.IgnoreFields(&protos.Instance{}, "expires", "host_type")); diff != "" {
655 t.Errorf("ListInstances() mismatch (-want, +got):\n%s", diff)
656 }
657}
658
Carlos Amedeecb2be0a2023-10-27 17:18:43 -0400659func TestSwarmingReadTGZToURLError(t *testing.T) {
660 // This test will create a gomote instance and attempt to call ReadTGZToURL.
661 // If overrideID is set to true, the test will use a different gomoteID than
662 // the one created for the test.
663 testCases := []struct {
664 desc string
665 ctx context.Context
666 overrideID bool
667 gomoteID string // Used iff overrideID is true.
668 directory string
669 wantCode codes.Code
670 }{
671 {
672 desc: "unauthenticated request",
673 ctx: context.Background(),
674 wantCode: codes.Unauthenticated,
675 },
676 {
677 desc: "missing gomote id",
678 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
679 overrideID: true,
680 gomoteID: "",
681 wantCode: codes.NotFound,
682 },
683 {
684 desc: "gomote does not exist",
685 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
686 overrideID: true,
687 gomoteID: "chucky",
688 wantCode: codes.NotFound,
689 },
690 {
691 desc: "wrong gomote id",
692 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
693 overrideID: false,
694 wantCode: codes.PermissionDenied,
695 },
696 }
697 for _, tc := range testCases {
698 t.Run(tc.desc, func(t *testing.T) {
699 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
700 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
701 if tc.overrideID {
702 gomoteID = tc.gomoteID
703 }
704 req := &protos.ReadTGZToURLRequest{
705 GomoteId: gomoteID,
706 Directory: tc.directory,
707 }
708 got, err := client.ReadTGZToURL(tc.ctx, req)
709 if err != nil && status.Code(err) != tc.wantCode {
710 t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
711 }
712 if err == nil {
713 t.Fatalf("client.ReadTGZToURL(ctx, %v) = %v, nil; want error", req, got)
714 }
715 })
716 }
717}
718
Carlos Amedeeffca8082023-10-27 17:23:06 -0400719func TestSwarmingRemoveFiles(t *testing.T) {
720 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
721 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
722 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
723 if _, err := client.RemoveFiles(ctx, &protos.RemoveFilesRequest{
724 GomoteId: gomoteID,
725 Paths: []string{"temp_file.log"},
726 }); err != nil {
727 t.Fatalf("client.RemoveFiles(ctx, req) = response, %s; want no error", err)
728 }
729}
730
Carlos Amedeec3ae6812023-10-27 17:27:22 -0400731func TestSwarmingSignSSHKey(t *testing.T) {
732 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
733 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
734 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
735 if _, err := client.SignSSHKey(ctx, &protos.SignSSHKeyRequest{
736 GomoteId: gomoteID,
737 PublicSshKey: []byte(devCertCAPublic),
738 }); err != nil {
739 t.Fatalf("client.SignSSHKey(ctx, req) = response, %s; want no error", err)
740 }
741}
742
743func TestSwarmingSignSSHKeyError(t *testing.T) {
744 // This test will create a gomote instance and attempt to call SignSSHKey.
745 // If overrideID is set to true, the test will use a different gomoteID than
746 // the one created for the test.
747 testCases := []struct {
748 desc string
749 ctx context.Context
750 overrideID bool
751 gomoteID string // Used iff overrideID is true.
752 publickSSHKey []byte
753 wantCode codes.Code
754 }{
755 {
756 desc: "unauthenticated request",
757 ctx: context.Background(),
758 wantCode: codes.Unauthenticated,
759 },
760 {
761 desc: "missing gomote id",
762 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
763 overrideID: true,
764 gomoteID: "",
765 wantCode: codes.NotFound,
766 },
767 {
768 desc: "missing public key",
769 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
770 wantCode: codes.InvalidArgument,
771 },
772 {
773 desc: "gomote does not exist",
774 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
775 overrideID: true,
776 gomoteID: "chucky",
777 publickSSHKey: []byte(devCertCAPublic),
778 wantCode: codes.NotFound,
779 },
780 {
781 desc: "wrong gomote id",
782 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
783 overrideID: false,
784 publickSSHKey: []byte(devCertCAPublic),
785 wantCode: codes.PermissionDenied,
786 },
787 }
788 for _, tc := range testCases {
789 t.Run(tc.desc, func(t *testing.T) {
790 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
791 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
792 if tc.overrideID {
793 gomoteID = tc.gomoteID
794 }
795 req := &protos.SignSSHKeyRequest{
796 GomoteId: gomoteID,
797 PublicSshKey: tc.publickSSHKey,
798 }
799 got, err := client.SignSSHKey(tc.ctx, req)
800 if err != nil && status.Code(err) != tc.wantCode {
801 t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
802 }
803 if err == nil {
804 t.Fatalf("client.SignSSHKey(ctx, %v) = %v, nil; want error", req, got)
805 }
806 })
807 }
808}
809
Carlos Amedee12d957f2023-10-27 17:31:24 -0400810func TestSwarmingUploadFile(t *testing.T) {
811 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
812 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
813 _ = mustCreateSwarmingInstance(t, client, fakeIAP())
814 if _, err := client.UploadFile(ctx, &protos.UploadFileRequest{}); err != nil {
815 t.Fatalf("client.UploadFile(ctx, req) = response, %s; want no error", err)
816 }
817}
818
819func TestSwarmingUploadFileError(t *testing.T) {
820 // This test will create a gomote instance and attempt to call UploadFile.
821 // If overrideID is set to true, the test will use a different gomoteID than
822 // the one created for the test.
823 testCases := []struct {
824 desc string
825 ctx context.Context
826 overrideID bool
827 filename string
828 wantCode codes.Code
829 }{
830 {
831 desc: "unauthenticated request",
832 ctx: context.Background(),
833 wantCode: codes.Unauthenticated,
834 },
835 }
836 for _, tc := range testCases {
837 t.Run(tc.desc, func(t *testing.T) {
838 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
839 _ = mustCreateSwarmingInstance(t, client, fakeIAP())
840 req := &protos.UploadFileRequest{}
841 got, err := client.UploadFile(tc.ctx, req)
842 if err != nil && status.Code(err) != tc.wantCode {
843 t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
844 }
845 if err == nil {
846 t.Fatalf("client.UploadFile(ctx, %v) = %v, nil; want error", req, got)
847 }
848 })
849 }
850}
851
Carlos Amedeeffca8082023-10-27 17:23:06 -0400852func TestSwarmingRemoveFilesError(t *testing.T) {
853 // This test will create a gomote instance and attempt to call RemoveFiles.
854 // If overrideID is set to true, the test will use a different gomoteID than
855 // the one created for the test.
856 testCases := []struct {
857 desc string
858 ctx context.Context
859 overrideID bool
860 gomoteID string // Used iff overrideID is true.
861 paths []string
862 wantCode codes.Code
863 }{
864 {
865 desc: "unauthenticated request",
866 ctx: context.Background(),
867 wantCode: codes.Unauthenticated,
868 },
869 {
870 desc: "missing gomote id",
871 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
872 overrideID: true,
873 gomoteID: "",
874 wantCode: codes.InvalidArgument,
875 },
876 {
877 desc: "missing paths",
878 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
879 paths: []string{},
880 wantCode: codes.InvalidArgument,
881 },
882 {
883 desc: "gomote does not exist",
884 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
885 overrideID: true,
886 gomoteID: "chucky",
887 paths: []string{"file.a"},
888 wantCode: codes.NotFound,
889 },
890 {
891 desc: "wrong gomote id",
892 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
893 overrideID: false,
894 paths: []string{"file.a"},
895 wantCode: codes.PermissionDenied,
896 },
897 }
898 for _, tc := range testCases {
899 t.Run(tc.desc, func(t *testing.T) {
900 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
901 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
902 if tc.overrideID {
903 gomoteID = tc.gomoteID
904 }
905 req := &protos.RemoveFilesRequest{
906 GomoteId: gomoteID,
907 Paths: tc.paths,
908 }
909 got, err := client.RemoveFiles(tc.ctx, req)
910 if err != nil && status.Code(err) != tc.wantCode {
911 t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
912 }
913 if err == nil {
914 t.Fatalf("client.RemoveFiles(ctx, %v) = %v, nil; want error", req, got)
915 }
916 })
917 }
918}
919
Carlos Amedee97084772023-10-27 17:35:16 -0400920// TODO(go.dev/issue/48737) add test for files on GCS
921func TestSwarmingWriteFileFromURL(t *testing.T) {
922 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
923 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
924 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
925 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
926 fmt.Fprintln(w, "Go is an open source programming language")
927 }))
928 defer ts.Close()
929 if _, err := client.WriteFileFromURL(ctx, &protos.WriteFileFromURLRequest{
930 GomoteId: gomoteID,
931 Url: ts.URL,
932 Filename: "foo",
933 Mode: 0777,
934 }); err != nil {
935 t.Fatalf("client.WriteFileFromURL(ctx, req) = response, %s; want no error", err)
936 }
937}
938
939func TestSwarmingWriteFileFromURLError(t *testing.T) {
940 // This test will create a gomote instance and attempt to call TestWriteFileFromURL.
941 // If overrideID is set to true, the test will use a different gomoteID than
942 // the one created for the test.
943 testCases := []struct {
944 desc string
945 ctx context.Context
946 overrideID bool
947 gomoteID string // Used iff overrideID is true.
948 url string
949 filename string
950 mode uint32
951 wantCode codes.Code
952 }{
953 {
954 desc: "unauthenticated request",
955 ctx: context.Background(),
956 wantCode: codes.Unauthenticated,
957 },
958 {
959 desc: "missing gomote id",
960 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
961 overrideID: true,
962 gomoteID: "",
963 wantCode: codes.NotFound,
964 },
965 {
966 desc: "gomote does not exist",
967 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
968 overrideID: true,
969 gomoteID: "chucky",
970 url: "go.dev/dl/1_14.tar.gz",
971 wantCode: codes.NotFound,
972 },
973 {
974 desc: "wrong gomote id",
975 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
976 overrideID: false,
977 url: "go.dev/dl/1_14.tar.gz",
978 wantCode: codes.PermissionDenied,
979 },
980 }
981 for _, tc := range testCases {
982 t.Run(tc.desc, func(t *testing.T) {
983 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
984 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
985 if tc.overrideID {
986 gomoteID = tc.gomoteID
987 }
988 req := &protos.WriteFileFromURLRequest{
989 GomoteId: gomoteID,
990 Url: tc.url,
991 Filename: tc.filename,
992 Mode: 0,
993 }
994 got, err := client.WriteFileFromURL(tc.ctx, req)
995 if err != nil && status.Code(err) != tc.wantCode {
996 t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
997 }
998 if err == nil {
999 t.Fatalf("client.WriteFileFromURL(ctx, %v) = %v, nil; want error", req, got)
1000 }
1001 })
1002 }
1003}
1004
Carlos Amedee869409f2023-10-27 17:44:59 -04001005func TestSwarmingWriteTGZFromURL(t *testing.T) {
1006 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
1007 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
1008 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
1009 if _, err := client.WriteTGZFromURL(ctx, &protos.WriteTGZFromURLRequest{
1010 GomoteId: gomoteID,
1011 Directory: "foo",
1012 Url: `https://go.dev/dl/go1.17.6.linux-amd64.tar.gz`,
1013 }); err != nil {
1014 t.Fatalf("client.WriteTGZFromURL(ctx, req) = response, %s; want no error", err)
1015 }
1016}
1017
1018func TestSwarmingWriteTGZFromURLGomoteStaging(t *testing.T) {
1019 ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
1020 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
1021 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
1022 if _, err := client.WriteTGZFromURL(ctx, &protos.WriteTGZFromURLRequest{
1023 GomoteId: gomoteID,
1024 Directory: "foo",
1025 Url: fmt.Sprintf("https://storage.googleapis.com/%s/go1.17.6.linux-amd64.tar.gz?field=x", testBucketName),
1026 }); err != nil {
1027 t.Fatalf("client.WriteTGZFromURL(ctx, req) = response, %s; want no error", err)
1028 }
1029}
1030
1031func TestSwarmingWriteTGZFromURLError(t *testing.T) {
1032 // This test will create a gomote instance and attempt to call TestWriteTGZFromURL.
1033 // If overrideID is set to true, the test will use a different gomoteID than
1034 // the one created for the test.
1035 testCases := []struct {
1036 desc string
1037 ctx context.Context
1038 overrideID bool
1039 gomoteID string // Used iff overrideID is true.
1040 url string
1041 directory string
1042 wantCode codes.Code
1043 }{
1044 {
1045 desc: "unauthenticated request",
1046 ctx: context.Background(),
1047 wantCode: codes.Unauthenticated,
1048 },
1049 {
1050 desc: "missing gomote id",
1051 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
1052 overrideID: true,
1053 gomoteID: "",
1054 wantCode: codes.InvalidArgument,
1055 },
1056 {
1057 desc: "missing URL",
1058 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
1059 wantCode: codes.InvalidArgument,
1060 },
1061 {
1062 desc: "gomote does not exist",
1063 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
1064 overrideID: true,
1065 gomoteID: "chucky",
1066 url: "go.dev/dl/1_14.tar.gz",
1067 wantCode: codes.NotFound,
1068 },
1069 {
1070 desc: "wrong gomote id",
1071 ctx: access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
1072 overrideID: false,
1073 url: "go.dev/dl/1_14.tar.gz",
1074 wantCode: codes.PermissionDenied,
1075 },
1076 }
1077 for _, tc := range testCases {
1078 t.Run(tc.desc, func(t *testing.T) {
1079 client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
1080 gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
1081 if tc.overrideID {
1082 gomoteID = tc.gomoteID
1083 }
1084 req := &protos.WriteTGZFromURLRequest{
1085 GomoteId: gomoteID,
1086 Url: tc.url,
1087 Directory: tc.directory,
1088 }
1089 got, err := client.WriteTGZFromURL(tc.ctx, req)
1090 if err != nil && status.Code(err) != tc.wantCode {
1091 t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
1092 }
1093 if err == nil {
1094 t.Fatalf("client.WriteTGZFromURL(ctx, %v) = %v, nil; want error", req, got)
1095 }
1096 })
1097 }
1098}
1099
Carlos Amedee07e14b42023-09-21 16:03:58 -04001100func TestStartNewSwarmingTask(t *testing.T) {
1101 log.SetOutput(io.Discard)
1102 defer log.SetOutput(os.Stdout)
1103
1104 ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
1105 rdv := rendezvous.New(ctx, rendezvous.OptionValidator(func(ctx context.Context, jwt string) bool {
1106 return true
1107 }))
1108 ts := httptest.NewTLSServer(http.HandlerFunc(rdv.HandleReverse))
1109 defer ts.Close()
1110
1111 msc := mockSwarmClient()
1112 msc.NewTaskMock = func(_ context.Context, req *swarmpb.NewTaskRequest) (*swarmpb.TaskRequestMetadataResponse, error) {
1113 taskID := uuid.New().String()
1114 return &swarmpb.TaskRequestMetadataResponse{
1115 TaskId: taskID,
1116 Request: &swarmpb.TaskRequestResponse{
1117 TaskId: taskID,
1118 Name: req.Name,
1119 },
1120 }, nil
1121 }
1122 msc.TaskResultMock = func(_ context.Context, taskID string, _ *swarming.TaskResultFields) (*swarmpb.TaskResultResponse, error) {
1123 return &swarmpb.TaskResultResponse{
1124 TaskId: taskID,
1125 State: swarmpb.TaskState_RUNNING,
1126 }, nil
1127 }
1128 ss := &SwarmingServer{
1129 UnimplementedGomoteServiceServer: protos.UnimplementedGomoteServiceServer{},
1130 bucket: nil,
1131 buildlets: &remote.SessionPool{},
1132 gceBucketName: "",
Carlos Amedee07e14b42023-09-21 16:03:58 -04001133 sshCertificateAuthority: nil,
1134 rendezvous: rdv,
1135 swarmingClient: msc,
Carlos Amedeef44ba662023-11-06 12:41:15 -05001136 buildersClient: &FakeBuildersClient{},
Carlos Amedee07e14b42023-09-21 16:03:58 -04001137 }
1138 id := "task-123"
1139 errCh := make(chan error, 2)
Carlos Amedeed0f6cc12023-12-15 16:35:40 -05001140 if _, err := ss.startNewSwarmingTask(ctx, id, map[string]string{"cipd_platform": "linux-amd64"}, &configProperties{}, &SwarmOpts{
Carlos Amedee07e14b42023-09-21 16:03:58 -04001141 OnInstanceRegistration: func() {
1142 client := ts.Client()
1143 req, err := http.NewRequest("GET", ts.URL, nil)
1144 req.Header.Set(rendezvous.HeaderID, id)
1145 req.Header.Set(rendezvous.HeaderToken, "test-token")
1146 req.Header.Set(rendezvous.HeaderHostname, "test-hostname")
1147 resp, err := client.Do(req)
1148 if err != nil {
1149 errCh <- fmt.Errorf("client.Do() = %s; want no error", err)
1150 return
1151 }
1152 if b, err := io.ReadAll(resp.Body); err != nil {
1153 errCh <- fmt.Errorf("io.ReadAll(body) = %b, %s, want no error", b, err)
1154 return
1155 }
1156 defer resp.Body.Close()
1157 if resp.StatusCode != 101 {
1158 errCh <- fmt.Errorf("resp.StatusCode %d; want 101", resp.StatusCode)
1159 return
1160 }
1161 },
Carlos Amedee275c7172024-02-20 11:57:35 -05001162 }, false); err == nil || !strings.Contains(err.Error(), "revdial.Dialer closed") {
Carlos Amedee07e14b42023-09-21 16:03:58 -04001163 errCh <- fmt.Errorf("startNewSwarmingTask() = bc, %s; want \"revdial.Dialer closed\" error", err)
1164 }
1165 select {
1166 case err := <-errCh:
1167 t.Fatal(err)
1168 default:
1169 }
1170}
1171
1172func mockSwarmClient() *swarmingtest.Client {
1173 return &swarmingtest.Client{
1174 NewTaskMock: func(context.Context, *swarmpb.NewTaskRequest) (*swarmpb.TaskRequestMetadataResponse, error) {
1175 panic("NewTask not implemented")
1176 },
1177 CountTasksMock: func(context.Context, float64, swarmpb.StateQuery, []string) (*swarmpb.TasksCount, error) {
1178 panic("CountTasks not implemented")
1179 },
1180 ListTasksMock: func(context.Context, int32, float64, swarmpb.StateQuery, []string) ([]*swarmpb.TaskResultResponse, error) {
1181 panic("ListTasks not implemented")
1182 },
1183 CancelTaskMock: func(context.Context, string, bool) (*swarmpb.CancelResponse, error) {
1184 panic("CancelTask not implemented")
1185 },
1186 TaskRequestMock: func(context.Context, string) (*swarmpb.TaskRequestResponse, error) {
1187 panic("TaskRequest not implemented")
1188 },
1189 TaskOutputMock: func(context.Context, string) (*swarmpb.TaskOutputResponse, error) {
1190 panic("TaskOutput not implemented")
1191 },
1192 TaskResultMock: func(context.Context, string, *swarming.TaskResultFields) (*swarmpb.TaskResultResponse, error) {
1193 panic("TaskResult not implemented")
1194 },
1195 CountBotsMock: func(context.Context, []*swarmpb.StringPair) (*swarmpb.BotsCount, error) {
1196 panic("CountBots not implemented")
1197 },
1198 ListBotsMock: func(context.Context, []*swarmpb.StringPair) ([]*swarmpb.BotInfo, error) {
1199 panic("ListBots not implemented")
1200 },
1201 DeleteBotMock: func(context.Context, string) (*swarmpb.DeleteResponse, error) { panic("TerminateBot not implemented") },
1202 TerminateBotMock: func(context.Context, string, string) (*swarmpb.TerminateResponse, error) {
1203 panic("TerminateBot not implemented")
1204 },
1205 ListBotTasksMock: func(context.Context, string, int32, float64, swarmpb.StateQuery) ([]*swarmpb.TaskResultResponse, error) {
1206 panic("ListBotTasks not implemented")
1207 },
1208 FilesFromCASMock: func(context.Context, string, *swarmpb.CASReference) ([]string, error) {
1209 panic("FilesFromCAS not implemented")
1210 },
1211 }
1212}
1213
Carlos Amedee08f08c62023-10-25 17:45:33 -04001214func mockSwarmClientSimple() *swarmingtest.Client {
1215 msc := mockSwarmClient()
1216 msc.NewTaskMock = func(_ context.Context, req *swarmpb.NewTaskRequest) (*swarmpb.TaskRequestMetadataResponse, error) {
1217 taskID := uuid.New().String()
1218 return &swarmpb.TaskRequestMetadataResponse{
1219 TaskId: taskID,
1220 Request: &swarmpb.TaskRequestResponse{
1221 TaskId: taskID,
1222 Name: req.Name,
1223 },
1224 }, nil
1225 }
1226 msc.TaskResultMock = func(_ context.Context, taskID string, _ *swarming.TaskResultFields) (*swarmpb.TaskResultResponse, error) {
1227 return &swarmpb.TaskResultResponse{
1228 TaskId: taskID,
1229 State: swarmpb.TaskState_RUNNING,
1230 }, nil
1231 }
1232 return msc
1233}
1234
Carlos Amedee07e14b42023-09-21 16:03:58 -04001235func mustCreateSwarmingInstance(t *testing.T, client protos.GomoteServiceClient, iap access.IAPFields) string {
1236 req := &protos.CreateInstanceRequest{
1237 BuilderType: "gotip-linux-amd64-boringcrypto",
1238 }
1239 stream, err := client.CreateInstance(access.FakeContextWithOutgoingIAPAuth(context.Background(), iap), req)
1240 if err != nil {
1241 t.Fatalf("client.CreateInstance(ctx, %v) = %v, %s; want no error", req, stream, err)
1242 }
1243 var updateComplete bool
1244 var gomoteID string
1245 for {
1246 update, err := stream.Recv()
1247 if err == io.EOF && !updateComplete {
1248 t.Fatal("stream.Recv = stream, io.EOF; want no EOF")
1249 }
1250 if err == io.EOF {
1251 break
1252 }
1253 if err != nil {
1254 t.Fatalf("stream.Recv() = nil, %s; want no error", err)
1255 }
1256 if update.GetStatus() == protos.CreateInstanceResponse_COMPLETE {
1257 gomoteID = update.Instance.GetGomoteId()
1258 updateComplete = true
1259 }
1260 }
1261 return gomoteID
1262}
Carlos Amedeef44ba662023-11-06 12:41:15 -05001263
1264type FakeBuildersClient struct{}
1265
1266func (fbc *FakeBuildersClient) GetBuilder(ctx context.Context, in *buildbucketpb.GetBuilderRequest, opts ...grpc.CallOption) (*buildbucketpb.BuilderItem, error) {
1267 builders := map[string]bool{
1268 "gotip-linux-amd64-boringcrypto-test_only": true,
1269 }
1270 name := in.GetId().GetBuilder()
1271 _, ok := builders[name]
1272 if !ok {
1273 return nil, errors.New("builder type not found")
1274 }
1275 return &buildbucketpb.BuilderItem{
1276 Id: &buildbucketpb.BuilderID{
1277 Project: "golang",
1278 Bucket: "ci-workers",
1279 Builder: name,
1280 },
1281 Config: &buildbucketpb.BuilderConfig{
1282 Name: name,
1283 Dimensions: []string{
1284 "cipd_platform:linux-amd64",
1285 },
1286 },
1287 }, nil
1288}
1289
1290func (fbc *FakeBuildersClient) ListBuilders(ctx context.Context, in *buildbucketpb.ListBuildersRequest, opts ...grpc.CallOption) (*buildbucketpb.ListBuildersResponse, error) {
Carlos Amedeed0f6cc12023-12-15 16:35:40 -05001291 makeBuilderItem := func(bucket string, builders ...string) []*buildbucketpb.BuilderItem {
1292 out := make([]*buildbucketpb.BuilderItem, 0, len(builders))
1293 for _, b := range builders {
1294 out = append(out, &buildbucketpb.BuilderItem{
1295 Id: &buildbucketpb.BuilderID{
1296 Project: "golang",
1297 Bucket: bucket,
1298 Builder: b,
1299 },
1300 Config: &buildbucketpb.BuilderConfig{
1301 Name: b,
1302 Dimensions: []string{
1303 "cipd_platform:linux-amd64",
1304 },
1305 Properties: `{"mode": 0, "bootstrap_version":"latest"}`,
1306 },
1307 })
1308 }
1309 return out
1310 }
1311 var builders []*buildbucketpb.BuilderItem
1312 switch bucket := in.GetBucket(); bucket {
1313 case "ci-workers":
1314 builders = makeBuilderItem(bucket, "gotip-linux-amd64-boringcrypto", "gotip-linux-amd64-boringcrypto-test_only")
1315 case "ci":
1316 builders = makeBuilderItem(bucket, "gotip-linux-arm", "gotip-linux-amd64")
1317 default:
1318 builders = []*buildbucketpb.BuilderItem{}
1319 }
Carlos Amedeef44ba662023-11-06 12:41:15 -05001320 out := &buildbucketpb.ListBuildersResponse{
Carlos Amedeed0f6cc12023-12-15 16:35:40 -05001321 Builders: builders,
Carlos Amedeef44ba662023-11-06 12:41:15 -05001322 NextPageToken: "",
1323 }
1324 return out, nil
1325}