blob: a3d4b834e0bf52da5ee8d28d6204ea20877a6975 [file]
// Package runner provides funcs to run skiaserve either in or outside of a container.
package runner
import (
"context"
"fmt"
"path/filepath"
"go.skia.org/infra/go/exec"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
)
// GetCurrentGitHash is a function that returns the current Git hash that
// skiaserve was built at, usually provided by buildskia.ContinuousBuilder.
type GetCurrentGitHash func() string
// Runner runs skiaserve either in or outside of a chroot jail container.
type Runner struct {
// workRoot is the directory where we check out versions of Skia.
//
// See the description of how workRoot is used by
// buildskia.ContinuousBuilder.
workRoot string
// imageDir is the directory that contains the chroot jail image.
imageDir string
// local is true if we are running locally, and thus skiaserve shouldn't be
// run in a container.
local bool
// getHash is a func that returns the last good git hash that skiaserve
// was built at.
getHash GetCurrentGitHash
}
// Start a single instance of skiaserve running at the given port.
//
// This func doesn't return until skiaserve exits.
//
// NOTE: When trying to run a binary that exists on a mounted directory under
// nspawn, it will fail with:
//
// $ sudo systemd-nspawn -D /mnt/pd0/container/ --bind=/mnt/pd0/foo /mnt/pd0/blahblah/someexe
// Directory /mnt/pd0/container lacks the binary to execute or doesn't look like a binary tree. Refusing.
//
// That's because nspawn is looking for the exe before doing the bindings. The
// fix? A pure hack, insert "xargs --arg-file=/dev/null " before the command
// you want to run. Since xargs exists in the container this will proceed to
// the point of making the bindings and then xargs will be able to execute the
// exe within the container.
func (c *Runner) Start(ctx context.Context, port int) error {
hash := c.getHash()
machine := fmt.Sprintf("debug%05d", port)
name := "sudo"
skiaserve := filepath.Join(c.workRoot, "versions", hash, "skia", "out", "Release", "skiaserve")
args := []string{
"systemd-nspawn", "-D", c.imageDir,
"--read-only", // Mount the root file system as read only.
"--machine", machine, // Give the container a unique name, so we can run fiddles concurrently.
"--bind-ro", c.workRoot, // Mount workRoot as read-only.
"xargs", "--arg-file=/dev/null", // See Note above for explanation of xargs.
skiaserve, "--port", fmt.Sprintf("%d", port), "--hosted",
}
if c.local {
name = skiaserve
args = []string{"--port", fmt.Sprintf("%d", port), "--source", "", "--hosted"}
}
runCmd := &exec.Command{
Name: name,
Args: args,
LogStderr: true,
LogStdout: true,
}
if err := exec.Run(ctx, runCmd); err != nil {
return fmt.Errorf("skaiserve failed to run %#v: %s", *runCmd, err)
}
sklog.Infof("Returned from running skiaserve.")
return nil
}
// New creates a new Runner.
//
// workRoot - The directory where we check out versions of Skia.
// imageDir - The directory that contains the chroot jail image.
// local - True if we are running locally, and thus skiaserve shouldn't be
// run in a container.
// getHash - A func that returns the last good git hash that skiaserve
// was built at.
func New(workRoot, imageDir string, getHash GetCurrentGitHash, local bool) *Runner {
return &Runner{
workRoot: workRoot,
imageDir: imageDir,
local: local,
getHash: getHash,
}
}
// Stop, when called from a different Go routine, will cause the skiaserve
// running on the given port to exit and thus the container to exit.
func Stop(port int) {
client := httputils.NewTimeoutClient()
resp, _ := client.Get(fmt.Sprintf("http://localhost:%d/quitquitquit", port))
if resp != nil && resp.Body != nil {
util.Close(resp.Body)
}
}