blob: 96ce4cbf41448144d634a216684e95ad75a19277 [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 tracking
import (
"context"
"flag"
"fmt"
"log"
"os"
"testing"
"time"
"go.fuchsia.dev/fuchsia/src/sys/pkg/tests/system-tests/check"
"go.fuchsia.dev/fuchsia/src/sys/pkg/tests/system-tests/pave"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/artifacts"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/device"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/packages"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/sl4f"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/updater"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/util"
"go.fuchsia.dev/fuchsia/tools/lib/color"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
)
var c *config
func TestMain(m *testing.M) {
log.SetPrefix("tracking-test: ")
log.SetFlags(log.Ldate | log.Ltime | log.LUTC | log.Lshortfile)
var err error
c, err = newConfig(flag.CommandLine)
if err != nil {
log.Fatalf("failed to create config: %s", err)
}
flag.Parse()
if err = c.validate(); err != nil {
log.Fatalf("config is invalid: %s", err)
}
os.Exit(m.Run())
}
func TestOTA(t *testing.T) {
ctx := context.Background()
l := logger.NewLogger(
logger.TraceLevel,
color.NewColor(color.ColorAuto),
os.Stdout,
os.Stderr,
"tracking-test: ")
l.SetFlags(logger.Ldate | logger.Ltime | logger.LUTC | logger.Lshortfile)
ctx = logger.WithLogger(ctx, l)
deviceClient, err := c.deviceConfig.NewDeviceClient(ctx)
if err != nil {
t.Fatalf("failed to create ota test client: %s", err)
}
defer deviceClient.Close()
l = logger.NewLogger(
logger.TraceLevel,
color.NewColor(color.ColorAuto),
os.Stdout,
os.Stderr,
device.NewEstimatedMonotonicTime(deviceClient, "tracking-test: "),
)
l.SetFlags(logger.Ldate | logger.Ltime | logger.LUTC | logger.Lshortfile)
ctx = logger.WithLogger(ctx, l)
// Creating a sl4f.Client requires knowing the build currently running
// on the device, which not all test cases know during start. Store the
// one true client here, and pass around pointers to the various
// functions that may use it or device.Client to interact with the
// target. All OTA attempts must first Close and nil out an existing
// rpcClient and replace it with a new one after reboot. The final
// rpcClient, if present, will be closed by the defer here.
rpcClient, err := initializeDevice(ctx, deviceClient)
if err != nil {
t.Fatalf("failed to initialize deivce: %v", err)
}
defer func() {
if rpcClient != nil {
rpcClient.Close()
}
}()
testTrackingOTAs(t, ctx, deviceClient, &rpcClient)
}
func testTrackingOTAs(t *testing.T, ctx context.Context, device *device.Client, rpcClient **sl4f.Client) {
builder, err := c.upgradeBuildConfig.GetBuilder()
if err != nil {
t.Fatal(err)
}
// We only check ABR after the first update, since we can't be sure if
// the initial version of Fuchsia is recent enough to support ABR, but
// it should support ABR after the first OTA.
checkABR := false
lastBuildID := ""
attempt := 1
for {
logger.Infof(ctx, "Look up latest build for builder %s", builder)
buildID, err := builder.GetLatestBuildID(ctx)
if err != nil {
t.Fatalf("error getting latest build for builder %s: %s", builder, err)
}
if buildID == lastBuildID {
logger.Infof(ctx, "already updated to %s, sleeping", buildID)
time.Sleep(60 * time.Second)
continue
}
logger.Infof(ctx, "Tracking Test Attempt %d upgrading from build %s to build %s", attempt, lastBuildID, buildID)
if err := util.RunWithTimeout(ctx, c.cycleTimeout, func() error {
return testTrackingOTAAttempt(ctx, device, rpcClient, buildID, checkABR)
}); err != nil {
t.Fatalf("Tracking Test Attempt %d failed: %s", attempt, err)
}
logger.Infof(ctx, "Tracking Test Attempt %d successful", attempt)
logger.Infof(ctx, "------------------------------------------------------------------------------")
checkABR = true
lastBuildID = buildID
attempt += 1
}
}
func testTrackingOTAAttempt(
ctx context.Context,
device *device.Client,
rpcClient **sl4f.Client,
buildID string,
checkABR bool,
) error {
outputDir, cleanup, err := c.archiveConfig.OutputDir()
if err != nil {
return fmt.Errorf("failed to get output directory: %w", err)
}
defer cleanup()
build, err := c.archiveConfig.BuildArchive().GetBuildByID(ctx, buildID, outputDir, nil)
if err != nil {
return fmt.Errorf("failed to find build %s: %w", buildID, err)
}
repo, err := build.GetPackageRepository(ctx)
if err != nil {
return fmt.Errorf("failed to get repo for build: %w", err)
}
expectedSystemImageMerkle, err := repo.LookupUpdateSystemImageMerkle(ctx)
if err != nil {
return fmt.Errorf("failed to get repo system image merkle: %w", err)
}
upToDate, err := check.IsDeviceUpToDate(ctx, device, expectedSystemImageMerkle)
if err != nil {
return fmt.Errorf("failed to check if device is up to date: %w", err)
}
if upToDate {
logger.Infof(ctx, "device already up to date")
return nil
}
logger.Infof(ctx, "\n\n")
logger.Infof(ctx, "OTAing to %s", build)
return systemOTA(ctx, device, rpcClient, repo, checkABR)
}
func initializeDevice(ctx context.Context, deviceClient *device.Client) (*sl4f.Client, error) {
outputDir, cleanup, err := c.archiveConfig.OutputDir()
if err != nil {
return nil, err
}
defer cleanup()
downgradeBuild, err := c.downgradeBuildConfig.GetBuild(ctx, deviceClient, outputDir)
if err != nil {
return nil, fmt.Errorf("failed to get downgrade build: %w", err)
}
var rpcClient *sl4f.Client
ch := make(chan *sl4f.Client)
if downgradeBuild != nil {
if err := util.RunWithTimeout(ctx, c.paveTimeout, func() error {
rpcClient, err = paveDevice(ctx, deviceClient, downgradeBuild)
ch <- rpcClient
return err
}); err != nil {
return nil, err
}
rpcClient = <-ch
}
return rpcClient, nil
}
func paveDevice(ctx context.Context, device *device.Client, downgradeBuild artifacts.Build) (*sl4f.Client, error) {
downgradeRepo, err := downgradeBuild.GetPackageRepository(ctx)
if err != nil {
return nil, fmt.Errorf("error getting downgrade repository: %w", err)
}
logger.Infof(ctx, "starting pave")
expectedSystemImageMerkle, err := downgradeRepo.LookupUpdateSystemImageMerkle(ctx)
if err != nil {
return nil, fmt.Errorf("error extracting expected system image merkle: %w", err)
}
if err := pave.PaveDevice(ctx, device, downgradeBuild); err != nil {
return nil, fmt.Errorf("failed to pave device during initialization: %w", err)
}
rpcClient, err := device.StartRpcSession(ctx, downgradeRepo)
if err != nil {
// FIXME(40913): every downgrade builder should at least build
// sl4f as a universe package.
logger.Infof(ctx, "unable to connect to sl4f after pave: %s", err)
//t.Fatalf("unable to connect to sl4f after pave: %s", err)
}
// We always boot into the A partition after a pave.
expectedConfig := sl4f.ConfigurationA
if err := check.ValidateDevice(
ctx,
device,
rpcClient,
expectedSystemImageMerkle,
&expectedConfig,
true,
); err != nil {
return nil, err
}
logger.Infof(ctx, "paving successful")
return rpcClient, nil
}
func systemOTA(ctx context.Context, device *device.Client, rpcClient **sl4f.Client, repo *packages.Repository, checkABR bool) error {
expectedSystemImageMerkle, err := repo.LookupUpdateSystemImageMerkle(ctx)
if err != nil {
return fmt.Errorf("error extracting expected system image merkle: %w", err)
}
return otaToPackage(
ctx,
device,
rpcClient,
repo,
expectedSystemImageMerkle,
"fuchsia-pkg://fuchsia.com/update",
checkABR,
)
}
func otaToPackage(
ctx context.Context,
device *device.Client,
rpcClient **sl4f.Client,
repo *packages.Repository,
expectedSystemImageMerkle string,
updatePackageUrl string,
checkABR bool,
) error {
expectedConfig, err := check.DetermineTargetABRConfig(ctx, *rpcClient)
if err != nil {
return fmt.Errorf("error determining target config: %w", err)
}
upToDate, err := check.IsDeviceUpToDate(ctx, device, expectedSystemImageMerkle)
if err != nil {
return fmt.Errorf("failed to check if device is up to date: %w", err)
}
if upToDate {
return fmt.Errorf("device already updated to the expected version %q", expectedSystemImageMerkle)
}
u := updater.NewSystemUpdater(repo, updatePackageUrl)
if err := u.Update(ctx, device); err != nil {
return fmt.Errorf("failed to download OTA: %w", err)
}
logger.Infof(ctx, "Rebooting device")
startTime := time.Now()
if err = device.Reboot(ctx); err != nil {
return fmt.Errorf("device failed to reboot after OTA applied: %w", err)
}
logger.Infof(ctx, "Reboot complete in %s", time.Now().Sub(startTime))
logger.Infof(ctx, "Validating device")
// Disconnect from sl4f since we rebooted the device.
//
// FIXME(47145) To avoid fxbug.dev/47145, we need to delay
// disconnecting from sl4f until after we reboot the device. Otherwise
// we risk leaving the ssh session in a bad state.
if *rpcClient != nil {
(*rpcClient).Close()
*rpcClient = nil
}
*rpcClient, err = device.StartRpcSession(ctx, repo)
if err != nil {
return fmt.Errorf("unable to connect to sl4f after OTA: %w", err)
}
if err := check.ValidateDevice(
ctx,
device,
*rpcClient,
expectedSystemImageMerkle,
expectedConfig,
checkABR,
); err != nil {
return fmt.Errorf("failed to validate after OTA: %w", err)
}
return nil
}