use crate::interfaces::{Ethernet, InterfaceClient, InterfacesClient, Serial};
use crate::labs::{LabClient, LabPath};
use crate::templates::NodeTemplate;
use crate::utils::{WireMap, empty_string_is_none, private::Sealed};
use crate::{Client, Error, Result};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug, Serialize, Deserialize)]
pub struct Node {
pub config: StartupConfig,
pub delay: u32,
pub icon: String,
pub image: String,
pub left: u32,
pub name: String,
#[serde(rename = "type")]
pub node_type: NodeType,
pub status: NodeStatus,
pub template: String,
pub top: u32,
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub config_list: Option<Vec<Value>>,
#[serde(
deserialize_with = "empty_string_is_none",
skip_serializing_if = "Option::is_none"
)]
pub console: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cpu: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cpulimit: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ethernet: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub idlepc: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nvram: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub qemu_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub qemu_arch: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub qemu_nic: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub qemu_options: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ram: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub serial: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slot1: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slot2: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slot3: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slot4: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slot5: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slot6: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uuid: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum NodeType {
Docker,
Dynamips,
Iol,
Qemu,
Vpcs,
}
impl std::fmt::Display for NodeType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let value = match self {
NodeType::Qemu => "qemu",
NodeType::Iol => "iol",
NodeType::Docker => "docker",
NodeType::Dynamips => "dynamips",
NodeType::Vpcs => "vpcs",
};
f.write_str(value)
}
}
#[derive(Debug, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum NodeStatus {
Stopped = 0,
Starting = 1,
Running = 2,
Stopping = 3,
}
#[derive(Debug, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum StartupConfig {
None = 0,
Exported = 1,
}
impl From<u8> for StartupConfig {
fn from(v: u8) -> Self {
match v {
0 => StartupConfig::None,
1 => StartupConfig::Exported,
_ => StartupConfig::None,
}
}
}
pub struct NodesClient {
client: Client,
path: LabPath,
}
impl NodesClient {
pub(crate) fn new(client: Client, path: LabPath) -> Self {
Self { client, path }
}
fn lab(&self) -> LabClient {
LabClient::from_path(self.client.clone(), self.path.clone())
}
pub async fn list(&self) -> Result<HashMap<u32, Node>> {
Ok(self
.client
.get::<WireMap<u32, Node>>(&format!("labs{}/nodes", self.path))
.await?
.into_data()?
.0)
}
pub async fn add<T: TypedNode>(&self, params: AddNodeRequest<T>) -> Result<NodeClient> {
self.lab().open().await?;
#[derive(Deserialize)]
struct CreateNodeResponse {
id: u32,
}
let resp: CreateNodeResponse = self
.client
.post(&format!("labs{}/nodes", self.path), ¶ms)
.await?
.into_data()?;
Ok(NodeClient::new(
self.client.clone(),
self.path.clone(),
resp.id,
))
}
pub async fn start(&self) -> Result<()> {
self.lab().open().await?;
self.client
.get::<()>(&format!("labs{}/nodes/start", self.path))
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
Ok(())
}
pub async fn stop(&self) -> Result<()> {
self.lab().open().await?;
self.client
.get::<()>(&format!("labs{}/nodes/stop", self.path))
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
Ok(())
}
pub async fn wipe(&self) -> Result<()> {
self.lab().open().await?;
self.client
.get::<()>(&format!("labs{}/nodes/wipe", self.path))
.await?;
Ok(())
}
pub async fn export(&self) -> Result<()> {
self.lab().open().await?;
self.client
.get::<()>(&format!("labs{}/nodes/export", self.path))
.await?;
Ok(())
}
}
pub struct NodeClient {
client: Client,
path: LabPath,
id: u32,
}
impl NodeClient {
pub(crate) fn new(client: Client, path: LabPath, id: u32) -> Self {
Self { client, path, id }
}
fn lab(&self) -> LabClient {
LabClient::from_path(self.client.clone(), self.path.clone())
}
pub async fn get(&self) -> Result<Node> {
self.client
.get(&format!("labs{}/nodes/{}", self.path, self.id))
.await?
.into_data()
}
pub async fn status(&self) -> Result<NodeStatus> {
Ok(self.get().await?.status)
}
pub async fn node_type(&self) -> Result<NodeType> {
Ok(self.get().await?.node_type)
}
pub async fn edit<T: TypedNode>(&self, params: EditNodeRequest<T>) -> Result<()> {
self.lab().open().await?;
if matches!(params.status, NodeStatus::Running) {
return Err(Error::Client(
"Cannot edit node as it is still running.".to_string(),
));
}
self.client
.put::<(), EditNodeRequest<T>>(&format!("labs{}/nodes/{}", self.path, self.id), ¶ms)
.await?;
Ok(())
}
pub async fn delete(self) -> Result<()> {
self.lab().open().await?;
if matches!(self.status().await?, NodeStatus::Running) {
return Err(Error::Client(
"Cannot delete node as it is still running.".to_string(),
));
}
self.client
.delete::<()>(&format!("labs{}/nodes/{}", self.path, self.id))
.await?;
Ok(())
}
pub async fn start(&self) -> Result<()> {
self.lab().open().await?;
self.client
.get::<()>(&format!("labs{}/nodes/{}/start", self.path, self.id))
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
Ok(())
}
pub async fn stop(&self) -> Result<()> {
self.lab().open().await?;
self.client
.get::<()>(&format!("labs{}/nodes/{}/stop", self.path, self.id))
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
Ok(())
}
pub async fn wipe(&self) -> Result<()> {
self.lab().open().await?;
self.client
.get::<()>(&format!("labs{}/nodes/{}/wipe", self.path, self.id))
.await?;
Ok(())
}
pub async fn export(&self) -> Result<()> {
self.lab().open().await?;
self.client
.get::<()>(&format!("labs{}/nodes/{}/export", self.path, self.id))
.await?;
Ok(())
}
pub fn interfaces(&self) -> InterfacesClient {
InterfacesClient::new(self.client.clone(), self.path.clone(), self.id)
}
pub fn ethernet(&self, id: u32) -> InterfaceClient<Ethernet> {
InterfaceClient::ethernet(self.client.clone(), self.path.clone(), self.id, id)
}
pub fn serial(&self, id: u32) -> InterfaceClient<Serial> {
InterfaceClient::serial(self.client.clone(), self.path.clone(), self.id, id)
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Docker {
ethernet: u32,
image: String,
ram: u32,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Dynamips {
idlepc: String,
image: String,
nvram: u32,
ram: u32,
slot1: String,
slot2: String,
slot3: Option<String>,
slot4: Option<String>,
slot5: Option<String>,
slot6: Option<String>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Iol {
ethernet: u32,
image: String,
nvram: u32,
ram: u32,
serial: u32,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Qemu {
cpu: u32,
cpulimit: u8,
ethernet: u32,
image: String,
ram: u32,
qemu_version: String,
qemu_arch: String,
qemu_nic: String,
qemu_options: String,
uuid: String,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Vpcs {
#[serde(skip_serializing_if = "Option::is_none")]
ethernet: Option<u32>,
}
pub trait TypedNode: Default + Serialize + DeserializeOwned + Sealed {
const NODE_TYPE: NodeType;
}
impl Sealed for Qemu {}
impl TypedNode for Qemu {
const NODE_TYPE: NodeType = NodeType::Qemu;
}
impl Sealed for Docker {}
impl TypedNode for Docker {
const NODE_TYPE: NodeType = NodeType::Docker;
}
impl Sealed for Dynamips {}
impl TypedNode for Dynamips {
const NODE_TYPE: NodeType = NodeType::Dynamips;
}
impl Sealed for Vpcs {}
impl TypedNode for Vpcs {
const NODE_TYPE: NodeType = NodeType::Vpcs;
}
impl Sealed for Iol {}
impl TypedNode for Iol {
const NODE_TYPE: NodeType = NodeType::Iol;
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AddNodeRequest<T> {
count: u32,
left: u32,
#[serde(rename = "type")]
node_type: NodeType,
template: String,
top: u32,
config: StartupConfig,
delay: u32,
icon: String,
name: String,
#[serde(flatten, default)]
params: T,
}
impl<T: TypedNode> AddNodeRequest<T> {
fn from_template(template: &NodeTemplate) -> Result<Self> {
if template.node_type != T::NODE_TYPE {
return Err(Error::Client(format!(
"Incorrect type, expected '{}', got '{}'",
template.node_type,
T::NODE_TYPE
)));
}
if template.description.ends_with(".missing") {
return Err(Error::Client(format!(
"Cannot create node as image for template '{}' is missing",
template.name
)));
}
let mut defaults = template.defaults_map()?;
defaults.insert("type".to_string(), serde_json::json!(template.node_type));
defaults.insert("template".to_string(), serde_json::json!(template.name));
defaults.insert("left".to_string(), serde_json::json!(0));
defaults.insert("top".to_string(), serde_json::json!(0));
defaults.insert("count".to_string(), serde_json::json!(1));
serde_json::from_value(Value::Object(defaults)).map_err(Into::into)
}
pub fn config(mut self, config: StartupConfig) -> Self {
self.config = config;
self
}
pub fn delay(mut self, delay: u32) -> Self {
self.delay = delay;
self
}
pub fn icon(mut self, icon: impl Into<String>) -> Self {
self.icon = icon.into();
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
pub fn position(mut self, left: u32, top: u32) -> Self {
self.left = left;
self.top = top;
self
}
}
impl AddNodeRequest<Docker> {
pub fn docker(template: &NodeTemplate) -> Result<Self> {
Self::from_template(template)
}
pub fn ethernet(mut self, ethernet: u32) -> Self {
self.params.ethernet = ethernet;
self
}
pub fn image(mut self, image: impl Into<String>) -> Self {
self.params.image = image.into();
self
}
pub fn ram(mut self, ram: u32) -> Self {
self.params.ram = ram;
self
}
}
impl AddNodeRequest<Dynamips> {
pub fn dynamips(template: &NodeTemplate) -> Result<Self> {
Self::from_template(template)
}
pub fn idlepc(mut self, idlepc: impl Into<String>) -> Self {
self.params.idlepc = idlepc.into();
self
}
pub fn image(mut self, image: impl Into<String>) -> Self {
self.params.image = image.into();
self
}
pub fn nvram(mut self, nvram: u32) -> Self {
self.params.nvram = nvram;
self
}
pub fn ram(mut self, ram: u32) -> Self {
self.params.ram = ram;
self
}
pub fn slot1(mut self, slot1: impl Into<String>) -> Self {
self.params.slot1 = slot1.into();
self
}
pub fn slot2(mut self, slot2: impl Into<String>) -> Self {
self.params.slot2 = slot2.into();
self
}
pub fn slot3(mut self, slot3: impl Into<String>) -> Self {
self.params.slot3 = Some(slot3.into());
self
}
pub fn slot4(mut self, slot4: impl Into<String>) -> Self {
self.params.slot4 = Some(slot4.into());
self
}
pub fn slot5(mut self, slot5: impl Into<String>) -> Self {
self.params.slot5 = Some(slot5.into());
self
}
pub fn slot6(mut self, slot6: impl Into<String>) -> Self {
self.params.slot6 = Some(slot6.into());
self
}
}
impl AddNodeRequest<Iol> {
pub fn iol(template: &NodeTemplate) -> Result<Self> {
Self::from_template(template)
}
pub fn ethernet(mut self, ethernet: u32) -> Self {
self.params.ethernet = ethernet;
self
}
pub fn image(mut self, image: impl Into<String>) -> Self {
self.params.image = image.into();
self
}
pub fn nvram(mut self, nvram: u32) -> Self {
self.params.nvram = nvram;
self
}
pub fn ram(mut self, ram: u32) -> Self {
self.params.ram = ram;
self
}
pub fn serial(mut self, serial: u32) -> Self {
self.params.serial = serial;
self
}
}
impl AddNodeRequest<Qemu> {
pub fn qemu(template: &NodeTemplate) -> Result<Self> {
Self::from_template(template)
}
pub fn cpu(mut self, cpu: u32) -> Self {
self.params.cpu = cpu;
self
}
pub fn cpulimit(mut self, cpulimit: u8) -> Self {
self.params.cpulimit = cpulimit;
self
}
pub fn ethernet(mut self, ethernet: u32) -> Self {
self.params.ethernet = ethernet;
self
}
pub fn image(mut self, image: impl Into<String>) -> Self {
self.params.image = image.into();
self
}
pub fn ram(mut self, ram: u32) -> Self {
self.params.ram = ram;
self
}
pub fn qemu_version(mut self, qemu_version: impl Into<String>) -> Self {
self.params.qemu_version = qemu_version.into();
self
}
pub fn qemu_arch(mut self, qemu_arch: impl Into<String>) -> Self {
self.params.qemu_arch = qemu_arch.into();
self
}
pub fn qemu_nic(mut self, qemu_nic: impl Into<String>) -> Self {
self.params.qemu_nic = qemu_nic.into();
self
}
pub fn qemu_options(mut self, qemu_options: impl Into<String>) -> Self {
self.params.qemu_options = qemu_options.into();
self
}
pub fn uuid(mut self, uuid: impl Into<String>) -> Self {
self.params.uuid = uuid.into();
self
}
}
impl AddNodeRequest<Vpcs> {
pub fn vpcs(template: &NodeTemplate) -> Result<Self> {
Self::from_template(template)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct EditNodeRequest<T> {
left: u32,
top: u32,
config: StartupConfig,
delay: u32,
icon: String,
name: String,
#[serde(rename = "type")]
node_type: NodeType,
#[serde(skip_serializing)]
status: NodeStatus,
template: String,
#[serde(flatten, default)]
params: T,
}
impl<T: TypedNode> EditNodeRequest<T> {
pub(crate) fn from_node(node: &Node) -> Result<Self> {
if node.node_type != T::NODE_TYPE {
return Err(Error::Client(format!(
"Incorrect type, expected '{}', got '{}'",
node.node_type,
T::NODE_TYPE
)));
}
let value = serde_json::to_value(node)?;
serde_json::from_value(value).map_err(Into::into)
}
pub fn config(mut self, config: StartupConfig) -> Self {
self.config = config;
self
}
pub fn delay(mut self, delay: u32) -> Self {
self.delay = delay;
self
}
pub fn icon(mut self, icon: impl Into<String>) -> Self {
self.icon = icon.into();
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
pub fn position(mut self, left: u32, top: u32) -> Self {
self.left = left;
self.top = top;
self
}
}
impl EditNodeRequest<Docker> {
pub fn docker(node: &Node) -> Result<Self> {
Self::from_node(node)
}
pub fn ethernet(mut self, ethernet: u32) -> Self {
self.params.ethernet = ethernet;
self
}
pub fn image(mut self, image: impl Into<String>) -> Self {
self.params.image = image.into();
self
}
pub fn ram(mut self, ram: u32) -> Self {
self.params.ram = ram;
self
}
}
impl EditNodeRequest<Dynamips> {
pub fn dynamips(node: &Node) -> Result<Self> {
Self::from_node(node)
}
pub fn idlepc(mut self, idlepc: impl Into<String>) -> Self {
self.params.idlepc = idlepc.into();
self
}
pub fn image(mut self, image: impl Into<String>) -> Self {
self.params.image = image.into();
self
}
pub fn nvram(mut self, nvram: u32) -> Self {
self.params.nvram = nvram;
self
}
pub fn ram(mut self, ram: u32) -> Self {
self.params.ram = ram;
self
}
pub fn slot1(mut self, slot1: impl Into<String>) -> Self {
self.params.slot1 = slot1.into();
self
}
pub fn slot2(mut self, slot2: impl Into<String>) -> Self {
self.params.slot2 = slot2.into();
self
}
pub fn slot3(mut self, slot3: impl Into<String>) -> Self {
self.params.slot3 = Some(slot3.into());
self
}
pub fn slot4(mut self, slot4: impl Into<String>) -> Self {
self.params.slot4 = Some(slot4.into());
self
}
pub fn slot5(mut self, slot5: impl Into<String>) -> Self {
self.params.slot5 = Some(slot5.into());
self
}
pub fn slot6(mut self, slot6: impl Into<String>) -> Self {
self.params.slot6 = Some(slot6.into());
self
}
}
impl EditNodeRequest<Iol> {
pub fn iol(node: &Node) -> Result<Self> {
Self::from_node(node)
}
pub fn ethernet(mut self, ethernet: u32) -> Self {
self.params.ethernet = ethernet;
self
}
pub fn image(mut self, image: impl Into<String>) -> Self {
self.params.image = image.into();
self
}
pub fn nvram(mut self, nvram: u32) -> Self {
self.params.nvram = nvram;
self
}
pub fn ram(mut self, ram: u32) -> Self {
self.params.ram = ram;
self
}
pub fn serial(mut self, serial: u32) -> Self {
self.params.serial = serial;
self
}
}
impl EditNodeRequest<Qemu> {
pub fn qemu(node: &Node) -> Result<Self> {
Self::from_node(node)
}
pub fn cpu(mut self, cpu: u32) -> Self {
self.params.cpu = cpu;
self
}
pub fn cpulimit(mut self, cpulimit: u8) -> Self {
self.params.cpulimit = cpulimit;
self
}
pub fn ethernet(mut self, ethernet: u32) -> Self {
self.params.ethernet = ethernet;
self
}
pub fn image(mut self, image: impl Into<String>) -> Self {
self.params.image = image.into();
self
}
pub fn ram(mut self, ram: u32) -> Self {
self.params.ram = ram;
self
}
pub fn qemu_version(mut self, qemu_version: impl Into<String>) -> Self {
self.params.qemu_version = qemu_version.into();
self
}
pub fn qemu_arch(mut self, qemu_arch: impl Into<String>) -> Self {
self.params.qemu_arch = qemu_arch.into();
self
}
pub fn qemu_nic(mut self, qemu_nic: impl Into<String>) -> Self {
self.params.qemu_nic = qemu_nic.into();
self
}
pub fn qemu_options(mut self, qemu_options: impl Into<String>) -> Self {
self.params.qemu_options = qemu_options.into();
self
}
pub fn uuid(mut self, uuid: impl Into<String>) -> Self {
self.params.uuid = uuid.into();
self
}
}
impl EditNodeRequest<Vpcs> {
pub fn vpcs(node: &Node) -> Result<Self> {
Self::from_node(node)
}
}