mirror of
https://github.com/faas-rs/faasd-in-rust.git
synced 2025-06-21 13:56:31 +00:00
feat(cni): add unit test for commands (#70)
This commit is contained in:
@ -12,3 +12,5 @@ log = "0.4.27"
|
||||
dotenv = "0.15.0"
|
||||
netns-rs = "0.1.0"
|
||||
lazy_static = "1.4.0"
|
||||
env_logger = "0.11.8"
|
||||
defer = "0.2.1"
|
||||
|
92
crates/cni/src/command.rs
Normal file
92
crates/cni/src/command.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use std::{
|
||||
io::Error,
|
||||
process::{Command, Output},
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CNI_BIN_DIR: String =
|
||||
std::env::var("CNI_BIN_DIR").expect("Environment variable CNI_BIN_DIR is not set");
|
||||
static ref CNI_TOOL: String =
|
||||
std::env::var("CNI_TOOL").expect("Environment variable CNI_TOOL is not set");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn netns_path(netns: &str) -> String {
|
||||
"/var/run/netns/".to_string() + netns
|
||||
}
|
||||
|
||||
pub(super) fn cni_add_bridge(netns: &str, bridge_network_name: &str) -> Result<Output, Error> {
|
||||
Command::new(CNI_TOOL.as_str())
|
||||
.arg("add")
|
||||
.arg(bridge_network_name)
|
||||
.arg(netns_path(netns))
|
||||
.env("CNI_PATH", CNI_BIN_DIR.as_str())
|
||||
.output()
|
||||
}
|
||||
|
||||
pub(super) fn cni_del_bridge(netns: &str, bridge_network_name: &str) -> Result<Output, Error> {
|
||||
Command::new(CNI_TOOL.as_str())
|
||||
.arg("del")
|
||||
.arg(bridge_network_name)
|
||||
.arg(netns_path(netns))
|
||||
.env("CNI_PATH", CNI_BIN_DIR.as_str())
|
||||
.output()
|
||||
}
|
||||
|
||||
/// THESE TESTS SHOULD BE RUN WITH ROOT PRIVILEGES
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{netns, util};
|
||||
use std::path::Path;
|
||||
|
||||
use super::*;
|
||||
|
||||
const CNI_DATA_DIR: &str = "/var/run/cni";
|
||||
const TEST_CNI_CONF_FILENAME: &str = "11-faasrstest.conflist";
|
||||
const TEST_NETWORK_NAME: &str = "faasrstest-cni-bridge";
|
||||
const TEST_BRIDGE_NAME: &str = "faasrstest0";
|
||||
const TEST_SUBNET: &str = "10.99.0.0/16";
|
||||
const CNI_CONF_DIR: &str = "/etc/cni/net.d";
|
||||
|
||||
fn init_test_net_fs() {
|
||||
crate::util::init_net_fs(
|
||||
Path::new(CNI_CONF_DIR),
|
||||
TEST_CNI_CONF_FILENAME,
|
||||
TEST_NETWORK_NAME,
|
||||
TEST_BRIDGE_NAME,
|
||||
TEST_SUBNET,
|
||||
CNI_DATA_DIR,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_cni_resource() {
|
||||
dotenv::dotenv().unwrap();
|
||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("trace"));
|
||||
init_test_net_fs();
|
||||
let netns = util::netns_from_cid_and_cns("123456", "cns");
|
||||
|
||||
netns::create(&netns).unwrap();
|
||||
defer::defer!({
|
||||
let _ = netns::remove(&netns);
|
||||
});
|
||||
|
||||
let result = cni_add_bridge(&netns, TEST_NETWORK_NAME);
|
||||
log::debug!("add CNI result: {:?}", result);
|
||||
assert!(
|
||||
result.is_ok_and(|output| output.status.success()),
|
||||
"Failed to add CNI"
|
||||
);
|
||||
|
||||
defer::defer!({
|
||||
let result = cni_del_bridge(&netns, TEST_NETWORK_NAME);
|
||||
log::debug!("del CNI result: {:?}", result);
|
||||
assert!(
|
||||
result.is_ok_and(|output| output.status.success()),
|
||||
"Failed to delete CNI"
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,23 +1,17 @@
|
||||
type Err = Box<dyn std::error::Error>;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use netns_rs::NetNs;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
fmt::Error,
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
net::IpAddr,
|
||||
path::Path,
|
||||
};
|
||||
use std::{fmt::Error, net::IpAddr, path::Path};
|
||||
|
||||
mod command;
|
||||
mod netns;
|
||||
mod util;
|
||||
use command as cmd;
|
||||
|
||||
lazy_static! {
|
||||
static ref CNI_BIN_DIR: String =
|
||||
std::env::var("CNI_BIN_DIR").expect("Environment variable CNI_BIN_DIR is not set");
|
||||
static ref CNI_CONF_DIR: String =
|
||||
std::env::var("CNI_CONF_DIR").expect("Environment variable CNI_CONF_DIR is not set");
|
||||
static ref CNI_TOOL: String =
|
||||
std::env::var("CNI_TOOL").expect("Environment variable CNI_TOOL is not set");
|
||||
}
|
||||
|
||||
// const NET_NS_PATH_FMT: &str = "/proc/{}/ns/net";
|
||||
@ -28,74 +22,25 @@ const DEFAULT_BRIDGE_NAME: &str = "faasrs0";
|
||||
const DEFAULT_SUBNET: &str = "10.66.0.0/16";
|
||||
// const DEFAULT_IF_PREFIX: &str = "eth";
|
||||
|
||||
fn default_cni_conf() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "{}",
|
||||
"plugins": [
|
||||
{{
|
||||
"type": "bridge",
|
||||
"bridge": "{}",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"ipam": {{
|
||||
"type": "host-local",
|
||||
"subnet": "{}",
|
||||
"dataDir": "{}",
|
||||
"routes": [
|
||||
{{ "dst": "0.0.0.0/0" }}
|
||||
]
|
||||
}}
|
||||
}},
|
||||
{{
|
||||
"type": "firewall"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
"#,
|
||||
DEFAULT_NETWORK_NAME, DEFAULT_BRIDGE_NAME, DEFAULT_SUBNET, CNI_DATA_DIR
|
||||
pub fn init_net_work() -> Result<(), Err> {
|
||||
util::init_net_fs(
|
||||
Path::new(CNI_CONF_DIR.as_str()),
|
||||
DEFAULT_CNI_CONF_FILENAME,
|
||||
DEFAULT_NETWORK_NAME,
|
||||
DEFAULT_BRIDGE_NAME,
|
||||
DEFAULT_SUBNET,
|
||||
CNI_DATA_DIR,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn init_net_work() -> Result<(), Err> {
|
||||
let cni_conf_dir = CNI_CONF_DIR.as_str();
|
||||
if !dir_exists(Path::new(cni_conf_dir)) {
|
||||
fs::create_dir_all(cni_conf_dir)?;
|
||||
}
|
||||
let net_config = Path::new(cni_conf_dir).join(DEFAULT_CNI_CONF_FILENAME);
|
||||
let mut file = File::create(&net_config)?;
|
||||
file.write_all(default_cni_conf().as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_netns(ns: &str, cid: &str) -> String {
|
||||
format!("{}-{}", ns, cid)
|
||||
}
|
||||
|
||||
fn get_path(netns: &str) -> String {
|
||||
format!("/var/run/netns/{}", netns)
|
||||
}
|
||||
|
||||
//TODO: 创建网络和删除网络的错误处理
|
||||
pub fn create_cni_network(cid: String, ns: String) -> Result<(String, String), Err> {
|
||||
// let netid = format!("{}-{}", cid, pid);
|
||||
let netns = get_netns(ns.as_str(), cid.as_str());
|
||||
let path = get_path(netns.as_str());
|
||||
pub fn create_cni_network(cid: String, ns: String) -> Result<String, Err> {
|
||||
let netns = util::netns_from_cid_and_cns(&cid, &ns);
|
||||
let mut ip = String::new();
|
||||
|
||||
create_netns(&netns);
|
||||
netns::create(&netns)?;
|
||||
|
||||
let bin = CNI_BIN_DIR.as_str();
|
||||
let cnitool = CNI_TOOL.as_str();
|
||||
let output = std::process::Command::new(cnitool)
|
||||
.arg("add")
|
||||
.arg("faasrs-cni-bridge")
|
||||
.arg(&path)
|
||||
.env("CNI_PATH", bin)
|
||||
.output();
|
||||
let output = cmd::cni_add_bridge(netns.as_str(), DEFAULT_NETWORK_NAME);
|
||||
|
||||
match output {
|
||||
Ok(output) => {
|
||||
@ -124,55 +69,14 @@ pub fn create_cni_network(cid: String, ns: String) -> Result<(String, String), E
|
||||
}
|
||||
}
|
||||
|
||||
Ok((ip, path))
|
||||
Ok(ip)
|
||||
}
|
||||
|
||||
pub fn delete_cni_network(ns: &str, cid: &str) {
|
||||
let netns = get_netns(ns, cid);
|
||||
let path = get_path(&netns);
|
||||
let bin = CNI_BIN_DIR.as_str();
|
||||
let cnitool = CNI_TOOL.as_str();
|
||||
let netns = util::netns_from_cid_and_cns(cid, ns);
|
||||
|
||||
let _output_del = std::process::Command::new(cnitool)
|
||||
.arg("del")
|
||||
.arg("faasrs-cni-bridge")
|
||||
.arg(&path)
|
||||
.env("CNI_PATH", bin)
|
||||
.output();
|
||||
delete_netns(&netns);
|
||||
}
|
||||
|
||||
fn create_netns(namespace_name: &str) {
|
||||
match NetNs::new(namespace_name) {
|
||||
Ok(ns) => {
|
||||
log::info!("Created netns: {}", ns);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Error creating netns: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_netns(namespace_name: &str) {
|
||||
match NetNs::get(namespace_name) {
|
||||
Ok(ns) => {
|
||||
ns.remove()
|
||||
.map_err(|e| log::error!("Error deleting netns: {}", e))
|
||||
.unwrap();
|
||||
log::info!("Deleted netns: {}", namespace_name);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Error getting netns: {}, NotFound", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dir_exists(dirname: &Path) -> bool {
|
||||
path_exists(dirname).is_some_and(|info| info.is_dir())
|
||||
}
|
||||
|
||||
fn path_exists(path: &Path) -> Option<fs::Metadata> {
|
||||
fs::metadata(path).ok()
|
||||
let _ = cmd::cni_del_bridge(&netns, DEFAULT_NETWORK_NAME);
|
||||
let _ = netns::remove(&netns);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
@ -185,14 +89,3 @@ fn cni_gateway() -> Result<String, Err> {
|
||||
}
|
||||
Err(Box::new(Error))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn dir_empty(dirname: &Path) -> bool {
|
||||
if !dir_exists(dirname) {
|
||||
return false;
|
||||
}
|
||||
match fs::read_dir(dirname) {
|
||||
Ok(mut entries) => entries.next().is_none(),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
32
crates/cni/src/netns.rs
Normal file
32
crates/cni/src/netns.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use netns_rs::{Error, NetNs};
|
||||
|
||||
pub(super) fn create(netns: &str) -> Result<NetNs, Error> {
|
||||
NetNs::new(netns)
|
||||
}
|
||||
|
||||
pub(super) fn remove(netns: &str) -> Result<(), Error> {
|
||||
match NetNs::get(netns) {
|
||||
Ok(ns) => {
|
||||
ns.remove()?;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to get netns {}: {}", netns, e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// THESE TESTS SHOULD BE RUN WITH ROOT PRIVILEGES
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_create_and_remove() {
|
||||
let netns_name = "test_netns";
|
||||
create(netns_name).unwrap();
|
||||
assert!(remove(netns_name).is_ok());
|
||||
}
|
||||
}
|
97
crates/cni/src/util.rs
Normal file
97
crates/cni/src/util.rs
Normal file
@ -0,0 +1,97 @@
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
static mut CNI_CONFIG_FILE: Option<CniConfFile> = None;
|
||||
|
||||
/// Generate "cns-cid"
|
||||
#[inline(always)]
|
||||
pub fn netns_from_cid_and_cns(cid: &str, cns: &str) -> String {
|
||||
format!("{}-{}", cns, cid)
|
||||
}
|
||||
|
||||
pub fn init_net_fs(
|
||||
conf_dir: &Path,
|
||||
conf_filename: &str,
|
||||
net_name: &str,
|
||||
bridge: &str,
|
||||
subnet: &str,
|
||||
data_dir: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let conf_file = CniConfFile::new(conf_dir, conf_filename, net_name, bridge, subnet, data_dir)?;
|
||||
unsafe {
|
||||
CNI_CONFIG_FILE = Some(conf_file);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cni_conf(name: &str, bridge: &str, subnet: &str, data_dir: &str) -> String {
|
||||
format!(
|
||||
r#"
|
||||
{{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "{}",
|
||||
"plugins": [
|
||||
{{
|
||||
"type": "bridge",
|
||||
"bridge": "{}",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"ipam": {{
|
||||
"type": "host-local",
|
||||
"subnet": "{}",
|
||||
"dataDir": "{}",
|
||||
"routes": [
|
||||
{{ "dst": "0.0.0.0/0" }}
|
||||
]
|
||||
}}
|
||||
}},
|
||||
{{
|
||||
"type": "firewall"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
"#,
|
||||
name, bridge, subnet, data_dir
|
||||
)
|
||||
}
|
||||
|
||||
struct CniConfFile {
|
||||
conf_dir: PathBuf,
|
||||
conf_filename: String,
|
||||
}
|
||||
|
||||
impl CniConfFile {
|
||||
fn new(
|
||||
conf_dir: &Path,
|
||||
conf_filename: &str,
|
||||
net_name: &str,
|
||||
bridge: &str,
|
||||
subnet: &str,
|
||||
data_dir: &str,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
if !conf_dir.exists() {
|
||||
std::fs::create_dir_all(conf_dir)?;
|
||||
}
|
||||
if !conf_dir.is_dir() {
|
||||
log::error!("CNI_CONF_DIR is not a directory");
|
||||
panic!("CNI_CONF_DIR is not a directory");
|
||||
}
|
||||
let net_config = conf_dir.join(conf_filename);
|
||||
File::create(&net_config)?
|
||||
.write_all(cni_conf(net_name, bridge, subnet, data_dir).as_bytes())?;
|
||||
Ok(Self {
|
||||
conf_dir: conf_dir.to_path_buf(),
|
||||
conf_filename: conf_filename.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CniConfFile {
|
||||
fn drop(&mut self) {
|
||||
let net_config = self.conf_dir.join(&self.conf_filename);
|
||||
if net_config.exists() {
|
||||
std::fs::remove_file(&net_config).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
@ -232,9 +232,9 @@ impl Service {
|
||||
log::info!("drop sc ok");
|
||||
let _ = cni::init_net_work();
|
||||
log::info!("init_net_work ok");
|
||||
let (ip, path) = cni::create_cni_network(cid.to_string(), ns.to_string())?;
|
||||
let ip = cni::create_cni_network(cid.to_string(), ns.to_string())?;
|
||||
let ports = ImageManager::get_runtime_config(img_name).unwrap().ports;
|
||||
let network_config = NetworkConfig::new(path, ip, ports);
|
||||
let network_config = NetworkConfig::new(ip, ports);
|
||||
log::info!("create_cni_network ok");
|
||||
self.save_network_config(cid, network_config.clone()).await;
|
||||
log::info!("save_netns_ip ok, netconfig: {:?}", network_config);
|
||||
@ -474,18 +474,13 @@ impl Service {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NetworkConfig {
|
||||
netns: String,
|
||||
ip: String,
|
||||
ports: Vec<String>,
|
||||
}
|
||||
|
||||
impl NetworkConfig {
|
||||
pub fn new(netns: String, ip: String, ports: Vec<String>) -> Self {
|
||||
NetworkConfig { netns, ip, ports }
|
||||
}
|
||||
|
||||
pub fn get_netns(&self) -> String {
|
||||
self.netns.clone()
|
||||
pub fn new(ip: String, ports: Vec<String>) -> Self {
|
||||
NetworkConfig { ip, ports }
|
||||
}
|
||||
|
||||
pub fn get_ip(&self) -> String {
|
||||
|
Reference in New Issue
Block a user