use std::env;
use std::sync::Once;
use std::time;
use tokio::fs::File;
use tokio::io::AsyncReadExt;
use openstack::waiter::Waiter;
use openstack::Refresh;
static INIT: Once = Once::new();
async fn set_up() -> openstack::Cloud {
INIT.call_once(|| {
env_logger::init();
});
openstack::Cloud::from_env()
.await
.expect("Failed to create an identity provider from the environment")
}
fn validate_port(port: &openstack::network::Port, server: &openstack::compute::Server) {
assert_eq!(port.device_id().as_ref().unwrap(), server.id());
assert!(port
.device_owner()
.as_ref()
.unwrap()
.starts_with("compute:"));
assert!(port.attached_to_server());
assert!(port.fixed_ips().len() > 0);
}
async fn power_on_off_server(server: &mut openstack::compute::Server) {
server
.stop()
.await
.expect("Failed to request power off")
.wait()
.await
.expect("Failed to power off");
assert_eq!(
server.power_state(),
openstack::compute::ServerPowerState::Shutdown
);
server
.start()
.await
.expect("Failed to request power on")
.wait()
.await
.expect("Failed to power on");
assert_eq!(
server.power_state(),
openstack::compute::ServerPowerState::Running
);
}
async fn validate_server(os: &openstack::Cloud, server: &mut openstack::compute::Server) {
assert_eq!(server.name(), "rust-openstack-integration");
assert_eq!(server.status(), openstack::compute::ServerStatus::Active);
assert_eq!(
server.power_state(),
openstack::compute::ServerPowerState::Running
);
assert_eq!(
server.metadata().get("meta"),
Some(&"a3f955c049f7416faa7".to_string())
);
power_on_off_server(server).await;
let port = os
.find_ports()
.with_device_id(server.id().clone())
.with_admin_state_up(true)
.one()
.await
.expect("Cannot find the port attached to the server");
validate_port(&port, &server);
let image = server.image().await.expect("Cannot fetch Server image");
assert_eq!(image.id(), server.image_id().unwrap());
let flavor = server.flavor().await.expect("Cannot fetch Server flavor");
assert!(flavor.vcpu_count > 0);
assert!(flavor.ram_size > 0);
assert!(flavor.root_size > 0);
}
#[tokio::test]
async fn test_basic_server_ops() {
let os = set_up().await;
let image_id = env::var("RUST_OPENSTACK_IMAGE").expect("Missing RUST_OPENSTACK_IMAGE");
let flavor_id = env::var("RUST_OPENSTACK_FLAVOR").expect("Missing RUST_OPENSTACK_FLAVOR");
let network_id = env::var("RUST_OPENSTACK_NETWORK").expect("Missing RUST_OPENSTACK_NETWORK");
let floating_network_id = env::var("RUST_OPENSTACK_FLOATING_NETWORK")
.expect("Missing RUST_OPENSTACK_FLOATING_NETWORK");
let (keypair, private_key) = os
.new_keypair("rust-openstack-integration")
.with_key_type(openstack::compute::KeyPairType::SSH)
.generate()
.await
.expect("Cannot create a key pair");
assert!(!private_key.is_empty());
let mut server = os
.new_server("rust-openstack-integration", flavor_id)
.with_image(image_id)
.with_network(network_id.clone())
.with_keypair(keypair)
.with_metadata("meta", "a3f955c049f7416faa7")
.create()
.await
.expect("Failed to request server creation")
.wait()
.await
.expect("Server was not created");
validate_server(&os, &mut server).await;
let ports = os
.find_ports()
.with_network(network_id)
.with_status(openstack::network::NetworkStatus::Active)
.all()
.await
.expect("Cannot find active ports for network");
assert!(ports.len() > 0);
let server_port = os
.find_ports()
.with_device_id(server.id().clone())
.one()
.await
.expect("Cannot find the port attached to the server");
let mut floating_ip = os
.new_floating_ip(floating_network_id)
.create()
.await
.expect("Cannot create a floating IP");
floating_ip
.associate(server_port, None)
.await
.expect("Cannot associate floating IP");
floating_ip
.delete()
.await
.expect("Failed to request floating IP deletion")
.wait()
.await
.expect("Failed to delete floating IP");
let console = server
.get_console_output(Some(10))
.await
.expect("Failed to get console output");
assert!(console.len() > 0);
server
.delete()
.await
.expect("Failed to request deletion")
.wait()
.await
.expect("Failed to delete server");
os.get_keypair("rust-openstack-integration")
.await
.expect("Cannot get key pair")
.delete()
.await
.expect("Cannot delete key pair");
}
#[tokio::test]
async fn test_server_ops_with_port() {
let os = set_up().await;
let image_id = env::var("RUST_OPENSTACK_IMAGE").expect("Missing RUST_OPENSTACK_IMAGE");
let flavor_id = env::var("RUST_OPENSTACK_FLAVOR").expect("Missing RUST_OPENSTACK_FLAVOR");
let network_id = env::var("RUST_OPENSTACK_NETWORK").expect("Missing RUST_OPENSTACK_NETWORK");
let keypair_file_name =
env::var("RUST_OPENSTACK_KEYPAIR").expect("Missing RUST_OPENSTACK_KEYPAIR");
let mut keypair_pkey = String::new();
let _ = File::open(keypair_file_name)
.await
.expect("Cannot open RUST_OPENSTACK_KEYPAIR")
.read_to_string(&mut keypair_pkey)
.await
.expect("Cannot read RUST_OPENSTACK_KEYPAIR");
let floating_network_id = env::var("RUST_OPENSTACK_FLOATING_NETWORK")
.expect("Missing RUST_OPENSTACK_FLOATING_NETWORK");
let keypair = os
.new_keypair("rust-openstack-integration")
.with_public_key(keypair_pkey)
.create()
.await
.expect("Cannot create a key pair");
let mut port = os
.new_port(network_id)
.with_name("rust-openstack-integration")
.create()
.await
.expect("Cannot create a port");
assert_eq!(port.name().as_ref().unwrap(), "rust-openstack-integration");
let mut server = os
.new_server("rust-openstack-integration", flavor_id)
.with_image(image_id)
.with_port("rust-openstack-integration")
.with_keypair(keypair)
.with_metadata("meta", "a3f955c049f7416faa7")
.create()
.await
.expect("Failed to request server creation")
.wait()
.await
.expect("Server was not created");
validate_server(&os, &mut server).await;
port.refresh().await.expect("Cannot refresh the port");
validate_port(&port, &server);
let network = port.network().await.expect("Could not find port's network");
assert_eq!(network.id(), port.network_id());
let mut floating_ip = os
.new_floating_ip(floating_network_id)
.with_port(port.clone())
.create()
.await
.expect("Cannot create a floating IP");
floating_ip.set_description("A floating IP");
floating_ip.save().await.expect("Cannot save floating IP");
assert_eq!(
floating_ip.description().as_ref().expect("No description"),
"A floating IP"
);
for attempt in 0..10 {
tokio::time::sleep(time::Duration::from_secs(1)).await;
server.refresh().await.expect("Cannot refresh the server");
match server.floating_ip() {
Some(server_ip) => {
assert_eq!(server_ip, floating_ip.floating_ip_address());
break;
}
None => {
if attempt == 9 {
panic!("Still no floating IP after 10 seconds");
}
}
}
}
floating_ip
.dissociate()
.await
.expect("Cannot dissociate a floating IP");
floating_ip
.delete()
.await
.expect("Failed to request floating IP deletion")
.wait()
.await
.expect("Failed to delete floating IP");
server
.delete()
.await
.expect("Failed to request deletion")
.wait()
.await
.expect("Failed to delete server");
os.get_keypair("rust-openstack-integration")
.await
.expect("Cannot get key pair")
.delete()
.await
.expect("Cannot delete key pair");
port.refresh().await.expect("Cannot refresh the port");
assert!(port.device_id().is_none());
assert!(!port.attached_to_server());
port.delete()
.await
.expect("Failed to request deletion")
.wait()
.await
.expect("Failed to delete port");
}
#[tokio::test]
async fn test_server_boot_from_new_volume() {
let os = set_up().await;
let image_id = env::var("RUST_OPENSTACK_IMAGE").expect("Missing RUST_OPENSTACK_IMAGE");
let flavor_id = env::var("RUST_OPENSTACK_FLAVOR").expect("Missing RUST_OPENSTACK_FLAVOR");
let network_id = env::var("RUST_OPENSTACK_NETWORK").expect("Missing RUST_OPENSTACK_NETWORK");
let keypair_name = "rust-openstack-integration-bfv";
let (keypair, private_key) = os
.new_keypair(keypair_name)
.with_key_type(openstack::compute::KeyPairType::SSH)
.generate()
.await
.expect("Cannot create a key pair");
assert!(!private_key.is_empty());
let mut server = os
.new_server("rust-openstack-integration", flavor_id)
.with_new_boot_volume(image_id, 8)
.with_block_device(openstack::compute::BlockDevice::from_empty_volume(8))
.with_network(network_id.clone())
.with_keypair(keypair)
.with_metadata("meta", "a3f955c049f7416faa7")
.create()
.await
.expect("Failed to request server creation")
.wait()
.await
.expect("Server was not created");
assert_eq!(server.status(), openstack::compute::ServerStatus::Active);
assert_eq!(
server.power_state(),
openstack::compute::ServerPowerState::Running
);
assert!(server.image_id().is_none());
power_on_off_server(&mut server).await;
server
.delete()
.await
.expect("Failed to request deletion")
.wait()
.await
.expect("Failed to delete server");
os.get_keypair(keypair_name)
.await
.expect("Cannot get key pair")
.delete()
.await
.expect("Cannot delete key pair");
}