blob: b199c05e88c5dd8b186ff76051bef4dbb0e7050b [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 service implementations and management
package services
import (
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"github.com/golang/protobuf/jsonpb"
buildapi "go.chromium.org/chromiumos/config/go/build/api"
"go.chromium.org/chromiumos/config/go/test/api"
)
const (
_LOGIN_USERNAME = "oauth2accesstoken"
_LOGIN_PASSWORD = "$(gcloud auth print-access-token)"
_LOGIN_REGISTRY = "us-docker.pkg.dev"
IMAGE_DATA_TEMPLATE = "gs://chromeos-image-archive/%s-release/%s*/metadata/containers.jsonpb"
CROS_TOOL_RUNNER_PATH = "chromiumos/infra/cros-tool-runner/${platform}"
)
type ctrServiceCommands struct {
LoginRegistry ServiceCommand_
StartContainer ServiceCommand_
}
// Enum of available commands for cros-tool-runner
func CTR_SERVICE_COMMANDS() ctrServiceCommands {
return ctrServiceCommands{
LoginRegistry: "login_registry",
StartContainer: "start_container",
}
}
// Service implementation for cros-tool-runner
type CTRService struct {
Service
ServiceBase
client api.CrosToolRunnerContainerServiceClient
}
func (c *CTRService) Start() error {
c.EnsurePort()
c.LocalLogger.Printf("Starting %s on port %d", c.Name, c.Port)
starter := &SetupCTR{
ctr: c,
}
if err := c.executor.Start(starter); err != nil {
return err
}
// Start CacheServer, SSHTunnel, SSHReverseTunnel, CrosDut
if c.manager.DutHost != "" {
cacheServerService := &CacheServerService{
ServiceBase: NewServiceBase(
c.manager,
c.executor,
SERVICES().CacheServer,
),
}
sshReverseTunnelService := &SSHTunnelService{
ServiceBase: NewServiceBase(
c.manager,
c.executor,
SERVICES().SSHReverseTunnel,
),
}
sshTunnelService := &SSHTunnelService{
ServiceBase: NewServiceBase(
c.manager,
c.executor,
SERVICES().SSHTunnel,
),
}
crosDutService := &CrosDutService{
ServiceBase: NewServiceBase(
c.manager,
c.executor,
SERVICES().CrosDut,
),
}
if err := c.manager.Start(cacheServerService.Name, cacheServerService); err != nil {
return err
}
if err := c.manager.Start(sshReverseTunnelService.Name, sshReverseTunnelService); err != nil {
return err
}
if err := c.manager.Start(sshTunnelService.Name, sshTunnelService); err != nil {
return err
}
if err := c.manager.Start(crosDutService.Name, crosDutService); err != nil {
return err
}
}
return nil
}
func (c *CTRService) Execute(commandName ServiceCommand_, args ...interface{}) error {
var cmd ServiceCommand = nil
switch commandName {
case CTR_SERVICE_COMMANDS().LoginRegistry:
cmd = &LoginRegistryCommand{
ctr: c,
request: &api.LoginRegistryRequest{
Username: _LOGIN_USERNAME,
Password: _LOGIN_PASSWORD,
Registry: _LOGIN_REGISTRY,
},
}
case CTR_SERVICE_COMMANDS().StartContainer:
startContainerRequest := args[0].([]interface{})[0].(*api.StartContainerRequest)
cmd = &StartContainerCommand{
ctr: c,
request: startContainerRequest,
}
default:
return fmt.Errorf("Command %s not found", commandName)
}
return c.executor.Execute(cmd)
}
func (c *CTRService) Stop() error {
stopper := &StopCTR{
ctr: c,
}
return c.executor.Stop(stopper)
}
// Setup
type SetupCTR struct {
ServiceSetup
ctr *CTRService
}
func (starter *SetupCTR) Setup() error {
path, err := starter.fetchCrosToolRunner()
if err != nil {
return err
}
go BuildServiceListener(
&starter.ctr.ServiceBase,
false,
exec.Command(fmt.Sprintf("%s/cros-tool-runner", path), "serve", "-port", fmt.Sprint(starter.ctr.Port)),
)()
if err := <-starter.ctr.ReadyChan; err != nil {
return err
}
if err := BuildConnection(&starter.ctr.ServiceBase); err != nil {
return err
}
starter.ctr.client = api.NewCrosToolRunnerContainerServiceClient(starter.ctr.conn)
if err := starter.ctr.manager.Execute(SERVICES().CrosToolRunner, CTR_SERVICE_COMMANDS().LoginRegistry); err != nil {
return err
}
if err := starter.fetchImageData(); err != nil {
return fmt.Errorf("could not fetch image data, %s", err)
}
return nil
}
func (starter *SetupCTR) fetchCrosToolRunner() (string, error) {
dir := fmt.Sprintf("%s/bin/", starter.ctr.BaseDir)
if err := os.RemoveAll(dir); err != nil {
return "", fmt.Errorf("Failed to remove previous binaries, %s", err)
}
if err := os.MkdirAll(dir, 0755); err != nil {
starter.ctr.LocalLogger.Printf("Failed to create local-cft binaries directory, %s\n", err)
}
if _, err := exec.Command("cipd", "init", dir).Output(); err != nil {
return "", fmt.Errorf("Could not run 'cipd init', %s", err)
}
if _, err := exec.Command("cipd", "install", CROS_TOOL_RUNNER_PATH, "prod", "-root", dir).Output(); err != nil {
return "", fmt.Errorf("Failed to install cros-tool-runner, %s", err)
}
return dir, nil
}
func (starter *SetupCTR) fetchImageData() error {
if strings.HasPrefix(starter.ctr.manager.Build, "LATEST") {
latest := exec.Command("gsutil", "cat", fmt.Sprintf("gs://chromeos-image-archive/%s-release/%s",
starter.ctr.manager.Board,
starter.ctr.manager.Build,
))
latestOut, err := latest.Output()
if err != nil {
return err
}
starter.ctr.manager.Build = string(latestOut)
}
template := fmt.Sprintf(
IMAGE_DATA_TEMPLATE,
starter.ctr.manager.Board,
starter.ctr.manager.Build,
)
gsutil := exec.Command("gsutil", "ls", "-l", template)
sort := exec.Command("sort", "-k", "2")
gPipe, err := gsutil.StdoutPipe()
if err != nil {
return err
}
sort.Stdin = gPipe
if err := gsutil.Start(); err != nil {
return err
}
imageDataRaw, err := sort.Output()
if err != nil {
return err
}
regContainerEx := regexp.MustCompile(`gs://.*.jsonpb`)
containerImages := regContainerEx.FindAllStringSubmatch(string(imageDataRaw), -1)
if len(containerImages) == 0 {
return fmt.Errorf("Could not find any container images with given build number %s", starter.ctr.manager.Build)
}
archivePath := containerImages[len(containerImages)-1][0]
starter.ctr.manager.imagePath = strings.Split(archivePath, "metadata")[0] // Add to Manager
cat := exec.Command("gsutil", "cat", archivePath)
catOut, err := cat.Output()
if err != nil {
return err
}
reader := strings.NewReader(string(catOut))
metadata := &buildapi.ContainerMetadata{}
unmarshaler := jsonpb.Unmarshaler{}
unmarshaler.Unmarshal(reader, metadata)
starter.ctr.manager.images = metadata.Containers[starter.ctr.manager.Board].Images // Add to Manager
return nil
}
// Stopper
type StopCTR struct {
ServiceStopper
ctr *CTRService
}
func (stopper *StopCTR) Stop() error {
if stopper.ctr.conn != nil {
stopper.ctr.conn.Close()
}
if stopper.ctr.Started {
stopper.ctr.CloseChan <- struct{}{}
<-stopper.ctr.CloseFinishedChan
}
WriteLogs(&stopper.ctr.ServiceBase)
return nil
}
// Commands
type LoginRegistryCommand struct {
ServiceCommand
ctr *CTRService
request *api.LoginRegistryRequest
}
func (cmd *LoginRegistryCommand) Execute() error {
_, err := cmd.ctr.client.LoginRegistry(cmd.ctr.manager.ctx, cmd.request)
return err
}
type StartContainerCommand struct {
ServiceCommand
ctr *CTRService
request *api.StartContainerRequest
}
func (cmd *StartContainerCommand) Execute() error {
_, err := cmd.ctr.client.StartContainer(cmd.ctr.manager.ctx, cmd.request)
return err
}