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 }