blob: 66d3a37e774d0b84d2f7f56b56a3409cf9abb4e5 [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.
package dut
import (
"fmt"
"log"
"strconv"
"strings"
"time"
"github.com/google/syz-repro-automation/cmd"
)
const (
dutSleep = 2 * time.Second
flashBuffer = 20 * time.Minute
)
type leasedDut struct {
hostname string
model string
imageID string
expTime time.Time
}
var currDut *leasedDut
// Get leases a DUT if the current DUT is nil, the model requested for the DUT has changed, or the DUT lease is about to expire.
// Otherwise if only the DUT imageID has changed, we will get the new image.
// If all fields are the same, Get does nothing.
// Returns the current DUT's hostname.
func Get(model, imageID string, minutes int, timeNeeded time.Duration) (string, error) {
if currDut == nil || currDut.model != model || time.Now().Add(timeNeeded).Add(flashBuffer).After(currDut.expTime) {
Abandon()
hostname, err := lease(model, minutes)
if err != nil {
return "", err
}
expTime := time.Now().Add(time.Duration(minutes) * time.Minute)
if err := getKernel(hostname, imageID); err != nil {
abandonDut(hostname)
return "", err
}
// Note imageID may not correspond to the actual image on the DUT.
// If user given imageID does not work and second-latest image is flashed onto DUT,
// imageID will still equal user given imageID. This way if user given imageID is present again,
// we will not try to flash the user given imageID again, and instead use the current DUT.
currDut = &leasedDut{
hostname: hostname,
model: model,
imageID: imageID,
expTime: expTime,
}
return currDut.hostname, nil
} else if currDut.imageID != imageID {
if err := getKernel(currDut.hostname, imageID); err != nil {
Abandon()
return "", err
}
currDut.imageID = imageID
}
return currDut.hostname, nil
}
// lease leases a DUT of type model and for specified minutes, runs crosfleet dut lease.
// Returns leased DUT's hostname.
func lease(model string, minutes int) (string, error) {
log.Printf("Leasing model %v device for %v minutes...\n", model, minutes)
ret, err := cmd.RunCmd(true, "crosfleet", "dut", "lease", "-model", model, "--minutes", strconv.Itoa(minutes))
if err != nil {
return "", fmt.Errorf("error leasing device: %v", err)
}
// Prints out lease information for user.
log.Println(ret)
// ret looks like "Leased chromeos6-row18-rack16-host10 until 19 Jul 21 19:58 UTC".
// Returns hostname, e.g. chromeos6-row18-rack16-host10.
return strings.Split(ret, " ")[1] + ".cros", nil
}
func getKernel(hostname, imageID string) error {
if err := flashKernel(hostname, imageID); err != nil {
if imageID == "" {
return fmt.Errorf("error flashing latest kernel: %v", err)
}
log.Printf("Flashing image %v failed: %v.\nTrying to flash latest image.", imageID, err)
if err := flashKernel(hostname, ""); err != nil {
return fmt.Errorf("error flashing latest kernel: %v", err)
}
}
return nil
}
// flashKernel flashes a kernel onto the DUT at hostname, runs cros flash.
func flashKernel(hostname, imageID string) error {
board, err := getBoard(hostname)
if err != nil {
return fmt.Errorf("unable to get board for DUT: %v", err)
}
if imageID == "" {
log.Printf("Image id not provided, fetching second-latest image for board %v...\n", board)
imageID, err = getSecondLatestImage(board)
if err != nil {
return fmt.Errorf("unable to get latest image for board: %v", err)
}
}
log.Printf("Flashing kernel onto DUT...")
ssh := "ssh://root@" + hostname
xBuddy := "xBuddy://remote/" + board + "-debug-kernel-postsubmit/" + imageID
if _, err = cmd.RunCmd(true, "cros", "flash", "--board="+board, ssh, xBuddy); err != nil {
return fmt.Errorf("error flashing kernel onto DUT: %v", err)
}
log.Printf("Finished flashing kernel onto DUT")
return nil
}
// WaitForDut checks if the DUT is up.
func WaitForDut(hostname string) {
log.Println("Pinging DUT at " + hostname + "...")
args := []string{
"ssh",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "BatchMode=yes",
"-o", "IdentitiesOnly=yes",
"-o", "StrictHostKeyChecking=no",
"-o", "ConnectTimeout=10",
"root@" + hostname, "pwd",
}
for {
if _, err := cmd.RunCmd(false, args...); err != nil {
log.Printf("ssh failed: %v. sleeping for %v and trying again.", err, dutSleep)
time.Sleep(dutSleep)
} else {
break
}
}
}
func abandonDut(hostname string) {
log.Println("Abandoning DUT at " + hostname + "...")
ret, err := cmd.RunCmd(false, "crosfleet", "dut", "abandon", hostname)
if err != nil {
log.Fatal(fmt.Errorf("error abandoning DUT: %v", err))
}
log.Println(ret)
}
// Abandon abandons the DUT at hostname, runs crosfleet dut abandon.
func Abandon() {
if currDut == nil {
return
}
abandonDut(currDut.hostname)
currDut = nil
}
func getSecondLatestImage(board string) (string, error) {
bucket := "gs://chromeos-image-archive/" + board + "-debug-kernel-postsubmit"
ret, err := cmd.RunCmd(false, "gsutil.py", "ls", bucket)
if err != nil {
return "", err
}
lines := strings.Split(ret, "\n")
// Get third to last line as the last line is blank.
// We get the second latest image because flashing the latest image runs into issues.
imageLine := lines[len(lines)-3]
// imageLine looks like gs://chromeos-image-archive/octopus-debug-kernel-postsubmit/R94-14102.0.0-51496-8841198623588369056/.
sections := strings.Split(imageLine, "/")
// Get second to last line as the last line is blank.
latestImage := sections[len(sections)-2]
log.Printf("Found latest image %v for board %v\n", latestImage, board)
return latestImage, nil
}
func getBoard(hostname string) (string, error) {
ret, err := cmd.RunCmd(false, "crosfleet", "dut", "info", hostname)
if err != nil {
return "", fmt.Errorf("error getting dut info: %v", err)
}
/* ret looks like:
DUT_HOSTNAME=chromeos6-row18-rack16-host4.cros
MODEL=garg
BOARD=octopus
SERVO_HOSTNAME=chromeos6-row17-rack16-labstation1
SERVO_PORT=9985
SERVO_SERIAL=G1911051544 */
lines := strings.Split(ret, "\n")
return strings.Split(lines[2], "=")[1], nil
}