blob: 622e4b942882d6180cdac34ea5cda4c4a1709c33 [file] [log] [blame]
// Copyright 2021 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.
// AshInstall state machine construction and helper
package ashservice
import (
"chromiumos/test/provision/cmd/provisionserver/bootstrap/services"
"context"
"fmt"
"log"
"path/filepath"
"time"
conf "go.chromium.org/chromiumos/config/go"
"go.chromium.org/chromiumos/config/go/test/api"
"google.golang.org/grpc"
)
// File specific consts
const (
autotestDir = "/usr/local/autotest/deps/chrome_test/test_src/out/Release/"
stagingDirectory = "/tmp/_provisioning_service_chrome_deploy"
targetDir = "/opt/google/chrome"
tastDir = "/usr/local/libexec/chrome-binary-tests/"
)
// Time specific consts
const (
twoSeconds = 2 * time.Second
tenSeconds = 10 * time.Second
)
// binaries to be copied in installation
var copyPaths = [...]string{
"ash_shell",
"aura_demo",
"chrome",
"chrome-wrapper",
"chrome.pak",
"chrome_100_percent.pak",
"chrome_200_percent.pak",
"content_shell",
"content_shell.pak",
"extensions/",
"lib/*.so",
"libffmpegsumo.so",
"libpdf.so",
"libppGoogleNaClPluginChrome.so",
"libosmesa.so",
"libwidevinecdmadapter.so",
"libwidevinecdm.so",
"locales/",
"nacl_helper_bootstrap",
"nacl_irt_*.nexe",
"nacl_helper",
"resources/",
"resources.pak",
"xdg-settings",
"*.png",
}
// test binaries to be copied in installation
var testPaths = [...]string{
"*test",
"*tests",
}
// AshService inherits ServiceInterface
type AshService struct {
connection services.ServiceAdapterInterface
imagePath *conf.StoragePath
}
func NewAshService(dutName string, dutClient api.DutServiceClient, wiringConn *grpc.ClientConn, req *api.InstallAshRequest) AshService {
service := AshService{
connection: services.NewServiceAdapter(dutName, dutClient, wiringConn, false /*noRebot*/),
imagePath: req.AshImagePath,
}
return service
}
// NewAshServiceFromExistingConnection is equivalent to the above constructor,
// but recycles a ServiceAdapter. Generally useful for tests.
func NewAshServiceFromExistingConnection(conn services.ServiceAdapterInterface, imagePath *conf.StoragePath) AshService {
return AshService{
connection: conn,
imagePath: imagePath,
}
}
// GetFirstState returns the first state of this state machine
func (a *AshService) GetFirstState() services.ServiceState {
return AshPrepareState{
service: *a,
}
}
// CleanUpStagingDirectory simply deletes the staging directory
func (a *AshService) CleanUpStagingDirectory(ctx context.Context) error {
return a.connection.DeleteDirectory(ctx, stagingDirectory)
}
// CreateStagingDirectory ensures a clean staging directory is present
func (a AshService) CreateStagingDirectory(ctx context.Context) error {
if err := a.CleanUpStagingDirectory(ctx); err != nil {
return err
}
return a.connection.CreateDirectories(ctx, []string{stagingDirectory})
}
// CreateBinaryDirectories creates all directories which will house the binaries for the install
func (a *AshService) CreateBinaryDirectories(ctx context.Context) error {
return a.connection.CreateDirectories(ctx, []string{targetDir, autotestDir, tastDir})
}
// CopyImageToDUT copies the desired image to the DUT, passing through the caching layer.
func (a *AshService) CopyImageToDUT(ctx context.Context) error {
if a.imagePath.HostType == conf.StoragePath_LOCAL || a.imagePath.HostType == conf.StoragePath_HOSTTYPE_UNSPECIFIED {
return fmt.Errorf("only GS copying is implemented")
}
url, err := a.connection.CopyData(ctx, a.imagePath.GetPath())
if err != nil {
return fmt.Errorf("failed to cache ash compressed, %w", err)
}
if _, err := a.connection.RunCmd(ctx, "", []string{
"curl", url,
"|",
"tar", "--ignore-command-error", "--overwrite", "--preserve-permissions", fmt.Sprintf("--directory=%s", stagingDirectory), "-xf", "-",
}); err != nil {
return fmt.Errorf("failed to copy ash compressed, %w", err)
}
return nil
}
// MountRootFS mounts the root filesystem as a read/write
func (a *AshService) MountRootFS(ctx context.Context) error {
if _, err := a.connection.RunCmd(ctx, "mount", []string{"-o", "remount,rw", "/"}); err != nil {
return fmt.Errorf("could not mount root file system, %w", err)
}
return nil
}
// isChromeInUse determines if chrome is currently running
func (a *AshService) isChromeInUse(ctx context.Context) bool {
_, err := a.connection.RunCmd(ctx, "lsof", []string{fmt.Sprintf("%s/chrome", targetDir)})
return err != nil
}
// StopChrome stops the UI
func (a *AshService) StopChrome(ctx context.Context) error {
if _, err := a.connection.RunCmd(ctx, "stop", []string{"ui"}); err != nil {
// stop ui returns error when UI is terminated, so ignore error here
log.Printf("failed to stop chrome, %s", err)
}
return nil
}
// KillChrome tries to pkill chrome, retrying/re-polling every two seconds
func (a *AshService) KillChrome(ctx context.Context) error {
for start := time.Now(); time.Since(start) < tenSeconds; time.Sleep(twoSeconds) {
if !a.isChromeInUse(ctx) {
return nil
}
log.Printf("chrome binary is still running, killing...")
if _, err := a.connection.RunCmd(ctx, "pkill", []string{"'chrome|session_manager'"}); err != nil {
return fmt.Errorf("failed run pkill, %s", err)
}
}
return fmt.Errorf("failed to kill chrome")
}
// Deploy rsyncs files relevant to the install to the correct bin locations
func (a *AshService) Deploy(ctx context.Context) error {
for _, file := range copyPaths {
if err := a.deployFile(ctx, file, targetDir); err != nil {
return fmt.Errorf("could not deploy copy file, %w", err)
}
}
for _, file := range testPaths {
if err := a.deployFile(ctx, file, autotestDir); err != nil {
return fmt.Errorf("could not deploy autotest file, %w", err)
}
if err := a.deployFile(ctx, file, tastDir); err != nil {
return fmt.Errorf("could not deploy tast file, %w", err)
}
}
return nil
}
// deployFile rsyncs one specific file to the desired bin dir
func (a *AshService) deployFile(ctx context.Context, file string, destination string) error {
source := fmt.Sprintf("%s/%s", stagingDirectory, file)
target := filepath.Dir(fmt.Sprintf("%s/%s", destination, file))
if exists, err := a.connection.PathExists(ctx, source); err != nil {
return fmt.Errorf("failed to determine file existance, %s", err)
} else if !exists {
return nil
}
if _, err := a.connection.RunCmd(ctx, "rsync", []string{"-av", source, target}); err != nil {
return fmt.Errorf("failed run rsync, %s", err)
}
return nil
}
// ReloadBus kill the bus daemon with a SIGHUP
func (a *AshService) ReloadBus(ctx context.Context) error {
if _, err := a.connection.RunCmd(ctx, "killall", []string{"-HUP", "dbus-daemon"}); err != nil {
return fmt.Errorf("failed to reload dbus, %s", err)
}
return nil
}
// StartChrome restarts the ui
func (a *AshService) StartChrome(ctx context.Context) error {
if _, err := a.connection.RunCmd(ctx, "start", []string{"ui"}); err != nil {
return fmt.Errorf("failed to start ui, %s", err)
}
return nil
}