Add Proxmox Backup Server datastore management: overview, datastore detail, download/prune/verify/GC dialogs, usePbs hook, backend commands, and tests. Fix macOS keychain re-writes failing with 'item already exists': replace keyring 3 with keyring-core plus per-platform stores (macOS Keychain, Windows Credential Manager, Linux keyutils), recover by deleting the stale item and retrying once, and surface actionable messages for locked keychains.
657 lines
22 KiB
Rust
657 lines
22 KiB
Rust
//! Integration tests for VM/container disk and network interface management.
|
|
//!
|
|
//! Every request goes through `Connection::request`, so these tests cover URL
|
|
//! construction (including the `qemu`/`lxc` path segment), form encoding,
|
|
//! config parsing, and slot allocation. Token-mode connections resolve the
|
|
//! token from the config, so the methods can be called directly on an added
|
|
//! connection without `connect()`.
|
|
//!
|
|
//! The form bodies are `application/x-www-form-urlencoded`, so values with
|
|
//! special characters (`:`, `=`, `,`) appear percent-encoded in the raw body;
|
|
//! `body_includes` assertions use the encoded form (e.g. `:` becomes `%3A`).
|
|
|
|
use httpmock::prelude::*;
|
|
use clustri::{
|
|
AddDiskConfig, AddNICConfig, ConnectionConfig, ConnectionManager, EditNICConfig,
|
|
EndpointConfig, Error,
|
|
};
|
|
|
|
/// Builds a `ConnectionManager` with a single token-mode connection whose
|
|
/// primary endpoint points at the mock server.
|
|
async fn setup_manager(
|
|
url: &str,
|
|
token: &str,
|
|
node: Option<&str>,
|
|
) -> (ConnectionManager, tempfile::TempDir) {
|
|
let dir = tempfile::tempdir().expect("temp dir should be created");
|
|
let path = dir.path().join("connections.json");
|
|
let mut manager = ConnectionManager::new();
|
|
let config = ConnectionConfig {
|
|
id: "conn".to_string(),
|
|
name: "conn".to_string(),
|
|
primary: EndpointConfig {
|
|
url: url.to_string(),
|
|
node: node.map(str::to_string),
|
|
token: Some(token.to_string()),
|
|
},
|
|
fallbacks: vec![],
|
|
cert_fingerprint: None,
|
|
trusted: false,
|
|
accept_untrusted: true,
|
|
status: "disconnected".to_string(),
|
|
cluster_name: None,
|
|
is_cluster: false,
|
|
auth_mode: "token".to_string(),
|
|
username: None,
|
|
nodes: vec![],
|
|
cluster_id: None,
|
|
server_type: "pve".to_string(),
|
|
};
|
|
manager
|
|
.add_connection(config, &path)
|
|
.await
|
|
.expect("connection should be added");
|
|
(manager, dir)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn get_disks_parses_config_strings() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!disks-token";
|
|
let mock = server.mock(|when, then| {
|
|
when.method(GET)
|
|
.path("/api2/json/nodes/pve1/qemu/100/config")
|
|
.header("Authorization", format!("PVEAPIToken={}", token));
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(
|
|
serde_json::json!({
|
|
"data": {
|
|
"scsi0": "local-lvm:vm-100-disk-0,size=32G,format=qcow2",
|
|
"scsi1": "local-lvm:vm-100-disk-1,size=50G",
|
|
"net0": "virtio=BC:24:11:AA:BB:CC,bridge=vmbr0",
|
|
"memory": 2048
|
|
}
|
|
})
|
|
.to_string(),
|
|
);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
let disks = manager
|
|
.get_disks("conn", "pve1", 100, "qemu")
|
|
.await
|
|
.expect("disks should be fetched");
|
|
|
|
assert_eq!(disks.len(), 2);
|
|
assert_eq!(disks[0].device, "scsi0");
|
|
assert_eq!(disks[0].storage, "local-lvm");
|
|
assert_eq!(disks[0].size, 34359738368);
|
|
assert_eq!(disks[0].format, "qcow2");
|
|
assert_eq!(disks[0].usage, None);
|
|
// The second disk has no size/format attributes: size is 0, format empty.
|
|
assert_eq!(disks[1].device, "scsi1");
|
|
assert_eq!(disks[1].storage, "local-lvm");
|
|
assert_eq!(disks[1].size, 53687091200);
|
|
assert_eq!(disks[1].format, "");
|
|
mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn add_disk_picks_free_slot_and_posts() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!add-disk-token";
|
|
let get_mock = server.mock(|when, then| {
|
|
when.method(GET)
|
|
.path("/api2/json/nodes/pve1/qemu/100/config");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(
|
|
serde_json::json!({
|
|
"data": {"scsi0": "local-lvm:vm-100-disk-0,size=32G"}
|
|
})
|
|
.to_string(),
|
|
);
|
|
});
|
|
let post_mock = server.mock(|when, then| {
|
|
when.method(POST)
|
|
.path("/api2/json/nodes/pve1/qemu/100/config")
|
|
// urlencoded: the `:` in `local-lvm:64G` becomes `%3A`.
|
|
.body_includes("scsi1=local-lvm%3A64G");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":null}"#);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
let config = AddDiskConfig {
|
|
storage: "local-lvm".to_string(),
|
|
size: 68719476736,
|
|
bus_type: "scsi".to_string(),
|
|
};
|
|
manager
|
|
.add_disk("conn", "pve1", 100, "qemu", config)
|
|
.await
|
|
.expect("disk should be added");
|
|
|
|
get_mock.assert();
|
|
post_mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn resize_disk_posts_resize() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!resize-token";
|
|
let mock = server.mock(|when, then| {
|
|
when.method(PUT)
|
|
.path("/api2/json/nodes/pve1/qemu/100/resize")
|
|
.body_includes("disk=scsi0")
|
|
.body_includes("size=50G");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":null}"#);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
manager
|
|
.resize_disk("conn", "pve1", 100, "qemu", "scsi0", 53687091200)
|
|
.await
|
|
.expect("resize should succeed");
|
|
|
|
mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn remove_disk_posts_delete() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!remove-disk-token";
|
|
let mock = server.mock(|when, then| {
|
|
when.method(DELETE)
|
|
.path("/api2/json/nodes/pve1/qemu/100/config")
|
|
.body_includes("delete=scsi0");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":null}"#);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
manager
|
|
.remove_disk("conn", "pve1", 100, "qemu", "scsi0")
|
|
.await
|
|
.expect("disk should be removed");
|
|
|
|
mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn move_disk_posts_move_disk() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!move-disk-token";
|
|
let mock = server.mock(|when, then| {
|
|
when.method(POST)
|
|
.path("/api2/json/nodes/pve1/qemu/100/move_disk")
|
|
.body_includes("disk=scsi0")
|
|
.body_includes("storage=local-zfs");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":null}"#);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
manager
|
|
.move_disk("conn", "pve1", 100, "qemu", "scsi0", "local-zfs")
|
|
.await
|
|
.expect("disk should be moved");
|
|
|
|
mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn get_network_interfaces_parses_nets() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!nets-token";
|
|
let mock = server.mock(|when, then| {
|
|
when.method(GET)
|
|
.path("/api2/json/nodes/pve1/qemu/100/config");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(
|
|
serde_json::json!({
|
|
"data": {
|
|
"net0": "virtio=BC:24:11:AA:BB:CC,bridge=vmbr0,tag=10,firewall=1,link_down=1",
|
|
"net1": "e1000=11:22:33:44:55:66,bridge=vmbr1"
|
|
}
|
|
})
|
|
.to_string(),
|
|
);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
let nics = manager
|
|
.get_network_interfaces("conn", "pve1", 100, "qemu")
|
|
.await
|
|
.expect("nics should be fetched");
|
|
|
|
assert_eq!(nics.len(), 2);
|
|
assert_eq!(nics[0].name, "net0");
|
|
assert_eq!(nics[0].model, "virtio");
|
|
assert_eq!(nics[0].macaddr, "BC:24:11:AA:BB:CC");
|
|
assert_eq!(nics[0].bridge.as_deref(), Some("vmbr0"));
|
|
assert_eq!(nics[0].tag, Some(10));
|
|
assert_eq!(nics[0].firewall, Some(1));
|
|
assert_eq!(nics[0].link_down, Some(1));
|
|
// The second NIC has no optional attributes.
|
|
assert_eq!(nics[1].name, "net1");
|
|
assert_eq!(nics[1].model, "e1000");
|
|
assert_eq!(nics[1].macaddr, "11:22:33:44:55:66");
|
|
assert_eq!(nics[1].bridge.as_deref(), Some("vmbr1"));
|
|
assert_eq!(nics[1].tag, None);
|
|
assert_eq!(nics[1].firewall, None);
|
|
assert_eq!(nics[1].link_down, None);
|
|
mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn add_nic_posts_form() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!add-nic-token";
|
|
let get_mock = server.mock(|when, then| {
|
|
when.method(GET)
|
|
.path("/api2/json/nodes/pve1/qemu/100/config");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(
|
|
serde_json::json!({
|
|
"data": {"memory": 2048}
|
|
})
|
|
.to_string(),
|
|
);
|
|
});
|
|
let post_mock = server.mock(|when, then| {
|
|
when.method(POST)
|
|
.path("/api2/json/nodes/pve1/qemu/100/config")
|
|
// urlencoded: `=` and `,` become `%3D` and `%2C`.
|
|
.body_includes("net0=virtio%3Drandom%2Cbridge%3Dvmbr0");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":null}"#);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
let config = AddNICConfig {
|
|
bridge: "vmbr0".to_string(),
|
|
model: "virtio".to_string(),
|
|
macaddr: None,
|
|
tag: None,
|
|
firewall: None,
|
|
};
|
|
manager
|
|
.add_nic("conn", "pve1", 100, "qemu", config)
|
|
.await
|
|
.expect("nic should be added");
|
|
|
|
get_mock.assert();
|
|
post_mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn edit_nic_preserves_model_and_mac() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!edit-nic-token";
|
|
let get_mock = server.mock(|when, then| {
|
|
when.method(GET)
|
|
.path("/api2/json/nodes/pve1/qemu/100/config");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(
|
|
serde_json::json!({
|
|
"data": {"net0": "virtio=MAC,bridge=vmbr0"}
|
|
})
|
|
.to_string(),
|
|
);
|
|
});
|
|
let post_mock = server.mock(|when, then| {
|
|
when.method(POST)
|
|
.path("/api2/json/nodes/pve1/qemu/100/config")
|
|
.body_includes("net0=virtio%3DMAC%2Cbridge%3Dvmbr1");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":null}"#);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
let config = EditNICConfig {
|
|
bridge: Some("vmbr1".to_string()),
|
|
model: None,
|
|
tag: None,
|
|
firewall: None,
|
|
};
|
|
manager
|
|
.edit_nic("conn", "pve1", 100, "qemu", "net0", config)
|
|
.await
|
|
.expect("nic should be edited");
|
|
|
|
get_mock.assert();
|
|
post_mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn edit_nic_tag_clear_preserves_other_attributes() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!edit-nic-tag-token";
|
|
let get_mock = server.mock(|when, then| {
|
|
when.method(GET)
|
|
.path("/api2/json/nodes/pve1/qemu/100/config");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(
|
|
serde_json::json!({
|
|
"data": {"net0": "virtio=AA:BB:CC:DD:EE:FF,bridge=vmbr0,tag=10,link_down=1"}
|
|
})
|
|
.to_string(),
|
|
);
|
|
});
|
|
let post_mock = server.mock(|when, then| {
|
|
when.method(POST)
|
|
.path("/api2/json/nodes/pve1/qemu/100/config")
|
|
// urlencoded: `=` and `,` become `%3D` and `%2C`; the tag is gone
|
|
// while the unmodeled `link_down` attribute is carried over.
|
|
.body_includes("net0=virtio%3DAA%3ABB%3ACC%3ADD%3AEE%3AFF%2Cbridge%3Dvmbr0%2Clink_down%3D1")
|
|
.body_excludes("tag");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":null}"#);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
let config = EditNICConfig {
|
|
bridge: None,
|
|
model: None,
|
|
tag: Some(0),
|
|
firewall: None,
|
|
};
|
|
manager
|
|
.edit_nic("conn", "pve1", 100, "qemu", "net0", config)
|
|
.await
|
|
.expect("nic should be edited");
|
|
|
|
get_mock.assert();
|
|
post_mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn remove_nic_posts_delete() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!remove-nic-token";
|
|
let mock = server.mock(|when, then| {
|
|
when.method(DELETE)
|
|
.path("/api2/json/nodes/pve1/qemu/100/config")
|
|
.body_includes("delete=net0");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":null}"#);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
manager
|
|
.remove_nic("conn", "pve1", 100, "qemu", "net0")
|
|
.await
|
|
.expect("nic should be removed");
|
|
|
|
mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn lxc_vm_types_use_lxc_path() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!lxc-token";
|
|
let config_mock = server.mock(|when, then| {
|
|
when.method(GET)
|
|
.path("/api2/json/nodes/pve1/lxc/201/config");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(
|
|
serde_json::json!({
|
|
"data": {
|
|
"rootfs": "local:vm-201-disk-0,size=8G",
|
|
"mp0": "local:vm-201-mp-0,size=4G",
|
|
"net0": "name=eth0,bridge=vmbr0,ip=dhcp"
|
|
}
|
|
})
|
|
.to_string(),
|
|
);
|
|
});
|
|
let resize_mock = server.mock(|when, then| {
|
|
when.method(PUT)
|
|
.path("/api2/json/nodes/pve1/lxc/201/resize")
|
|
.body_includes("disk=rootfs")
|
|
.body_includes("size=16G");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":null}"#);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
|
|
// Containers use `rootfs`/`mpN` keys; they map onto Disk entries with the
|
|
// container-specific device names, and LXC network keys without an
|
|
// explicit model are still parsed.
|
|
let disks = manager
|
|
.get_disks("conn", "pve1", 201, "lxc")
|
|
.await
|
|
.expect("disks should be fetched");
|
|
assert_eq!(disks.len(), 2);
|
|
let rootfs = disks
|
|
.iter()
|
|
.find(|d| d.device == "rootfs")
|
|
.expect("rootfs disk should be parsed");
|
|
assert_eq!(rootfs.storage, "local");
|
|
assert_eq!(rootfs.size, 8589934592); // 8G
|
|
let mp0 = disks
|
|
.iter()
|
|
.find(|d| d.device == "mp0")
|
|
.expect("mp0 disk should be parsed");
|
|
assert_eq!(mp0.storage, "local");
|
|
assert_eq!(mp0.size, 4294967296); // 4G
|
|
|
|
let nics = manager
|
|
.get_network_interfaces("conn", "pve1", 201, "lxc")
|
|
.await
|
|
.expect("nics should be fetched");
|
|
assert_eq!(nics.len(), 1);
|
|
assert_eq!(nics[0].name, "net0");
|
|
assert_eq!(nics[0].bridge.as_deref(), Some("vmbr0"));
|
|
// LXC format: `name=eth0` lead means no QEMU model=mac first segment.
|
|
assert_eq!(nics[0].model, "");
|
|
assert_eq!(nics[0].macaddr, "");
|
|
|
|
manager
|
|
.resize_disk("conn", "pve1", 201, "lxc", "rootfs", 17179869184)
|
|
.await
|
|
.expect("resize should succeed");
|
|
|
|
config_mock.assert_calls(2);
|
|
resize_mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn add_nic_lxc_writes_lxc_net_format() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!add-nic-lxc-token";
|
|
let get_mock = server.mock(|when, then| {
|
|
when.method(GET)
|
|
.path("/api2/json/nodes/pve1/lxc/201/config");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(
|
|
serde_json::json!({
|
|
"data": {"memory": 2048}
|
|
})
|
|
.to_string(),
|
|
);
|
|
});
|
|
let post_mock = server.mock(|when, then| {
|
|
when.method(POST)
|
|
.path("/api2/json/nodes/pve1/lxc/201/config")
|
|
// urlencoded: `name=eth0,type=veth,bridge=vmbr0,hwaddr=BC:24:11:8D:DF:95,firewall=1`
|
|
.body_includes(
|
|
"net0=name%3Deth0%2Ctype%3Dveth%2Cbridge%3Dvmbr0%2Chwaddr%3DBC%3A24%3A11%3A8D%3ADF%3A95%2Cfirewall%3D1",
|
|
);
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":null}"#);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
let config = AddNICConfig {
|
|
bridge: "vmbr0".to_string(),
|
|
// The model is ignored for LXC (containers always use veth), so an
|
|
// otherwise-invalid model still succeeds.
|
|
model: "veth".to_string(),
|
|
macaddr: Some("BC:24:11:8D:DF:95".to_string()),
|
|
tag: None,
|
|
firewall: Some(true),
|
|
};
|
|
manager
|
|
.add_nic("conn", "pve1", 201, "lxc", config)
|
|
.await
|
|
.expect("nic should be added");
|
|
|
|
get_mock.assert();
|
|
post_mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn add_nic_lxc_random_mac_and_no_firewall_omits_hwaddr_and_firewall() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!add-nic-lxc-plain-token";
|
|
let get_mock = server.mock(|when, then| {
|
|
when.method(GET)
|
|
.path("/api2/json/nodes/pve1/lxc/201/config");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":{"memory":2048}}"#);
|
|
});
|
|
let post_mock = server.mock(|when, then| {
|
|
when.method(POST)
|
|
.path("/api2/json/nodes/pve1/lxc/201/config")
|
|
.body_includes("net0=name%3Deth0%2Ctype%3Dveth%2Cbridge%3Dvmbr0")
|
|
.body_excludes("hwaddr")
|
|
.body_excludes("firewall");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":null}"#);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
let config = AddNICConfig {
|
|
bridge: "vmbr0".to_string(),
|
|
model: "veth".to_string(),
|
|
// `random` (or a missing MAC) means "let Proxmox assign one".
|
|
macaddr: Some("random".to_string()),
|
|
tag: None,
|
|
firewall: None,
|
|
};
|
|
manager
|
|
.add_nic("conn", "pve1", 201, "lxc", config)
|
|
.await
|
|
.expect("nic should be added");
|
|
|
|
get_mock.assert();
|
|
post_mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn edit_nic_lxc_reencodes_lxc_format() {
|
|
let server = MockServer::start();
|
|
let token = "root@pam!edit-nic-lxc-token";
|
|
let get_mock = server.mock(|when, then| {
|
|
when.method(GET)
|
|
.path("/api2/json/nodes/pve1/lxc/201/config");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(
|
|
serde_json::json!({
|
|
"data": {"net0": "name=eth0,type=veth,hwaddr=BC:24:11:8D:DF:95,bridge=vmbr0,ip=dhcp,firewall=0"}
|
|
})
|
|
.to_string(),
|
|
);
|
|
});
|
|
let post_mock = server.mock(|when, then| {
|
|
when.method(POST)
|
|
.path("/api2/json/nodes/pve1/lxc/201/config")
|
|
// urlencoded; the LXC form is rebuilt (`name=eth0,type=veth,
|
|
// hwaddr=...`) with the new bridge/firewall and the unknown
|
|
// `ip=dhcp` attribute carried over, but no tag.
|
|
.body_includes(
|
|
"net0=name%3Deth0%2Ctype%3Dveth%2Chwaddr%3DBC%3A24%3A11%3A8D%3ADF%3A95%2Cbridge%3Dvmbr1%2Cfirewall%3D1%2Cip%3Ddhcp",
|
|
)
|
|
.body_excludes("tag");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":null}"#);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), token, None).await;
|
|
let config = EditNICConfig {
|
|
bridge: Some("vmbr1".to_string()),
|
|
model: None,
|
|
tag: None,
|
|
firewall: Some(true),
|
|
};
|
|
manager
|
|
.edit_nic("conn", "pve1", 201, "lxc", "net0", config)
|
|
.await
|
|
.expect("nic should be edited");
|
|
|
|
get_mock.assert();
|
|
post_mock.assert();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_vm_type_errors() {
|
|
let server = MockServer::start();
|
|
let probe = server.mock(|when, then| {
|
|
when.method(GET)
|
|
.path("/api2/json/nodes/pve1/kvm/100/config");
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(r#"{"data":{}}"#);
|
|
});
|
|
|
|
let (manager, _dir) = setup_manager(&server.base_url(), "root@pam!bad-type-token", None).await;
|
|
|
|
let error = manager
|
|
.get_disks("conn", "pve1", 100, "kvm")
|
|
.await
|
|
.expect_err("invalid vm type must be rejected");
|
|
assert!(
|
|
matches!(error, Error::InvalidUrl(ref message) if message.contains("kvm")),
|
|
"expected InvalidUrl mentioning 'kvm', got: {}",
|
|
error
|
|
);
|
|
|
|
let config = AddNICConfig {
|
|
bridge: "vmbr0".to_string(),
|
|
model: "virtio".to_string(),
|
|
macaddr: None,
|
|
tag: None,
|
|
firewall: None,
|
|
};
|
|
let error = manager
|
|
.add_nic("conn", "pve1", 100, "kvm", config)
|
|
.await
|
|
.expect_err("invalid vm type must be rejected");
|
|
assert!(
|
|
matches!(error, Error::InvalidUrl(ref message) if message.contains("kvm")),
|
|
"expected InvalidUrl mentioning 'kvm', got: {}",
|
|
error
|
|
);
|
|
|
|
assert_eq!(
|
|
probe.calls(),
|
|
0,
|
|
"no HTTP request should be made for an invalid vm type"
|
|
);
|
|
}
|