mod common;
use common::VsrxTarget;
use rustnetconf::pool::{DeviceConfig, DevicePool};
use rustnetconf::transport::ssh::{HostKeyVerification, SshAuth};
use rustnetconf::{Client, Datastore};
use std::time::Duration;
async fn connect_vsrx(target: &VsrxTarget) -> Client {
Client::connect(target.endpoint())
.username(&target.username)
.key_file(&target.key_path)
.host_key_verification(HostKeyVerification::AcceptAll)
.connect()
.await
.expect("failed to connect to vSRX")
}
fn vsrx_device_config(target: &VsrxTarget) -> DeviceConfig {
DeviceConfig {
host: target.endpoint().to_string(),
username: target.username.clone(),
auth: SshAuth::KeyFile {
path: target.key_path.clone(),
passphrase: None,
},
host_key_verification: Some(HostKeyVerification::AcceptAll),
vendor: None,
}
}
#[tokio::test]
async fn test_vsrx_auto_detected_as_junos() {
let Some(target) = common::skip_unless_vsrx_configured() else {
return;
};
let client = connect_vsrx(&target).await;
assert_eq!(client.vendor_name(), "junos");
}
#[tokio::test]
async fn test_edit_config_with_junos_auto_wrap() {
let Some(target) = common::skip_unless_vsrx_configured() else {
return;
};
let mut client = connect_vsrx(&target).await;
assert_eq!(client.vendor_name(), "junos");
client
.lock(Datastore::Candidate)
.await
.expect("lock failed");
client
.edit_config(Datastore::Candidate)
.config("<system><location><building>vendor-wrap-test</building></location></system>")
.default_operation(rustnetconf::DefaultOperation::Merge)
.send()
.await
.expect("edit-config with auto-wrap should succeed");
let config = client
.get_config_filtered(
Datastore::Candidate,
"<configuration><system><location/></system></configuration>",
)
.await
.expect("get-config failed");
assert!(
config.contains("vendor-wrap-test"),
"should contain our building, got: {config}"
);
client
.unlock(Datastore::Candidate)
.await
.expect("unlock failed");
client.close_session().await.expect("close failed");
}
#[tokio::test]
async fn test_get_config_junos_unwrap() {
let Some(target) = common::skip_unless_vsrx_configured() else {
return;
};
let mut client = connect_vsrx(&target).await;
let config = client
.get_config_filtered(
Datastore::Running,
"<configuration><system><host-name/></system></configuration>",
)
.await
.expect("get-config failed");
assert!(
config.contains("host-name"),
"should contain host-name, got: {config}"
);
assert!(
!config.trim().starts_with("<configuration"),
"Junos vendor should strip <configuration> wrapper, got: {config}"
);
}
#[tokio::test]
async fn test_pool_checkout_and_use() {
let Some(target) = common::skip_unless_vsrx_configured() else {
return;
};
let pool = DevicePool::builder()
.max_connections(5)
.add_device("vsrx", vsrx_device_config(&target))
.build();
{
let mut conn = pool.checkout("vsrx").await.expect("checkout failed");
let config = conn
.get_config(Datastore::Running)
.await
.expect("get-config failed");
assert!(!config.is_empty());
}
assert_eq!(
pool.available_connections(),
5,
"permit should be released after drop"
);
}
#[tokio::test]
async fn test_pool_connection_reuse() {
let Some(target) = common::skip_unless_vsrx_configured() else {
return;
};
let pool = DevicePool::builder()
.max_connections(5)
.add_device("vsrx", vsrx_device_config(&target))
.build();
{
let mut conn = pool.checkout("vsrx").await.expect("first checkout failed");
conn.get_config(Datastore::Running)
.await
.expect("first get-config failed");
}
tokio::time::sleep(Duration::from_millis(100)).await;
{
let mut conn = pool.checkout("vsrx").await.expect("second checkout failed");
conn.get_config(Datastore::Running)
.await
.expect("second get-config failed (reused)");
}
}
#[tokio::test]
async fn test_pool_unknown_device() {
let Some(target) = common::skip_unless_vsrx_configured() else {
return;
};
let pool = DevicePool::builder()
.max_connections(5)
.add_device("vsrx", vsrx_device_config(&target))
.build();
let result = pool.checkout("nonexistent").await;
assert!(result.is_err(), "unknown device should fail");
}
#[tokio::test]
async fn test_pool_checkout_timeout() {
let Some(target) = common::skip_unless_vsrx_configured() else {
return;
};
let pool = DevicePool::builder()
.max_connections(1)
.checkout_timeout(Duration::from_secs(2))
.add_device("vsrx", vsrx_device_config(&target))
.build();
let _guard = pool.checkout("vsrx").await.expect("first checkout failed");
let result = pool.checkout("vsrx").await;
assert!(result.is_err(), "should timeout with max_connections=1");
}
#[tokio::test]
async fn test_pool_concurrent_checkouts() {
let Some(target) = common::skip_unless_vsrx_configured() else {
return;
};
let pool = DevicePool::builder()
.max_connections(3)
.add_device("vsrx", vsrx_device_config(&target))
.build();
let (r1, r2) = tokio::join!(pool.checkout("vsrx"), pool.checkout("vsrx"),);
let mut conn1 = r1.expect("conn1 checkout failed");
let mut conn2 = r2.expect("conn2 checkout failed");
let (c1, c2) = tokio::join!(
conn1.get_config(Datastore::Running),
conn2.get_config(Datastore::Running),
);
assert!(!c1.expect("conn1 get-config failed").is_empty());
assert!(!c2.expect("conn2 get-config failed").is_empty());
assert_eq!(pool.available_connections(), 1, "2 of 3 permits in use");
}
#[tokio::test]
async fn test_pool_auto_detects_vendor() {
let Some(target) = common::skip_unless_vsrx_configured() else {
return;
};
let pool = DevicePool::builder()
.max_connections(5)
.add_device("vsrx", vsrx_device_config(&target))
.build();
let conn = pool.checkout("vsrx").await.expect("checkout failed");
assert_eq!(
conn.vendor_name(),
"junos",
"pool connection should auto-detect Junos"
);
}