blob: 408a52659235604ea50911d17711e4175c58dc92 [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 cros-servod for starting/stopping servod daemon
// and sending commands to it to control and test DUTs via servo hardware by
// simulating user actions such as power on/off, flashing of firmware/OS,
// screen close, etc.
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"time"
"go.chromium.org/chromiumos/test/servod/cmd/commandexecutor"
"go.chromium.org/chromiumos/test/servod/cmd/model"
"go.chromium.org/chromiumos/test/servod/cmd/servodserver"
"go.chromium.org/luci/common/errors"
)
const (
// version is the version info of this command. It is filled in during emerge.
version = "<unknown>"
helpDescription = `cros-servod tool
The tool provides the ability to start/stop a servod Docker container either on the same host or a remote servo host and execute servod commands on it.
go/cros-servod-design to learn more about the design.
Commands:
cli Runs the server in CLI mode, executes one of the subcommands (start_servod, stop_servod, exec_cmd, call_servod) by using the input parameters and prints the result to the output file.
Usage:
To start servod:
cros-servod cli start_servod --servo_host_path <ip:port> --servod_docker_container_name <a_unique_name> --servod_docker_image_path <servod_docker_image_path> --servod_port <servod_port> --board <board> --model <model> --serial_name <serial_name> --debug <debug> --recovery_mode <recovery_mode> --config <config> --allow_dual_v4 <allow_dual_v4> [--log_path /tmp/servod/]
Example (Start non-containerized servod):
cros-servod cli start_servod --servo_host_path localhost:9876 --servod_port 9901 --board dedede --model galith --serial_name G1911050826
Example (Start containerized servod):
cros-servod cli start_servod --servo_host_path localhost:9876 --servod_docker_container_name servod_container1 --servod_docker_image_path gcr.io/chromeos-bot/servod@sha256:2d25f6313c7bbac349607 --servod_port 9901 --board dedede --model galith --serial_name G1911050826
To stop servod:
cros-servod cli stop_servod --servo_host_path <ip:port> --servod_docker_container_name <a_unique_name> --servod_port <servod_port> [--log_path /tmp/servod/]
Example (Stop non-containerized servod):
cros-servod cli stop_servod --servo_host_path localhost:9876 --servod_port 9901
Example (Stop containerized servod):
cros-servod cli stop_servod --servo_host_path localhost:9876 --servod_docker_container_name servod_container1 --servod_port 9901
To execute command:
cros-servod cli exec_cmd --servo_host_path <ip:port> --servod_docker_container_name <a_unique_name> --command <command> [--log_path /tmp/servod/]
Example (Execute fake disconnect command on non-containerized servod):
cros-servod cli exec_cmd --servo_host_path localhost:9876 --command "dut-control -p 9901 servo_v4_uart_cmd:'fakedisconnect 130 2400'"
Example (Execute system command on containerized servod):
cros-servod cli exec_cmd --servo_host_path localhost:9876 --servod_docker_container_name servod_container1 --command "ps -ef | grep servod"
To call servod for doc:
cros-servod cli call_servod --servo_host_path <ip:port> --servod_docker_container_name <a_unique_name> --method doc --args <args> [--log_path /tmp/servod/]
Example (Call DOC method for lid_open control on non-containerized servod):
cros-servod cli call_servod --servo_host_path localhost:9876 --method doc --args lid_open
To call servod for get:
cros-servod cli call_servod --servo_host_path <ip:port> --servod_docker_container_name <a_unique_name> --method get --args <args> [--log_path /tmp/servod/]
Example (Call GET method for lid_open control on non-containerized servod):
cros-servod cli call_servod --servo_host_path localhost:9876 --method get --args lid_open
To call servod for set:
cros-servod cli call_servod --servo_host_path <ip:port> --servod_docker_container_name <a_unique_name> --method set --args <args> [--log_path /tmp/servod/]
Example (Call SET method to set lid_open control value to yes on containerized servod):
cros-servod cli call_servod --servo_host_path localhost:9876 --servod_docker_container_name servod_container1 --method set --args "lid_open:yes"
Example (Call SET method to execute fake disconnect on non-containerized servod):
cros-servod cli call_servod --servo_host_path localhost:9876 --method set --args "servo_v4_uart_cmd:'fakedisconnect 130 2400'"
server Starts the servod server for RPC calls. Mostly used for tests.
Usage:
cros-servod server [--log_path /tmp/servod/] [--server_port 80]
--version Prints the version.
--help Prints the help.`
defaultLogDirectory = "/tmp/servod/"
defaultServerPort = 80
defaultServodPort = 9999
)
// createLogFile creates a file and its parent directory for logging purpose.
func createLogFile(logPath string) (*os.File, error) {
t := time.Now()
fullPath := filepath.Join(logPath, t.Format("20060102-150405"))
if err := os.MkdirAll(fullPath, 0755); err != nil {
return nil, fmt.Errorf("failed to create directory %v: %v", 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, fmt.Errorf("failed to create file %v: %v", 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)
}
func runCLI(ctx context.Context, cs model.CliSubcommand, d []string) int {
a := model.CliArgs{}
fs := flag.NewFlagSet("Run servod", flag.ExitOnError)
fs.StringVar(&a.LogPath, "log_path", defaultLogDirectory, fmt.Sprintf("Path to record execution logs. The default value is %s", defaultLogDirectory))
fs.StringVar(&a.ServoHostPath, "servo_host_path", "", "The path (URI) for the servod (containerized or running as a daemon) host.")
fs.StringVar(&a.ServodDockerContainerName, "servod_docker_container_name", "", "The servod Docker container name.")
fs.StringVar(&a.ServodDockerImagePath, "servod_docker_image_path", "", "The servod Docker image path to pull from GCR.")
var servodPort int
fs.IntVar(&servodPort, "servod_port", defaultServodPort, fmt.Sprintf("The --PORT parameter value for servod command. The default value is %d.", defaultServodPort))
fs.StringVar(&a.Board, "board", "", "The --BOARD parameter value for servod command.")
fs.StringVar(&a.Model, "model", "", "The --MODEL parameter value for servod command.")
fs.StringVar(&a.SerialName, "serial_name", "", "The --SERIALNAME parameter value for servod command.")
fs.StringVar(&a.Debug, "debug", "", "The --DEBUG parameter value for servod command.")
fs.StringVar(&a.RecoveryMode, "recovery_mode", "", "The --RECOVERY_MODE parameter value for servod command.")
fs.StringVar(&a.Config, "config", "", "The --CONFIG parameter value for servod command.")
fs.StringVar(&a.AllowDualV4, "allow_dual_v4", "", "The --ALLOW-DUAL-V4 parameter value for servod command.")
fs.StringVar(&a.Command, "command", "", "The command to execute inside the servod Docker container.")
fs.StringVar(&a.Method, "method", "", "The method to call. Accepted values are doc, get, and set.")
fs.StringVar(&a.Args, "args", "", "The arguments to pass to the method.")
fs.Parse(d)
a.ServodPort = int32(servodPort)
logFile, err := createLogFile(a.LogPath)
if err != nil {
log.Fatalln("Failed to create log file", err)
return 2
}
defer logFile.Close()
logger := newLogger(logFile)
commandexecutor := commandexecutor.NewServodCommandExecutor(logger)
servodService, destructor, err := servodserver.NewServodService(ctx, logger, commandexecutor)
defer destructor()
if err != nil {
logger.Fatalln("Failed to create servod service: ", err)
return 2
}
if _, _, err := servodService.RunCli(cs, a, nil, true); err != nil {
logger.Fatalln("Failed to run CLI: ", err)
return 1
}
return 0
}
func startServer(ctx context.Context, d []string) int {
a := model.CliArgs{}
fs := flag.NewFlagSet("Start servod server", flag.ExitOnError)
fs.StringVar(&a.LogPath, "log_path", defaultLogDirectory, fmt.Sprintf("The path to record execution logs. The default value is %s", defaultLogDirectory))
var serverPort int
fs.IntVar(&serverPort, "server_port", defaultServerPort, fmt.Sprintf("The port for the servod GRPC server. The default value is %d.", defaultServerPort))
fs.Parse(d)
a.ServerPort = int32(serverPort)
logFile, err := createLogFile(a.LogPath)
if err != nil {
log.Fatalln("Failed to create log file", err)
return 2
}
defer logFile.Close()
logger := newLogger(logFile)
commandexecutor := commandexecutor.NewServodCommandExecutor(logger)
servodService, destructor, err := servodserver.NewServodService(ctx, logger, commandexecutor)
defer destructor()
if err != nil {
logger.Fatalln("Failed to create servod service: ", err)
return 2
}
if err := servodService.StartServer(a.ServerPort); err != nil {
logger.Fatalln("Failed to start servod server: ", err)
return 1
}
return 0
}
// Specify run mode for cros-servod.
type runMode string
const (
runCli runMode = "cli"
runServer runMode = "server"
runVersion runMode = "version"
runHelp runMode = "help"
)
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
}
}
// If we did not find special run mode then just print help for user.
return runHelp, nil
}
func getCliSubcommand() (model.CliSubcommand, error) {
if len(os.Args) > 2 {
switch strings.ToLower(os.Args[2]) {
case "start_servod":
return model.CliStartServod, nil
case "stop_servod":
return model.CliStopServod, nil
case "exec_cmd":
return model.CliExecCmd, nil
case "call_servod":
return model.CliCallServod, nil
}
}
return model.CliUnknown, errors.Reason("Unknown CLI subcommand").Err()
}
func mainInternal() int {
rm, err := getRunMode()
if err != nil {
log.Fatalln(err)
return 2
}
ctx := context.Background()
switch rm {
case runCli:
log.Printf("Running CLI mode!")
cs, err := getCliSubcommand()
if err != nil {
log.Fatalln(err)
return 2
}
return runCLI(ctx, cs, os.Args[3:])
case runServer:
log.Printf("Running server mode!")
return startServer(ctx, os.Args[2:])
case runVersion:
log.Printf("cros-servod version: %s", version)
return 0
}
log.Println(helpDescription)
return 0
}
func main() {
os.Exit(mainInternal())
}