Skip to content

Commit 9b1e121

Browse files
dhrgitalxiord
authored andcommitted
Unit tests for PATCH /network-interfaces/
Added unit tests for the new code that enables support for post-boot updating of the network interface rate limiters. Signed-off-by: Dan Horobeanu <[email protected]>
1 parent 5ac9d97 commit 9b1e121

File tree

4 files changed

+273
-9
lines changed

4 files changed

+273
-9
lines changed

api_server/src/http_service.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1225,7 +1225,50 @@ mod tests {
12251225
assert!(
12261226
parse_netif_req(path, Method::Options, &body,)
12271227
== Err(Error::InvalidPathMethod(path, Method::Options))
1228-
)
1228+
);
1229+
1230+
// PATCH tests
1231+
1232+
// Fail when path ID != body ID.
1233+
assert!(NetworkInterfaceUpdateConfig {
1234+
iface_id: "1".to_string(),
1235+
rx_rate_limiter: None,
1236+
tx_rate_limiter: None,
1237+
}
1238+
.into_parsed_request(Some("2".to_string()), Method::Patch)
1239+
.is_err());
1240+
1241+
let json = r#"{
1242+
"iface_id": "1",
1243+
"rx_rate_limiter": {
1244+
"bandwidth": {
1245+
"size": 1024,
1246+
"refill_time": 100
1247+
}
1248+
}
1249+
}"#;
1250+
let body = Chunk::from(json);
1251+
let nuc = serde_json::from_slice::<NetworkInterfaceUpdateConfig>(json.as_bytes()).unwrap();
1252+
let nuc_pr = nuc
1253+
.into_parsed_request(Some("1".to_string()), Method::Patch)
1254+
.unwrap();
1255+
match parse_netif_req(&"/network-interfaces/1", Method::Patch, &body) {
1256+
Ok(pr) => assert!(nuc_pr.eq(&pr)),
1257+
_ => assert!(false),
1258+
};
1259+
1260+
let json = r#"{
1261+
"iface_id": "1",
1262+
"invalid_key": true
1263+
}"#;
1264+
let body = Chunk::from(json);
1265+
assert!(parse_netif_req(&"/network-interfaces/1", Method::Patch, &body).is_err());
1266+
1267+
let json = r#"{
1268+
"iface_id": "1"
1269+
}"#;
1270+
let body = Chunk::from(json);
1271+
assert!(parse_netif_req(&"/network-interfaces/2", Method::Patch, &body).is_err());
12291272
}
12301273

12311274
#[test]

vmm/src/lib.rs

