Skip to content

Commit 8e06b07

Browse files
committed
internal/gomote: implement list swarming bots endpoint
This change adds an endpoint to list the swarming builders which are available for the gomote user to create and instance from. Updates golang/go#61773 For golang/go#61772 Change-Id: Ic2bd0bb5d9b5866399370197fa543ea949addc31 Reviewed-on: https://go-review.googlesource.com/c/build/+/518798 Reviewed-by: Dmitri Shuralyov <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Run-TryBot: Carlos Amedee <[email protected]> TryBot-Result: Gopher Robot <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent a8f30e9 commit 8e06b07

File tree

3 files changed

+74
-4
lines changed

3 files changed

+74
-4
lines changed

cmd/coordinator/coordinator.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import (
6060
"golang.org/x/build/internal/https"
6161
"golang.org/x/build/internal/metrics"
6262
"golang.org/x/build/internal/secret"
63+
"golang.org/x/build/internal/swarmclient"
6364
"golang.org/x/build/kubernetes/gke"
6465
"golang.org/x/build/maintner/maintnerd/apipb"
6566
"golang.org/x/build/repos"
@@ -347,7 +348,7 @@ func main() {
347348
dashV2 := &builddash.Handler{Datastore: gce.GoDSClient(), Maintner: maintnerClient}
348349
gs := &gRPCServer{dashboardURL: "https://build.golang.org"}
349350
setSessionPool(sp)
350-
gomoteServer := gomote.New(sp, sched, sshCA, gomoteBucket, mustStorageClient())
351+
gomoteServer := gomote.New(sp, sched, sshCA, gomoteBucket, mustStorageClient(), mustLUCIConfigClient())
351352
protos.RegisterCoordinatorServer(grpcServer, gs)
352353
gomoteprotos.RegisterGomoteServiceServer(grpcServer, gomoteServer)
353354
mux.HandleFunc("/", grpcHandlerFunc(grpcServer, handleStatus)) // Serve a status page at farmer.golang.org.
@@ -2254,3 +2255,13 @@ func retrieveSSHKeys(ctx context.Context, sc *secret.Client, m string) (publicKe
22542255
}
22552256
return nil, nil, fmt.Errorf("unable to retrieve ssh keys")
22562257
}
2258+
2259+
func mustLUCIConfigClient() *swarmclient.ConfigClient {
2260+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
2261+
defer cancel()
2262+
c, err := swarmclient.NewConfigClient(ctx)
2263+
if err != nil {
2264+
log.Fatalf("unable to create LUCI config client: %s", err)
2265+
}
2266+
return c
2267+
}

internal/gomote/gomote.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"golang.org/x/build/internal/coordinator/schedule"
3131
"golang.org/x/build/internal/envutil"
3232
"golang.org/x/build/internal/gomote/protos"
33+
"golang.org/x/build/internal/swarmclient"
3334
"golang.org/x/build/types"
3435
"golang.org/x/crypto/ssh"
3536
"google.golang.org/grpc/codes"
@@ -59,10 +60,11 @@ type Server struct {
5960
gceBucketName string
6061
scheduler scheduler
6162
sshCertificateAuthority ssh.Signer
63+
luciConfigClient *swarmclient.ConfigClient
6264
}
6365

6466
// New creates a gomote server. If the rawCAPriKey is invalid, the program will exit.
65-
func New(rsp *remote.SessionPool, sched *schedule.Scheduler, rawCAPriKey []byte, gomoteGCSBucket string, storageClient *storage.Client) *Server {
67+
func New(rsp *remote.SessionPool, sched *schedule.Scheduler, rawCAPriKey []byte, gomoteGCSBucket string, storageClient *storage.Client, configClient *swarmclient.ConfigClient) *Server {
6668
signer, err := ssh.ParsePrivateKey(rawCAPriKey)
6769
if err != nil {
6870
log.Fatalf("unable to parse raw certificate authority private key into signer=%s", err)
@@ -73,6 +75,7 @@ func New(rsp *remote.SessionPool, sched *schedule.Scheduler, rawCAPriKey []byte,
7375
gceBucketName: gomoteGCSBucket,
7476
scheduler: sched,
7577
sshCertificateAuthority: signer,
78+
luciConfigClient: configClient,
7679
}
7780
}
7881

@@ -273,6 +276,27 @@ func (s *Server) ListInstances(ctx context.Context, req *protos.ListInstancesReq
273276
return res, nil
274277
}
275278

279+
// ListSwarmingBuilders lists all of the swarming builders which run for gotip. The requester must be authenticated.
280+
func (s Server) ListSwarmingBuilders(ctx context.Context, req *protos.ListSwarmingBuildersRequest) (*protos.ListSwarmingBuildersResponse, error) {
281+
_, err := access.IAPFromContext(ctx)
282+
if err != nil {
283+
log.Printf("ListSwarmingInstances access.IAPFromContext(ctx) = nil, %s", err)
284+
return nil, status.Errorf(codes.Unauthenticated, "request does not contain the required authentication")
285+
}
286+
bots, err := s.luciConfigClient.ListSwarmingBots(ctx)
287+
if err != nil {
288+
log.Printf("luciConfigClient.ListSwarmingBots(ctx) = %s", err)
289+
return nil, status.Errorf(codes.Internal, "unable to query for bots")
290+
}
291+
builders := []string{}
292+
for _, bot := range bots {
293+
if bot.BucketName == "ci" && strings.HasPrefix(bot.Name, "gotip") {
294+
builders = append(builders, bot.Name)
295+
}
296+
}
297+
return &protos.ListSwarmingBuildersResponse{Builders: builders}, nil
298+
}
299+
276300
// DestroyInstance will destroy a gomote instance. It will ensure that the caller is authenticated and is the owner of the instance
277301
// before it destroys the instance.
278302
func (s *Server) DestroyInstance(ctx context.Context, req *protos.DestroyInstanceRequest) (*protos.DestroyInstanceResponse, error) {

internal/gomote/gomote_test.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"io"
1515
"net/http"
1616
"net/http/httptest"
17+
"os"
1718
"testing"
1819
"time"
1920

@@ -23,6 +24,7 @@ import (
2324
"golang.org/x/build/internal/coordinator/remote"
2425
"golang.org/x/build/internal/coordinator/schedule"
2526
"golang.org/x/build/internal/gomote/protos"
27+
"golang.org/x/build/internal/swarmclient"
2628
"golang.org/x/crypto/ssh"
2729
"golang.org/x/net/nettest"
2830
"google.golang.org/grpc"
@@ -33,7 +35,7 @@ import (
3335

3436
const testBucketName = "unit-testing-bucket"
3537

36-
func fakeGomoteServer(t *testing.T, ctx context.Context) protos.GomoteServiceServer {
38+
func fakeGomoteServer(t *testing.T, ctx context.Context, configClient *swarmclient.ConfigClient) protos.GomoteServiceServer {
3739
signer, err := ssh.ParsePrivateKey([]byte(devCertCAPrivate))
3840
if err != nil {
3941
t.Fatalf("unable to parse raw certificate authority private key into signer=%s", err)
@@ -44,17 +46,25 @@ func fakeGomoteServer(t *testing.T, ctx context.Context) protos.GomoteServiceSer
4446
gceBucketName: testBucketName,
4547
scheduler: schedule.NewFake(),
4648
sshCertificateAuthority: signer,
49+
luciConfigClient: configClient,
4750
}
4851
}
4952

5053
func setupGomoteTest(t *testing.T, ctx context.Context) protos.GomoteServiceClient {
54+
contents, err := os.ReadFile("../swarmclient/testdata/bb-sample.cfg")
55+
if err != nil {
56+
t.Fatalf("unable to read test buildbucket config: %s", err)
57+
}
58+
configClient := swarmclient.NewMemoryConfigClient(ctx, []*swarmclient.ConfigEntry{
59+
&swarmclient.ConfigEntry{"cr-buildbucket.cfg", contents},
60+
})
5161
lis, err := nettest.NewLocalListener("tcp")
5262
if err != nil {
5363
t.Fatalf("unable to create net listener: %s", err)
5464
}
5565
sopts := access.FakeIAPAuthInterceptorOptions()
5666
s := grpc.NewServer(sopts...)
57-
protos.RegisterGomoteServiceServer(s, fakeGomoteServer(t, ctx))
67+
protos.RegisterGomoteServiceServer(s, fakeGomoteServer(t, ctx, configClient))
5868
go s.Serve(lis)
5969

6070
// create GRPC client
@@ -415,6 +425,31 @@ func TestListInstance(t *testing.T) {
415425
}
416426
}
417427

428+
func TestListSwarmingBuilders(t *testing.T) {
429+
client := setupGomoteTest(t, context.Background())
430+
ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
431+
response, err := client.ListSwarmingBuilders(ctx, &protos.ListSwarmingBuildersRequest{})
432+
if err != nil {
433+
t.Fatalf("client.ListSwarmingBuilders = nil, %s; want no error", err)
434+
}
435+
got := response.GetBuilders()
436+
if diff := cmp.Diff([]string{"gotip-linux-amd64-boringcrypto"}, got); diff != "" {
437+
t.Errorf("ListBuilders() mismatch (-want, +got):\n%s", diff)
438+
}
439+
}
440+
441+
func TestListSwarmingBuildersError(t *testing.T) {
442+
client := setupGomoteTest(t, context.Background())
443+
req := &protos.ListSwarmingBuildersRequest{}
444+
got, err := client.ListSwarmingBuilders(context.Background(), req)
445+
if err != nil && status.Code(err) != codes.Unauthenticated {
446+
t.Fatalf("unexpected error: %s; want %s", err, codes.Unauthenticated)
447+
}
448+
if err == nil {
449+
t.Fatalf("client.ListSwarmingBuilder(ctx, %v) = %v, nil; want error", req, got)
450+
}
451+
}
452+
418453
func TestDestroyInstance(t *testing.T) {
419454
ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
420455
client := setupGomoteTest(t, context.Background())

0 commit comments

Comments
 (0)