blob: cc0e4151d445005fdf6cf51aec2a524313bebcda [file] [log] [blame] [edit]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"encoding/json"
"errors"
"fmt"
"log"
"os/exec"
"regexp"
"go.chromium.org/chromiumos/config/go/test/api"
labapi "go.chromium.org/chromiumos/config/go/test/lab/api"
)
// dutModelFromDut returns the dut model for a DUT.
func dutModelFromDut(dut *labapi.Dut) *labapi.DutModel {
if dut == nil {
return nil
}
switch hw := dut.GetDutType().(type) {
case *labapi.Dut_Chromeos:
return hw.Chromeos.GetDutModel()
case *labapi.Dut_Android_:
return hw.Android.GetDutModel()
case *labapi.Dut_Devboard_:
return hw.Devboard.GetDutModel()
}
return nil
}
// getBoard returns board value for a scheduling unit.
func getBoard(unit *api.SchedulingUnit) string {
return dutModelFromDut(unit.GetPrimaryTarget().GetSwarmingDef().GetDutInfo()).GetBuildTarget()
}
func checkBoardInVMBoardsList(board string) bool {
for _, vmBoard := range vmBoards {
if board == vmBoard {
return true
}
}
return false
}
func getBuildReportJSON(gcsPath string) ([]byte, error) {
// Execute the gsutil ls command
log.Printf("Checking image %s status...", gcsPath)
cmd := exec.Command("gsutil", "cat", gcsPath+"/"+buildReportJSON)
log.Printf("Executing command: %s", cmd.String())
output, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("error executing gsutil command: %v, output: %s", err, string(output))
}
// Check if the stdout is empty. No build report found
if len(output) == 0 {
return nil, fmt.Errorf("No build report found")
}
return output, nil
}
// isVMImageAvailable reads build report in gs bucket and infers if the builder has successfully completed.
func isVMImageAvailable(gcsPath string) (bool, error) {
buildReportJSON, err := getBuildReportJSON(gcsPath)
if err != nil {
log.Printf("Error while getting build report: %s", err)
}
// Unmarshal the JSON string into the map
var result map[string]interface{}
err = json.Unmarshal(buildReportJSON, &result)
if err != nil {
return false, fmt.Errorf("error parsing JSON: %v", err)
}
// safely parse the nested structure
status, ok := result["status"].(map[string]interface{})
if !ok {
return false, fmt.Errorf("status key not found or is not in expected format")
}
value, ok := status["value"].(string)
if !ok {
return false, fmt.Errorf("value key not found or is not in expected format")
}
// check the status value from build report
log.Printf("Status is %s\n", value)
if value == success {
log.Printf("Image %s is available", gcsPath)
return true, nil
}
log.Printf("Image %s is not available", gcsPath)
return false, nil
}
// getImageVersion returns the image version from a given image gs url(installPath) ex-`R128-15964.4.0`.
func getImageVersionFromInstallPath(installPath string) (string, error) {
if installPath == "" {
return "", errors.New("Empty Installpath")
}
// Capturing group regex. match[0] contains everything including /R, match[1] contains everything captured as part of (.+)
re := regexp.MustCompile(`/R(.+)$`)
match := re.FindStringSubmatch(installPath)
if len(match) < 2 {
return "", errors.New("milestone version not found in installPath")
}
milestone := match[1]
return "R" + milestone, nil
}
// getTargetedImageVersion fetches the chromeos image version of boards on which tests are to be executed.
// Note: Is same for all boards for a single CTP request.
func getTargetedImageVersion(req *api.InternalTestplan, log *log.Logger) string {
// find the image version from the first valid gcs installPath
for _, tc := range req.TestCases {
for _, units := range tc.GetSchedulingUnitOptions() {
for _, unit := range units.GetSchedulingUnits() {
if unit.GetDynamicUpdateLookupTable() != nil {
gcsInstallPath, exists := unit.GetDynamicUpdateLookupTable()[installPath]
if exists {
imageVersion, err := getImageVersionFromInstallPath(gcsInstallPath)
if err != nil {
log.Printf("Error while getting image version for gcsInstallPath %s: %s", gcsInstallPath, err)
continue
}
log.Printf("Found image version for the request: %s", imageVersion)
return imageVersion
}
}
}
}
}
return ""
}
// isAnyVMImageAvailable returns true if any corresponding VM image for same version is available. Availability
// means that the builder has successfully completed building image and should be available to lease a VM with it.
func isAnyVMImageAvailable(req *api.InternalTestplan, log *log.Logger) (bool, string, string) {
// fetch image version of intended boards. Same version of VM boards will be used, if available.
targetedVersion := getTargetedImageVersion(req, log)
if targetedVersion == "" {
log.Printf("Failed to find image version from the request. Skipping AutoVM test shifter filter execution.")
return false, "", ""
}
// check for all valid VM boards for image availability. Stop as soon as any is found available.
log.Printf("Checking availability for VM board images ...")
for _, board := range vmBoards {
// form the gcsImagePath for VM board to check image availability.
gcsImagePath := gsBucketPath + board + "-" + release + targetedVersion
log.Printf("GCS image path for board %s: %s", board, gcsImagePath)
exists, err := isVMImageAvailable(gcsImagePath)
if err != nil {
log.Printf("Error while checking image availability: %s", err)
continue
}
if exists {
return exists, board, targetedVersion
}
}
log.Printf("No images found available.")
return false, "", ""
}