Skip to content

Commit 8e4cf16

Browse files
dhrgitalxiord
authored andcommitted
New API call: PATCH /network-interfaces
Added support for updating a network device, both pre- and post-boot. This is implemented via a PATCH API request. Currently, the only properties supported for update are the RX and TX rate limiters. Signed-off-by: Dan Horobeanu <[email protected]>
1 parent eabdab7 commit 8e4cf16

File tree

11 files changed

+355
-57
lines changed

11 files changed

+355
-57
lines changed

api_server/src/http_service.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use vmm::vmm_config::drive::BlockDeviceConfig;
2424
use vmm::vmm_config::instance_info::InstanceInfo;
2525
use vmm::vmm_config::logger::LoggerConfig;
2626
use vmm::vmm_config::machine_config::VmConfig;
27-
use vmm::vmm_config::net::NetworkInterfaceConfig;
27+
use vmm::vmm_config::net::{NetworkInterfaceConfig, NetworkInterfaceUpdateConfig};
2828
#[cfg(feature = "vsock")]
2929
use vmm::vmm_config::vsock::VsockDeviceConfig;
3030
use vmm::VmmAction;
@@ -335,6 +335,20 @@ fn parse_netif_req<'a>(path: &'a str, method: Method, body: &Chunk) -> Result<'a
335335
Error::Generic(StatusCode::BadRequest, s)
336336
})?)
337337
}
338+
1 if method == Method::Patch => {
339+
METRICS.patch_api_requests.network_count.inc();
340+
341+
Ok(serde_json::from_slice::<NetworkInterfaceUpdateConfig>(body)
342+
.map_err(|e| {
343+
METRICS.patch_api_requests.network_fails.inc();
344+
Error::SerdeJson(e)
345+
})?
346+
.into_parsed_request(Some(id_from_path.to_string()), method)
347+
.map_err(|s| {
348+
METRICS.patch_api_requests.network_fails.inc();
349+
Error::Generic(StatusCode::BadRequest, s)
350+
})?)
351+
}
338352
_ => Err(Error::InvalidPathMethod(path, method)),
339353
}
340354
}
@@ -1207,10 +1221,10 @@ mod tests {
12071221
== Err(Error::SerdeJson(get_dummy_serde_error()))
12081222
);
12091223

1210-
// Error Case: Invalid Path.
1224+
// Error Case: Invalid method.
12111225
assert!(
1212-
parse_netif_req(path, Method::Patch, &body,)
1213-
== Err(Error::InvalidPathMethod(path, Method::Patch))
1226+
parse_netif_req(path, Method::Options, &body,)
1227+
== Err(Error::InvalidPathMethod(path, Method::Options))
12141228
)
12151229
}
12161230

api_server/src/request/net.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use futures::sync::oneshot;
77
use hyper::Method;
88

99
use request::{IntoParsedRequest, ParsedRequest};
10-
use vmm::vmm_config::net::NetworkInterfaceConfig;
10+
use vmm::vmm_config::net::{NetworkInterfaceConfig, NetworkInterfaceUpdateConfig};
1111
use vmm::VmmAction;
1212

1313
impl IntoParsedRequest for NetworkInterfaceConfig {
@@ -31,6 +31,27 @@ impl IntoParsedRequest for NetworkInterfaceConfig {
3131
}
3232
}
3333

