blob: 58d045dd73cf9869b5de034d16c1067213b8a6c9 [file] [log] [blame]
// Copyright 2019 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.
// Package minicontainer implements the tests to verify ARC Mini container's
// conditions.
package minicontainer
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"chromiumos/tast/common/testexec"
"chromiumos/tast/errors"
"chromiumos/tast/local/arc"
"chromiumos/tast/local/bundles/cros/arc/cpuset"
"chromiumos/tast/local/syslog"
"chromiumos/tast/local/upstart"
"chromiumos/tast/testing"
)
func testZygote(ctx context.Context, s *testing.State) {
s.Log("Running testZygote")
var roZygote string
if err := testing.Poll(ctx, func(ctx context.Context) error {
out, err := arc.BootstrapCommand(ctx, "/system/bin/getprop", "ro.zygote").Output()
if err != nil {
return errors.Wrap(err, "getprop ro.zygote failed")
}
roZygote = strings.TrimSpace(string(out))
if roZygote == "" {
// Note: Even if Android boots, getprop may return
// empty string till it is initialized.
return errors.New("ro.zygote is empty")
}
return nil
}, nil); err != nil {
s.Fatal("Mini container didn't start: ", err)
}
// Depends on the configuration, there may be 32 or 64 bit zygote with
// a different name. We need regular expressions because zygote
// changes its process name to 'main' after preloading Java classes.
// The preloading may or may not happen depending on $BOARD and DUT's
// state.
zygoteName, ok := map[string]string{
"zygote32": "(app_process|main)",
"zygote64": "(app_process64|main)",
"zygote32_64": "(app_process32|main)",
"zygote64_32": "(app_process64|main)",
}[roZygote]
if !ok {
s.Errorf("Unrecognized ro.zygote: %q", roZygote)
return
}
if err := testing.Poll(ctx, func(ctx context.Context) error {
if err := testexec.CommandContext(ctx, "pgrep", zygoteName).Run(); err != nil {
return errors.Wrap(err, "zygote is not yet running")
}
return nil
}, nil); err != nil {
s.Fatal("Zygote did't start: ", err)
}
}
func testCoreServices(ctx context.Context, s *testing.State) {
s.Log("Running testCoreServices")
targets := []string{
"healthd",
"logd",
"servicemanager",
"surfaceflinger",
"vold",
"android.hardware.audio@2.0-service-bertha",
"android.hardware.cas@1.0-service",
"android.hardware.configstore@1.1-service",
"android.hardware.graphics.allocator@2.0-service",
"android.hidl.allocator@1.0-service",
"audioserver",
"hwservicemanager",
"thermalserviced",
"ueventd",
"vndservicemanager",
}
for _, target := range targets {
// Although testZygote waits for the mini container boot,
// some service may not yet started at this moment.
// So, poll that the processes start.
if err := testing.Poll(ctx, func(ctx context.Context) error {
return testexec.CommandContext(ctx, "pidof", "-s", target).Run()
}, nil); err != nil {
s.Errorf("Could not find %s: %v", target, err)
}
}
}
func testAndroidLogs(ctx context.Context, s *testing.State, sr *syslog.Reader) {
s.Log("Running testAndroidLogs")
// Obtain arc-kmsg-logger logs for the latest Mini container.
var logs bytes.Buffer
for {
e, err := sr.Read()
if err == io.EOF {
break
}
if err != nil {
s.Error("Failed to read syslog: ", err)
return
}
if e.Program != "arc-kmsg-logger" {
continue
}
logs.WriteString(e.Line)
}
// The log size is about 8KB after starting the mini container.
// If the size is less than 2KB, something must be broken.
const minLogSize = 2048
if size := logs.Len(); size < minLogSize {
// Dump arc-kmsg-logger log to output directory for debugging on failure.
if err := ioutil.WriteFile(filepath.Join(s.OutDir(), "arc-kmsg-logger.log"), logs.Bytes(), 0644); err != nil {
s.Error("Failed to write arc-kmsg-logger.log: ", err)
}
s.Errorf("log size is too small: got %d; want >= %d", size, minLogSize)
}
}
func testSELinuxLabels(ctx context.Context, s *testing.State) {
s.Log("Running testSELinuxLabels")
const (
root = "/opt/google/containers/android/rootfs/root"
cacheDir = root + "/cache"
dataDir = root + "/data"
dalvikCacheDir = dataDir + "/dalvik-cache"
)
verify := func(path, expect string) {
out, err := testexec.CommandContext(ctx, "getfattr", "--no-dereference", "--only-values", "--name", "security.selinux", path).Output(testexec.DumpLogOnError)
if err != nil {
s.Errorf("Failed to get selinux label for %s: %v", path, err)
return
}
label := strings.TrimSuffix(strings.TrimSpace(string(out)), "\x00")
if label != expect {
s.Errorf("Unexpected selinux label for %q: got %q; want %s", path, label, expect)
}
}
verify(cacheDir, "u:object_r:cache_file:s0")
verify(dataDir, "u:object_r:system_data_file:s0")
verify(dalvikCacheDir, "u:object_r:dalvikcache_data_file:s0")
if err := filepath.Walk(dalvikCacheDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
verify(path, "u:object_r:dalvikcache_data_file:s0")
return nil
}); err != nil {
s.Error("Failed to walk dalvik-cache dir: ", err)
}
}
func testCgroupDirectory(ctx context.Context, s *testing.State) {
s.Log("Running testCgroupDirectory")
const path = "/sys/fs/cgroup/devices/session_manager_containers/android"
if fi, err := os.Stat(path); err != nil {
s.Errorf("cgroup directory %s for the container does not exist: %v", path, err)
} else if !fi.IsDir() {
s.Errorf("cgroup directory %s is not a directory", path)
}
}
func testBootOATSymlinks(ctx context.Context, s *testing.State) {
s.Log("Running testBootOATSymlinks")
if err := arc.BootstrapCommand(ctx, "/system/bin/sh", "-c", "cat /data/dalvik-cache/*/*.oat > /dev/null").Run(testexec.DumpLogOnError); err != nil {
s.Error("Could not read *.oat files: ", err)
}
}
func testDevFiles(ctx context.Context, s *testing.State) {
s.Log("Running testDevFiles")
// /dev/.coldboot_done is a file to tell Android's init that the
// firmware initialization is done. In our case, the file has to be
// created before the mini container is started. Otherwise, init will
// freeze for a while waiting for the file until it times out.
const target = "/dev/.coldboot_done"
if err := arc.BootstrapCommand(ctx, "/system/bin/stat", target).Run(testexec.DumpLogOnError); err != nil {
s.Errorf("Could not stat %s: %v", target, err)
}
}
func testCPUSet(ctx context.Context, s *testing.State) {
s.Log("Running testCPUSet")
// Verify that /dev/cpuset is properly set up.
types := []string{"foreground", "background", "system-background", "top-app", "restricted"}
for _, t := range types {
path := fmt.Sprintf("/dev/cpuset/%s/effective_cpus", t)
out, err := arc.BootstrapCommand(ctx, "/system/bin/cat", path).Output(testexec.DumpLogOnError)
if err != nil {
s.Errorf("Failed to read %s: %v", path, err)
continue
}
val := strings.TrimSpace(string(out))
cpusInUse, err := cpuset.Parse(ctx, val)
if err != nil {
s.Errorf("Failed to parse %s: %v", path, err)
continue
}
if len(cpusInUse) != runtime.NumCPU() {
s.Errorf("Unexpected CPU setting %q for %s: got %d CPUs, want %d CPUs", val, path,
len(cpusInUse), runtime.NumCPU())
}
}
}
// setUp restarts the ui job to make sure that the login screen is shown and
// ARC Mini container is started. While the ui job is stopped, /var/log/messages
// is opened to remember the log position, which is returned as syslog.Reader.
// It can be later used to read syslog after restarting the ui job.
func setUp(ctx context.Context) (_ *syslog.Reader, retErr error) {
if err := upstart.StopJob(ctx, "ui"); err != nil {
return nil, err
}
sr, err := syslog.NewReader(ctx)
if err != nil {
// Note: leave "ui" job stopped. A following test should handle
// such a situation properly, if necessary.
return nil, err
}
defer func() {
if retErr != nil {
sr.Close()
}
}()
if err := upstart.StartJob(ctx, "ui"); err != nil {
return nil, err
}
return sr, nil
}
// RunTest exercises conditions the ARC mini container needs to satisfy.
func RunTest(ctx context.Context, s *testing.State) {
sr, err := setUp(ctx)
if err != nil {
s.Fatal("Failed to set up test: ", err)
}
defer sr.Close()
testZygote(ctx, s)
// Note: testZygote will wait for the mini container boot, or fail
// with s.Fatal() family.
// So, the following subtests can assume that the mini container is
// running somehow.
// In case of boot failure, the following subtests won't run
// intentionally.
testCoreServices(ctx, s)
testAndroidLogs(ctx, s, sr)
testSELinuxLabels(ctx, s)
testCgroupDirectory(ctx, s)
testBootOATSymlinks(ctx, s)
testDevFiles(ctx, s)
testCPUSet(ctx, s)
}