diff --git a/lib/src/error.rs b/lib/src/error.rs index ed8f1d4..5e2fea3 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -31,6 +31,8 @@ pub enum Error { Libnet(#[from] libnet::Error), #[error("cli: {0}")] Cli(String), + #[error("exclusive iface used multiple times: {0}")] + ExternalNicReused(String), RonSpan(#[from] ron::error::SpannedError), Ron(#[from] ron::Error), TomL(#[from] toml::ser::Error), diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 007e2dd..f207402 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -23,8 +23,8 @@ use ron::ser::{to_string_pretty, PrettyConfig}; use serde::{Deserialize, Serialize}; use slog::Drain; use slog::{debug, error, info, warn, Logger}; -use std::collections::BTreeMap; -use std::collections::HashMap; +use std::collections::hash_map::Entry; +use std::collections::{BTreeMap, HashMap}; use std::convert::TryInto; use std::fs::{self, OpenOptions}; use std::io::BufWriter; @@ -220,6 +220,7 @@ pub struct Link { pub struct ExtLink { pub endpoint: Endpoint, pub host_ifx: String, + pub exclusive: bool, } /// Endpoint kind determines what type of device will be chosen to underpin a @@ -458,17 +459,37 @@ impl Runner { self.deployment.nodes[n.index].primary_disk_backing = backing } - /// Create an external link attached to `host_ifx`. + /// Create an external link attached to `host_ifx` via a new VNIC. pub fn ext_link(&mut self, host_ifx: impl AsRef, n: NodeRef) { + self.ext_link_common(host_ifx, n, false); + } + + /// Create an external link, consuming the link `host_ifx`. + pub fn ext_link_exclusive( + &mut self, + host_ifx: impl AsRef, + n: NodeRef, + ) { + self.ext_link_common(host_ifx, n, true); + } + + fn ext_link_common( + &mut self, + host_ifx: impl AsRef, + n: NodeRef, + exclusive: bool, + ) { let endpoint = Endpoint { node: n, index: self.deployment.nodes[n.index].radix, kind: EndpointKind::Viona(None), }; let host_ifx = host_ifx.as_ref().into(); - self.deployment - .ext_links - .push(ExtLink { endpoint, host_ifx }); + self.deployment.ext_links.push(ExtLink { + endpoint, + host_ifx, + exclusive, + }); self.deployment.nodes[n.index].radix += 1; } @@ -541,6 +562,26 @@ impl Runner { ))); } + // validate that no external links are being jointly specified + // as exclusive and as parents of vnics. + let mut link_is_exclusive = HashMap::new(); + for link in self.deployment.ext_links.iter() { + let entry = link_is_exclusive.entry(link.host_ifx.clone()); + + match entry { + Entry::Vacant(vacant_entry) => { + vacant_entry.insert(link.exclusive); + } + Entry::Occupied(occupied_entry) => { + if link.exclusive || *occupied_entry.get() { + return Err(Error::ExternalNicReused( + link.host_ifx.clone(), + )); + } + } + } + } + // ensure falcon working dir fs::create_dir_all(&self.falcon_dir)?; @@ -716,11 +757,25 @@ impl Deployment { async fn nodes_preflight(&mut self, log: &Logger) -> Result<(), Error> { let mut endpoints = Vec::new(); + for l in &self.links { - endpoints.extend_from_slice(&l.endpoints); + for e in &l.endpoints { + endpoints.push(NodeEndpoint { + vnic_name: self.vnic_link_name(e), + endpoint: e.clone(), + }); + } } for l in &self.ext_links { - endpoints.push(l.endpoint.clone()); + let vnic_name = if l.exclusive { + l.host_ifx.clone() + } else { + self.vnic_link_name(&l.endpoint) + }; + endpoints.push(NodeEndpoint { + vnic_name, + endpoint: l.endpoint.clone(), + }) } let mut node_endpoints_map: HashMap> = @@ -730,11 +785,8 @@ impl Deployment { for n in self.nodes.iter() { let node_endpoints: Vec = endpoints .iter() - .filter(|e| self.nodes[e.node.index].name == n.name) - .map(|e| NodeEndpoint { - vnic_name: self.vnic_link_name(e), - endpoint: e.clone(), - }) + .filter(|e| self.nodes[e.endpoint.node.index].name == n.name) + .cloned() .collect(); let has_softnpu = node_endpoints.iter().any(|ne| { @@ -771,6 +823,7 @@ impl Drop for Runner { } } +#[derive(Clone)] struct NodeEndpoint { vnic_name: String, endpoint: Endpoint, @@ -1470,6 +1523,10 @@ impl Link { impl ExtLink { fn create(&self, r: &Runner) -> Result<(), Error> { + if self.exclusive { + return Ok(()); + } + let vnic_name = r.deployment.vnic_link_name(&self.endpoint); let vnic = libnet::LinkHandle::Name(vnic_name.clone()); let host_ifx = libnet::LinkHandle::Name(self.host_ifx.clone()); @@ -1496,6 +1553,10 @@ impl ExtLink { } fn destroy(&self, r: &Runner) -> Result<(), Error> { + if self.exclusive { + return Ok(()); + } + let vnic_name = r.deployment.vnic_link_name(&self.endpoint); let vnic = libnet::LinkHandle::Name(vnic_name.clone()); info!(r.log, "destroying external link {}", &vnic_name);