Skip to content

Commit 769de3c

Browse files
committed
libvirt: Add automatic mounting of bind mounts
Keep the raw functionality to set up mounts with --volume, but add new --bind and --bind-ro flags that use SMBIOS injection to do automatic mounting via systemd mount units. This also updates the --bind-storage-ro functionality to use automatic mounting at /run/host-container-storage. Assisted-by: Claude Code (Sonnet 4.5) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent be1931c commit 769de3c

4 files changed

Lines changed: 664 additions & 72 deletions

File tree

crates/integration-tests/src/tests/libvirt_verb.rs

Lines changed: 257 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -759,58 +759,24 @@ fn test_libvirt_bind_storage_ro() -> Result<()> {
759759
panic!("Failed to establish SSH connection: {}", e);
760760
}
761761

762-
// Create mount point and mount virtiofs filesystem
763-
println!("Creating mount point and mounting virtiofs filesystem...");
764-
let mount_setup = run_bcvk(&[
765-
"libvirt",
766-
"ssh",
767-
&domain_name,
768-
"--",
769-
"sudo",
770-
"mkdir",
771-
"-p",
772-
"/run/virtiofs-mnt-hoststorage",
773-
])
774-
.expect("Failed to create mount point");
775-
776-
if !mount_setup.success() {
777-
println!(
778-
"Warning: Failed to create mount point: {}",
779-
mount_setup.stderr
780-
);
781-
}
782-
783-
let mount_cmd = run_bcvk(&[
784-
"libvirt",
785-
"ssh",
786-
&domain_name,
787-
"--",
788-
"sudo",
789-
"mount",
790-
"-t",
791-
"virtiofs",
792-
"hoststorage",
793-
"/run/virtiofs-mnt-hoststorage",
794-
])
795-
.expect("Failed to mount virtiofs");
796-
797-
if !mount_cmd.success() {
798-
cleanup_domain(&domain_name);
799-
panic!("Failed to mount virtiofs filesystem: {}", mount_cmd.stderr);
800-
}
762+
// Wait for VM to boot and automatic mount to complete
763+
println!("Waiting for VM to boot and automatic mount to complete...");
764+
std::thread::sleep(std::time::Duration::from_secs(10));
801765

802-
// Test SSH connection and verify container storage mount inside VM
803-
println!("Testing SSH connection and checking container storage mount...");
766+
// Test SSH connection and verify container storage is automatically mounted
767+
println!(
768+
"Verifying container storage is automatically mounted at /run/host-container-storage..."
769+
);
804770
run_bcvk_nocapture(&[
805771
"libvirt",
806772
"ssh",
807773
&domain_name,
808774
"--",
809775
"ls",
810776
"-la",
811-
"/run/virtiofs-mnt-hoststorage/overlay",
777+
"/run/host-container-storage/overlay",
812778
])
813-
.expect("Failed to run SSH command to check container storage");
779+
.expect("Failed to verify automatic mount of container storage");
814780

815781
// Verify that the mount is read-only
816782
println!("Verifying that the mount is read-only...");
@@ -820,7 +786,7 @@ fn test_libvirt_bind_storage_ro() -> Result<()> {
820786
&domain_name,
821787
"--",
822788
"touch",
823-
"/run/virtiofs-mnt-hoststorage/test-write",
789+
"/run/host-container-storage/test-write",
824790
])
825791
.expect("Failed to run SSH command to test read-only mount");
826792

