github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/internal/runner/service.go (about)

     1  // Copyright 2021 The ChromiumOS Authors
     2  // Use of this source code is governed by a BSD-style license that can be
     3  // found in the LICENSE file.
     4  
     5  package runner
     6  
     7  import (
     8  	"context"
     9  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"sort"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/golang/protobuf/ptypes"
    19  	"golang.org/x/sys/unix"
    20  
    21  	"go.chromium.org/tast/core/errors"
    22  	"go.chromium.org/tast/core/internal/devserver"
    23  	"go.chromium.org/tast/core/internal/logging"
    24  	"go.chromium.org/tast/core/internal/protocol"
    25  	"go.chromium.org/tast/core/internal/rpc"
    26  )
    27  
    28  type testServer struct {
    29  	protocol.UnimplementedTestServiceServer
    30  	scfg         *StaticConfig
    31  	runnerParams *protocol.RunnerInitParams
    32  	bundleParams *protocol.BundleInitParams
    33  }
    34  
    35  // ErrFailedToReadFile is used for indicating a file failed to open at the beginning.
    36  var ErrFailedToReadFile = errors.New("failed to read file at the beginning")
    37  
    38  func newTestServer(scfg *StaticConfig, runnerParams *protocol.RunnerInitParams, bundleParams *protocol.BundleInitParams) *testServer {
    39  	exec.Command("logger", "local_test_runner: New test server is up for serving requests").Run()
    40  	return &testServer{
    41  		scfg:         scfg,
    42  		runnerParams: runnerParams,
    43  		bundleParams: bundleParams,
    44  	}
    45  }
    46  
    47  func (s *testServer) GetDUTInfo(ctx context.Context, req *protocol.GetDUTInfoRequest) (*protocol.GetDUTInfoResponse, error) {
    48  	// Logging added for b/213616631.
    49  	logging.Debug(ctx, "Serving GetDUTInfo Request")
    50  	exec.Command("logger", "local_test_runner: Serving GetDUTInfo Request").Run()
    51  	if s.scfg.GetDUTInfo == nil {
    52  		return &protocol.GetDUTInfoResponse{}, nil
    53  	}
    54  	return s.scfg.GetDUTInfo(ctx, req)
    55  }
    56  
    57  func (s *testServer) GetSysInfoState(ctx context.Context, req *protocol.GetSysInfoStateRequest) (*protocol.GetSysInfoStateResponse, error) {
    58  	// Logging added for b/213616631.
    59  	logging.Debug(ctx, "Serving GetSysInfoState Request")
    60  	exec.Command("logger", "local_test_runner: Serving GetSysInfoState Request").Run()
    61  	if s.scfg.GetSysInfoState == nil {
    62  		return &protocol.GetSysInfoStateResponse{}, nil
    63  	}
    64  	return s.scfg.GetSysInfoState(ctx, req)
    65  }
    66  
    67  func (s *testServer) CollectSysInfo(ctx context.Context, req *protocol.CollectSysInfoRequest) (*protocol.CollectSysInfoResponse, error) {
    68  	// Logging added for b/213616631.
    69  	logging.Debug(ctx, "Serving CollectSysInfo Request")
    70  	exec.Command("logger", "local_test_runner: Serving CollectSysInfo Request").Run()
    71  	if s.scfg.CollectSysInfo == nil {
    72  		return &protocol.CollectSysInfoResponse{}, nil
    73  	}
    74  	return s.scfg.CollectSysInfo(ctx, req)
    75  }
    76  
    77  func (s *testServer) DownloadPrivateBundles(ctx context.Context, req *protocol.DownloadPrivateBundlesRequest) (*protocol.DownloadPrivateBundlesResponse, error) {
    78  	// Logging added for b/213616631.
    79  	logging.Debug(ctx, "Serving DownloadPrivateBundles Request")
    80  	exec.Command("logger", "local_test_runner: Serving DownloadPrivateBundles Request").Run()
    81  
    82  	if s.scfg.PrivateBundlesStampPath == "" {
    83  		return nil, errors.New("this test runner is not configured for private bundles")
    84  	}
    85  
    86  	if req.GetBuildArtifactUrl() == "" {
    87  		return nil, errors.New("failed to determine the build artifacts URL (non-official image?)")
    88  	}
    89  
    90  	// If the stamp file exists, private bundles have been already downloaded.
    91  	if _, err := os.Stat(s.scfg.PrivateBundlesStampPath); err == nil {
    92  		return &protocol.DownloadPrivateBundlesResponse{}, nil
    93  	}
    94  
    95  	// Download the archive via devserver.
    96  	archiveURL := req.GetBuildArtifactUrl() + "tast_bundles.tar.bz2"
    97  	logging.Infof(ctx, "Downloading private bundles from %s", archiveURL)
    98  	cl, err := devserver.NewClient(
    99  		ctx, req.GetServiceConfig().GetDevservers(),
   100  		req.GetServiceConfig().GetTlwServer(), req.GetServiceConfig().GetTlwSelfName(),
   101  		req.GetServiceConfig().GetDutServer(),
   102  		req.GetServiceConfig().GetSwarmingTaskID(),
   103  		req.GetServiceConfig().GetBuildBucketID(),
   104  	)
   105  	if err != nil {
   106  		return nil, errors.Wrapf(err, "failed to create new client [devservers=%v, TLWServer=%s]",
   107  			req.GetServiceConfig().GetDevservers(), req.GetServiceConfig().GetTlwServer())
   108  	}
   109  	defer cl.TearDown()
   110  
   111  	r, err := cl.Open(ctx, archiveURL)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	defer r.Close()
   116  
   117  	tf, err := ioutil.TempFile("", "tast_bundles.")
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	defer os.Remove(tf.Name())
   122  
   123  	_, err = io.Copy(tf, r)
   124  
   125  	if cerr := tf.Close(); err == nil {
   126  		err = cerr
   127  	}
   128  
   129  	if err == nil {
   130  		// Extract the archive, and touch the stamp file.
   131  		cmd := exec.Command("tar", "xf", tf.Name())
   132  		cmd.Dir = "/usr/local"
   133  		if err := cmd.Run(); err != nil {
   134  			return nil, errors.Errorf("failed to extract %s: %v", strings.Join(cmd.Args, " "), err)
   135  		}
   136  		logging.Info(ctx, "Download finished successfully")
   137  	} else if os.IsNotExist(err) {
   138  		logging.Info(ctx, "Private bundles not found")
   139  	} else {
   140  		return nil, errors.Errorf("failed to download %s: %v", archiveURL, err)
   141  	}
   142  
   143  	if err := ioutil.WriteFile(s.scfg.PrivateBundlesStampPath, nil, 0644); err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	return &protocol.DownloadPrivateBundlesResponse{}, nil
   148  }
   149  
   150  func (s *testServer) ListEntities(ctx context.Context, req *protocol.ListEntitiesRequest) (*protocol.ListEntitiesResponse, error) {
   151  	var entities []*protocol.ResolvedEntity
   152  	// Logging added for b/213616631 to see ListEntities progress on the DUT.
   153  	logging.Debug(ctx, "Serving ListEntities Request")
   154  	exec.Command("logger", "local_test_runner: Serving ListEntities Request").Run()
   155  	// ListEntities should not set runtime global information during handshake.
   156  	// TODO(b/187793617): Always pass s.bundleParams to bundles once we fully migrate to gRPC-based protocol.
   157  	// This workaround is currently needed because BundleInitParams is unavailable when this method is called internally for handling JSON-based protocol methods.
   158  	if err := s.forEachBundle(ctx, nil, func(ctx context.Context, ts protocol.TestServiceClient) error {
   159  		res, err := ts.ListEntities(ctx, req) // pass through req
   160  		if err != nil {
   161  			return err
   162  		}
   163  		entities = append(entities, res.GetEntities()...)
   164  		return nil
   165  	}); err != nil {
   166  		return nil, err
   167  	}
   168  	// Logging added for b/213616631 to see ListEntities progress on the DUT.
   169  	logging.Debug(ctx, "Finish serving ListEntities Request")
   170  	exec.Command("logger", "local_test_runner: Finish serving ListEntities Request").Run()
   171  	return &protocol.ListEntitiesResponse{Entities: entities}, nil
   172  }
   173  
   174  func (s *testServer) GlobalRuntimeVars(ctx context.Context, req *protocol.GlobalRuntimeVarsRequest) (*protocol.GlobalRuntimeVarsResponse, error) {
   175  	var vars []*protocol.GlobalRuntimeVar
   176  	logging.Debug(ctx, "Serving GlobalRuntimeVars Request")
   177  	exec.Command("logger", "local_test_runner: Serving GlobalRuntimeVars Request").Run()
   178  	// GlobalRuntimeVars should not set runtime global information during handshake.
   179  
   180  	if err := s.forEachBundle(ctx, nil, func(ctx context.Context, ts protocol.TestServiceClient) error {
   181  		res, err := ts.GlobalRuntimeVars(ctx, req) // pass through req
   182  		if err != nil {
   183  			return err
   184  		}
   185  		vars = append(vars, res.GetVars()...)
   186  		return nil
   187  	}); err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	logging.Debug(ctx, "Finish serving GlobalRuntimeVars Request")
   192  	exec.Command("logger", "local_test_runner: Finish serving GlobalRuntimeVars Request").Run()
   193  	return &protocol.GlobalRuntimeVarsResponse{Vars: vars}, nil
   194  }
   195  
   196  func (s *testServer) RunTests(srv protocol.TestService_RunTestsServer) error {
   197  	// Logging added for b/213616631.
   198  	exec.Command("logger", "local_test_runner: Serving RunTests Request").Run()
   199  	ctx := srv.Context()
   200  	logger := logging.NewSinkLogger(logging.LevelInfo, false, logging.NewFuncSink(func(msg string) {
   201  		srv.Send(&protocol.RunTestsResponse{
   202  			Type: &protocol.RunTestsResponse_RunLog{
   203  				RunLog: &protocol.RunLogEvent{
   204  					Time: ptypes.TimestampNow(),
   205  					Text: msg,
   206  				},
   207  			},
   208  		})
   209  	}))
   210  	// Logs from RunTests should not be routed to protocol.Logging service.
   211  	ctx = logging.AttachLoggerNoPropagation(ctx, logger)
   212  
   213  	initReq, err := srv.Recv()
   214  	if err != nil {
   215  		return err
   216  	}
   217  	if _, ok := initReq.GetType().(*protocol.RunTestsRequest_RunTestsInit); !ok {
   218  		return errors.Errorf("RunTests: unexpected initial request message: got %T, want %T", initReq.GetType(), &protocol.RunTestsRequest_RunTestsInit{})
   219  	}
   220  
   221  	if s.scfg.KillStaleRunners {
   222  		killStaleRunners(ctx, unix.SIGTERM)
   223  	}
   224  
   225  	return s.forEachBundle(ctx, s.bundleParams, func(ctx context.Context, ts protocol.TestServiceClient) error {
   226  		st, err := ts.RunTests(ctx)
   227  		if err != nil {
   228  			return err
   229  		}
   230  		defer st.CloseSend()
   231  
   232  		// Duplicate the initial request.
   233  		if err := st.Send(initReq); err != nil {
   234  			return err
   235  		}
   236  
   237  		// Relay responses.
   238  		for {
   239  			res, err := st.Recv()
   240  			if err == io.EOF {
   241  				return nil
   242  			}
   243  			if err != nil {
   244  				return err
   245  			}
   246  			if err := srv.Send(res); err != nil {
   247  				return err
   248  			}
   249  		}
   250  	})
   251  }
   252  
   253  func (s *testServer) forEachBundle(ctx context.Context, bundleParams *protocol.BundleInitParams, f func(ctx context.Context, ts protocol.TestServiceClient) error) error {
   254  	bundlePaths, err := filepath.Glob(s.runnerParams.GetBundleGlob())
   255  	if err != nil {
   256  		return err
   257  	}
   258  	// Sort bundles for determinism.
   259  	sort.Strings(bundlePaths)
   260  
   261  	for _, bundlePath := range bundlePaths {
   262  		if err := func() error {
   263  			// Logging added for b/213616631 to see ListEntities progress on the DUT.
   264  			logging.Debugf(ctx, "Sending request to bundle %s", bundlePath)
   265  			cl, err := rpc.DialExec(ctx, bundlePath, true,
   266  				&protocol.HandshakeRequest{BundleInitParams: bundleParams})
   267  			if err != nil {
   268  				return err
   269  			}
   270  			defer cl.Close()
   271  
   272  			return f(ctx, protocol.NewTestServiceClient(cl.Conn()))
   273  		}(); err != nil {
   274  			return errors.Wrap(err, filepath.Base(bundlePath))
   275  		}
   276  	}
   277  	return nil
   278  }
   279  
   280  func (s *testServer) StreamFile(req *protocol.StreamFileRequest, srv protocol.TestService_StreamFileServer) error {
   281  	// Logging added for b/213616631.
   282  	exec.Command("logger", "local_test_runner: Serving StreamFile Request").Run()
   283  	path := req.Name
   284  	ctx := srv.Context()
   285  
   286  	fs, err := os.Stat(path)
   287  	if err != nil {
   288  		return errors.Wrapf(ErrFailedToReadFile, "file %v does not exist on the DUT: %v", path, err)
   289  	}
   290  	offset := req.GetOffset()
   291  	// If offset is less than 0, start streaming from the bottom of the file.
   292  	if req.Offset < 0 {
   293  		offset = fs.Size()
   294  	}
   295  
   296  	const maxRetries = 10
   297  	const interval = time.Second
   298  	failures := 0
   299  
   300  	for {
   301  		select {
   302  		case <-ctx.Done():
   303  			return nil
   304  		case <-time.After(interval):
   305  		}
   306  		fs, err := os.Stat(path)
   307  		if err != nil {
   308  			if failures < maxRetries {
   309  				failures = failures + 1
   310  				continue
   311  			}
   312  			return errors.Wrapf(err, "failed to get size of file %v", path)
   313  		}
   314  		failures = 0
   315  		if fs.Size() == offset {
   316  			// Nothing new was added to the file.
   317  			continue
   318  		}
   319  		if fs.Size() < offset {
   320  			// The file is smaller now which may be due to file rotation.
   321  			// Read the entire file instead.
   322  			offset = 0
   323  		}
   324  		data, n, err := readFileWithOffset(path, offset)
   325  		if err != nil && !errors.Is(err, io.EOF) {
   326  			return errors.Wrapf(err, "failed to read file %v", path)
   327  		}
   328  		if n == 0 {
   329  			continue
   330  		}
   331  		nextOffset := offset + n
   332  		rspn := &protocol.StreamFileResponse{Data: data, Offset: nextOffset}
   333  		if err := srv.Send(rspn); err != nil {
   334  			return err
   335  		}
   336  		offset = nextOffset
   337  	}
   338  }
   339  
   340  func readFileWithOffset(path string, offset int64) ([]byte, int64, error) {
   341  	const megabyte = 1 << 20
   342  	buf := make([]byte, megabyte*2)
   343  	f, err := os.Open(path)
   344  	if err != nil {
   345  		return nil, 0, err
   346  	}
   347  	defer f.Close()
   348  	if _, err := f.Seek(offset, io.SeekStart); err != nil {
   349  		return nil, 0, err
   350  	}
   351  	n, err := f.Read(buf)
   352  	if err != nil {
   353  		return nil, 0, err
   354  	}
   355  	return buf[0:n], int64(n), nil
   356  }