blob: 747167f9e1677ab6a92881efb4184ff28bec7f1f [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Provides entry points to local cft
package tasks
import (
"bufio"
"context"
"flag"
"fmt"
"log"
"os"
"strconv"
"strings"
"github.com/maruel/subcommands"
"go.chromium.org/chromiumos/test/local-cft/internal/services"
"go.chromium.org/chromiumos/test/local-cft/internal/utils"
)
const (
RELATIVE_BASE_DIR = "local-cft"
)
// CLI command structure for running local cft
type localCftCmd struct {
subcommands.CommandRunBase
flagSet *flag.FlagSet
tests string
tags string
localServices string
tagsExclude string
model string
board string
build string
dutHost string
baseDir string
chroot string
fullRun bool
provision bool
test bool
testFind bool
// publish bool
ports map[string]uint16
stayAlive bool
errorLogger *log.Logger
}
// Combines services with their relevant commands for simple execution
type serviceToRun struct {
name string
service services.Service
cmds []services.ServiceCommand_
}
// Entrypoint for running local-cft
func LocalCft() int {
localCft := &localCftCmd{
flagSet: flag.NewFlagSet("cli", flag.ContinueOnError),
}
localCft.flagSet.StringVar(&localCft.tests, "tests", "", "test(s) to run, comma separated. Example: -tests test1,test2")
localCft.flagSet.StringVar(&localCft.tags, "tags", "", "tag(s) to run, comma separated. Example: -tags group:1,group:2")
localCft.flagSet.StringVar(&localCft.localServices, "localservices", "", "Services with local updates to include in containers\n Eg cros-dut,cros-test,cros-test")
localCft.flagSet.StringVar(&localCft.tagsExclude, "tagsExclude", "", "Excluded tag(s) to run, comma separated. Example: -tags group:1,group:2")
localCft.flagSet.StringVar(&localCft.model, "model", "", "Model name")
localCft.flagSet.StringVar(&localCft.board, "board", "", "Board name")
localCft.flagSet.StringVar(&localCft.build, "build", "LATEST-main", "Build number to run containers from.\n Eg R108 or R108-14143. Defaults to LATEST-main.")
localCft.flagSet.StringVar(&localCft.dutHost, "host", "", "Hostname of dut")
localCft.flagSet.StringVar(&localCft.baseDir, "dir", "/tmp", "The base absolute path for the local-cft runner's interactions and output.\n Eg /tmp")
localCft.flagSet.StringVar(&localCft.chroot, "chroot", "", "Absolute path of your chromiumos chroot. Necessary for updating local services.")
localCft.flagSet.BoolVar(&localCft.fullRun, "fullrun", false, "Run the full flow of cft.\n\tWhen true, provision and test are defaulted to true")
localCft.flagSet.BoolVar(&localCft.provision, "provision", false, "Run cros-provision")
localCft.flagSet.BoolVar(&localCft.test, "test", false, "Run cros-test-finder and cros-test")
localCft.flagSet.BoolVar(&localCft.testFind, "findtest", false, "Run cros-test-finder")
// localCft.flagSet.BoolVar(&localCft.publish, "publish", false, "Run cros-publish")
localCft.flagSet.Func("port", "set port for a service. Example -port cros-dut=8080", func(servicePort string) error {
parts := strings.SplitN(servicePort, "=", 2)
if len(parts) == 2 {
port, err := strconv.ParseUint(parts[1], 10, 16)
if err != nil {
return err
}
if port > 0 && parts[0] != "" {
if localCft.ports == nil {
localCft.ports = make(map[string]uint16)
}
localCft.ports[parts[0]] = uint16(port)
return nil
}
}
return fmt.Errorf("use service-name=12345")
})
localCft.flagSet.BoolVar(&localCft.stayAlive, "stayalive", false, "Keep the containers alive until user input")
return localCft.Run()
}
// Defines the usage lines for local-cft
func (c *localCftCmd) printUsage() {
localCftUsage := `
Usage: local-cft [OPTIONS] <argument>
`
println(localCftUsage)
c.flagSet.PrintDefaults()
}
// Execution for local-cft.
// Validates the state, parses arguments,
// and computes which services will run,
// subsequently running each service and command.
func (c *localCftCmd) Run() int {
c.errorLogger = log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime)
if err := c.Validate(); err != nil {
c.errorLogger.Println(err)
return 2
}
ctx := context.Background()
parsedTests := strings.Split(c.tests, ",")
parsedTags := strings.Split(c.tags, ",")
parsedExcludedTags := strings.Split(c.tagsExclude, ",")
parsedLocalServices := strings.Split(c.localServices, ",")
c.baseDir = fmt.Sprintf("%s/%s", c.baseDir, RELATIVE_BASE_DIR)
manager := services.NewLocalCFTManager(
ctx,
c.board, c.model, c.build, c.dutHost, c.baseDir, c.chroot,
parsedTests, parsedTags, parsedExcludedTags,
parsedLocalServices, c.ports,
)
servicesToRun := c.compileServicesToRun(manager)
defer func() {
if c.stayAlive {
log.Println("Press 'Enter' to close...")
bufio.NewReader(os.Stdin).ReadBytes('\n')
}
if err := manager.Stop(); err != nil {
c.errorLogger.Println(err)
}
manager.PrintResults()
}()
for _, serviceToRun := range servicesToRun {
if err := manager.Start(serviceToRun.name, serviceToRun.service); err != nil {
c.errorLogger.Printf("Failed to run %s, %s", serviceToRun.name, err)
return 1
}
for _, cmd := range serviceToRun.cmds {
if err := manager.Execute(serviceToRun.name, cmd); err != nil {
c.errorLogger.Printf("Failed to execute %s, %s", cmd, err)
return 1
}
}
}
return 0
}
// Uses CLI args to determine which services and commands will be run for local-cft
func (c *localCftCmd) compileServicesToRun(manager *services.LocalCFTManager) []serviceToRun {
servicesToRun := []serviceToRun{}
ctrService := &services.CTRService{
ServiceBase: services.NewDefaultServiceBase(manager, services.SERVICES().CrosToolRunner),
}
servicesToRun = append(servicesToRun, serviceToRun{
name: services.SERVICES().CrosToolRunner,
service: ctrService,
cmds: []services.ServiceCommand_{},
})
if c.fullRun {
c.provision = true
c.testFind = true
c.test = true
}
if c.provision {
servicesToRun = append(servicesToRun, serviceToRun{
name: services.SERVICES().CrosProvision,
service: &services.CrosProvisionService{
ServiceBase: services.NewDefaultServiceBase(manager, services.SERVICES().CrosProvision),
},
cmds: []services.ServiceCommand_{services.CROS_PROVISION_SERVICE_COMMANDS().Install},
})
}
if c.test || c.testFind {
servicesToRun = append(servicesToRun, serviceToRun{
name: services.SERVICES().CrosTestFinder,
service: &services.CrosTestFinderService{
ServiceBase: services.NewDefaultServiceBase(manager, services.SERVICES().CrosTestFinder),
},
cmds: []services.ServiceCommand_{services.CROS_TEST_FINDER_SERVICE_COMMANDS().FindTests},
})
}
if c.test {
servicesToRun = append(servicesToRun, serviceToRun{
name: services.SERVICES().CrosTest,
service: &services.CrosTestService{
ServiceBase: services.NewDefaultServiceBase(manager, services.SERVICES().CrosTest),
},
cmds: []services.ServiceCommand_{services.CROS_TEST_SERVICE_COMMANDS().RunTests},
})
}
// if c.publish || c.fullRun {
// servicesToRun = append(servicesToRun, ServiceToRun{
// name: services.SERVICES().CrosProvision,
// service: &services.CrosProvisionService{
// ServiceBase: services.NewDefaultServiceBase(manager, services.SERVICES().CrosProvision),
// },
// cmds: []services.ServiceCommand_{services.CROS_PROVISION_SERVICE_COMMANDS().Install},
// })
// }
return servicesToRun
}
func (c *localCftCmd) validateArgs() error {
if c.board == "" {
return fmt.Errorf("-board must be provided.")
}
if c.build == "" {
return fmt.Errorf("-build must be provided.")
}
if (c.fullRun || c.test || c.provision) && c.dutHost == "" {
return fmt.Errorf("-host must be provided.")
}
parsedLocalServices := strings.Split(c.localServices, ",")
if c.localServices != "" && len(parsedLocalServices) > 0 && c.chroot == "" {
return fmt.Errorf("Local services requires having the -chroot flag")
}
return nil
}
func (c *localCftCmd) checkPreReqs() error {
if err := utils.CheckAutoSSHInstalled(); err != nil {
return err
}
if err := utils.CheckDockerInstalled(); err != nil {
return err
}
return nil
}
func (c *localCftCmd) Validate() error {
if err := c.flagSet.Parse(os.Args[1:]); err != nil {
c.printUsage()
return fmt.Errorf("Failed to parse args, %s", err)
}
if err := c.validateArgs(); err != nil {
c.printUsage()
return fmt.Errorf("Failed to validate args, %s", err)
}
if err := c.checkPreReqs(); err != nil {
return fmt.Errorf("Failed to pass prereq checks, %s", err)
}
return nil
}