AutoVM filter updates
Do not remove VM scheduling unit if already present
BUG=None
TEST=UT
Change-Id: Iac71e34e59dfed55593a0e8a34805aab9c1ef148
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/5860131
Tested-by: Varun Srivastav <varunsrivastav@google.com>
Auto-Submit: Varun Srivastav <varunsrivastav@google.com>
Reviewed-by: Derek Beckett <dbeckett@chromium.org>
Commit-Queue: Varun Srivastav <varunsrivastav@google.com>
diff --git a/src/go.chromium.org/chromiumos/test/ctpv2/autovm_test_shifter_filter/main.go b/src/go.chromium.org/chromiumos/test/ctpv2/autovm_test_shifter_filter/main.go
index 6abadd7..6d08ec2 100644
--- a/src/go.chromium.org/chromiumos/test/ctpv2/autovm_test_shifter_filter/main.go
+++ b/src/go.chromium.org/chromiumos/test/ctpv2/autovm_test_shifter_filter/main.go
@@ -4,13 +4,9 @@
package main
import (
- "encoding/json"
- "errors"
"fmt"
"log"
"os"
- "os/exec"
- "regexp"
server "go.chromium.org/chromiumos/test/ctpv2/common/server_template"
@@ -28,7 +24,7 @@
success = "SUCCESS"
)
-var vmBoards = []string{"betty", "betty-arc-t", "reven-vmtest", "amd64-generic", "betty-kernelnext", "betty-arc-r", "betty-arc-v"}
+var vmBoards = []string{"betty", "reven-vmtest", "amd64-generic"}
// isTestVMCompatible returns true if the test can be safely executed on a VM.
func isTestVMCompatible(testCase *api.CTPTestCase) bool {
@@ -55,8 +51,8 @@
return false
}
-func generateVMSchedulingUnitOptions(board string, version string) []*api.SchedulingUnitOptions {
- schedulingUnit := &api.SchedulingUnit{
+func generateVMSchedulingUnit(board string, version string) *api.SchedulingUnit {
+ return &api.SchedulingUnit{
PrimaryTarget: &api.Target{
SwarmingDef: &api.SwarmingDefinition{
DutInfo: &testapi.Dut{
@@ -72,16 +68,22 @@
"installPath": "gs://chromeos-image-archive/" + board + "-" + release + version,
},
}
- var schedulingUnits []*api.SchedulingUnit
- schedulingUnits = append(schedulingUnits, schedulingUnit)
- schedulingUnitOption := &api.SchedulingUnitOptions{
- SchedulingUnits: schedulingUnits,
+}
+
+// updateSchedulingUnitOption updates the list of Scheduling units. Returns nil if no VMlab Scheduling unit found.
+func updateSchedulingUnitOption(schedulingUnitOption *api.SchedulingUnitOptions, board string, version string) *api.SchedulingUnitOptions {
+ var filteredSchedulingUnits []*api.SchedulingUnit
+ for _, schedulingUnit := range schedulingUnitOption.GetSchedulingUnits() {
+ // if VM board in scheduling unit then do not remove. This will trigger VMlab flow
+ if checkBoardInVMBoardsList(getBoard(schedulingUnit)) {
+ filteredSchedulingUnits = append(filteredSchedulingUnits, schedulingUnit)
+ }
}
-
- var schedulingUnitOptions []*api.SchedulingUnitOptions
- schedulingUnitOptions = append(schedulingUnitOptions, schedulingUnitOption)
-
- return schedulingUnitOptions
+ if len(filteredSchedulingUnits) == 0 {
+ return nil
+ }
+ schedulingUnitOption.SchedulingUnits = filteredSchedulingUnits
+ return schedulingUnitOption
}
// updateSchedulingUnitOptions updates the schedulingUnitOptions for each test case in internal test plan request.
@@ -89,8 +91,27 @@
for _, testCase := range req.GetTestCases() {
if isTestVMCompatible(testCase) {
- updatedSchedulingUnitOptions := generateVMSchedulingUnitOptions(board, version)
- testCase.SchedulingUnitOptions = updatedSchedulingUnitOptions
+ var updatedSchedulingUnitOptions []*api.SchedulingUnitOptions
+ for _, schedulingUnitOption := range testCase.GetSchedulingUnitOptions() {
+ updatedSchedulingUnitOption := updateSchedulingUnitOption(schedulingUnitOption, board, version)
+ if updatedSchedulingUnitOption == nil {
+ continue
+ }
+ updatedSchedulingUnitOptions = append(updatedSchedulingUnitOptions, updatedSchedulingUnitOption)
+ }
+ // if no schedulign unit found from intended scheduling units, then create a VM schedulign unit with available board/version
+ if len(updatedSchedulingUnitOptions) == 0 {
+ testCase.SchedulingUnitOptions = []*api.SchedulingUnitOptions{
+ {
+ SchedulingUnits: []*api.SchedulingUnit{
+ generateVMSchedulingUnit(board, version),
+ },
+ },
+ }
+ } else {
+ testCase.SchedulingUnitOptions = updatedSchedulingUnitOptions
+ }
+
} else {
log.Printf("Skippping as test : %s, isn't VM compatible", testCase.GetName())
}
@@ -99,125 +120,6 @@
return nil
}
-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
- fmt.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")
- }
- // Define a regex pattern to match "R" followed by digits and a hyphen
- 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() {
- gcsInstallPath := unit.GetDynamicUpdateLookupTable()[installPath]
- imageVersion, err := getImageVersionFromInstallPath(gcsInstallPath)
- if err != nil {
- // log the error and try for other install paths.
- 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, "", ""
-}
-
func executor(req *api.InternalTestplan, log *log.Logger) (*api.InternalTestplan, error) {
available, board, version := isAnyVMImageAvailable(req, log)
diff --git a/src/go.chromium.org/chromiumos/test/ctpv2/autovm_test_shifter_filter/main_test.go b/src/go.chromium.org/chromiumos/test/ctpv2/autovm_test_shifter_filter/main_test.go
index 9e67ea5..d76ce85 100644
--- a/src/go.chromium.org/chromiumos/test/ctpv2/autovm_test_shifter_filter/main_test.go
+++ b/src/go.chromium.org/chromiumos/test/ctpv2/autovm_test_shifter_filter/main_test.go
@@ -70,6 +70,31 @@
},
},
}
+ SchedulingUnitDedede := []*api.SchedulingUnit{
+ {
+ PrimaryTarget: &api.Target{
+ SwarmingDef: &api.SwarmingDefinition{
+ DutInfo: &testapi.Dut{
+ DutType: &testapi.Dut_Chromeos{
+ Chromeos: &testapi.Dut_ChromeOS{
+ DutModel: &testapi.DutModel{
+ BuildTarget: "dedede",
+ },
+ },
+ },
+ },
+ SwarmingLabels: []string{
+ "label-peripheral_wifi_state:WORKING",
+ "label-wificell:True",
+ },
+ },
+ },
+ DynamicUpdateLookupTable: map[string]string{
+ "board": "betty",
+ "installPath": "gs://chromeos-image-archive/dedede-release/R128-15964.4.0",
+ },
+ },
+ }
var SchedulingUnitOptions []*api.SchedulingUnitOptions
for _, board := range boards {
@@ -78,7 +103,10 @@
SchedulingUnitOptions = append(SchedulingUnitOptions, &api.SchedulingUnitOptions{SchedulingUnits: SchedulingUnitSkyrim})
case "betty":
SchedulingUnitOptions = append(SchedulingUnitOptions, &api.SchedulingUnitOptions{SchedulingUnits: SchedulingUnitBetty})
+ case "dedede":
+ SchedulingUnitOptions = append(SchedulingUnitOptions, &api.SchedulingUnitOptions{SchedulingUnits: SchedulingUnitDedede})
}
+
}
return SchedulingUnitOptions
@@ -255,7 +283,7 @@
t.Errorf("Error expected to be nil, got %s", err)
}
if version != expectedVersion {
- t.Errorf("Expected R128-15964.4.0, got %s", version)
+ t.Errorf("Expected %s, got %s", expectedVersion, version)
}
}
}
@@ -344,7 +372,7 @@
}
-func Test_UpdateSchedulingUnitOptions_VMCompatibleTest(t *testing.T) {
+func Test_UpdateSchedulingUnitOptions_VMCompatibleTest_WithVMSchedulingUnit(t *testing.T) {
schedulingUnitOptions := GenerateSchedulingUnitOptions([]string{"skyrim", "betty"})
testCase := GenerateCTPTestCase("test1", true, schedulingUnitOptions)
internalTestPlan := &api.InternalTestplan{
@@ -355,13 +383,41 @@
if err != nil {
t.Errorf("Expected nil, got %s", err)
}
+
+ if len(internalTestPlan.TestCases[0].GetSchedulingUnitOptions()) != 1 {
+ t.Errorf("Expected 2, got %d", len(internalTestPlan.TestCases[0].SchedulingUnitOptions))
+ }
+ expectedBoard := "betty"
+ updatedBoard := internalTestPlan.TestCases[0].GetSchedulingUnitOptions()[0].GetSchedulingUnits()[0].GetDynamicUpdateLookupTable()["board"]
+ if expectedBoard != updatedBoard {
+ t.Errorf("Expected %s, got %s", expectedBoard, updatedBoard)
+ }
+ if len(internalTestPlan.TestCases[0].GetSchedulingUnitOptions()[0].GetSchedulingUnits()) != 1 {
+ t.Errorf("Expected 1, got %d", len(internalTestPlan.TestCases[0].SchedulingUnitOptions))
+ }
+}
+
+func Test_UpdateSchedulingUnitOptions_VMCompatibleTest_WithNoVMSchedulingUnit(t *testing.T) {
+ schedulingUnitOptions := GenerateSchedulingUnitOptions([]string{"skyrim", "dedede"})
+ testCase := GenerateCTPTestCase("test1", true, schedulingUnitOptions)
+ internalTestPlan := &api.InternalTestplan{
+ TestCases: []*api.CTPTestCase{testCase},
+ }
+
+ err := updateSchedulingUnitOptions(internalTestPlan, "reven-vmtest", "R128-15964.4.0", log.New(os.Stdout, "DEBUG", log.LstdFlags))
+ if err != nil {
+ t.Errorf("Expected nil, got %s", err)
+ }
+
if len(internalTestPlan.TestCases[0].GetSchedulingUnitOptions()) != 1 {
t.Errorf("Expected 2, got %d", len(internalTestPlan.TestCases[0].SchedulingUnitOptions))
}
expectedBoard := "reven-vmtest"
updatedBoard := internalTestPlan.TestCases[0].GetSchedulingUnitOptions()[0].GetSchedulingUnits()[0].GetDynamicUpdateLookupTable()["board"]
if expectedBoard != updatedBoard {
- t.Errorf("Expected reven-vmtest, got %d", len(internalTestPlan.TestCases[0].SchedulingUnitOptions))
+ t.Errorf("Expected %s, got %s", expectedBoard, updatedBoard)
}
-
+ if len(internalTestPlan.TestCases[0].GetSchedulingUnitOptions()[0].GetSchedulingUnits()) != 1 {
+ t.Errorf("Expected 1, got %d", len(internalTestPlan.TestCases[0].SchedulingUnitOptions))
+ }
}
diff --git a/src/go.chromium.org/chromiumos/test/ctpv2/autovm_test_shifter_filter/utils.go b/src/go.chromium.org/chromiumos/test/ctpv2/autovm_test_shifter_filter/utils.go
new file mode 100644
index 0000000..cc0e415
--- /dev/null
+++ b/src/go.chromium.org/chromiumos/test/ctpv2/autovm_test_shifter_filter/utils.go
@@ -0,0 +1,170 @@
+// 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, "", ""
+}