Merge with upstream 2026-04-21 2/2 f105a548ca4 Roll recipe dependencies (trivial). 217f9752213 Roll recipe dependencies (trivial). cfc72a29d52 infra: Add builder for uprev_refvm_image 69160fe72f7 infra: Add recipe for upreving reference VM image a6f3039e046 devices: pci: stub: Implement setup_pci_config_mapping 9ec67f31091 Roll recipe dependencies (trivial). c6beb2afd0e Roll recipe dependencies (trivial). e4c638d5c4c devices: pci: vfio_pci: Don't use host IRQ as preferred guest IRQ 594ac95e04f cros_async: sys: linux: Handle malformed eventfd reads gracefully 02e79727307 Roll recipe dependencies (trivial). 38b12716bd3 Roll recipe dependencies (trivial). https://chromium.googlesource.com/crosvm/crosvm/+log/2e1df462c1b95edef475dc8d7e210c672aca08cd..f105a548ca472f1d061879a7ddaa5e725b4e7b95 BUG=b:481167698 Change-Id: I2a6218920f00ece1fcf3302f3a172fb1fc446574 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/7780040 Commit-Queue: Vineeth Pillai <vineethrp@google.com> Bot-Commit: crosvm LUCI CI <crosvm-luci-ci-builder@crosvm-infra.iam.gserviceaccount.com>
diff --git a/cros_async/src/sys/linux/event.rs b/cros_async/src/sys/linux/event.rs index a937974..1cb8fc5 100644 --- a/cros_async/src/sys/linux/event.rs +++ b/cros_async/src/sys/linux/event.rs
@@ -24,7 +24,17 @@ if n != 8 { return Err(AsyncError::EventAsync(base::Error::new(libc::ENODATA))); } - Ok(u64::from_ne_bytes(v.try_into().unwrap())) + match v.try_into() { + Ok(bytes) => Ok(u64::from_ne_bytes(bytes)), + Err(e) => { + base::error!( + "eventfd async read corrupted! n=8, Vec length is {}. Raw bytes: {:?}", + e.len(), + e + ); + Err(AsyncError::EventAsync(base::Error::new(libc::EINVAL))) + } + } } }
diff --git a/devices/src/pci/stub.rs b/devices/src/pci/stub.rs index 925d60d..3b7283b 100644 --- a/devices/src/pci/stub.rs +++ b/devices/src/pci/stub.rs
@@ -13,6 +13,7 @@ //! something to the guest on function 0. use base::RawDescriptor; +use base::SharedMemory; use resources::SystemAllocator; use serde::Deserialize; use serde::Deserializer; @@ -171,6 +172,18 @@ .ok_or(PciDeviceError::PciAllocationFailed) } + fn setup_pci_config_mapping( + &mut self, + shmem: &SharedMemory, + base: usize, + len: usize, + ) -> Result<bool> { + self.config_regs + .setup_mapping(shmem, base, len) + .map(|_| true) + .map_err(PciDeviceError::MmioSetup) + } + fn keep_rds(&self) -> Vec<RawDescriptor> { Vec::new() }
diff --git a/devices/src/pci/vfio_pci.rs b/devices/src/pci/vfio_pci.rs index e294b24..5422b53 100644 --- a/devices/src/pci/vfio_pci.rs +++ b/devices/src/pci/vfio_pci.rs
@@ -6,7 +6,6 @@ use std::cmp::Reverse; use std::collections::BTreeMap; use std::collections::BTreeSet; -use std::fs; use std::path::Path; use std::path::PathBuf; use std::str::FromStr; @@ -1681,23 +1680,11 @@ } fn preferred_irq(&self) -> PreferredIrq { - // Is INTx configured? - let pin = match self.config.read_config::<u8>(PCI_INTERRUPT_PIN) { - 1 => PciInterruptPin::IntA, - 2 => PciInterruptPin::IntB, - 3 => PciInterruptPin::IntC, - 4 => PciInterruptPin::IntD, - _ => return PreferredIrq::None, - }; - - // TODO: replace sysfs/irq value parsing with vfio interface - // reporting host allocated interrupt number and type. - let path = self.sysfs_path.join("irq"); - let gsi = fs::read_to_string(path) - .map(|v| v.trim().parse::<u32>().unwrap_or(0)) - .unwrap_or(0); - - PreferredIrq::Fixed { pin, gsi } + // Do not use a fixed IRQ for VFIO devices. The sysfs "irq" file reports a host-assigned IRQ + // number that is not meaningful for the guest and can exceed the u8 range required by the + // MP table. Let the VMM allocate a guest IRQ instead; VFIO handles host/guest + // interrupt mapping regardless of the guest IRQ number chosen. + PreferredIrq::Any } fn assign_irq(&mut self, irq_evt: IrqLevelEvent, pin: PciInterruptPin, irq_num: u32) {
diff --git a/infra/README.recipes.md b/infra/README.recipes.md index 99875b3..35d9b65 100644 --- a/infra/README.recipes.md +++ b/infra/README.recipes.md
@@ -20,6 +20,7 @@ * [push_to_github](#recipes-push_to_github) * [update_chromeos_merges](#recipes-update_chromeos_merges) * [uprev_baguette_image](#recipes-uprev_baguette_image) — Recipe for uploading uprevs of the baguette image. + * [uprev_refvm_image](#recipes-uprev_refvm_image) — Recipe for uploading uprevs of the refvm image. ## Recipe Modules ### *recipe_modules* / [crosvm](/infra/recipe_modules/crosvm) @@ -198,21 +199,29 @@ Recipe for uploading uprevs of the baguette image. — **def [RunSteps](/infra/recipes/uprev_baguette_image.py#37)(api: RecipeApi, properties: UprevBaguetteImageProperties):** +### *recipes* / [uprev\_refvm\_image](/infra/recipes/uprev_refvm_image.py) -[depot_tools/recipe_modules/bot_update]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/442cbd6d584d2c992ca9bcc19ecbd2235bea772e/recipes/README.recipes.md#recipe_modules-bot_update -[depot_tools/recipe_modules/depot_tools]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/442cbd6d584d2c992ca9bcc19ecbd2235bea772e/recipes/README.recipes.md#recipe_modules-depot_tools -[depot_tools/recipe_modules/gclient]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/442cbd6d584d2c992ca9bcc19ecbd2235bea772e/recipes/README.recipes.md#recipe_modules-gclient -[depot_tools/recipe_modules/git]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/442cbd6d584d2c992ca9bcc19ecbd2235bea772e/recipes/README.recipes.md#recipe_modules-git -[depot_tools/recipe_modules/gsutil]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/442cbd6d584d2c992ca9bcc19ecbd2235bea772e/recipes/README.recipes.md#recipe_modules-gsutil -[recipe_engine/recipe_modules/buildbucket]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/363e865839c69e49ce2f6b6857bed6a66eae0dca/README.recipes.md#recipe_modules-buildbucket -[recipe_engine/recipe_modules/cipd]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/363e865839c69e49ce2f6b6857bed6a66eae0dca/README.recipes.md#recipe_modules-cipd -[recipe_engine/recipe_modules/context]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/363e865839c69e49ce2f6b6857bed6a66eae0dca/README.recipes.md#recipe_modules-context -[recipe_engine/recipe_modules/file]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/363e865839c69e49ce2f6b6857bed6a66eae0dca/README.recipes.md#recipe_modules-file -[recipe_engine/recipe_modules/json]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/363e865839c69e49ce2f6b6857bed6a66eae0dca/README.recipes.md#recipe_modules-json -[recipe_engine/recipe_modules/path]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/363e865839c69e49ce2f6b6857bed6a66eae0dca/README.recipes.md#recipe_modules-path -[recipe_engine/recipe_modules/platform]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/363e865839c69e49ce2f6b6857bed6a66eae0dca/README.recipes.md#recipe_modules-platform -[recipe_engine/recipe_modules/properties]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/363e865839c69e49ce2f6b6857bed6a66eae0dca/README.recipes.md#recipe_modules-properties -[recipe_engine/recipe_modules/raw_io]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/363e865839c69e49ce2f6b6857bed6a66eae0dca/README.recipes.md#recipe_modules-raw_io -[recipe_engine/recipe_modules/step]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/363e865839c69e49ce2f6b6857bed6a66eae0dca/README.recipes.md#recipe_modules-step -[recipe_engine/recipe_modules/time]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/363e865839c69e49ce2f6b6857bed6a66eae0dca/README.recipes.md#recipe_modules-time -[recipe_engine/wkt/RecipeApi]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/363e865839c69e49ce2f6b6857bed6a66eae0dca/recipe_engine/recipe_api.py#439 +[DEPS](/infra/recipes/uprev_refvm_image.py#18): [depot\_tools/git][depot_tools/recipe_modules/git], [recipe\_engine/buildbucket][recipe_engine/recipe_modules/buildbucket], [recipe\_engine/cipd][recipe_engine/recipe_modules/cipd], [recipe\_engine/context][recipe_engine/recipe_modules/context], [recipe\_engine/file][recipe_engine/recipe_modules/file], [recipe\_engine/path][recipe_engine/recipe_modules/path], [recipe\_engine/properties][recipe_engine/recipe_modules/properties], [recipe\_engine/raw\_io][recipe_engine/recipe_modules/raw_io], [recipe\_engine/step][recipe_engine/recipe_modules/step] + + +Recipe for uploading uprevs of the refvm image. + +— **def [RunSteps](/infra/recipes/uprev_refvm_image.py#36)(api: RecipeApi, properties: UprevRefvmImageProperties):** + +[depot_tools/recipe_modules/bot_update]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/e16ce5a52dbf64646a1de370f562672bbeb9d9f4/recipes/README.recipes.md#recipe_modules-bot_update +[depot_tools/recipe_modules/depot_tools]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/e16ce5a52dbf64646a1de370f562672bbeb9d9f4/recipes/README.recipes.md#recipe_modules-depot_tools +[depot_tools/recipe_modules/gclient]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/e16ce5a52dbf64646a1de370f562672bbeb9d9f4/recipes/README.recipes.md#recipe_modules-gclient +[depot_tools/recipe_modules/git]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/e16ce5a52dbf64646a1de370f562672bbeb9d9f4/recipes/README.recipes.md#recipe_modules-git +[depot_tools/recipe_modules/gsutil]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/e16ce5a52dbf64646a1de370f562672bbeb9d9f4/recipes/README.recipes.md#recipe_modules-gsutil +[recipe_engine/recipe_modules/buildbucket]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/36229065a1e627aa00341564303ac1336c51d925/README.recipes.md#recipe_modules-buildbucket +[recipe_engine/recipe_modules/cipd]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/36229065a1e627aa00341564303ac1336c51d925/README.recipes.md#recipe_modules-cipd +[recipe_engine/recipe_modules/context]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/36229065a1e627aa00341564303ac1336c51d925/README.recipes.md#recipe_modules-context +[recipe_engine/recipe_modules/file]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/36229065a1e627aa00341564303ac1336c51d925/README.recipes.md#recipe_modules-file +[recipe_engine/recipe_modules/json]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/36229065a1e627aa00341564303ac1336c51d925/README.recipes.md#recipe_modules-json +[recipe_engine/recipe_modules/path]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/36229065a1e627aa00341564303ac1336c51d925/README.recipes.md#recipe_modules-path +[recipe_engine/recipe_modules/platform]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/36229065a1e627aa00341564303ac1336c51d925/README.recipes.md#recipe_modules-platform +[recipe_engine/recipe_modules/properties]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/36229065a1e627aa00341564303ac1336c51d925/README.recipes.md#recipe_modules-properties +[recipe_engine/recipe_modules/raw_io]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/36229065a1e627aa00341564303ac1336c51d925/README.recipes.md#recipe_modules-raw_io +[recipe_engine/recipe_modules/step]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/36229065a1e627aa00341564303ac1336c51d925/README.recipes.md#recipe_modules-step +[recipe_engine/recipe_modules/time]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/36229065a1e627aa00341564303ac1336c51d925/README.recipes.md#recipe_modules-time +[recipe_engine/wkt/RecipeApi]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/36229065a1e627aa00341564303ac1336c51d925/recipe_engine/recipe_api.py#439
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg index 92770ed..88259e0 100644 --- a/infra/config/generated/cr-buildbucket.cfg +++ b/infra/config/generated/cr-buildbucket.cfg
@@ -194,6 +194,21 @@ service_account: "crosvm-luci-ci-builder@crosvm-infra.iam.gserviceaccount.com" } builders { + name: "refvm_uprev" + swarming_host: "chromium-swarm.appspot.com" + dimensions: "cpu:x86-64" + dimensions: "os:Ubuntu" + dimensions: "pool:luci.crosvm.ci" + recipe { + name: "uprev_refvm_image" + cipd_package: "infra/recipe_bundles/chromium.googlesource.com/crosvm/crosvm" + cipd_version: "refs/heads/main" + properties_j: "bot:true" + properties_j: "push:true" + } + service_account: "crosvm-luci-ci-builder@crosvm-infra.iam.gserviceaccount.com" + } + builders { name: "update_chromeos_merges" swarming_host: "chromium-swarm.appspot.com" dimensions: "cpu:x86-64"
diff --git a/infra/config/generated/luci-milo.cfg b/infra/config/generated/luci-milo.cfg index e0751a3..c01b2bb 100644 --- a/infra/config/generated/luci-milo.cfg +++ b/infra/config/generated/luci-milo.cfg
@@ -77,5 +77,8 @@ builders { name: "buildbucket/luci.crosvm.ci/baguette_uprev" } + builders { + name: "buildbucket/luci.crosvm.ci/refvm_uprev" + } builder_view_only: true }
diff --git a/infra/config/generated/luci-notify.cfg b/infra/config/generated/luci-notify.cfg index 4855300..8ef3c06 100644 --- a/infra/config/generated/luci-notify.cfg +++ b/infra/config/generated/luci-notify.cfg
@@ -164,6 +164,20 @@ } builders { bucket: "ci" + name: "refvm_uprev" + } +} +notifiers { + notifications { + on_change: true + email { + recipients: "crosvm-uprev@grotations.appspotmail.com" + recipients: "keiichiw@google.com" + recipients: "zihanchen@google.com" + } + } + builders { + bucket: "ci" name: "update_chromeos_merges" } }
diff --git a/infra/config/generated/luci-scheduler.cfg b/infra/config/generated/luci-scheduler.cfg index 192c245..c851c6a 100644 --- a/infra/config/generated/luci-scheduler.cfg +++ b/infra/config/generated/luci-scheduler.cfg
@@ -117,6 +117,17 @@ } } job { + id: "refvm_uprev" + realm: "ci" + schedule: "0 12 * * *" + acl_sets: "ci" + buildbucket { + server: "cr-buildbucket.appspot.com" + bucket: "ci" + builder: "refvm_uprev" + } +} +job { id: "update_chromeos_merges" realm: "ci" schedule: "0,30 * * * *"
diff --git a/infra/config/main.star b/infra/config/main.star index 9240946..e15ea4a 100755 --- a/infra/config/main.star +++ b/infra/config/main.star
@@ -459,3 +459,16 @@ schedule = "0 12 * * *", # Check for uprevs daily postsubmit = False, ) + +infra_builder( + name = "refvm_uprev", + executable = luci.recipe( + name = "uprev_refvm_image", + ), + properties = { + "push": True, + "bot": True, + }, + schedule = "0 12 * * *", # Check for uprevs daily + postsubmit = False, +)
diff --git a/infra/config/recipes.cfg b/infra/config/recipes.cfg index d6117b6..80df6fa 100644 --- a/infra/config/recipes.cfg +++ b/infra/config/recipes.cfg
@@ -18,12 +18,12 @@ "deps": { "depot_tools": { "branch": "refs/heads/main", - "revision": "442cbd6d584d2c992ca9bcc19ecbd2235bea772e", + "revision": "e16ce5a52dbf64646a1de370f562672bbeb9d9f4", "url": "https://chromium.googlesource.com/chromium/tools/depot_tools.git" }, "recipe_engine": { "branch": "refs/heads/main", - "revision": "363e865839c69e49ce2f6b6857bed6a66eae0dca", + "revision": "36229065a1e627aa00341564303ac1336c51d925", "url": "https://chromium.googlesource.com/infra/luci/recipes-py.git" } },
diff --git a/infra/recipes/uprev_refvm_image.expected/Invalid size output.json b/infra/recipes/uprev_refvm_image.expected/Invalid size output.json new file mode 100644 index 0000000..b7c64aa --- /dev/null +++ b/infra/recipes/uprev_refvm_image.expected/Invalid size output.json
@@ -0,0 +1,146 @@ +[ + { + "cmd": [ + "python3", + "-u", + "RECIPE_MODULE[depot_tools::git]/resources/git_setup.py", + "--path", + "[CLEANUP]/tmp_tmp_1", + "--url", + "https://chromium.googlesource.com/chromiumos/platform/tast-tests" + ], + "name": "git setup" + }, + { + "cmd": [ + "git", + "fetch", + "origin", + "main", + "--progress", + "--depth", + "1" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "env": { + "PATH": "RECIPE_REPO[depot_tools]:<PATH>" + }, + "infra_step": true, + "name": "git fetch" + }, + { + "cmd": [ + "git", + "checkout", + "-f", + "FETCH_HEAD" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git checkout" + }, + { + "cmd": [ + "git", + "rev-parse", + "HEAD" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "read revision", + "~followup_annotations": [ + "@@@STEP_TEXT@<br/>checked out 'deadbeef'<br/>@@@" + ] + }, + { + "cmd": [ + "git", + "clean", + "-f", + "-d", + "-x" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git clean" + }, + { + "cmd": [ + "git", + "submodule", + "sync" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "submodule sync" + }, + { + "cmd": [ + "git", + "submodule", + "update", + "--init", + "--recursive" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "submodule update" + }, + { + "cmd": [ + "curl", + "-Lo", + ".git/hooks/commit-msg", + "https://chromium-review.googlesource.com/tools/hooks/commit-msg" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Install commit hook" + }, + { + "cmd": [ + "chmod", + "+x", + ".git/hooks/commit-msg" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Make commit hook executable" + }, + { + "cmd": [ + "gcloud", + "storage", + "ls", + "gs://refvm-images/[0-9][0-9][0-9][0-9]-[0-9][0-9]/refvm-*.qcow2" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Find latest image" + }, + { + "cmd": [ + "gcloud", + "storage", + "cp", + "gs://refvm-images/2026-03/refvm-20260316_000339.qcow2", + "[CLEANUP]/tmp_tmp_2/image.br" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Download image" + }, + { + "cmd": [ + "stat", + "-c", + "%s", + "[CLEANUP]/tmp_tmp_2/image.br" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Get compressed size" + }, + { + "failure": { + "failure": {}, + "humanReason": "Failed to parse compressed size" + }, + "name": "$result" + } +] \ No newline at end of file
diff --git a/infra/recipes/uprev_refvm_image.expected/No images.json b/infra/recipes/uprev_refvm_image.expected/No images.json new file mode 100644 index 0000000..407ec0d --- /dev/null +++ b/infra/recipes/uprev_refvm_image.expected/No images.json
@@ -0,0 +1,125 @@ +[ + { + "cmd": [ + "python3", + "-u", + "RECIPE_MODULE[depot_tools::git]/resources/git_setup.py", + "--path", + "[CLEANUP]/tmp_tmp_1", + "--url", + "https://chromium.googlesource.com/chromiumos/platform/tast-tests" + ], + "name": "git setup" + }, + { + "cmd": [ + "git", + "fetch", + "origin", + "main", + "--progress", + "--depth", + "1" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "env": { + "PATH": "RECIPE_REPO[depot_tools]:<PATH>" + }, + "infra_step": true, + "name": "git fetch" + }, + { + "cmd": [ + "git", + "checkout", + "-f", + "FETCH_HEAD" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git checkout" + }, + { + "cmd": [ + "git", + "rev-parse", + "HEAD" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "read revision", + "~followup_annotations": [ + "@@@STEP_TEXT@<br/>checked out 'deadbeef'<br/>@@@" + ] + }, + { + "cmd": [ + "git", + "clean", + "-f", + "-d", + "-x" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git clean" + }, + { + "cmd": [ + "git", + "submodule", + "sync" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "submodule sync" + }, + { + "cmd": [ + "git", + "submodule", + "update", + "--init", + "--recursive" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "submodule update" + }, + { + "cmd": [ + "curl", + "-Lo", + ".git/hooks/commit-msg", + "https://chromium-review.googlesource.com/tools/hooks/commit-msg" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Install commit hook" + }, + { + "cmd": [ + "chmod", + "+x", + ".git/hooks/commit-msg" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Make commit hook executable" + }, + { + "cmd": [ + "gcloud", + "storage", + "ls", + "gs://refvm-images/[0-9][0-9][0-9][0-9]-[0-9][0-9]/refvm-*.qcow2" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Find latest image" + }, + { + "failure": { + "failure": {}, + "humanReason": "No images found in gs://refvm-images/" + }, + "name": "$result" + } +] \ No newline at end of file
diff --git a/infra/recipes/uprev_refvm_image.expected/Nothing to submit.json b/infra/recipes/uprev_refvm_image.expected/Nothing to submit.json new file mode 100644 index 0000000..741377d --- /dev/null +++ b/infra/recipes/uprev_refvm_image.expected/Nothing to submit.json
@@ -0,0 +1,246 @@ +[ + { + "cmd": [ + "python3", + "-u", + "RECIPE_MODULE[depot_tools::git]/resources/git_setup.py", + "--path", + "[CLEANUP]/tmp_tmp_1", + "--url", + "https://chromium.googlesource.com/chromiumos/platform/tast-tests" + ], + "name": "git setup" + }, + { + "cmd": [ + "git", + "fetch", + "origin", + "main", + "--progress", + "--depth", + "1" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "env": { + "PATH": "RECIPE_REPO[depot_tools]:<PATH>" + }, + "infra_step": true, + "name": "git fetch" + }, + { + "cmd": [ + "git", + "checkout", + "-f", + "FETCH_HEAD" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git checkout" + }, + { + "cmd": [ + "git", + "rev-parse", + "HEAD" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "read revision", + "~followup_annotations": [ + "@@@STEP_TEXT@<br/>checked out 'deadbeef'<br/>@@@" + ] + }, + { + "cmd": [ + "git", + "clean", + "-f", + "-d", + "-x" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git clean" + }, + { + "cmd": [ + "git", + "submodule", + "sync" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "submodule sync" + }, + { + "cmd": [ + "git", + "submodule", + "update", + "--init", + "--recursive" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "submodule update" + }, + { + "cmd": [ + "curl", + "-Lo", + ".git/hooks/commit-msg", + "https://chromium-review.googlesource.com/tools/hooks/commit-msg" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Install commit hook" + }, + { + "cmd": [ + "chmod", + "+x", + ".git/hooks/commit-msg" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Make commit hook executable" + }, + { + "cmd": [ + "gcloud", + "storage", + "ls", + "gs://refvm-images/[0-9][0-9][0-9][0-9]-[0-9][0-9]/refvm-*.qcow2" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Find latest image" + }, + { + "cmd": [ + "gcloud", + "storage", + "cp", + "gs://refvm-images/2026-03/refvm-20260316_000339.qcow2", + "[CLEANUP]/tmp_tmp_2/image.br" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Download image" + }, + { + "cmd": [ + "stat", + "-c", + "%s", + "[CLEANUP]/tmp_tmp_2/image.br" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Get compressed size" + }, + { + "cmd": [ + "sha256sum", + "[CLEANUP]/tmp_tmp_2/image.br" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Get compressed sha256" + }, + { + "cmd": [ + "vpython3", + "-vpython-spec", + "RECIPE[crosvm::uprev_refvm_image].resources/brotli_decompress.vpython3", + "RECIPE[crosvm::uprev_refvm_image].resources/brotli_decompress.py", + "[CLEANUP]/tmp_tmp_2/image.br", + "[CLEANUP]/tmp_tmp_2/image" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Decompress image" + }, + { + "cmd": [ + "sha256sum", + "[CLEANUP]/tmp_tmp_2/image" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Get decompressed sha256" + }, + { + "cmd": [ + "vpython3", + "-u", + "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py", + "--json-output", + "/path/to/tmp/json", + "copy", + "{\n \"sha256sum\": \"aaaa\",\n \"size\": 1024,\n \"url\": \"gs://refvm-images/2026-03/refvm-20260316_000339.qcow2\"\n}\n", + "[CLEANUP]/tmp_tmp_1/src/go.chromium.org/tast-tests/cros/local/bruschetta/data/refvm.qcow2.external" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "Update external file", + "~followup_annotations": [ + "@@@STEP_LOG_LINE@refvm.qcow2.external@{@@@", + "@@@STEP_LOG_LINE@refvm.qcow2.external@ \"sha256sum\": \"aaaa\",@@@", + "@@@STEP_LOG_LINE@refvm.qcow2.external@ \"size\": 1024,@@@", + "@@@STEP_LOG_LINE@refvm.qcow2.external@ \"url\": \"gs://refvm-images/2026-03/refvm-20260316_000339.qcow2\"@@@", + "@@@STEP_LOG_LINE@refvm.qcow2.external@}@@@", + "@@@STEP_LOG_END@refvm.qcow2.external@@@" + ] + }, + { + "cmd": [ + "vpython3", + "-u", + "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py", + "--json-output", + "/path/to/tmp/json", + "copy", + "bbbb\n", + "[CLEANUP]/tmp_tmp_1/src/go.chromium.org/tast-tests/cros/local/bruschetta/data/refvm.qcow2.SHA256" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "Update SHA256 file", + "~followup_annotations": [ + "@@@STEP_LOG_LINE@refvm.qcow2.SHA256@bbbb@@@", + "@@@STEP_LOG_END@refvm.qcow2.SHA256@@@" + ] + }, + { + "cmd": [ + "git", + "add", + "[CLEANUP]/tmp_tmp_1/src/go.chromium.org/tast-tests/cros/local/bruschetta/data/refvm.qcow2.external" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git add" + }, + { + "cmd": [ + "git", + "add", + "[CLEANUP]/tmp_tmp_1/src/go.chromium.org/tast-tests/cros/local/bruschetta/data/refvm.qcow2.SHA256" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git add (2)" + }, + { + "cmd": [ + "git", + "diff", + "--cached", + "--exit-code" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git diff", + "~followup_annotations": [ + "@@@STEP_TEXT@Nothing to submit@@@" + ] + }, + { + "name": "$result" + } +] \ No newline at end of file
diff --git a/infra/recipes/uprev_refvm_image.expected/Submit bot uprev.json b/infra/recipes/uprev_refvm_image.expected/Submit bot uprev.json new file mode 100644 index 0000000..8334340 --- /dev/null +++ b/infra/recipes/uprev_refvm_image.expected/Submit bot uprev.json
@@ -0,0 +1,265 @@ +[ + { + "cmd": [ + "python3", + "-u", + "RECIPE_MODULE[depot_tools::git]/resources/git_setup.py", + "--path", + "[CLEANUP]/tmp_tmp_1", + "--url", + "https://chromium.googlesource.com/chromiumos/platform/tast-tests" + ], + "name": "git setup" + }, + { + "cmd": [ + "git", + "fetch", + "origin", + "main", + "--progress", + "--depth", + "1" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "env": { + "PATH": "RECIPE_REPO[depot_tools]:<PATH>" + }, + "infra_step": true, + "name": "git fetch" + }, + { + "cmd": [ + "git", + "checkout", + "-f", + "FETCH_HEAD" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git checkout" + }, + { + "cmd": [ + "git", + "rev-parse", + "HEAD" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "read revision", + "~followup_annotations": [ + "@@@STEP_TEXT@<br/>checked out 'deadbeef'<br/>@@@" + ] + }, + { + "cmd": [ + "git", + "clean", + "-f", + "-d", + "-x" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git clean" + }, + { + "cmd": [ + "git", + "submodule", + "sync" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "submodule sync" + }, + { + "cmd": [ + "git", + "submodule", + "update", + "--init", + "--recursive" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "submodule update" + }, + { + "cmd": [ + "curl", + "-Lo", + ".git/hooks/commit-msg", + "https://chromium-review.googlesource.com/tools/hooks/commit-msg" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Install commit hook" + }, + { + "cmd": [ + "chmod", + "+x", + ".git/hooks/commit-msg" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Make commit hook executable" + }, + { + "cmd": [ + "gcloud", + "storage", + "ls", + "gs://refvm-images/[0-9][0-9][0-9][0-9]-[0-9][0-9]/refvm-*.qcow2" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Find latest image" + }, + { + "cmd": [ + "gcloud", + "storage", + "cp", + "gs://refvm-images/2026-03/refvm-20260316_000339.qcow2", + "[CLEANUP]/tmp_tmp_2/image.br" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Download image" + }, + { + "cmd": [ + "stat", + "-c", + "%s", + "[CLEANUP]/tmp_tmp_2/image.br" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Get compressed size" + }, + { + "cmd": [ + "sha256sum", + "[CLEANUP]/tmp_tmp_2/image.br" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Get compressed sha256" + }, + { + "cmd": [ + "vpython3", + "-vpython-spec", + "RECIPE[crosvm::uprev_refvm_image].resources/brotli_decompress.vpython3", + "RECIPE[crosvm::uprev_refvm_image].resources/brotli_decompress.py", + "[CLEANUP]/tmp_tmp_2/image.br", + "[CLEANUP]/tmp_tmp_2/image" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Decompress image" + }, + { + "cmd": [ + "sha256sum", + "[CLEANUP]/tmp_tmp_2/image" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Get decompressed sha256" + }, + { + "cmd": [ + "vpython3", + "-u", + "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py", + "--json-output", + "/path/to/tmp/json", + "copy", + "{\n \"sha256sum\": \"aaaa\",\n \"size\": 1024,\n \"url\": \"gs://refvm-images/2026-03/refvm-20260316_000339.qcow2\"\n}\n", + "[CLEANUP]/tmp_tmp_1/src/go.chromium.org/tast-tests/cros/local/bruschetta/data/refvm.qcow2.external" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "Update external file", + "~followup_annotations": [ + "@@@STEP_LOG_LINE@refvm.qcow2.external@{@@@", + "@@@STEP_LOG_LINE@refvm.qcow2.external@ \"sha256sum\": \"aaaa\",@@@", + "@@@STEP_LOG_LINE@refvm.qcow2.external@ \"size\": 1024,@@@", + "@@@STEP_LOG_LINE@refvm.qcow2.external@ \"url\": \"gs://refvm-images/2026-03/refvm-20260316_000339.qcow2\"@@@", + "@@@STEP_LOG_LINE@refvm.qcow2.external@}@@@", + "@@@STEP_LOG_END@refvm.qcow2.external@@@" + ] + }, + { + "cmd": [ + "vpython3", + "-u", + "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py", + "--json-output", + "/path/to/tmp/json", + "copy", + "bbbb\n", + "[CLEANUP]/tmp_tmp_1/src/go.chromium.org/tast-tests/cros/local/bruschetta/data/refvm.qcow2.SHA256" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "Update SHA256 file", + "~followup_annotations": [ + "@@@STEP_LOG_LINE@refvm.qcow2.SHA256@bbbb@@@", + "@@@STEP_LOG_END@refvm.qcow2.SHA256@@@" + ] + }, + { + "cmd": [ + "git", + "add", + "[CLEANUP]/tmp_tmp_1/src/go.chromium.org/tast-tests/cros/local/bruschetta/data/refvm.qcow2.external" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git add" + }, + { + "cmd": [ + "git", + "add", + "[CLEANUP]/tmp_tmp_1/src/go.chromium.org/tast-tests/cros/local/bruschetta/data/refvm.qcow2.SHA256" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git add (2)" + }, + { + "cmd": [ + "git", + "diff", + "--cached", + "--exit-code" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git diff" + }, + { + "cmd": [ + "git", + "commit", + "-m", + "bruschetta: Uprev refvm image to 20260316_000339.\n\nGenerated by https://cr-buildbucket.appspot.com/build/0." + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git commit" + }, + { + "cmd": [ + "git", + "push", + "origin", + "HEAD:refs/for/main%r=crosvm-uprev@google.com,l=Bot-Commit+1,l=Commit-Queue+2" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git push" + }, + { + "name": "$result" + } +] \ No newline at end of file
diff --git a/infra/recipes/uprev_refvm_image.expected/Submit test uprev.json b/infra/recipes/uprev_refvm_image.expected/Submit test uprev.json new file mode 100644 index 0000000..ef6489d --- /dev/null +++ b/infra/recipes/uprev_refvm_image.expected/Submit test uprev.json
@@ -0,0 +1,265 @@ +[ + { + "cmd": [ + "python3", + "-u", + "RECIPE_MODULE[depot_tools::git]/resources/git_setup.py", + "--path", + "[CLEANUP]/tmp_tmp_1", + "--url", + "https://chromium.googlesource.com/chromiumos/platform/tast-tests" + ], + "name": "git setup" + }, + { + "cmd": [ + "git", + "fetch", + "origin", + "main", + "--progress", + "--depth", + "1" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "env": { + "PATH": "RECIPE_REPO[depot_tools]:<PATH>" + }, + "infra_step": true, + "name": "git fetch" + }, + { + "cmd": [ + "git", + "checkout", + "-f", + "FETCH_HEAD" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git checkout" + }, + { + "cmd": [ + "git", + "rev-parse", + "HEAD" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "read revision", + "~followup_annotations": [ + "@@@STEP_TEXT@<br/>checked out 'deadbeef'<br/>@@@" + ] + }, + { + "cmd": [ + "git", + "clean", + "-f", + "-d", + "-x" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git clean" + }, + { + "cmd": [ + "git", + "submodule", + "sync" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "submodule sync" + }, + { + "cmd": [ + "git", + "submodule", + "update", + "--init", + "--recursive" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "submodule update" + }, + { + "cmd": [ + "curl", + "-Lo", + ".git/hooks/commit-msg", + "https://chromium-review.googlesource.com/tools/hooks/commit-msg" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Install commit hook" + }, + { + "cmd": [ + "chmod", + "+x", + ".git/hooks/commit-msg" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Make commit hook executable" + }, + { + "cmd": [ + "gcloud", + "storage", + "ls", + "gs://refvm-images/[0-9][0-9][0-9][0-9]-[0-9][0-9]/refvm-*.qcow2" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Find latest image" + }, + { + "cmd": [ + "gcloud", + "storage", + "cp", + "gs://refvm-images/2026-03/refvm-20260316_000339.qcow2", + "[CLEANUP]/tmp_tmp_2/image.br" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Download image" + }, + { + "cmd": [ + "stat", + "-c", + "%s", + "[CLEANUP]/tmp_tmp_2/image.br" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Get compressed size" + }, + { + "cmd": [ + "sha256sum", + "[CLEANUP]/tmp_tmp_2/image.br" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Get compressed sha256" + }, + { + "cmd": [ + "vpython3", + "-vpython-spec", + "RECIPE[crosvm::uprev_refvm_image].resources/brotli_decompress.vpython3", + "RECIPE[crosvm::uprev_refvm_image].resources/brotli_decompress.py", + "[CLEANUP]/tmp_tmp_2/image.br", + "[CLEANUP]/tmp_tmp_2/image" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Decompress image" + }, + { + "cmd": [ + "sha256sum", + "[CLEANUP]/tmp_tmp_2/image" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "name": "Get decompressed sha256" + }, + { + "cmd": [ + "vpython3", + "-u", + "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py", + "--json-output", + "/path/to/tmp/json", + "copy", + "{\n \"sha256sum\": \"aaaa\",\n \"size\": 1024,\n \"url\": \"gs://refvm-images/2026-03/refvm-20260316_000339.qcow2\"\n}\n", + "[CLEANUP]/tmp_tmp_1/src/go.chromium.org/tast-tests/cros/local/bruschetta/data/refvm.qcow2.external" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "Update external file", + "~followup_annotations": [ + "@@@STEP_LOG_LINE@refvm.qcow2.external@{@@@", + "@@@STEP_LOG_LINE@refvm.qcow2.external@ \"sha256sum\": \"aaaa\",@@@", + "@@@STEP_LOG_LINE@refvm.qcow2.external@ \"size\": 1024,@@@", + "@@@STEP_LOG_LINE@refvm.qcow2.external@ \"url\": \"gs://refvm-images/2026-03/refvm-20260316_000339.qcow2\"@@@", + "@@@STEP_LOG_LINE@refvm.qcow2.external@}@@@", + "@@@STEP_LOG_END@refvm.qcow2.external@@@" + ] + }, + { + "cmd": [ + "vpython3", + "-u", + "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py", + "--json-output", + "/path/to/tmp/json", + "copy", + "bbbb\n", + "[CLEANUP]/tmp_tmp_1/src/go.chromium.org/tast-tests/cros/local/bruschetta/data/refvm.qcow2.SHA256" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "Update SHA256 file", + "~followup_annotations": [ + "@@@STEP_LOG_LINE@refvm.qcow2.SHA256@bbbb@@@", + "@@@STEP_LOG_END@refvm.qcow2.SHA256@@@" + ] + }, + { + "cmd": [ + "git", + "add", + "[CLEANUP]/tmp_tmp_1/src/go.chromium.org/tast-tests/cros/local/bruschetta/data/refvm.qcow2.external" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git add" + }, + { + "cmd": [ + "git", + "add", + "[CLEANUP]/tmp_tmp_1/src/go.chromium.org/tast-tests/cros/local/bruschetta/data/refvm.qcow2.SHA256" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git add (2)" + }, + { + "cmd": [ + "git", + "diff", + "--cached", + "--exit-code" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git diff" + }, + { + "cmd": [ + "git", + "commit", + "-m", + "bruschetta: Uprev refvm image to 20260316_000339.\n\nGenerated by https://cr-buildbucket.appspot.com/build/0." + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git commit" + }, + { + "cmd": [ + "git", + "push", + "origin", + "HEAD:refs/for/main%r=crosvm-uprev@google.com,l=Commit-Queue+1" + ], + "cwd": "[CLEANUP]/tmp_tmp_1", + "infra_step": true, + "name": "git push" + }, + { + "name": "$result" + } +] \ No newline at end of file
diff --git a/infra/recipes/uprev_refvm_image.proto b/infra/recipes/uprev_refvm_image.proto new file mode 100644 index 0000000..6045608 --- /dev/null +++ b/infra/recipes/uprev_refvm_image.proto
@@ -0,0 +1,14 @@ +// Copyright 2026 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +syntax = "proto3"; + +package recipes.crosvm.uprev_refvm_image; + +message UprevRefvmImageProperties { + // Set to true to enable pushing to gerrit + bool push = 1; + // Set to true if the recipe is allowed to set Bot-Commit+1 + bool bot = 2; +}
diff --git a/infra/recipes/uprev_refvm_image.py b/infra/recipes/uprev_refvm_image.py new file mode 100644 index 0000000..fe86660 --- /dev/null +++ b/infra/recipes/uprev_refvm_image.py
@@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Recipe for uploading uprevs of the refvm image.""" + +import json +import re +from typing import Generator + +from PB.recipes.crosvm.uprev_refvm_image import UprevRefvmImageProperties +from recipe_engine.recipe_api import RecipeApi +from recipe_engine.recipe_api import StepFailure +from recipe_engine.recipe_test_api import RecipeTestApi +from recipe_engine.recipe_test_api import TestData + +DEPS = [ + "recipe_engine/buildbucket", + "recipe_engine/cipd", + "recipe_engine/context", + "recipe_engine/file", + "recipe_engine/path", + "recipe_engine/properties", + "recipe_engine/raw_io", + "recipe_engine/step", + "depot_tools/git", +] + +PROPERTIES = UprevRefvmImageProperties + +_TAST_TESTS_REPO_URL = "https://chromium.googlesource.com/chromiumos/platform/tast-tests" +_DATA_DIR_PATH = "src/go.chromium.org/tast-tests/cros/local/bruschetta/data" + + +def RunSteps(api: RecipeApi, properties: UprevRefvmImageProperties) -> None: + # Clone the tast-tests repo in a temp dir + checkout = api.path.mkdtemp() + api.git.checkout(_TAST_TESTS_REPO_URL, dir_path=checkout, depth=1) + + with api.context(cwd=checkout): + # Ensure the commit hook is configured + api.step( + "Install commit hook", + [ + "curl", + "-Lo", + ".git/hooks/commit-msg", + "https://chromium-review.googlesource.com/tools/hooks/commit-msg", + ], + ) + api.step("Make commit hook executable", ["chmod", "+x", ".git/hooks/commit-msg"]) + + # Step 1. Find latest image + res_dir = api.step( + "Find latest image", + [ + "gcloud", + "storage", + "ls", + "gs://refvm-images/[0-9][0-9][0-9][0-9]-[0-9][0-9]/refvm-*.qcow2", + ], + stdout=api.raw_io.output_text(), + ) + objs = res_dir.stdout.strip().splitlines() + if not objs: + raise StepFailure("No images found in gs://refvm-images/") + last_obj = objs[-1].strip() + + match = re.search(r"refvm-([\d_]+)\.qcow2", last_obj) + version = match.group(1) if match else "unknown" + + # Step 2. Download image to temp dir + tmp_dir = api.path.mkdtemp() + downloaded_file = tmp_dir / "image.br" + + api.step( + "Download image", + ["gcloud", "storage", "cp", last_obj, downloaded_file], + ) + + # Step 3. Calculate hashes + decompressed_file = tmp_dir / "image" + + res_size = api.step( + "Get compressed size", + ["stat", "-c", "%s", downloaded_file], + stdout=api.raw_io.output_text(), + ) + try: + compressed_size = int(res_size.stdout.strip()) + except ValueError: + raise StepFailure("Failed to parse compressed size") + + res_c_sha = api.step( + "Get compressed sha256", + ["sha256sum", downloaded_file], + stdout=api.raw_io.output_text(), + ) + compressed_sha256 = res_c_sha.stdout.strip().split()[0] + + api.step( + "Decompress image", + [ + "vpython3", + "-vpython-spec", + api.resource("brotli_decompress.vpython3"), + api.resource("brotli_decompress.py"), + downloaded_file, + decompressed_file, + ], + ) + + res_d_sha = api.step( + "Get decompressed sha256", + ["sha256sum", decompressed_file], + stdout=api.raw_io.output_text(), + ) + decompressed_sha256 = res_d_sha.stdout.strip().split()[0] + + datadir = checkout / _DATA_DIR_PATH + external_file = datadir / "refvm.qcow2.external" + sha256_file = datadir / "refvm.qcow2.SHA256" + + external_content = { + "sha256sum": compressed_sha256, + "size": compressed_size, + "url": last_obj, + } + + api.file.write_text( + "Update external file", + external_file, + json.dumps(external_content, indent=4) + "\n", + ) + + api.file.write_text( + "Update SHA256 file", + sha256_file, + decompressed_sha256 + "\n", + ) + + # Step 4. Create commit if there are changes + api.git("add", str(external_file)) + api.git("add", str(sha256_file)) + + diff_result = api.git("diff", "--cached", "--exit-code", ok_ret="any") + if diff_result.retcode == 0: + diff_result.presentation.step_text = "Nothing to submit" + return + + commit_lines = [ + f"bruschetta: Uprev refvm image to {version}.", + "", + f"Generated by {api.buildbucket.build_url()}.", + ] + + api.git("commit", "-m", "\n".join(commit_lines)) + + # Push the change to gerrit if requested + if properties.push: + gerrit_params = ["r=crosvm-uprev@google.com"] + if properties.bot: + gerrit_params += ["l=Bot-Commit+1", "l=Commit-Queue+2"] + else: + gerrit_params += ["l=Commit-Queue+1"] + api.git("push", "origin", f"HEAD:refs/for/main%{','.join(gerrit_params)}") + + +def GenTests(api: RecipeTestApi) -> Generator[TestData, None, None]: + def mock_decompress_output(fail_size=False): + steps = [] + if fail_size: + steps.append( + api.step_data("Get compressed size", stdout=api.raw_io.output_text("not an int")) + ) + return tuple(steps) + else: + steps.append( + api.step_data("Get compressed size", stdout=api.raw_io.output_text("1024")) + ) + + steps.append( + api.step_data( + "Get compressed sha256", stdout=api.raw_io.output_text("aaaa /tmp/image.br") + ) + ) + steps.append(api.step_data("Decompress image", retcode=0)) + steps.append( + api.step_data( + "Get decompressed sha256", stdout=api.raw_io.output_text("bbbb /tmp/image") + ) + ) + return tuple(steps) + + yield api.test( + "Submit test uprev", + api.properties(push=True, bot=False), + api.step_data( + "Find latest image", + stdout=api.raw_io.output_text("gs://refvm-images/2026-03/refvm-20260316_000339.qcow2"), + ), + *mock_decompress_output(), + api.step_data("git diff", retcode=1), + ) + + yield api.test( + "Submit bot uprev", + api.properties(push=True, bot=True), + api.step_data( + "Find latest image", + stdout=api.raw_io.output_text("gs://refvm-images/2026-03/refvm-20260316_000339.qcow2"), + ), + *mock_decompress_output(), + api.step_data("git diff", retcode=1), + ) + + yield api.test( + "Nothing to submit", + api.properties(push=True), + api.step_data( + "Find latest image", + stdout=api.raw_io.output_text("gs://refvm-images/2026-03/refvm-20260316_000339.qcow2"), + ), + *mock_decompress_output(), + api.step_data("git diff", retcode=0), + ) + + yield api.test( + "No images", + api.step_data( + "Find latest image", + stdout=api.raw_io.output_text("gs://refvm-images/2026-03/refvm-20260316_000339.qcow2"), + ), + api.step_data("Find latest image", stdout=api.raw_io.output_text("")), + status="FAILURE", + ) + + yield api.test( + "Invalid size output", + api.step_data( + "Find latest image", + stdout=api.raw_io.output_text("gs://refvm-images/2026-03/refvm-20260316_000339.qcow2"), + ), + *mock_decompress_output(fail_size=True), + status="FAILURE", + )
diff --git a/infra/recipes/uprev_refvm_image.resources/brotli_decompress.py b/infra/recipes/uprev_refvm_image.resources/brotli_decompress.py new file mode 100644 index 0000000..50e1f88 --- /dev/null +++ b/infra/recipes/uprev_refvm_image.resources/brotli_decompress.py
@@ -0,0 +1,28 @@ +# Copyright 2026 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Decompresses a Brotli-compressed file.""" + +import brotli +import sys + + +def main(): + if len(sys.argv) != 3: + print("Usage: brotli_decompress.py <input> <output>") + sys.exit(1) + input_file = sys.argv[1] + output_file = sys.argv[2] + + with open(input_file, "rb") as f: + compressed_data = f.read() + + decompressed_data = brotli.decompress(compressed_data) + + with open(output_file, "wb") as f: + f.write(decompressed_data) + + +if __name__ == "__main__": + main()
diff --git a/infra/recipes/uprev_refvm_image.resources/brotli_decompress.vpython3 b/infra/recipes/uprev_refvm_image.resources/brotli_decompress.vpython3 new file mode 100644 index 0000000..1c5dc2d --- /dev/null +++ b/infra/recipes/uprev_refvm_image.resources/brotli_decompress.vpython3
@@ -0,0 +1,6 @@ +python_version: "3.8" + +wheel: < + name: "infra/python/wheels/brotli/${vpython_platform}" + version: "version:1.0.9" +>