feat(cni): add unit test for commands (#70)

This commit is contained in:
2025-04-16 17:01:44 +08:00
committed by GitHub
parent 6ecde71c52
commit edf5d68d0a
7 changed files with 427 additions and 178 deletions

View File

@ -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
View 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"
);
});
}
}

View File

@ -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
View 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
View 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();
}
}
}

View File

@ -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 {