use std::{collections::HashMap, path::PathBuf};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error::Error;
pub(super) const CNI_COMMAND: &str = "CNI_COMMAND";
pub(super) const CNI_CONTAINERID: &str = "CNI_CONTAINERID";
pub(super) const CNI_NETNS: &str = "CNI_NETNS";
pub(super) const CNI_IFNAME: &str = "CNI_IFNAME";
pub(super) const CNI_ARGS: &str = "CNI_ARGS";
pub(super) const CNI_PATH: &str = "CNI_PATH";
#[derive(Debug, Default, Clone)]
pub struct Args {
pub container_id: String,
pub netns: Option<PathBuf>,
pub ifname: String,
pub args: Option<String>,
pub path: Vec<PathBuf>,
pub config: Option<NetConf>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct NetConf {
pub cni_version: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cni_versions: Option<Vec<String>>,
pub name: String,
pub r#type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub disable_check: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub runtime_config: Option<RuntimeConf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capabilities: Option<HashMap<String, Value>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ip_masq: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ipam: Option<Ipam>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dns: Option<Dns>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub args: Option<HashMap<String, Value>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prev_result: Option<CNIResult>,
#[serde(flatten)]
pub custom: HashMap<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NetConfList {
pub cni_version: String,
pub cni_versions: Vec<String>,
pub name: String,
#[serde(default)] pub disable_check: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub plugins: Vec<NetConf>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeConf {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub port_mappings: Vec<PortMapping>,
#[serde(flatten)]
pub custom: HashMap<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Ipam {
pub r#type: String,
#[serde(flatten)]
pub custom: HashMap<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Dns {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub nameservers: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub domain: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub search: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub options: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Route {
pub dst: String, #[serde(skip_serializing_if = "Option::is_none")]
pub gw: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mtu: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub advmss: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct CNIResult {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub interfaces: Vec<Interface>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ips: Vec<IpConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub routes: Vec<Route>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dns: Option<Dns>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CNIResultWithCNIVersion {
pub cni_version: String,
#[serde(flatten)]
inner: CNIResult,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Interface {
pub name: String,
pub mac: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub sandbox: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct IpConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub interface: Option<u32>,
pub address: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub gateway: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PortMapping {
pub host_port: u32,
pub container_port: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub protocol: Option<Protocol>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Protocol {
Tcp,
Udp,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ErrorResult {
pub cni_version: String,
pub code: u32,
pub msg: String,
pub details: String,
}
impl From<&ErrorResult> for Error {
fn from(res: &ErrorResult) -> Self {
if res.code > 100 {
return Error::Custom(res.code, res.msg.clone(), res.details.clone());
}
match res.code {
1 => Error::IncompatibleVersion(res.details.clone()),
2 => Error::UnsupportedNetworkConfiguration(res.details.clone()),
3 => Error::NotExist(res.details.clone()),
4 => Error::InvalidEnvValue(res.details.clone()),
5 => Error::IOFailure(res.details.clone()),
6 => Error::FailedToDecode(res.details.clone()),
7 => Error::InvalidNetworkConfig(res.details.clone()),
11 => Error::TryAgainLater(res.details.clone()),
_ => Error::FailedToDecode(format!("unknown error code: {}", res.code)),
}
}
}
#[cfg(test)]
mod tests {
use std::{collections::HashMap, vec};
use rstest::rstest;
use serde_json::json;
use super::{
CNIResult, Dns, Interface, IpConfig, Ipam, NetConf, PortMapping, Protocol, Route,
RuntimeConf,
};
#[rstest(
input,
expected,
case(
// ref: https://github.com/containernetworking/cni/blob/b62753aa2bfa365c1ceaff6f25774a8047c896b5/SPEC.md#add-example
r#"{
"cniVersion": "1.1.0",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
"keyA": ["some more", "plugin specific", "configuration"],
"ipam": {
"type": "host-local",
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}"#.to_string(),
NetConf {
cni_version: "1.1.0".to_string(),
cni_versions: None,
name: "dbnet".to_string(),
r#type: "bridge".to_string(),
disable_check: None,
runtime_config: None,
capabilities: None,
ip_masq: None,
ipam: Some(Ipam{
r#type: "host-local".to_string(),
custom: HashMap::from([
("subnet".to_string(), serde_json::Value::String("10.1.0.0/16".to_string())),
("gateway".to_string(), serde_json::Value::String("10.1.0.1".to_string())),
]),
}),
dns: Some(Dns{
nameservers: vec!["10.1.0.1".to_string()],
domain: None,
search: None,
options: None,
}),
args: None,
prev_result: None,
custom: HashMap::from([
("bridge".to_string(), serde_json::Value::String("cni0".to_string())),
("keyA".to_string(), serde_json::Value::Array(vec![serde_json::Value::String("some more".to_string()), serde_json::Value::String("plugin specific".to_string()), serde_json::Value::String("configuration".to_string())])),
]),
},
),
case(
r#"{
"cniVersion": "1.1.0",
"name": "test",
"type": "myPlugin",
"capabilities": {
"portMappings": true
}
}"#.to_string(),
NetConf {
cni_version: "1.1.0".to_string(),
cni_versions: None,
name: "test".to_string(),
r#type: "myPlugin".to_string(),
disable_check: None,
runtime_config: None,
capabilities: Some(HashMap::from([
("portMappings".to_string(), serde_json::Value::Bool(true)),
])),
ip_masq: None,
ipam: None,
dns: None,
args: None,
prev_result: None,
custom: HashMap::new(),
},
),
case(
r#"{
"cniVersion": "1.1.0",
"name": "test",
"type": "myPlugin",
"capabilities": {
"portMappings": true
},
"runtimeConfig": {
"portMappings": [ { "hostPort": 8080, "containerPort": 80, "protocol": "tcp" } ]
}
}"#.to_string(),
NetConf {
cni_version: "1.1.0".to_string(),
cni_versions: None,
name: "test".to_string(),
r#type: "myPlugin".to_string(),
disable_check: None,
runtime_config: Some(RuntimeConf{
port_mappings: vec![
PortMapping{
host_port: 8080,
container_port: 80,
protocol: Some(Protocol::Tcp),
},
],
custom: HashMap::new(),
}),
capabilities: Some(HashMap::from([
("portMappings".to_string(), serde_json::Value::Bool(true)),
])),
ip_masq: None,
ipam: None,
dns: None,
args: None,
prev_result: None,
custom: HashMap::new(),
},
),
case(
r#"{
"cniVersion": "1.1.0",
"name": "dbnet",
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
},
"runtimeConfig": {
"mac": "00:11:22:33:44:66"
},
"prevResult": {
"ips": [
{
"address": "10.1.0.5/16",
"gateway": "10.1.0.1",
"interface": 2
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55"
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11"
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue"
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}"#.to_string(),
NetConf {
cni_version: "1.1.0".to_string(),
cni_versions: None,
name: "dbnet".to_string(),
r#type: "tuning".to_string(),
disable_check: None,
runtime_config: Some(RuntimeConf{
port_mappings: Vec::new(),
custom: HashMap::from([("mac".to_string(), serde_json::Value::String("00:11:22:33:44:66".to_string()))])
}),
capabilities: None,
ip_masq: None,
ipam: None,
dns: None,
args: None,
prev_result: Some(CNIResult{
ips: vec![
IpConfig{
interface: Some(2),
address: "10.1.0.5/16".to_string(),
gateway:Some("10.1.0.1".to_string()),
},
],
interfaces: vec![
Interface{
name: "cni0".to_string(),
mac: "00:11:22:33:44:55".to_string(),
sandbox: None,
},
Interface{
name: "veth3243".to_string(),
mac: "55:44:33:22:11:11".to_string(),
sandbox: None,
},
Interface{
name: "eth0".to_string(),
mac: "99:88:77:66:55:44".to_string(),
sandbox: Some("/var/run/netns/blue".to_string()),
},
],
routes: vec![
Route{
dst: "0.0.0.0/0".to_string(),
gw: None,
mtu: None,
advmss: None,
},
],
dns: Some(Dns{
nameservers: vec!["10.1.0.1".to_string()],
domain: None,
search: None,
options: None,
}),
}),
custom: HashMap::from([
("sysctl".to_string(), json!({"net.core.somaxconn": "500"})),
]),
},
),
case(
r#"{
"cniVersion": "1.1.0",
"name": "dbnet",
"type": "portmap",
"runtimeConfig": {
"portMappings" : [
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp" }
]
},
"prevResult": {
"ips": [
{
"address": "10.1.0.5/16",
"gateway": "10.1.0.1",
"interface": 2
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55"
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11"
},
{
"name": "eth0",
"mac": "00:11:22:33:44:66",
"sandbox": "/var/run/netns/blue"
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}"#.to_string(),
NetConf {
cni_version: "1.1.0".to_string(),
cni_versions: None,
name: "dbnet".to_string(),
r#type: "portmap".to_string(),
disable_check: None,
runtime_config: Some(RuntimeConf{
port_mappings: vec![
PortMapping{
host_port: 8080,
container_port: 80,
protocol: Some(Protocol::Tcp),
},
],
custom: HashMap::new(),
}),
capabilities: None,
ip_masq: None,
ipam: None,
dns: None,
args: None,
prev_result: Some(CNIResult{
ips: vec![
IpConfig{
interface: Some(2),
address: "10.1.0.5/16".to_string(),
gateway:Some("10.1.0.1".to_string()),
},
],
interfaces: vec![
Interface{
name: "cni0".to_string(),
mac: "00:11:22:33:44:55".to_string(),
sandbox: None,
},
Interface{
name: "veth3243".to_string(),
mac: "55:44:33:22:11:11".to_string(),
sandbox: None,
},
Interface{
name: "eth0".to_string(),
mac: "00:11:22:33:44:66".to_string(),
sandbox: Some("/var/run/netns/blue".to_string()),
},
],
routes: vec![
Route{
dst: "0.0.0.0/0".to_string(),
gw: None,
mtu: None,
advmss: None,
},
],
dns: Some(Dns{
nameservers: vec!["10.1.0.1".to_string()],
domain: None,
search: None,
options: None,
}),
}),
custom: HashMap::new(),
},
),
case(
r#"{
"cniVersion": "1.1.0",
"name": "dbnet",
"type": "portmap",
"runtimeConfig": {
"portMappings" : [
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp" }
]
},
"prevResult": {
"ips": [
{
"address": "10.1.0.5/16",
"gateway": "10.1.0.1",
"interface": 2
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55"
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11"
},
{
"name": "eth0",
"mac": "00:11:22:33:44:66",
"sandbox": "/var/run/netns/blue"
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}"#.to_string(),
NetConf {
cni_version: "1.1.0".to_string(),
cni_versions: None,
name: "dbnet".to_string(),
r#type: "portmap".to_string(),
disable_check: None,
runtime_config: Some(RuntimeConf{
port_mappings: vec![
PortMapping{
host_port: 8080,
container_port: 80,
protocol: Some(Protocol::Tcp),
},
],
custom: HashMap::new(),
}),
capabilities: None,
ip_masq: None,
ipam: None,
dns: None,
args: None,
prev_result: Some(CNIResult{
ips: vec![
IpConfig{
interface: Some(2),
address: "10.1.0.5/16".to_string(),
gateway:Some("10.1.0.1".to_string()),
},
],
interfaces: vec![
Interface{
name: "cni0".to_string(),
mac: "00:11:22:33:44:55".to_string(),
sandbox: None,
},
Interface{
name: "veth3243".to_string(),
mac: "55:44:33:22:11:11".to_string(),
sandbox: None,
},
Interface{
name: "eth0".to_string(),
mac: "00:11:22:33:44:66".to_string(),
sandbox: Some("/var/run/netns/blue".to_string()),
},
],
routes: vec![
Route{
dst: "0.0.0.0/0".to_string(),
gw: None,
mtu: None,
advmss: None,
},
],
dns: Some(Dns{
nameservers: vec!["10.1.0.1".to_string()],
domain: None,
search: None,
options: None,
}),
}),
custom: HashMap::new(),
},
),
case(
r#"{
"cniVersion": "1.1.0",
"name": "dbnet",
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
},
"runtimeConfig": {
"mac": "00:11:22:33:44:66"
},
"prevResult": {
"ips": [
{
"address": "10.1.0.5/16",
"gateway": "10.1.0.1",
"interface": 2
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55"
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11"
},
{
"name": "eth0",
"mac": "00:11:22:33:44:66",
"sandbox": "/var/run/netns/blue"
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}"#.to_string(),
NetConf {
cni_version: "1.1.0".to_string(),
cni_versions: None,
name: "dbnet".to_string(),
r#type: "tuning".to_string(),
disable_check: None,
runtime_config: Some(RuntimeConf{
port_mappings: Vec::new(),
custom: HashMap::from([("mac".to_string(), serde_json::Value::String("00:11:22:33:44:66".to_string()))])
}),
capabilities: None,
ip_masq: None,
ipam: None,
dns: None,
args: None,
prev_result: Some(CNIResult{
ips: vec![
IpConfig{
interface: Some(2),
address: "10.1.0.5/16".to_string(),
gateway:Some("10.1.0.1".to_string()),
},
],
interfaces: vec![
Interface{
name: "cni0".to_string(),
mac: "00:11:22:33:44:55".to_string(),
sandbox: None,
},
Interface{
name: "veth3243".to_string(),
mac: "55:44:33:22:11:11".to_string(),
sandbox: None,
},
Interface{
name: "eth0".to_string(),
mac: "00:11:22:33:44:66".to_string(),
sandbox: Some("/var/run/netns/blue".to_string()),
},
],
routes: vec![
Route{
dst: "0.0.0.0/0".to_string(),
gw: None,
mtu: None,
advmss: None,
},
],
dns: Some(Dns{
nameservers: vec!["10.1.0.1".to_string()],
domain: None,
search: None,
options: None,
}),
}),
custom: HashMap::from([
("sysctl".to_string(), json!({"net.core.somaxconn": "500"})),
]),
},
),
case(
r#"{
"cniVersion": "1.1.0",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
"keyA": ["some more", "plugin specific", "configuration"],
"ipam": {
"type": "host-local",
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
},
"prevResult": {
"ips": [
{
"address": "10.1.0.5/16",
"gateway": "10.1.0.1",
"interface": 2
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55"
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11"
},
{
"name": "eth0",
"mac": "00:11:22:33:44:66",
"sandbox": "/var/run/netns/blue"
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}"#.to_string(),
NetConf {
cni_version: "1.1.0".to_string(),
cni_versions: None,
name: "dbnet".to_string(),
r#type: "bridge".to_string(),
disable_check: None,
runtime_config: None,
capabilities: None,
ip_masq: None,
ipam: Some(Ipam{
r#type: "host-local".to_string(),
custom: HashMap::from([
("subnet".to_string(), serde_json::Value::String("10.1.0.0/16".to_string())),
("gateway".to_string(), serde_json::Value::String("10.1.0.1".to_string())),
]),
}),
dns: Some(Dns{
nameservers: vec!["10.1.0.1".to_string()],
domain: None,
search: None,
options: None
}),
args: None,
prev_result: Some(CNIResult{
ips: vec![
IpConfig{
interface: Some(2),
address: "10.1.0.5/16".to_string(),
gateway:Some("10.1.0.1".to_string()),
},
],
interfaces: vec![
Interface{
name: "cni0".to_string(),
mac: "00:11:22:33:44:55".to_string(),
sandbox: None,
},
Interface{
name: "veth3243".to_string(),
mac: "55:44:33:22:11:11".to_string(),
sandbox: None,
},
Interface{
name: "eth0".to_string(),
mac: "00:11:22:33:44:66".to_string(),
sandbox: Some("/var/run/netns/blue".to_string()),
},
],
routes: vec![
Route{
dst: "0.0.0.0/0".to_string(),
gw: None,
mtu: None,
advmss: None,
},
],
dns: Some(Dns{
nameservers: vec!["10.1.0.1".to_string()],
domain: None,
search: None,
options: None,
}),
}),
custom: HashMap::from([
("bridge".to_string(), serde_json::Value::String("cni0".to_string())),
("keyA".to_string(), serde_json::Value::Array(vec![serde_json::Value::String("some more".to_string()), serde_json::Value::String("plugin specific".to_string()), serde_json::Value::String("configuration".to_string())])),
]),
},
),
case(
r#"{
"cniVersion": "1.1.0",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
"keyA": ["some more", "plugin specific", "configuration"],
"ipam": {
"type": "host-local",
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
},
"prevResult": {
"ips": [
{
"address": "10.1.0.5/16",
"gateway": "10.1.0.1",
"interface": 2
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55"
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11"
},
{
"name": "eth0",
"mac": "00:11:22:33:44:66",
"sandbox": "/var/run/netns/blue"
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}"#.to_string(),
NetConf {
cni_version: "1.1.0".to_string(),
cni_versions: None,
name: "dbnet".to_string(),
r#type: "bridge".to_string(),
disable_check: None,
runtime_config: None,
capabilities: None,
ip_masq: None,
ipam: Some(Ipam{
r#type: "host-local".to_string(),
custom: HashMap::from([
("subnet".to_string(), serde_json::Value::String("10.1.0.0/16".to_string())),
("gateway".to_string(), serde_json::Value::String("10.1.0.1".to_string())),
]),
}),
dns: Some(Dns{
nameservers: vec!["10.1.0.1".to_string()],
domain: None,
search: None,
options: None
}),
args: None,
prev_result: Some(CNIResult{
ips: vec![
IpConfig{
interface: Some(2),
address: "10.1.0.5/16".to_string(),
gateway:Some("10.1.0.1".to_string()),
},
],
interfaces: vec![
Interface{
name: "cni0".to_string(),
mac: "00:11:22:33:44:55".to_string(),
sandbox: None,
},
Interface{
name: "veth3243".to_string(),
mac: "55:44:33:22:11:11".to_string(),
sandbox: None,
},
Interface{
name: "eth0".to_string(),
mac: "00:11:22:33:44:66".to_string(),
sandbox: Some("/var/run/netns/blue".to_string()),
},
],
routes: vec![
Route{
dst: "0.0.0.0/0".to_string(),
gw: None,
mtu: None,
advmss: None,
},
],
dns: Some(Dns{
nameservers: vec!["10.1.0.1".to_string()],
domain: None,
search: None,
options: None,
}),
}),
custom: HashMap::from([
("keyA".to_string(), serde_json::Value::Array(vec![serde_json::Value::String("some more".to_string()), serde_json::Value::String("plugin specific".to_string()), serde_json::Value::String("configuration".to_string())])),
("bridge".to_string(), serde_json::Value::String("cni0".to_string())),
]),
},
),
)]
fn deserialize_and_serialize_net_conf(input: String, expected: NetConf) {
let conf: NetConf = serde_json::from_str(&input).unwrap();
assert_eq!(expected, conf);
let data = serde_json::to_string_pretty(&conf).unwrap();
let conf_again: NetConf = serde_json::from_str(&data).unwrap();
assert_eq!(expected, conf_again);
}
#[rstest(
input,
expected,
case(
// ref: https://github.com/containernetworking/cni/blob/b62753aa2bfa365c1ceaff6f25774a8047c896b5/SPEC.md#add-example
r#"{
"ips": [
{
"address": "10.1.0.5/16",
"gateway": "10.1.0.1"
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}"#.to_string(),
CNIResult{
interfaces: Vec::new(),
ips: vec![IpConfig{
interface: None,
address: "10.1.0.5/16".to_string(),
gateway: Some("10.1.0.1".to_string()),
}],
routes: vec![
Route{
dst: "0.0.0.0/0".to_string(),
gw: None,
mtu: None,
advmss: None,
},
],
dns: Some(Dns{
nameservers: vec!["10.1.0.1".to_string()],
domain: None,
search: None,
options: None,
})
},
),
case(
// ref: https://github.com/containernetworking/cni/blob/b62753aa2bfa365c1ceaff6f25774a8047c896b5/SPEC.md#add-example
r#"{
"ips": [
{
"address": "10.1.0.5/16",
"gateway": "10.1.0.1",
"interface": 2
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55"
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11"
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue"
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}"#.to_string(),
CNIResult{
interfaces: vec![
Interface{
name: "cni0".to_string(),
mac: "00:11:22:33:44:55".to_string(),
sandbox: None,
},
Interface{
name: "veth3243".to_string(),
mac: "55:44:33:22:11:11".to_string(),
sandbox: None,
},
Interface{
name: "eth0".to_string(),
mac: "99:88:77:66:55:44".to_string(),
sandbox: Some("/var/run/netns/blue".to_string()),
},
],
ips: vec![IpConfig{
interface: Some(2),
address: "10.1.0.5/16".to_string(),
gateway: Some("10.1.0.1".to_string()),
}],
routes: vec![
Route{
dst: "0.0.0.0/0".to_string(),
gw: None,
mtu: None,
advmss: None,
},
],
dns: Some(Dns{
nameservers: vec!["10.1.0.1".to_string()],
domain: None,
search: None,
options: None,
}),
},
),
case(
// ref: https://github.com/containernetworking/cni/blob/b62753aa2bfa365c1ceaff6f25774a8047c896b5/SPEC.md#add-example
r#"{
"ips": [
{
"address": "10.1.0.5/16",
"gateway": "10.1.0.1",
"interface": 2
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55"
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11"
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue"
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}"#.to_string(),
CNIResult{
interfaces: vec![
Interface{
name: "cni0".to_string(),
mac: "00:11:22:33:44:55".to_string(),
sandbox: None,
},
Interface{
name: "veth3243".to_string(),
mac: "55:44:33:22:11:11".to_string(),
sandbox: None,
},
Interface{
name: "eth0".to_string(),
mac: "99:88:77:66:55:44".to_string(),
sandbox: Some("/var/run/netns/blue".to_string()),
},
],
ips: vec![IpConfig{
interface: Some(2),
address: "10.1.0.5/16".to_string(),
gateway: Some("10.1.0.1".to_string()),
}],
routes: vec![
Route{
dst: "0.0.0.0/0".to_string(),
gw: None,
mtu: None,
advmss: None,
},
],
dns: Some(Dns{
nameservers: vec!["10.1.0.1".to_string()],
domain: None,
search: None,
options: None,
})
},
),
)]
fn deserialize_and_serialize_success_result(input: String, expected: CNIResult) {
let result: CNIResult = serde_json::from_str(&input).unwrap();
assert_eq!(expected, result);
let data = serde_json::to_string_pretty(&result).unwrap();
let result_again: CNIResult = serde_json::from_str(&data).unwrap();
assert_eq!(expected, result_again);
}
}