blob: 734083719ce05902c20d0011d2198eee92c2ed49 [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package main implements the executionservice for running tests.
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"net"
"os"
"path/filepath"
"strings"
"time"
"go.chromium.org/chromiumos/test/execution/cmd/cros-test/internal/common"
"go.chromium.org/chromiumos/test/execution/errors"
"go.chromium.org/chromiumos/test/util/metadata"
"go.chromium.org/chromiumos/test/util/portdiscovery"
)
// Version is the version info of this command. It is filled in during emerge.
var Version = "<unknown>"
// defaultPort is the port where cros-test will bind to in server mode if a port is not provided.
var defaultPort = 8001
// createLogFile creates a file and its parent directory for logging purpose.
func createLogFile(fullPath string) (*os.File, error) {
if err := os.MkdirAll(fullPath, 0755); err != nil {
return nil, errors.NewStatusError(errors.IOCreateError,
fmt.Errorf("failed to create directory %v: %w", fullPath, err))
}
logFullPathName := filepath.Join(fullPath, "log.txt")
// Log the full output of the command to disk.
logFile, err := os.Create(logFullPathName)
if err != nil {
return nil, errors.NewStatusError(errors.IOCreateError,
fmt.Errorf("failed to create file %v: %w", fullPath, err))
}
return logFile, nil
}
// newLogger creates a logger. Using go default logger for now.
func newLogger(logFile *os.File) *log.Logger {
mw := io.MultiWriter(logFile, os.Stderr)
return log.New(mw, "", log.LstdFlags|log.LUTC)
}
type args struct {
// Common input params.
logPath string
inputPath string
outputPath string
resultsDirPath string
tlwAddr string
metadataDirPath string
version bool
// Server mode params
port int
}
// runCLI is the entry point for running cros-test (executionservice) in CLI mode.
func runCLI(ctx context.Context, d []string) int {
t := time.Now()
defaultLogPath := filepath.Join(common.TestExecServerRoot, t.Format("20060102-150405"))
defaultRequestFile := filepath.Join(common.TestExecServerRoot, common.TestRequestJSONFile)
defaultResultFile := filepath.Join(common.TestExecServerRoot, common.TestResultJSONFile)
a := args{}
fs := flag.NewFlagSet("Run cros-test", flag.ExitOnError)
fs.StringVar(&a.logPath, "log", defaultLogPath, fmt.Sprintf("Path to record execution logs. Default value is %s", defaultLogPath))
fs.StringVar(&a.inputPath, "input", defaultRequestFile, "specify the test execution request json input file")
fs.StringVar(&a.outputPath, "output", defaultResultFile, "specify the test execution response json output file")
fs.StringVar(&a.resultsDirPath, "resultdir", common.TestResultDir, "specify default directory for test harnesses to store their run result")
fs.StringVar(&a.tlwAddr, "tlwaddr", "", "specify the tlw address")
fs.StringVar(&a.metadataDirPath, "metadatadir", common.TestMetadataDir, "specify a directory that contain all test metadata proto files.")
fs.BoolVar(&a.version, "version", false, "print version and exit")
fs.Parse(d)
if a.version {
fmt.Println("executionservice version ", Version)
return 0
}
logFile, err := createLogFile(a.logPath)
if err != nil {
log.Fatalln("Failed to create log file", err)
return 2
}
defer logFile.Close()
logger := newLogger(logFile)
logger.Println("Starting executionservice version ", Version)
req, err := readInput(a.inputPath)
if err != nil {
log.Fatalf("Failed to read request input: %s", err)
return 2
}
metadata, err := metadata.ReadDir(a.metadataDirPath)
if err != nil {
log.Fatalf("Failed to read metadata input: %s", err)
return 2
}
rspn, err := runTests(ctx, logger, a.resultsDirPath, a.tlwAddr, metadata, req)
if err != nil {
logger.Fatalln("Failed to run tests: ", err)
return 1
}
logger.Printf("Writing line to %s", a.outputPath)
if err := writeOutput(a.outputPath, rspn); err != nil {
logger.Fatalf("Failed to write output file to %s: %d", a.outputPath, err)
return 2
}
return 0
}
// startServer is the entry point for running cros-test (executionservice) in server mode.
func startServer(d []string) int {
a := args{}
t := time.Now()
defaultLogPath := filepath.Join(common.TestExecServerRoot, t.Format("20060102-150405"))
fs := flag.NewFlagSet("Start executionservice server", flag.ExitOnError)
fs.StringVar(&a.logPath, "log", defaultLogPath, fmt.Sprintf("Path to record execution logs. Default value is %s", defaultLogPath))
fs.StringVar(&a.resultsDirPath, "resultdir", common.TestResultDir, "specify the test execution request json input file")
fs.StringVar(&a.tlwAddr, "tlwaddr", "", "specify the tlw address")
fs.StringVar(&a.metadataDirPath, "metadatadir", common.TestMetadataDir, "specify a directory that contain all test metadata proto files.")
fs.IntVar(&a.port, "port", defaultPort, fmt.Sprintf("Specify the port for the server. Default value %d.", defaultPort))
fs.Parse(d)
logFile, err := createLogFile(a.logPath)
if err != nil {
log.Fatalln("Failed to create log file", err)
return 2
}
defer logFile.Close()
logger := newLogger(logFile)
l, err := net.Listen("tcp", fmt.Sprintf(":%d", a.port))
if err != nil {
logger.Fatalln("Failed to create a net listener: ", err)
return 2
}
logger.Println("Starting executionservice on port ", a.port)
// Write port number to ~/.cftmeta for go/cft-port-discovery
err = portdiscovery.WriteServiceMetadata("cros-test", l.Addr().String(), logger)
if err != nil {
logger.Println("Warning: error when writing to metadata file: ", err)
}
metadata, err := metadata.ReadDir(a.metadataDirPath)
if err != nil {
log.Fatalf("Failed to read metadata input: %s", err)
return 2
}
server, closer := NewServer(logger, a.resultsDirPath, a.tlwAddr, metadata)
defer closer()
err = server.Serve(l)
if err != nil {
logger.Fatalln("Failed to initialize server: ", err)
return 2
}
return 0
}
// Specify run mode for CLI.
type runMode string
const (
runCli runMode = "cli"
runServer runMode = "server"
runVersion runMode = "version"
runHelp runMode = "help"
runCliDefault runMode = "cliDefault"
)
func getRunMode() (runMode, error) {
if len(os.Args) > 1 {
for _, a := range os.Args {
if a == "-version" {
return runVersion, nil
}
}
switch strings.ToLower(os.Args[1]) {
case "cli":
return runCli, nil
case "server":
return runServer, nil
case "help":
return runHelp, nil
}
}
// If we did not find special run mode then just run CLI to match legacy behavior.
return runCliDefault, nil
}
func mainInternal(ctx context.Context) int {
runMode, err := getRunMode()
if err != nil {
log.Fatalln(err)
return 2
}
switch runMode {
case runCliDefault:
log.Printf("No mode specified, assuming CLI.")
return runCLI(ctx, os.Args)
case runCli:
log.Printf("Running CLI mode!")
return runCLI(ctx, os.Args[2:])
case runServer:
log.Printf("Running server mode!")
return startServer(os.Args[2:])
case runVersion:
log.Printf("executionservice version: %s", Version)
return 0
}
return 0
}
func main() {
os.Exit(mainInternal(context.Background()))
}