Lines changed: 115 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ pub enum Error {
136136
EpollFd(io::Error),
137137
/// Cannot read from an Event file descriptor.
138138
EventFd(std::io::Error),
139-
/// Describes a logical problem.
140-
GeneralFailure, // TODO: there are some cases in which this error should be replaced.
139+
/// An event arrived for a device, but the dispatcher can't find the event (epoll) handler.
140+
DeviceEventHandlerNotFound,
141141
/// Cannot open /dev/kvm. Either the host does not have KVM or Firecracker does not have
142142
/// permission to open the file descriptor.
143143
Kvm(io::Error),
@@ -161,11 +161,21 @@ impl std::fmt::Debug for Error {
161161
use self::Error::*;
162162

163163
match self {
164-
// This only implements Debug for the most common Firecracker error
165-
// which is the one coming from KVM at the time.
166-
// TODO: implement debug for the other errors as well.
164+
ApiChannel => write!(f, "ApiChannel: error receiving data from the API server"),
165+
CreateLegacyDevice(e) => write!(f, "Error creating legacy device: {:?}", e),
166+
EpollFd(e) => write!(f, "Epoll fd error: {:?}", e),
167+
EventFd(e) => write!(f, "Event fd error: {}", e.to_string()),
168+
DeviceEventHandlerNotFound => write!(
169+
f,
170+
"Device event handler not found. This might point to a guest device driver issue."
171+
),
167172
Kvm(ref os_err) => write!(f, "Cannot open /dev/kvm. Error: {}", os_err.to_string()),
168-
_ => write!(f, "{:?}", self),
173+
KvmApiVersion(ver) => write!(f, "Bad KVM API version: {}", ver),
174+
KvmCap(cap) => write!(f, "Missing KVM capability: {:?}", cap),
175+
Poll(e) => write!(f, "Epoll wait failed: {}", e.to_string()),
176+
Serial(e) => write!(f, "Error writing to the serial console: {:?}", e),
177+
TimerFd(e) => write!(f, "Error creating timer fd: {}", e.to_string()),
178+
Vm(e) => write!(f, "Error opening VM fd: {:?}", e),
169179
}
170180
}
171181
}
@@ -250,6 +260,7 @@ impl Display for VmmActionError {
250260

251261
/// This enum represents the public interface of the VMM. Each action contains various
252262
/// bits of information (ids, paths, etc.), together with an OutcomeSender, which is always present.
263+
#[derive(Debug)]
253264
pub enum VmmAction {
254265
/// Configure the boot source of the microVM using as input the `ConfigureBootSource`. This
255266
/// action can only be called before the microVM has booted. The response is sent using the
@@ -543,7 +554,7 @@ impl EpollContext {
543554
let received = maybe
544555
.receiver
545556
.try_recv()
546-
.map_err(|_| Error::GeneralFailure)?;
557+
.map_err(|_| Error::DeviceEventHandlerNotFound)?;
547558
Ok(maybe.handler.get_or_insert(received).as_mut())
548559
}
549560
}
@@ -1946,6 +1957,10 @@ impl PartialEq for VmmAction {
19461957
&VmmAction::InsertNetworkDevice(ref net_dev, _),
19471958
&VmmAction::InsertNetworkDevice(ref other_net_dev, _),
19481959
) => net_dev == other_net_dev,
1960+
(
1961+
&VmmAction::UpdateNetworkInterface(ref net_dev, _),
1962+
&VmmAction::UpdateNetworkInterface(ref other_net_dev, _),
1963+
) => net_dev == other_net_dev,
19491964
(
19501965
&VmmAction::RescanBlockDevice(ref req, _),
19511966
&VmmAction::RescanBlockDevice(ref other_req, _),
@@ -2019,6 +2034,7 @@ mod tests {
20192034
use devices::virtio::ActivateResult;
20202035
use net_util::MacAddr;
20212036
use vmm_config::machine_config::CpuFeaturesTemplate;
2037+
use vmm_config::{RateLimiterConfig, TokenBucketConfig};
20222038

20232039
impl Vmm {
20242040
fn get_kernel_cmdline_str(&self) -> &str {
@@ -2312,6 +2328,98 @@ mod tests {
23122328
assert!(vmm.insert_net_device(network_interface).is_err());
23132329
}
23142330

2331+
#[test]
2332+
fn test_update_net_device() {
2333+
let mut vmm = create_vmm_object(InstanceState::Uninitialized);
2334+
2335+
let tbc_1mtps = TokenBucketConfig {
2336+
size: 1024 * 1024,
2337+
one_time_burst: None,
2338+
refill_time: 1000,
2339+
};
2340+
let tbc_2mtps = TokenBucketConfig {
2341+
size: 2 * 1024 * 1024,
2342+
one_time_burst: None,
2343+
refill_time: 1000,
2344+
};
2345+
2346+
vmm.insert_net_device(NetworkInterfaceConfig {
2347+
iface_id: String::from("1"),
2348+
host_dev_name: String::from("hostname5"),
2349+
guest_mac: None,
2350+
rx_rate_limiter: Some(RateLimiterConfig {
2351+
bandwidth: Some(tbc_1mtps),
2352+
ops: None,
2353+
}),
2354+
tx_rate_limiter: None,
2355+
allow_mmds_requests: false,
2356+
tap: None,
2357+
})
2358+
.unwrap();
2359+
2360+
vmm.update_net_device(NetworkInterfaceUpdateConfig {
2361+
iface_id: "1".to_string(),
2362+
rx_rate_limiter: Some(RateLimiterConfig {
2363+
bandwidth: None,
2364+
ops: Some(tbc_2mtps),
2365+
}),
2366+
tx_rate_limiter: Some(RateLimiterConfig {
2367+
bandwidth: None,
2368+
ops: Some(tbc_2mtps),
2369+
}),
2370+
})
2371+
.unwrap();
2372+
2373+
{
2374+
let nic_1: &mut NetworkInterfaceConfig =
2375+
vmm.network_interface_configs.iter_mut().next().unwrap();
2376+
// The RX bandwidth should be unaffected.
2377+
assert_eq!(nic_1.rx_rate_limiter.unwrap().bandwidth.unwrap(), tbc_1mtps);
2378+
// The RX ops should be set to 2mtps.
2379+
assert_eq!(nic_1.rx_rate_limiter.unwrap().ops.unwrap(), tbc_2mtps);
2380+
// The TX bandwith should be unlimited (unaffected).
2381+
assert_eq!(nic_1.tx_rate_limiter.unwrap().bandwidth, None);
2382+
// The TX ops should be set to 2mtps.
2383+
assert_eq!(nic_1.tx_rate_limiter.unwrap().ops.unwrap(), tbc_2mtps);
2384+
}
2385+
2386+
vmm.init_guest_memory().unwrap();
2387+
vmm.default_kernel_config();
2388+
let guest_mem = vmm.guest_memory.clone().unwrap();
2389+
let mut device_manager =
2390+
MMIODeviceManager::new(guest_mem.clone(), arch::get_reserved_mem_addr() as u64);
2391+
vmm.attach_net_devices(&mut device_manager).unwrap();
2392+
vmm.set_instance_state(InstanceState::Running);
2393+
2394+
// The update should fail before device activation.
2395+
assert!(vmm
2396+
.update_net_device(NetworkInterfaceUpdateConfig {
2397+
iface_id: "1".to_string(),
2398+
rx_rate_limiter: None,
2399+
tx_rate_limiter: None,
2400+
})
2401+
.is_err());
2402+
2403+
// Fake device activation by explicitly setting a dummy epoll handler.
2404+
vmm.epoll_context.device_handlers[0].handler = Some(Box::new(DummyEpollHandler {
2405+
evt: None,
2406+
flags: None,
2407+
payload: None,
2408+
}));
2409+
vmm.update_net_device(NetworkInterfaceUpdateConfig {
2410+
iface_id: "1".to_string(),
2411+
rx_rate_limiter: Some(RateLimiterConfig {
2412+
bandwidth: Some(tbc_2mtps),
2413+
ops: None,
2414+
}),
2415+
tx_rate_limiter: Some(RateLimiterConfig {
2416+
bandwidth: Some(tbc_1mtps),
2417+
ops: None,
2418+
}),
2419+
})
2420+
.unwrap();
2421+
}
2422+
23152423
#[test]
23162424
fn test_machine_configuration() {
23172425
let mut vmm = create_vmm_object(InstanceState::Uninitialized);

vmm/src/vmm_config/mod.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,63 @@ impl RateLimiterConfig {
8686
}
8787
}
8888
}
89+
90+
#[cfg(test)]
91+
mod tests {
92+
use super::*;
93+
94+
#[test]
95+
fn test_rate_limiter_configs() {
96+
const SIZE: u64 = 1024 * 1024;
97+
const ONE_TIME_BURST: u64 = 1024;
98+
const REFILL_TIME: u64 = 1000;
99+
100+
let b = TokenBucketConfig {
101+
size: SIZE,
102+
one_time_burst: Some(ONE_TIME_BURST),
103+
refill_time: REFILL_TIME,
104+
}
105+
.into_token_bucket();
106+
assert_eq!(b.capacity(), SIZE);
107+
assert_eq!(b.one_time_burst(), ONE_TIME_BURST);
108+
assert_eq!(b.refill_time_ms(), REFILL_TIME);
109+
110+
let mut rlconf = RateLimiterConfig {
111+
bandwidth: Some(TokenBucketConfig {
112+
size: SIZE,
113+
one_time_burst: Some(ONE_TIME_BURST),
114+
refill_time: REFILL_TIME,
115+
}),
116+
ops: Some(TokenBucketConfig {
117+
size: SIZE * 2,
118+
one_time_burst: None,
119+
refill_time: REFILL_TIME * 2,
120+
}),
121+
};
122+
let rl = rlconf.into_rate_limiter().unwrap();
123+
assert_eq!(rl.bandwidth().unwrap().capacity(), SIZE);
124+
assert_eq!(rl.bandwidth().unwrap().one_time_burst(), ONE_TIME_BURST);
125+
assert_eq!(rl.bandwidth().unwrap().refill_time_ms(), REFILL_TIME);
126+
assert_eq!(rl.ops().unwrap().capacity(), SIZE * 2);
127+
assert_eq!(rl.ops().unwrap().one_time_burst(), 0);
128+
assert_eq!(rl.ops().unwrap().refill_time_ms(), REFILL_TIME * 2);
129+
130+
rlconf.update(&RateLimiterConfig {
131+
bandwidth: Some(TokenBucketConfig {
132+
size: SIZE * 2,
133+
one_time_burst: Some(ONE_TIME_BURST * 2),
134+
refill_time: REFILL_TIME * 2,
135+
}),
136+
ops: None,
137+
});
138+
assert_eq!(rlconf.bandwidth.unwrap().size, SIZE * 2);
139+
assert_eq!(
140+
rlconf.bandwidth.unwrap().one_time_burst,
141+
Some(ONE_TIME_BURST * 2)
142+
);
143+
assert_eq!(rlconf.bandwidth.unwrap().refill_time, REFILL_TIME * 2);
144+
assert_eq!(rlconf.ops.unwrap().size, SIZE * 2);
145+
assert_eq!(rlconf.ops.unwrap().one_time_burst, None);
146+
assert_eq!(rlconf.ops.unwrap().refill_time, REFILL_TIME * 2);
147+
}
148+
}

vmm/src/vmm_config/net.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ impl NetworkInterfaceConfig {
6565

6666
/// The data fed into a network iface update request. Currently, only the RX and TX rate limiters
6767
/// can be updated.
68-
#[derive(Debug, Deserialize)]
68+
#[derive(Debug, Deserialize, PartialEq)]
6969
#[serde(deny_unknown_fields)]
7070
pub struct NetworkInterfaceUpdateConfig {
7171
/// The net iface ID, as provided by the user at iface creation time.
@@ -290,6 +290,7 @@ mod tests {
290290

291291
use super::*;
292292
use net_util::MacAddr;
293+
use VmmActionError::NetworkConfig;
293294

294295
fn create_netif(id: &str, name: &str, mac: &str) -> NetworkInterfaceConfig {
295296
NetworkInterfaceConfig {
@@ -426,4 +427,56 @@ mod tests {
426427
expected_error
427428
);
428429
}
430+
431+
#[test]
432+
fn test_error_display() {
433+
let _ = format!(
434+
"{}{:?}",
435+
NetworkInterfaceError::GuestMacAddressInUse("00:00:00:00:00:00".to_string()),
436+
NetworkInterfaceError::GuestMacAddressInUse("00:00:00:00:00:00".to_string())
437+
);
438+
let _ = format!(
439+
"{}{:?}",
440+
NetworkInterfaceError::EpollHandlerNotFound(
441+
VmmInternalError::DeviceEventHandlerNotFound
442+
),
443+
NetworkInterfaceError::EpollHandlerNotFound(
444+
VmmInternalError::DeviceEventHandlerNotFound
445+
)
446+
);
447+
let _ = format!(
448+
"{}{:?}",
449+
NetworkInterfaceError::HostDeviceNameInUse("hostdev".to_string()),
450+
NetworkInterfaceError::HostDeviceNameInUse("hostdev".to_string())
451+
);
452+
let _ = format!(
453+
"{}{:?}",
454+
NetworkInterfaceError::DeviceIdNotFound,
455+
NetworkInterfaceError::DeviceIdNotFound
456+
);
457+
let _ = format!(
458+
"{}{:?}",
459+
NetworkInterfaceError::OpenTap(TapError::InvalidIfname),
460+
NetworkInterfaceError::OpenTap(TapError::InvalidIfname)
461+
);
462+
let _ = format!(
463+
"{}{:?}",
464+
NetworkInterfaceError::RateLimiterError(io::Error::last_os_error()),
465+
NetworkInterfaceError::RateLimiterError(io::Error::last_os_error())
466+
);
467+
let _ = format!(
468+
"{}{:?}",
469+
NetworkInterfaceError::RateLimiterUpdateFailed(devices::Error::IoError(
470+
io::Error::last_os_error()
471+
)),
472+
NetworkInterfaceError::RateLimiterUpdateFailed(devices::Error::IoError(
473+
io::Error::last_os_error()
474+
))
475+
);
476+
let _ = format!(
477+
"{}{:?}",
478+
NetworkInterfaceError::UpdateNotAllowedPostBoot,
479+
NetworkInterfaceError::UpdateNotAllowedPostBoot
480+
);
481+
}
429482
}

0 commit comments

Comments
 (0)