@@ -1176,3 +1142,250 @@ fn test_libvirt_transient_vm() -> Result<()> {
11761142
println!("✓ Transient VM test passed");
11771143
Ok(())
11781144
}
1145+
1146+
#[distributed_slice(INTEGRATION_TESTS)]
1147+
static TEST_LIBVIRT_BIND_MOUNTS: IntegrationTest =
1148+
IntegrationTest::new("test_libvirt_bind_mounts", test_libvirt_bind_mounts);
1149+
1150+
/// Test automatic bind mount functionality with systemd mount units
1151+
fn test_libvirt_bind_mounts() -> Result<()> {
1152+
use camino::Utf8Path;
1153+
use std::fs;
1154+
use tempfile::TempDir;
1155+
1156+
let test_image = get_test_image();
1157+
1158+
// Generate unique domain name for this test
1159+
let domain_name = format!(
1160+
"test-bind-mounts-{}",
1161+
std::time::SystemTime::now()
1162+
.duration_since(std::time::UNIX_EPOCH)
1163+
.unwrap()
1164+
.as_secs()
1165+
);
1166+
1167+
println!("Testing bind mounts with domain: {}", domain_name);
1168+
1169+
// Create temporary directories for testing bind mounts
1170+
let rw_dir = TempDir::new().expect("Failed to create read-write temp directory");
1171+
let rw_dir_path = Utf8Path::from_path(rw_dir.path()).expect("rw dir path is not utf8");
1172+
let rw_test_file = rw_dir_path.join("rw-test.txt");
1173+
fs::write(&rw_test_file, "read-write content").expect("Failed to write rw test file");
1174+
1175+
let ro_dir = TempDir::new().expect("Failed to create read-only temp directory");
1176+
let ro_dir_path = Utf8Path::from_path(ro_dir.path()).expect("ro dir path is not utf8");
1177+
let ro_test_file = ro_dir_path.join("ro-test.txt");
1178+
fs::write(&ro_test_file, "read-only content").expect("Failed to write ro test file");
1179+
1180+
println!("RW directory: {}", rw_dir_path);
1181+
println!("RO directory: {}", ro_dir_path);
1182+
1183+
// Cleanup any existing domain with this name
1184+
cleanup_domain(&domain_name);
1185+
1186+
// Create domain with bind mounts
1187+
println!("Creating libvirt domain with bind mounts...");
1188+
let create_output = run_bcvk(&[
1189+
"libvirt",
1190+
"run",
1191+
"--name",
1192+
&domain_name,
1193+
"--label",
1194+
LIBVIRT_INTEGRATION_TEST_LABEL,
1195+
"--filesystem",
1196+
"ext4",
1197+
"--bind",
1198+
&format!("{}:/var/mnt/test-rw", rw_dir_path),
1199+
"--bind-ro",
1200+
&format!("{}:/var/mnt/test-ro", ro_dir_path),
1201+
&test_image,
1202+
])
1203+
.expect("Failed to run libvirt run with bind mounts");
1204+
1205+
println!("Create stdout: {}", create_output.stdout);
1206+
println!("Create stderr: {}", create_output.stderr);
1207+
1208+
if !create_output.success() {
1209+
cleanup_domain(&domain_name);
1210+
panic!(
1211+
"Failed to create domain with bind mounts: {}",
1212+
create_output.stderr
1213+
);
1214+
}
1215+
1216+
println!("Successfully created domain: {}", domain_name);
1217+
1218+
// Check domain XML for virtiofs filesystems and SMBIOS credentials
1219+
println!("Checking domain XML for virtiofs and SMBIOS credentials...");
1220+
let dumpxml_output = Command::new("virsh")
1221+
.args(&["dumpxml", &domain_name])
1222+
.output()
1223+
.expect("Failed to dump domain XML");
1224+
1225+
if !dumpxml_output.status.success() {
1226+
cleanup_domain(&domain_name);
1227+
let stderr = String::from_utf8_lossy(&dumpxml_output.stderr);
1228+
panic!("Failed to dump domain XML: {}", stderr);
1229+
}
1230+
1231+
let domain_xml = String::from_utf8_lossy(&dumpxml_output.stdout);
1232+
1233+
// Verify virtiofs filesystems are present
1234+
assert!(
1235+
domain_xml.contains("type='virtiofs'") || domain_xml.contains("driver type='virtiofs'"),
1236+
"Domain XML should contain virtiofs filesystem configuration"
1237+
);
1238+
1239+
// Verify SMBIOS credentials are injected
1240+
assert!(
1241+
domain_xml.contains("systemd.extra-unit"),
1242+
"Domain XML should contain systemd.extra-unit SMBIOS credentials for mount units"
1243+
);
1244+
1245+
println!("✓ Domain XML contains virtiofs and SMBIOS credentials");
1246+
1247+
// Wait for VM to boot and mounts to be ready
1248+
println!("Waiting for VM to boot and mounts to be ready...");
1249+
std::thread::sleep(std::time::Duration::from_secs(15));
1250+
1251+
// Debug: Check systemd credentials
1252+
println!("Debugging: Checking systemd credentials...");
1253+
let _creds_check = run_bcvk(&[
1254+
"libvirt",
1255+
"ssh",
1256+
&domain_name,
1257+
"--",
1258+
"ls",
1259+
"-la",
1260+
"/run/credentials",
1261+
])
1262+
.expect("Failed to check credentials");
1263+
1264+
// Debug: Check mount units
1265+
println!("Debugging: Checking mount units...");
1266+
let _units_check = run_bcvk(&[
1267+
"libvirt",
1268+
"ssh",
1269+
&domain_name,
1270+
"--",
1271+
"systemctl",
1272+
"list-units",
1273+
"*.mount",
1274+
])
1275+
.expect("Failed to check mount units");
1276+
1277+
// Debug: Check mount status
1278+
println!("Debugging: Checking if mounts exist...");
1279+
let _mount_check = run_bcvk(&[
1280+
"libvirt",
1281+
"ssh",
1282+
&domain_name,
1283+
"--",
1284+
"mount",
1285+
"|",
1286+
"grep",
1287+
"virtiofs",
1288+
])
1289+
.expect("Failed to check mounts");
1290+
1291+
// Test read-write bind mount - verify file exists and is readable
1292+
println!("Testing read-write bind mount...");
1293+
let rw_read_test = run_bcvk(&[
1294+
"libvirt",
1295+
"ssh",
1296+
&domain_name,
1297+
"--",
1298+
"cat",
1299+
"/var/mnt/test-rw/rw-test.txt",
1300+
])
1301+
.expect("Failed to read from rw bind mount");
1302+
1303+
assert!(
1304+
rw_read_test.success(),
1305+
"Should be able to read from rw bind mount. stderr: {}",
1306+
rw_read_test.stderr
1307+
);
1308+
assert!(
1309+
rw_read_test.stdout.contains("read-write content"),
1310+
"Should read correct content from rw bind mount"
1311+
);
1312+
println!("✓ RW bind mount is readable");
1313+
1314+
// Test write access on read-write mount
1315+
println!("Testing write access on read-write bind mount...");
1316+
let rw_write_test = run_bcvk(&[
1317+
"libvirt",
1318+
"ssh",
1319+
&domain_name,
1320+
"--",
1321+
"sh",
1322+
"-c",
1323+
"echo 'new content' > /var/mnt/test-rw/write-test.txt",
1324+
])
1325+
.expect("Failed to write to rw bind mount");
1326+
1327+
assert!(
1328+
rw_write_test.success(),
1329+
"Should be able to write to rw bind mount. stderr: {}",
1330+
rw_write_test.stderr
1331+
);
1332+
println!("✓ RW bind mount is writable");
1333+
1334+
// Verify written file exists on host
1335+
let written_file = rw_dir_path.join("write-test.txt");
1336+
assert!(
1337+
written_file.exists(),
1338+
"Written file should exist on host filesystem"
1339+
);
1340+
println!("✓ Written file exists on host");
1341+
1342+
// Test read-only bind mount - verify file exists and is readable
1343+
println!("Testing read-only bind mount...");
1344+
let ro_read_test = run_bcvk(&[
1345+
"libvirt",
1346+
"ssh",
1347+
&domain_name,
1348+
"--",
1349+
"cat",
1350+
"/var/mnt/test-ro/ro-test.txt",
1351+
])
1352+
.expect("Failed to read from ro bind mount");
1353+
1354+
assert!(
1355+
ro_read_test.success(),
1356+
"Should be able to read from ro bind mount. stderr: {}",
1357+
ro_read_test.stderr
1358+
);
1359+
assert!(
1360+
ro_read_test.stdout.contains("read-only content"),
1361+
"Should read correct content from ro bind mount"
1362+
);
1363+
println!("✓ RO bind mount is readable");
1364+
1365+
// Test that read-only mount rejects writes
1366+
println!("Testing that read-only bind mount rejects writes...");
1367+
let ro_write_test = run_bcvk(&[
1368+
"libvirt",
1369+
"ssh",
1370+
&domain_name,
1371+
"--",
1372+
"sh",
1373+
"-c",
1374+
"echo 'should fail' > /var/mnt/test-ro/write-test.txt 2>&1",
1375+
])
1376+
.expect("Failed to test write to ro bind mount");
1377+
1378+
assert!(
1379+
!ro_write_test.success(),
1380+
"Write to read-only bind mount should fail. stdout: {}, stderr: {}",
1381+
ro_write_test.stdout,
1382+
ro_write_test.stderr
1383+
);
1384+
println!("✓ RO bind mount correctly rejects writes");
1385+
1386+
// Cleanup domain
1387+
cleanup_domain(&domain_name);
1388+
1389+
println!("✓ Bind mounts test passed");
1390+
Ok(())
1391+
}

0 commit comments

Comments
 (0)