Skip to content

Commit 82dfee3

Browse files
Copilotbenhillis
andauthored
petri: truncate VM names to respect Hyper-V 100-character limit (#1739)
Hyper-V limits VM names to 100 characters, but Petri's test names can exceed this limit when combining VMM prefix, firmware type, architecture, guest OS, and function name components. The failing test case demonstrates this issue: ``` multiarch::openhcl_servicing::hyperv_openhcl_uefi_aarch64_ubuntu_2404_server_aarch64_openhcl_servicing ``` This 102-character name causes VM creation to fail with: ``` New-VM : Failed to create a new virtual machine. An unexpected error occurred: The parameter is incorrect. (0x80070057). ``` ## Solution Added `make_vm_safe_name()` function that: - Passes through names ≤ 100 characters unchanged - Truncates longer names to 96 characters + 4-character hash suffix - Ensures uniqueness through deterministic hash generation - Preserves meaningful name prefixes for test identification ## Example ```rust // Before (fails): "multiarch::openhcl_servicing::hyperv_openhcl_uefi_aarch64_ubuntu_2404_server_aarch64_openhcl_servicing" // 102 chars // After (succeeds): "multiarch::openhcl_servicing::hyperv_openhcl_uefi_aarch64_ubuntu_2404_server_aarch64_openhcl_ser94cb" // 100 chars ``` The fix is applied universally in `PetriVmBuilder::new()` to prevent similar issues with other VM backends that may have naming constraints. Fixes #1647. <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: benhillis <[email protected]> Co-authored-by: Ben Hillis <[email protected]>
1 parent 499b87e commit 82dfee3

File tree

1 file changed

+101
-1
lines changed

1 file changed

+101
-1
lines changed

petri/src/vm/mod.rs

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ use petri_artifacts_core::ArtifactResolver;
2727
use petri_artifacts_core::ResolvedArtifact;
2828
use petri_artifacts_core::ResolvedOptionalArtifact;
2929
use pipette_client::PipetteClient;
30+
use std::collections::hash_map::DefaultHasher;
31+
use std::hash::Hash;
32+
use std::hash::Hasher;
3033
use std::path::Path;
3134
use std::path::PathBuf;
3235
use std::time::Duration;
@@ -163,7 +166,7 @@ impl<T: PetriVmmBackend> PetriVmBuilder<T> {
163166
Ok(Self {
164167
backend: artifacts.backend,
165168
config: PetriVmConfig {
166-
name: params.test_name.to_owned(),
169+
name: make_vm_safe_name(params.test_name),
167170
arch: artifacts.arch,
168171
firmware: artifacts.firmware,
169172
memory: Default::default(),
@@ -1379,6 +1382,41 @@ pub enum SecureBootTemplate {
13791382
MicrosoftUefiCertificateAuthority,
13801383
}
13811384

1385+
/// Creates a VM-safe name that respects platform limitations.
1386+
///
1387+
/// Hyper-V limits VM names to 100 characters. For names that exceed this limit,
1388+
/// this function truncates to 96 characters and appends a 4-character hash
1389+
/// to ensure uniqueness while staying within the limit.
1390+
fn make_vm_safe_name(name: &str) -> String {
1391+
const MAX_VM_NAME_LENGTH: usize = 100;
1392+
const HASH_LENGTH: usize = 4;
1393+
const MAX_PREFIX_LENGTH: usize = MAX_VM_NAME_LENGTH - HASH_LENGTH;
1394+
1395+
if name.len() <= MAX_VM_NAME_LENGTH {
1396+
name.to_owned()
1397+
} else {
1398+
// Create a hash of the full name for uniqueness
1399+
let mut hasher = DefaultHasher::new();
1400+
name.hash(&mut hasher);
1401+
let hash = hasher.finish();
1402+
1403+
// Format hash as a 4-character hex string
1404+
let hash_suffix = format!("{:04x}", hash & 0xFFFF);
1405+
1406+
// Truncate the name and append the hash
1407+
let truncated = &name[..MAX_PREFIX_LENGTH];
1408+
tracing::debug!(
1409+
"VM name too long ({}), truncating '{}' to '{}{}'",
1410+
name.len(),
1411+
name,
1412+
truncated,
1413+
hash_suffix
1414+
);
1415+
1416+
format!("{}{}", truncated, hash_suffix)
1417+
}
1418+
}
1419+
13821420
fn append_cmdline(cmd: &mut Option<String>, add_cmd: &str) {
13831421
if let Some(cmd) = cmd.as_mut() {
13841422
cmd.push(' ');
@@ -1387,3 +1425,65 @@ fn append_cmdline(cmd: &mut Option<String>, add_cmd: &str) {
13871425
*cmd = Some(add_cmd.to_string());
13881426
}
13891427
}
1428+
1429+
#[cfg(test)]
1430+
mod tests {
1431+
use super::make_vm_safe_name;
1432+
1433+
#[test]
1434+
fn test_short_names_unchanged() {
1435+
let short_name = "short_test_name";
1436+
assert_eq!(make_vm_safe_name(short_name), short_name);
1437+
}
1438+
1439+
#[test]
1440+
fn test_exactly_100_chars_unchanged() {
1441+
let name_100 = "a".repeat(100);
1442+
assert_eq!(make_vm_safe_name(&name_100), name_100);
1443+
}
1444+
1445+
#[test]
1446+
fn test_long_name_truncated() {
1447+
let long_name = "multiarch::openhcl_servicing::hyperv_openhcl_uefi_aarch64_ubuntu_2404_server_aarch64_openhcl_servicing";
1448+
let result = make_vm_safe_name(long_name);
1449+
1450+
// Should be exactly 100 characters
1451+
assert_eq!(result.len(), 100);
1452+
1453+
// Should start with the truncated prefix
1454+
assert!(result.starts_with("multiarch::openhcl_servicing::hyperv_openhcl_uefi_aarch64_ubuntu_2404_server_aarch64_ope"));
1455+
1456+
// Should end with a 4-character hash
1457+
let suffix = &result[96..];
1458+
assert_eq!(suffix.len(), 4);
1459+
// Should be valid hex
1460+
assert!(u16::from_str_radix(suffix, 16).is_ok());
1461+
}
1462+
1463+
#[test]
1464+
fn test_deterministic_results() {
1465+
let long_name = "very_long_test_name_that_exceeds_the_100_character_limit_and_should_be_truncated_consistently_every_time";
1466+
let result1 = make_vm_safe_name(long_name);
1467+
let result2 = make_vm_safe_name(long_name);
1468+
1469+
assert_eq!(result1, result2);
1470+
assert_eq!(result1.len(), 100);
1471+
}
1472+
1473+
#[test]
1474+
fn test_different_names_different_hashes() {
1475+
let name1 = "very_long_test_name_that_definitely_exceeds_the_100_character_limit_and_should_be_truncated_by_the_function_version_1";
1476+
let name2 = "very_long_test_name_that_definitely_exceeds_the_100_character_limit_and_should_be_truncated_by_the_function_version_2";
1477+
1478+
let result1 = make_vm_safe_name(name1);
1479+
let result2 = make_vm_safe_name(name2);
1480+
1481+
// Both should be 100 chars
1482+
assert_eq!(result1.len(), 100);
1483+
assert_eq!(result2.len(), 100);
1484+
1485+
// Should have different suffixes since the full names are different
1486+
assert_ne!(result1, result2);
1487+
assert_ne!(&result1[96..], &result2[96..]);
1488+
}
1489+
}

0 commit comments

Comments
 (0)