blob: 47119f18320172e5572e4f769bb54636a3be607a [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package main implements the tast_rtd executable, used to invoke tast in RTD.
package main
import (
"bufio"
"fmt"
"log"
"net"
"os"
"os/exec"
"path/filepath"
"strconv"
"sync"
"time"
"github.com/golang/protobuf/proto"
rtd "go.chromium.org/chromiumos/config/go/api/test/rtd/v1"
"chromiumos/tast/errors"
)
// unmarshalInvocation unmarshals an invocation request and returns a pointer to rtd.Invocation.
func unmarshalInvocation(req []byte) (*rtd.Invocation, error) {
inv := &rtd.Invocation{}
if err := proto.Unmarshal(req, inv); err != nil {
return nil, errors.Wrap(err, "fail to unmarshal invocation data")
}
return inv, nil
}
// Command name and flag names.
const (
runSubcommand = "run"
verboseFlag = "-verbose"
logTimeFlag = "-logtime"
sshRetriesFlag = "-sshretries"
downloadDataFlag = "-downloaddata"
buildFlag = "-build"
remoteBundlerDirFlag = "-remotebundledir"
remoteDataDirFlag = "-remotedatadir"
remoteRunnerFlag = "-remoterunner"
defaultVarsDirFlag = "-defaultvarsdir"
downloadPrivateBundlesFlag = "-downloadprivatebundles"
devServerFlag = "-devservers"
resultsDirFlag = "-resultsdir"
tlwServerFlag = "-tlwserver"
waitUntilReadyFlag = "-waituntilready"
timeOutFlag = "-timeout"
keyfile = "-keyfile"
)
// runArgs stores arguments to invoke Tast
type runArgs struct {
target string // The url for the target machine.
patterns []string // The names of test to be run.
tastFlags map[string]string // The flags for tast.
runFlags map[string]string // The flags for tast run command.
}
// newArgs created an argument structure for invoking tast
func newArgs(inv *rtd.Invocation, rtdPath string) *runArgs {
args := runArgs{
target: inv.Duts[0].TlsDutName, // TODO: Support multiple DUTs for sharding.
tastFlags: map[string]string{
verboseFlag: "true",
logTimeFlag: "false",
},
runFlags: map[string]string{
sshRetriesFlag: "2",
downloadDataFlag: "batch",
buildFlag: "false",
downloadPrivateBundlesFlag: "true",
timeOutFlag: "3000",
},
}
// If it is running inside a RTD, change default path for tast related directories.
if rtdPath != "" {
rtdTastPath := filepath.Join(rtdPath, "tast")
args.runFlags[remoteBundlerDirFlag] = filepath.Join(rtdTastPath, "bundles", "remote")
args.runFlags[remoteDataDirFlag] = filepath.Join(rtdTastPath, "bundles", "data")
args.runFlags[remoteRunnerFlag] = filepath.Join(rtdTastPath, "bin", "remote_test_runner")
args.runFlags[defaultVarsDirFlag] = filepath.Join(rtdTastPath, "vars")
args.runFlags[keyfile] = filepath.Join(rtdTastPath, "ssh_keys", "testing_rsa")
}
if inv.TestLabServicesConfig != nil && inv.TestLabServicesConfig.TlwAddress != "" {
tlwServer := inv.TestLabServicesConfig.TlwAddress
if inv.TestLabServicesConfig.TlwPort != 0 {
tlwServer = net.JoinHostPort(tlwServer, strconv.Itoa(int(inv.TestLabServicesConfig.TlwPort)))
}
args.runFlags[tlwServerFlag] = tlwServer
}
resultsDir := args.runFlags[resultsDirFlag]
for _, r := range inv.Requests {
args.patterns = append(args.patterns, r.Test)
if resultsDir == "" {
resultsDir = r.Environment.WorkDir
}
}
if resultsDir == "" {
t := time.Now()
resultsDir = filepath.Join("/tmp/tast/results", t.Format("20060102-150405"))
}
args.runFlags[resultsDirFlag] = resultsDir
return &args
}
// genArgList generates argument list for invoking tast
func genArgList(args *runArgs) (argList []string) {
for flag, value := range args.tastFlags {
argList = append(argList, fmt.Sprintf("%v=%v", flag, value))
}
argList = append(argList, runSubcommand)
for flag, value := range args.runFlags {
argList = append(argList, fmt.Sprintf("%v=%v", flag, value))
}
argList = append(argList, args.target)
argList = append(argList, args.patterns...)
return argList
}
// invokeTast invoke tast with the parameters based on rtd.Invocation.
func invokeTast(logger *log.Logger, inv *rtd.Invocation, rtdPath string) (resultsDir string, err error) {
// The path to the tast executable in chroot is /usr/bin/tast.
path := "/usr/bin/tast"
// The path to the tast executable in RTD is /usr/src/rtd/tast/bin/tast.
if rtdPath != "" {
path = filepath.Join(rtdPath, "tast", "bin", "tast")
}
if len(inv.Duts) == 0 {
return "", errors.New("input invocation.duts is empty")
}
if len(inv.Requests) == 0 {
return "", errors.New("input invocation.requests is empty")
}
args := newArgs(inv, rtdPath)
// Make sure the result directory exists.
resultsDir = args.runFlags[resultsDirFlag]
if err := os.MkdirAll(resultsDir, 0755); err != nil {
return resultsDir, errors.Wrapf(err, "failed to create result directory %v", resultsDir)
}
// Create symbolic links to the the first result directory.
for _, r := range inv.Requests[1:] {
workDir := r.Environment.WorkDir
if workDir == "" {
continue
}
if workDir == resultsDir {
continue
}
if err := os.RemoveAll(workDir); err != nil {
return "", errors.Wrapf(err, "failed to remove working directory %v", workDir)
}
// Make sure the parent directory of the symbolic link exists.
if err := os.MkdirAll(filepath.Dir(workDir), 0755); err != nil {
return resultsDir, errors.Wrapf(err, "failed to create parent directory of symbolic link %v", workDir)
}
if err := os.Symlink(resultsDir, workDir); err != nil {
return resultsDir, errors.Wrapf(err, "failed to create symbolic link %v", workDir)
}
}
// Run tast.
cmd := exec.Command(path, genArgList(args)...)
stderr, err := cmd.StderrPipe()
if err != nil {
return "", errors.Wrap(err, "StderrPipe failed")
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return "", errors.Wrap(err, "StdoutPipe failed")
}
if err := cmd.Start(); err != nil {
return "", err
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
logger.Printf("[tast] %v", scanner.Text())
}
}()
go func() {
defer wg.Done()
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
logger.Printf("[tast] %v", scanner.Text())
}
}()
wg.Wait()
return resultsDir, cmd.Wait()
}