xref: /aosp_15_r20/external/pigweed/pw_target_runner/go/server.go (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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