1*61c4878aSAndroid Build Coastguard Worker// Copyright 2019 The Pigweed Authors 2*61c4878aSAndroid Build Coastguard Worker// 3*61c4878aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); you may not 4*61c4878aSAndroid Build Coastguard Worker// use this file except in compliance with the License. You may obtain a copy of 5*61c4878aSAndroid Build Coastguard Worker// the License at 6*61c4878aSAndroid Build Coastguard Worker// 7*61c4878aSAndroid Build Coastguard Worker// https://www.apache.org/licenses/LICENSE-2.0 8*61c4878aSAndroid Build Coastguard Worker// 9*61c4878aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*61c4878aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11*61c4878aSAndroid Build Coastguard Worker// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12*61c4878aSAndroid Build Coastguard Worker// License for the specific language governing permissions and limitations under 13*61c4878aSAndroid Build Coastguard Worker// the License. 14*61c4878aSAndroid Build Coastguard Worker 15*61c4878aSAndroid Build Coastguard Worker// Package pw_target_runner implements a target runner gRPC server which queues 16*61c4878aSAndroid Build Coastguard Worker// and distributes executables among a group of worker routines. 17*61c4878aSAndroid Build Coastguard Workerpackage pw_target_runner 18*61c4878aSAndroid Build Coastguard Worker 19*61c4878aSAndroid Build Coastguard Workerimport ( 20*61c4878aSAndroid Build Coastguard Worker "context" 21*61c4878aSAndroid Build Coastguard Worker "errors" 22*61c4878aSAndroid Build Coastguard Worker "fmt" 23*61c4878aSAndroid Build Coastguard Worker "log" 24*61c4878aSAndroid Build Coastguard Worker "net" 25*61c4878aSAndroid Build Coastguard Worker "os" 26*61c4878aSAndroid Build Coastguard Worker "time" 27*61c4878aSAndroid Build Coastguard Worker 28*61c4878aSAndroid Build Coastguard Worker "google.golang.org/grpc" 29*61c4878aSAndroid Build Coastguard Worker "google.golang.org/grpc/codes" 30*61c4878aSAndroid Build Coastguard Worker "google.golang.org/grpc/status" 31*61c4878aSAndroid Build Coastguard Worker 32*61c4878aSAndroid Build Coastguard Worker pb "pigweed/proto/pw_target_runner/target_runner_pb" 33*61c4878aSAndroid Build Coastguard Worker) 34*61c4878aSAndroid Build Coastguard Worker 35*61c4878aSAndroid Build Coastguard Workervar ( 36*61c4878aSAndroid Build Coastguard Worker errServerNotBound = errors.New("Server not bound to a port") 37*61c4878aSAndroid Build Coastguard Worker errServerNotRunning = errors.New("Server is not running") 38*61c4878aSAndroid Build Coastguard Worker) 39*61c4878aSAndroid Build Coastguard Worker 40*61c4878aSAndroid Build Coastguard Worker// Server is a gRPC server that runs a TargetRunner service. 41*61c4878aSAndroid Build Coastguard Workertype Server struct { 42*61c4878aSAndroid Build Coastguard Worker grpcServer *grpc.Server 43*61c4878aSAndroid Build Coastguard Worker listener net.Listener 44*61c4878aSAndroid Build Coastguard Worker tasksPassed uint32 45*61c4878aSAndroid Build Coastguard Worker tasksFailed uint32 46*61c4878aSAndroid Build Coastguard Worker startTime time.Time 47*61c4878aSAndroid Build Coastguard Worker active bool 48*61c4878aSAndroid Build Coastguard Worker workerPool *WorkerPool 49*61c4878aSAndroid Build Coastguard Worker} 50*61c4878aSAndroid Build Coastguard Worker 51*61c4878aSAndroid Build Coastguard Worker// NewServer creates a gRPC server with a registered TargetRunner service. 52*61c4878aSAndroid Build Coastguard Workerfunc NewServer() *Server { 53*61c4878aSAndroid Build Coastguard Worker s := &Server{ 54*61c4878aSAndroid Build Coastguard Worker grpcServer: grpc.NewServer(grpc.MaxRecvMsgSize(20 * 1024 * 1024)), 55*61c4878aSAndroid Build Coastguard Worker workerPool: newWorkerPool("ServerWorkerPool"), 56*61c4878aSAndroid Build Coastguard Worker } 57*61c4878aSAndroid Build Coastguard Worker 58*61c4878aSAndroid Build Coastguard Worker pb.RegisterTargetRunnerServer(s.grpcServer, &pwTargetRunnerService{s}) 59*61c4878aSAndroid Build Coastguard Worker 60*61c4878aSAndroid Build Coastguard Worker return s 61*61c4878aSAndroid Build Coastguard Worker} 62*61c4878aSAndroid Build Coastguard Worker 63*61c4878aSAndroid Build Coastguard Worker// Bind starts a TCP listener on a specified port. 64*61c4878aSAndroid Build Coastguard Workerfunc (s *Server) Bind(port int) error { 65*61c4878aSAndroid Build Coastguard Worker lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) 66*61c4878aSAndroid Build Coastguard Worker if err != nil { 67*61c4878aSAndroid Build Coastguard Worker return err 68*61c4878aSAndroid Build Coastguard Worker } 69*61c4878aSAndroid Build Coastguard Worker s.listener = lis 70*61c4878aSAndroid Build Coastguard Worker return nil 71*61c4878aSAndroid Build Coastguard Worker} 72*61c4878aSAndroid Build Coastguard Worker 73*61c4878aSAndroid Build Coastguard Worker// RegisterWorker adds a worker to the server's worker pool. 74*61c4878aSAndroid Build Coastguard Workerfunc (s *Server) RegisterWorker(worker DeviceRunner) { 75*61c4878aSAndroid Build Coastguard Worker s.workerPool.RegisterWorker(worker) 76*61c4878aSAndroid Build Coastguard Worker} 77*61c4878aSAndroid Build Coastguard Worker 78*61c4878aSAndroid Build Coastguard Worker// RunBinary runs an executable through a worker in the server, returning 79*61c4878aSAndroid Build Coastguard Worker// the worker's response. The function blocks until the executable has been 80*61c4878aSAndroid Build Coastguard Worker// processed. 81*61c4878aSAndroid Build Coastguard Workerfunc (s *Server) RunBinary(path string) (*RunResponse, error) { 82*61c4878aSAndroid Build Coastguard Worker if !s.active { 83*61c4878aSAndroid Build Coastguard Worker return nil, errServerNotRunning 84*61c4878aSAndroid Build Coastguard Worker } 85*61c4878aSAndroid Build Coastguard Worker 86*61c4878aSAndroid Build Coastguard Worker resChan := make(chan *RunResponse, 1) 87*61c4878aSAndroid Build Coastguard Worker defer close(resChan) 88*61c4878aSAndroid Build Coastguard Worker 89*61c4878aSAndroid Build Coastguard Worker s.workerPool.QueueExecutable(&RunRequest{ 90*61c4878aSAndroid Build Coastguard Worker Path: path, 91*61c4878aSAndroid Build Coastguard Worker ResponseChannel: resChan, 92*61c4878aSAndroid Build Coastguard Worker }) 93*61c4878aSAndroid Build Coastguard Worker 94*61c4878aSAndroid Build Coastguard Worker res := <-resChan 95*61c4878aSAndroid Build Coastguard Worker 96*61c4878aSAndroid Build Coastguard Worker if res.Err != nil { 97*61c4878aSAndroid Build Coastguard Worker return nil, res.Err 98*61c4878aSAndroid Build Coastguard Worker } 99*61c4878aSAndroid Build Coastguard Worker 100*61c4878aSAndroid Build Coastguard Worker if res.Status == pb.RunStatus_SUCCESS { 101*61c4878aSAndroid Build Coastguard Worker s.tasksPassed++ 102*61c4878aSAndroid Build Coastguard Worker } else { 103*61c4878aSAndroid Build Coastguard Worker s.tasksFailed++ 104*61c4878aSAndroid Build Coastguard Worker } 105*61c4878aSAndroid Build Coastguard Worker 106*61c4878aSAndroid Build Coastguard Worker return res, nil 107*61c4878aSAndroid Build Coastguard Worker} 108*61c4878aSAndroid Build Coastguard Worker 109*61c4878aSAndroid Build Coastguard Worker// Serve starts the gRPC server on its configured port. Bind must have been 110*61c4878aSAndroid Build Coastguard Worker// called before this; an error is returned if it is not. This function blocks 111*61c4878aSAndroid Build Coastguard Worker// until the server is terminated. 112*61c4878aSAndroid Build Coastguard Workerfunc (s *Server) Serve() error { 113*61c4878aSAndroid Build Coastguard Worker if s.listener == nil { 114*61c4878aSAndroid Build Coastguard Worker return errServerNotBound 115*61c4878aSAndroid Build Coastguard Worker } 116*61c4878aSAndroid Build Coastguard Worker 117*61c4878aSAndroid Build Coastguard Worker log.Printf("Starting gRPC server on %v\n", s.listener.Addr()) 118*61c4878aSAndroid Build Coastguard Worker 119*61c4878aSAndroid Build Coastguard Worker s.startTime = time.Now() 120*61c4878aSAndroid Build Coastguard Worker s.active = true 121*61c4878aSAndroid Build Coastguard Worker s.workerPool.Start() 122*61c4878aSAndroid Build Coastguard Worker 123*61c4878aSAndroid Build Coastguard Worker return s.grpcServer.Serve(s.listener) 124*61c4878aSAndroid Build Coastguard Worker} 125*61c4878aSAndroid Build Coastguard Worker 126*61c4878aSAndroid Build Coastguard Worker// pwTargetRunnerService implements the pw.target_runner.TargetRunner gRPC 127*61c4878aSAndroid Build Coastguard Worker// service. 128*61c4878aSAndroid Build Coastguard Workertype pwTargetRunnerService struct { 129*61c4878aSAndroid Build Coastguard Worker server *Server 130*61c4878aSAndroid Build Coastguard Worker} 131*61c4878aSAndroid Build Coastguard Worker 132*61c4878aSAndroid Build Coastguard Worker// RunBinary runs a single executable on-device and returns its result. 133*61c4878aSAndroid Build Coastguard Workerfunc (s *pwTargetRunnerService) RunBinary( 134*61c4878aSAndroid Build Coastguard Worker ctx context.Context, 135*61c4878aSAndroid Build Coastguard Worker desc *pb.RunBinaryRequest, 136*61c4878aSAndroid Build Coastguard Worker) (*pb.RunBinaryResponse, error) { 137*61c4878aSAndroid Build Coastguard Worker var path string 138*61c4878aSAndroid Build Coastguard Worker 139*61c4878aSAndroid Build Coastguard Worker switch bin := desc.Binary.(type) { 140*61c4878aSAndroid Build Coastguard Worker case *pb.RunBinaryRequest_FilePath: 141*61c4878aSAndroid Build Coastguard Worker path = bin.FilePath 142*61c4878aSAndroid Build Coastguard Worker break 143*61c4878aSAndroid Build Coastguard Worker case *pb.RunBinaryRequest_TestBinary: 144*61c4878aSAndroid Build Coastguard Worker f, err := os.CreateTemp("", "pw_target_runner_") 145*61c4878aSAndroid Build Coastguard Worker if err != nil { 146*61c4878aSAndroid Build Coastguard Worker return nil, status.Errorf(codes.Internal, "Internal server error: %v", err) 147*61c4878aSAndroid Build Coastguard Worker } 148*61c4878aSAndroid Build Coastguard Worker 149*61c4878aSAndroid Build Coastguard Worker defer os.Remove(f.Name()) 150*61c4878aSAndroid Build Coastguard Worker 151*61c4878aSAndroid Build Coastguard Worker _, err = f.Write(bin.TestBinary) 152*61c4878aSAndroid Build Coastguard Worker if err != nil { 153*61c4878aSAndroid Build Coastguard Worker return nil, status.Errorf(codes.Internal, "Internal server error: %v", err) 154*61c4878aSAndroid Build Coastguard Worker } 155*61c4878aSAndroid Build Coastguard Worker 156*61c4878aSAndroid Build Coastguard Worker err = os.Chmod(f.Name(), 0755) 157*61c4878aSAndroid Build Coastguard Worker if err != nil { 158*61c4878aSAndroid Build Coastguard Worker return nil, status.Errorf(codes.Internal, "Internal server error: %v", err) 159*61c4878aSAndroid Build Coastguard Worker } 160*61c4878aSAndroid Build Coastguard Worker 161*61c4878aSAndroid Build Coastguard Worker path = f.Name() 162*61c4878aSAndroid Build Coastguard Worker break 163*61c4878aSAndroid Build Coastguard Worker default: 164*61c4878aSAndroid Build Coastguard Worker return nil, status.Error(codes.InvalidArgument, "No test path or binary provided") 165*61c4878aSAndroid Build Coastguard Worker } 166*61c4878aSAndroid Build Coastguard Worker 167*61c4878aSAndroid Build Coastguard Worker runRes, err := s.server.RunBinary(path) 168*61c4878aSAndroid Build Coastguard Worker if err != nil { 169*61c4878aSAndroid Build Coastguard Worker return nil, status.Errorf(codes.Internal, "Internal server error: %v", err) 170*61c4878aSAndroid Build Coastguard Worker } 171*61c4878aSAndroid Build Coastguard Worker 172*61c4878aSAndroid Build Coastguard Worker res := &pb.RunBinaryResponse{ 173*61c4878aSAndroid Build Coastguard Worker Result: runRes.Status, 174*61c4878aSAndroid Build Coastguard Worker QueueTimeNs: uint64(runRes.QueueTime), 175*61c4878aSAndroid Build Coastguard Worker RunTimeNs: uint64(runRes.RunTime), 176*61c4878aSAndroid Build Coastguard Worker Output: runRes.Output, 177*61c4878aSAndroid Build Coastguard Worker } 178*61c4878aSAndroid Build Coastguard Worker return res, nil 179*61c4878aSAndroid Build Coastguard Worker} 180*61c4878aSAndroid Build Coastguard Worker 181*61c4878aSAndroid Build Coastguard Worker// Status returns information about the server. 182*61c4878aSAndroid Build Coastguard Workerfunc (s *pwTargetRunnerService) Status( 183*61c4878aSAndroid Build Coastguard Worker ctx context.Context, 184*61c4878aSAndroid Build Coastguard Worker _ *pb.Empty, 185*61c4878aSAndroid Build Coastguard Worker) (*pb.ServerStatus, error) { 186*61c4878aSAndroid Build Coastguard Worker resp := &pb.ServerStatus{ 187*61c4878aSAndroid Build Coastguard Worker UptimeNs: uint64(time.Since(s.server.startTime)), 188*61c4878aSAndroid Build Coastguard Worker TasksPassed: s.server.tasksPassed, 189*61c4878aSAndroid Build Coastguard Worker TasksFailed: s.server.tasksFailed, 190*61c4878aSAndroid Build Coastguard Worker } 191*61c4878aSAndroid Build Coastguard Worker 192*61c4878aSAndroid Build Coastguard Worker return resp, nil 193*61c4878aSAndroid Build Coastguard Worker} 194