34+
impl IntoParsedRequest for NetworkInterfaceUpdateConfig {
35+
fn into_parsed_request(
36+
self,
37+
id_from_path: Option<String>,
38+
_: Method,
39+
) -> result::Result<ParsedRequest, String> {
40+
let id_from_path = id_from_path.unwrap_or(String::new());
41+
if id_from_path != self.iface_id {
42+
return Err(String::from(
43+
"The id from the path does not match the id from the body!",
44+
));
45+
}
46+
47+
let (sender, receiver) = oneshot::channel();
48+
Ok(ParsedRequest::Sync(
49+
VmmAction::UpdateNetworkInterface(self, sender),
50+
receiver,
51+
))
52+
}
53+
}
54+
3455
#[cfg(test)]
3556
mod tests {
3657
extern crate net_util;

api_server/swagger/firecracker.yaml

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,6 @@ paths:
274274
summary: Creates a network interface.
275275
description:
276276
Creates new network interface with ID specified by iface_id path parameter.
277-
Updating existing interfaces is currently not allowed.
278277
operationId: putGuestNetworkInterfaceByID
279278
parameters:
280279
- name: iface_id
@@ -299,6 +298,34 @@ paths:
299298
description: Internal server error
300299
schema:
301300
$ref: "#/definitions/Error"
301+
patch:
302+
summary: Updates the rate limiters applied to a network interface.
303+
description:
304+
Updates the rate limiters applied to a network interface.
305+
operationId: patchGuestNetworkInterfaceByID
306+
parameters:
307+
- name: iface_id
308+
in: path
309+
description: The id of the guest network interface
310+
required: true
311+
type: string
312+
- name: body
313+
in: body
314+
description: A subset of the guest network interface properties
315+
required: true
316+
schema:
317+
$ref: "#/definitions/PartialNetworkInterface"
318+
responses:
319+
204:
320+
description: Network interface updated
321+
400:
322+
description: Network interface cannot be updated due to bad input
323+
schema:
324+
$ref: "#/definitions/Error"
325+
default:
326+
description: Internal server error
327+
schema:
328+
$ref: "#/definitions/Error"
302329

303330
definitions:
304331
BootSource:
@@ -501,6 +528,21 @@ definitions:
501528
type: string
502529
description: Host level path for the guest drive
503530

531+
PartialNetworkInterface:
532+
type: object
533+
description:
534+
Defines a partial network interface structure, used to update the rate limiters
535+
for that interface, after microvm start.
536+
required:
537+
- iface_id
538+
properties:
539+
iface_id:
540+
type: string
541+
rx_rate_limiter:
542+
$ref: "#/definitions/RateLimiter"
543+
tx_rate_limiter:
544+
$ref: "#/definitions/RateLimiter"
545+
504546
RateLimiter:
505547
type: object
506548
description:

devices/src/lib.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ pub enum EpollHandlerPayload {
4343
DrivePayload(File),
4444
/// Used to mutate current RateLimiter settings. The buckets are rx_bytes, rx_ops,
4545
/// tx_bytes, and tx_ops, respectively.
46-
PatchRateLimiters(
47-
Option<TokenBucket>,
48-
Option<TokenBucket>,
49-
Option<TokenBucket>,
50-
Option<TokenBucket>,
51-
),
46+
NetRateLimiterPayload {
47+
rx_bytes: Option<TokenBucket>,
48+
rx_ops: Option<TokenBucket>,
49+
tx_bytes: Option<TokenBucket>,
50+
tx_ops: Option<TokenBucket>,
51+
},
5252
/// Events that do not need a payload.
5353
Empty,
5454
}

devices/src/virtio/net.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ pub const NET_EVENTS_COUNT: usize = 5;
5959
// This is not a true DeviceEvent, as we explicitly invoke the handler with this value as a
6060
// parameter when the VMM handles as PATCH rate limiters request. Thus, there's not epoll event
6161
// associated with it.
62-
const PATCH_RATE_LIMITERS: DeviceEventT = 5;
62+
pub const PATCH_RATE_LIMITERS_FAKE_EVENT: DeviceEventT = NET_EVENTS_COUNT as DeviceEventT;
6363

6464
#[derive(Debug)]
6565
pub enum Error {
@@ -605,9 +605,13 @@ impl EpollHandler for NetEpollHandler {
605605
}
606606
}
607607
}
608-
PATCH_RATE_LIMITERS => {
609-
if let EpollHandlerPayload::PatchRateLimiters(rx_bytes, rx_ops, tx_bytes, tx_ops) =
610-
payload
608+
PATCH_RATE_LIMITERS_FAKE_EVENT => {
609+
if let EpollHandlerPayload::NetRateLimiterPayload {
610+
rx_bytes,
611+
rx_ops,
612+
tx_bytes,
613+
tx_ops,
614+
} = payload
611615
{
612616
self.rx.rate_limiter.update_buckets(rx_bytes, rx_ops);
613617
self.tx.rate_limiter.update_buckets(tx_bytes, tx_ops);
@@ -1941,14 +1945,14 @@ mod tests {
19411945
let tx_ops = TokenBucket::new(1009, Some(1010), 1011);
19421946

19431947
h.handle_event(
1944-
PATCH_RATE_LIMITERS,
1948+
PATCH_RATE_LIMITERS_FAKE_EVENT,
19451949
0,
1946-
EpollHandlerPayload::PatchRateLimiters(
1947-
Some(rx_bytes.clone()),
1948-
Some(rx_ops.clone()),
1949-
Some(tx_bytes.clone()),
1950-
Some(tx_ops.clone()),
1951-
),
1950+
EpollHandlerPayload::NetRateLimiterPayload {
1951+
rx_bytes: Some(rx_bytes.clone()),
1952+
rx_ops: Some(rx_ops.clone()),
1953+
tx_bytes: Some(tx_bytes.clone()),
1954+
tx_ops: Some(tx_ops.clone()),
1955+
},
19521956
)
19531957
.unwrap();
19541958

logger/src/metrics.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ pub struct PatchRequestsMetrics {
184184
pub drive_count: SharedMetric,
185185
/// Number of failures in PATCHing a block device.
186186
pub drive_fails: SharedMetric,
187+
/// Number of tries to PATCH a net device.
188+
pub network_count: SharedMetric,
189+
/// Number of failures in PATCHing a net device.
190+
pub network_fails: SharedMetric,
187191
}
188192

189193
/// Block Device associated metrics.

rate_limiter/src/lib.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ fn gcd(x: u64, y: u64) -> u64 {
9393
pub struct TokenBucket {
9494
// Bucket defining traits.
9595
size: u64,
96+
// Initial burst size (number of free initial tokens, that can be consumed at no cost)
9697
one_time_burst: Option<u64>,
9798
// Complete refill time in milliseconds.
9899
refill_time: u64,
@@ -410,13 +411,12 @@ impl RateLimiter {
410411
ops_complete_refill_time_ms,
411412
);
412413

413-
// If limiting is disabled on all token types, don't even create a timer fd.
414-
let timer_fd = if bytes_token_bucket.is_some() || ops_token_bucket.is_some() {
415-
// create TimerFd using monotonic clock, as nonblocking FD and set close-on-exec
416-
Some(TimerFd::new_custom(ClockId::Monotonic, true, true)?)
417-
} else {
418-
None
419-
};
414+
// TODO: Self::timer_fd should not be an `Option` anymore; clean that up.
415+
//
416+
// We'll need a timer_fd, even if our current config effectively disables rate limiting,
417+
// because `Self::update_buckets()` might re-enable it later, and we might be
418+
// seccomp-blocked from creating the timer_fd at that time.
419+
let timer_fd = Some(TimerFd::new_custom(ClockId::Monotonic, true, true)?);
420420

421421
Ok(RateLimiter {
422422
bandwidth: bytes_token_bucket,
@@ -509,7 +509,7 @@ impl RateLimiter {
509509
}
510510

511511
/// Updates the parameters of the token buckets associated with this RateLimiter.
512-
// TODO: Pls note that, right now, the buckets buckets become full after being updated.
512+
// TODO: Pls note that, right now, the buckets become full after being updated.
513513
pub fn update_buckets(&mut self, bytes: Option<TokenBucket>, ops: Option<TokenBucket>) {
514514
// TODO: We have to call make_bucket instead of directly assigning the bytes and/or ops
515515
// because the input buckets are likely build via deserialization, which currently does not
@@ -673,8 +673,6 @@ mod tests {
673673
"SpuriousRateLimiterEvent(\
674674
\"Rate limiter event handler called without a present timer\")"
675675
);
676-
// raw FD for this disabled rate-limiter should be -1
677-
assert_eq!(l.as_raw_fd(), -1);
678676
}
679677

680678
#[test]

tests/integration_tests/functional/test_api.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -477,14 +477,6 @@ def test_api_patch_pre_boot(test_microvm_with_api):
477477
assert test_microvm.api_session.is_status_bad_request(response.status_code)
478478
assert "Invalid request method" in response.text
479479

480-
# Partial updates to network interfaces are not allowed.
481-
response = test_microvm.network.patch(
482-
iface_id='1',
483-
guest_mac='06:00:00:00:00:02'
484-
)
485-
assert test_microvm.api_session.is_status_bad_request(response.status_code)
486-
assert "Invalid request method" in response.text
487-
488480
# Partial updates to the logger configuration are not allowed.
489481
response = test_microvm.logger.patch(level='Error')
490482
assert test_microvm.api_session.is_status_bad_request(response.status_code)
@@ -547,14 +539,6 @@ def test_api_patch_post_boot(test_microvm_with_api):
547539
assert test_microvm.api_session.is_status_bad_request(response.status_code)
548540
assert "Invalid request method" in response.text
549541

550-
# Partial updates to network interfaces are not allowed.
551-
response = test_microvm.network.patch(
552-
iface_id='1',
553-
guest_mac='06:00:00:00:00:02'
554-
)
555-
assert test_microvm.api_session.is_status_bad_request(response.status_code)
556-
assert "Invalid request method" in response.text
557-
558542
# Partial updates to the logger configuration are not allowed.
559543
response = test_microvm.logger.patch(level='Error')
560544
assert test_microvm.api_session.is_status_bad_request(response.status_code)

0 commit comments

Comments
 (0)