blob: 90e55cb0026f2f705d9ca5b823aafb6b099d7b6b [file] [log] [blame]
// Copyright 2024 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 upgrade
import (
"context"
"fmt"
"math/rand"
"strings"
"time"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/artifacts"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/ffx"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/packages"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/util"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
)
type otaData struct {
name string
build artifacts.Build
repo *packages.Repository
updatePackage *packages.UpdatePackage
}
func (o *otaData) String() string {
return fmt.Sprintf("%s(%s)", o.name, o.build.String())
}
func newOtas(
ctx context.Context,
rand *rand.Rand,
ffx *ffx.FFXTool,
builds []artifacts.Build,
) ([]*otaData, error) {
logger.Infof(ctx, "Preparing OTA packages")
startTime := time.Now()
// Fetch all the build artifacts up front so it's not included in the
// `cycleTimeout` time limit.
otas := []*otaData{}
for i, build := range builds {
var name string
nth := len(builds) - 1 - i
if nth == 0 {
name = "N"
} else {
name = fmt.Sprintf("N-%d", nth)
}
// We'll initialize with the first build, so we don't need to download the
// blobs.
var blobFetchMode artifacts.BlobFetchMode
// If this isn't the first build, we'll want to modify it to
// make sure it's unique.
var addRandomData bool
if i == 0 {
blobFetchMode = artifacts.LazilyFetchBlobs
addRandomData = false
} else {
blobFetchMode = artifacts.PrefetchBlobs
addRandomData = true
}
ota, err := newOta(
ctx,
rand,
ffx,
build,
name,
blobFetchMode,
addRandomData,
)
if err != nil {
return []*otaData{}, err
}
otas = append(otas, ota)
}
// Finally, redo the last build as our prime build.
ota, err := newOta(
ctx,
rand,
ffx,
builds[len(builds)-1],
"N-prime",
artifacts.LazilyFetchBlobs,
true,
)
if err != nil {
return []*otaData{}, err
}
otas = append(otas, ota)
logger.Infof(ctx, "OTAs prepared in %s", time.Now().Sub(startTime))
return otas, nil
}
func newOta(
ctx context.Context,
rand *rand.Rand,
ffx *ffx.FFXTool,
build artifacts.Build,
name string,
blobFetchMode artifacts.BlobFetchMode,
addRandomData bool,
) (*otaData, error) {
logger.Debugf(ctx, "Creating OTA %s", name)
repo, err := build.GetPackageRepository(ctx, blobFetchMode, ffx.IsolateDir())
if err != nil {
return nil, fmt.Errorf("error getting repository: %w", err)
}
// Refresh with the latest ffx to make sure the metadata hasn't expired.
// FIXME(https://fxbug.dev/336897946): We need to use the latest ffx because
// F11's ffx doesn't actually refresh metadata.
if err := repo.RefreshMetadataWithFfx(ctx, ffx); err != nil {
return nil, err
}
updatePath := "update/0"
updatePackage, err := repo.OpenUpdatePackage(ctx, updatePath)
if err != nil {
return nil, fmt.Errorf("error opening %s package: %w", updatePath, err)
}
// If this isn't the first build, we'll want to modify it to make sure it's
// unique.
if addRandomData {
dstUpdatePath := util.AddSuffixToPackageName(
updatePackage.Path(),
strings.ToLower(fmt.Sprintf("ota-test-%s", name)),
)
updatePackage, _, err = addRandomFilesToUpdate(
ctx,
rand,
updatePackage,
dstUpdatePath,
)
if err != nil {
return nil, fmt.Errorf("failed to create update package %s: %w", dstUpdatePath, err)
}
}
ota := &otaData{
name: name,
build: build,
repo: repo,
updatePackage: updatePackage,
}
logger.Infof(ctx, "OTA %s update package: %s", ota, ota.updatePackage)
return ota, nil
}
// addRandomFilesToUpdate creates a new update package with a system image that
// contains a number of extra files filled with random bytes, which should be
// incompressible. It will loop until it has created an update package that is
// smaller than `-max-ota-size`.
func addRandomFilesToUpdate(
ctx context.Context,
rand *rand.Rand,
srcUpdate *packages.UpdatePackage,
dstUpdatePath string,
) (*packages.UpdatePackage, *packages.SystemImagePackage, error) {
avbTool, err := c.installerConfig.AVBTool()
if err != nil {
return nil, nil, fmt.Errorf("failed to intialize AVBTool: %w", err)
}
zbiTool, err := c.installerConfig.ZBITool()
if err != nil {
return nil, nil, fmt.Errorf("failed to initialize ZBITool: %w", err)
}
srcSystemImage, err := srcUpdate.OpenSystemImagePackage(ctx)
if err != nil {
return nil, nil, err
}
systemImageSize, err := srcSystemImage.SystemImageAlignedBlobSize(ctx)
if err != nil {
return nil, nil, fmt.Errorf("error determining system image size: %w", err)
}
dstSystemImagePath := util.AddSuffixToPackageName(dstUpdatePath, "system-image")
// Add random files to the system image package in the update. Clamp the
// package size to the upper bound if we have one, otherwise we'll just add
// a single block to make it unique.
dstUpdate, dstSystemImage, err := srcUpdate.EditSystemImagePackage(
ctx,
avbTool,
zbiTool,
"fuchsia.com",
dstUpdatePath,
c.bootfsCompression,
func(systemImage *packages.SystemImagePackage) (*packages.SystemImagePackage, error) {
if c.maxSystemImageSize == 0 {
return systemImage.AddRandomFilesWithAdditionalBytes(
ctx,
rand,
dstSystemImagePath,
packages.BlobBlockSize,
)
} else if c.maxSystemImageSize < systemImageSize {
return nil, fmt.Errorf(
"max system image size %d is smaller than the size of the system image %d",
c.maxSystemImageSize,
systemImageSize,
)
} else {
return systemImage.AddRandomFilesWithUpperBound(
ctx,
rand,
dstSystemImagePath,
c.maxSystemImageSize,
)
}
},
)
if err != nil {
return nil, nil, fmt.Errorf(
"failed to add random files to system images %q in update package %q: %w",
dstSystemImagePath,
dstUpdatePath,
err,
)
}
// Optionally add random files to zbi package in the update images.
if c.maxUpdateImagesSize != 0 {
dstZbiPath := util.AddSuffixToPackageName(dstUpdatePath, "update-images-zbi")
dstUpdate, _, err = dstUpdate.EditUpdateImages(
ctx,
dstUpdatePath,
func(updateImages *packages.UpdateImages) (*packages.UpdateImages, error) {
return updateImages.AddRandomFilesWithUpperBound(
ctx,
rand,
dstZbiPath,
c.maxUpdateImagesSize,
)
},
)
if err != nil {
return nil, nil, fmt.Errorf(
"failed to add random files to zbi package %q in update package %q: %w",
dstZbiPath,
dstUpdatePath,
err,
)
}
}
// Optionally add random files to the update package.
if c.maxUpdatePackageSize != 0 {
dstUpdate, err = dstUpdate.EditPackage(
ctx,
func(p packages.Package) (packages.Package, error) {
return p.AddRandomFilesWithUpperBound(
ctx,
rand,
dstUpdatePath,
c.maxUpdatePackageSize,
)
},
)
if err != nil {
return nil, nil, fmt.Errorf(
"failed to add random files to update package %q: %w",
dstUpdatePath,
err,
)
}
}
return dstUpdate, dstSystemImage, nil
}