Skip to content

Commit 7e184e5

Browse files
[rdb] rework RPF cache rebuilds around a bounded worker
This replaces the interval-driven poptrie rebuild with a single long-lived worker that rebuilds on loc-RIB change. Mutation batches already end with a change notification, so `notify()` now triggers the rebuild as well. Paths that bypass notification (bestpath re-runs, peer staleness marking, direct path removal) still trigger explicitly. This long-lived worker keeps memory bounded with a one-slot pending queue per address family, where requests arriving within a fixed coalescing window merge, and distinct changed prefixes widen to a full sweep. Completed snapshots install unconditionally. A trigger that lands during a build leaves its request in the pending slot, so a possibly stale install is followed by a rebuild from a newer snapshot 1 coalescing window later. Installs are serialized in the single worker, so each is newer than the last. After updates quiesce, the final rebuild converges to the current RIB. Optimizations: - Snapshots derive the compact `RpfNexthops` payload in the same pass under the RIB lock, so full path sets are never cloned, and `get_rpf_neighbor` selects a neighbor without cloning the path set. - The linear-scan fallback now also matches /0 default routes. With these changes, the rebuild-interval setting and its plumbing are removed, including the admin API endpoints and a regeneration of the unblessed v12 OpenAPI spec.
1 parent d2bf04d commit 7e184e5

10 files changed

Lines changed: 660 additions & 802 deletions

File tree

mg-api-types/versions/src/multicast_support/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
//! Version `MULTICAST_SUPPORT` of the Maghemite Admin API.
88
//!
99
//! Adds MRIB (Multicast Routing Information Base) endpoints for static
10-
//! multicast route management, RPF rebuild rate control, and query of
11-
//! imported and selected multicast routes. Introduces the supporting wire
12-
//! types (validated unicast and multicast addresses, the underlay group
13-
//! within `ff04::/64`, multicast route keys, and route entries).
10+
//! multicast route management and query of imported and selected multicast
11+
//! routes. Introduces the supporting wire types (validated unicast and
12+
//! multicast addresses, the underlay group within `ff04::/64`, multicast
13+
//! route keys, and route entries).
1414
1515
pub mod mrib;

mg-api-types/versions/src/multicast_support/mrib.rs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -156,23 +156,6 @@ pub struct MribDeleteStaticRequest {
156156
pub keys: Vec<MulticastRouteKey>,
157157
}
158158

159-
/// Response containing the current RPF rebuild interval.
160-
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
161-
pub struct MribRpfRebuildIntervalResponse {
162-
/// Minimum interval between RPF cache rebuilds in milliseconds.
163-
/// A value of 0 means rate-limiting is disabled.
164-
pub interval_ms: u64,
165-
}
166-
167-
/// Request body for setting the RPF rebuild interval.
168-
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
169-
pub struct MribRpfRebuildIntervalRequest {
170-
/// Minimum interval between RPF cache rebuilds in milliseconds.
171-
/// A value of 0 disables rate-limiting.
172-
/// Every unicast RIB change triggers an immediate poptrie rebuild.
173-
pub interval_ms: u64,
174-
}
175-
176159
/// Filter for multicast route origin.
177160
#[derive(
178161
Debug, Clone, Copy, Deserialize, Serialize, JsonSchema, PartialEq, Eq,
@@ -1015,7 +998,12 @@ impl MulticastRouteKey {
1015998
pub struct MulticastRoute {
1016999
/// The multicast route key (S,G) or (*,G).
10171000
pub key: MulticastRouteKey,
1018-
/// Expected RPF neighbor for the source (for RPF checks).
1001+
/// Upstream neighbor selected for RPF checks.
1002+
///
1003+
/// This records one representative neighbor rather than the full ECMP
1004+
/// set. When the unicast route has multiple equal-cost paths, any active
1005+
/// member is valid. `None` means RPF does not apply or no active unicast
1006+
/// path is available.
10191007
pub rpf_neighbor: Option<IpAddr>,
10201008
/// Underlay multicast group address (ff04::/64).
10211009
///

mg-api/src/lib.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,20 +1568,4 @@ pub trait MgAdminApi {
15681568
async fn static_list_mcast_routes(
15691569
rqctx: RequestContext<Self::Context>,
15701570
) -> Result<HttpResponseOk<Vec<latest::mrib::MulticastRoute>>, HttpError>;
1571-
1572-
/// Get the RPF rebuild rate-limit interval.
1573-
#[endpoint { method = GET, path = "/mrib/config/rpf/rebuild-interval", versions = VERSION_MULTICAST_SUPPORT.. }]
1574-
async fn read_mrib_rpf_rebuild_interval(
1575-
rqctx: RequestContext<Self::Context>,
1576-
) -> Result<
1577-
HttpResponseOk<latest::mrib::MribRpfRebuildIntervalResponse>,
1578-
HttpError,
1579-
>;
1580-
1581-
/// Set the RPF rebuild rate-limit interval.
1582-
#[endpoint { method = POST, path = "/mrib/config/rpf/rebuild-interval", versions = VERSION_MULTICAST_SUPPORT.. }]
1583-
async fn update_mrib_rpf_rebuild_interval(
1584-
rqctx: RequestContext<Self::Context>,
1585-
request: TypedBody<latest::mrib::MribRpfRebuildIntervalRequest>,
1586-
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
15871571
}

mgadm/src/mrib.rs

Lines changed: 81 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ use clap::{Args, Subcommand};
1919
use client_common::println_nopipe;
2020
use mg_admin_client::Client;
2121
use mg_admin_client::types::{
22-
MribRpfRebuildIntervalRequest, MulticastRoute, MulticastRouteKey,
23-
RouteOriginFilter,
22+
MulticastRoute, MulticastRouteKey, RouteOriginFilter,
2423
};
2524
use mg_api_types::mrib::DEFAULT_MULTICAST_VNI;
2625
use mg_api_types::rdb::rib::AddressFamily;
@@ -41,9 +40,6 @@ fn parse_route_origin(s: &str) -> Result<RouteOriginFilter, String> {
4140
pub enum Commands {
4241
/// View MRIB state.
4342
Status(StatusCommand),
44-
45-
/// RPF (Reverse Path Forwarding) table configuration and lookup.
46-
Rpf(RpfCommand),
4743
}
4844

4945
#[derive(Debug, Args)]
@@ -109,24 +105,6 @@ pub enum StatusCmd {
109105
},
110106
}
111107

112-
#[derive(Debug, Args)]
113-
pub struct RpfCommand {
114-
#[command(subcommand)]
115-
command: RpfCmd,
116-
}
117-
118-
#[derive(Subcommand, Debug)]
119-
pub enum RpfCmd {
120-
/// Get RPF rebuild interval.
121-
GetInterval,
122-
123-
/// Set RPF rebuild interval.
124-
SetInterval {
125-
/// Rebuild interval in milliseconds
126-
interval_ms: u64,
127-
},
128-
}
129-
130108
pub async fn commands(command: Commands, c: Client) -> Result<()> {
131109
match command {
132110
Commands::Status(status_cmd) => match status_cmd.command {
@@ -157,12 +135,6 @@ pub async fn commands(command: Commands, c: Client) -> Result<()> {
157135
}
158136
}
159137
},
160-
Commands::Rpf(rpf_cmd) => match rpf_cmd.command {
161-
RpfCmd::GetInterval => get_rpf_interval(c).await?,
162-
RpfCmd::SetInterval { interval_ms } => {
163-
set_rpf_interval(c, interval_ms).await?
164-
}
165-
},
166138
}
167139
Ok(())
168140
}
@@ -229,21 +201,6 @@ async fn get_route_selected(
229201
Ok(())
230202
}
231203

232-
async fn get_rpf_interval(c: Client) -> Result<()> {
233-
let result = c.read_mrib_rpf_rebuild_interval().await?.into_inner();
234-
println_nopipe!("RPF rebuild interval: {}ms", result.interval_ms);
235-
Ok(())
236-
}
237-
238-
async fn set_rpf_interval(c: Client, interval_ms: u64) -> Result<()> {
239-
c.update_mrib_rpf_rebuild_interval(&MribRpfRebuildIntervalRequest {
240-
interval_ms,
241-
})
242-
.await?;
243-
println_nopipe!("Updated RPF rebuild interval to: {interval_ms}ms");
244-
Ok(())
245-
}
246-
247204
fn print_routes(routes: &[MulticastRoute]) {
248205
if routes.is_empty() {
249206
println_nopipe!("No multicast routes");
@@ -295,21 +252,19 @@ mod tests {
295252
])
296253
.unwrap();
297254

298-
match cli.command {
299-
Commands::Status(cmd) => match cmd.command {
300-
StatusCmd::Imported {
301-
group, source, vni, ..
302-
} => {
303-
assert_eq!(
304-
group,
305-
Some(IpAddr::V4(Ipv4Addr::new(225, 1, 2, 3)))
306-
);
307-
assert_eq!(source, None);
308-
assert_eq!(vni, DEFAULT_VNI);
309-
}
310-
_ => panic!("expected Imported"),
311-
},
312-
_ => panic!("expected Status command"),
255+
let Commands::Status(cmd) = cli.command;
256+
match cmd.command {
257+
StatusCmd::Imported {
258+
group, source, vni, ..
259+
} => {
260+
assert_eq!(
261+
group,
262+
Some(IpAddr::V4(Ipv4Addr::new(225, 1, 2, 3)))
263+
);
264+
assert_eq!(source, None);
265+
assert_eq!(vni, DEFAULT_VNI);
266+
}
267+
_ => panic!("expected Imported"),
313268
}
314269
}
315270

@@ -328,24 +283,22 @@ mod tests {
328283
])
329284
.unwrap();
330285

331-
match cli.command {
332-
Commands::Status(cmd) => match cmd.command {
333-
StatusCmd::Imported {
334-
group, source, vni, ..
335-
} => {
336-
assert_eq!(
337-
group,
338-
Some(IpAddr::V4(Ipv4Addr::new(225, 1, 2, 3)))
339-
);
340-
assert_eq!(
341-
source,
342-
Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)))
343-
);
344-
assert_eq!(vni, 100);
345-
}
346-
_ => panic!("expected Imported"),
347-
},
348-
_ => panic!("expected Status command"),
286+
let Commands::Status(cmd) = cli.command;
287+
match cmd.command {
288+
StatusCmd::Imported {
289+
group, source, vni, ..
290+
} => {
291+
assert_eq!(
292+
group,
293+
Some(IpAddr::V4(Ipv4Addr::new(225, 1, 2, 3)))
294+
);
295+
assert_eq!(
296+
source,
297+
Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)))
298+
);
299+
assert_eq!(vni, 100);
300+
}
301+
_ => panic!("expected Imported"),
349302
}
350303
}
351304

@@ -364,28 +317,26 @@ mod tests {
364317
])
365318
.unwrap();
366319

367-
match cli.command {
368-
Commands::Status(cmd) => match cmd.command {
369-
StatusCmd::Selected {
370-
group, source, vni, ..
371-
} => {
372-
assert_eq!(
373-
group,
374-
Some(IpAddr::V6(Ipv6Addr::new(
375-
0xff0e, 0, 0, 0, 0, 0, 0, 1
376-
)))
377-
);
378-
assert_eq!(
379-
source,
380-
Some(IpAddr::V6(Ipv6Addr::new(
381-
0x2001, 0xdb8, 0, 0, 0, 0, 0, 1
382-
)))
383-
);
384-
assert_eq!(vni, 42);
385-
}
386-
_ => panic!("expected Selected"),
387-
},
388-
_ => panic!("expected Status command"),
320+
let Commands::Status(cmd) = cli.command;
321+
match cmd.command {
322+
StatusCmd::Selected {
323+
group, source, vni, ..
324+
} => {
325+
assert_eq!(
326+
group,
327+
Some(IpAddr::V6(Ipv6Addr::new(
328+
0xff0e, 0, 0, 0, 0, 0, 0, 1
329+
)))
330+
);
331+
assert_eq!(
332+
source,
333+
Some(IpAddr::V6(Ipv6Addr::new(
334+
0x2001, 0xdb8, 0, 0, 0, 0, 0, 1
335+
)))
336+
);
337+
assert_eq!(vni, 42);
338+
}
339+
_ => panic!("expected Selected"),
389340
}
390341
}
391342

@@ -395,19 +346,17 @@ mod tests {
395346
TestCli::try_parse_from(["test", "status", "imported", "ipv4"])
396347
.unwrap();
397348

398-
match cli.command {
399-
Commands::Status(cmd) => match cmd.command {
400-
StatusCmd::Imported {
401-
group,
402-
address_family,
403-
..
404-
} => {
405-
assert_eq!(group, None);
406-
assert_eq!(address_family, Some(AddressFamily::Ipv4));
407-
}
408-
_ => panic!("expected Imported"),
409-
},
410-
_ => panic!("expected Status command"),
349+
let Commands::Status(cmd) = cli.command;
350+
match cmd.command {
351+
StatusCmd::Imported {
352+
group,
353+
address_family,
354+
..
355+
} => {
356+
assert_eq!(group, None);
357+
assert_eq!(address_family, Some(AddressFamily::Ipv4));
358+
}
359+
_ => panic!("expected Imported"),
411360
}
412361
}
413362

@@ -418,15 +367,13 @@ mod tests {
418367
])
419368
.unwrap();
420369

421-
match cli.command {
422-
Commands::Status(cmd) => match cmd.command {
423-
StatusCmd::Imported { group, origin, .. } => {
424-
assert_eq!(group, None);
425-
assert_eq!(origin, Some(RouteOriginFilter::Dynamic));
426-
}
427-
_ => panic!("expected Imported"),
428-
},
429-
_ => panic!("expected Status command"),
370+
let Commands::Status(cmd) = cli.command;
371+
match cmd.command {
372+
StatusCmd::Imported { group, origin, .. } => {
373+
assert_eq!(group, None);
374+
assert_eq!(origin, Some(RouteOriginFilter::Dynamic));
375+
}
376+
_ => panic!("expected Imported"),
430377
}
431378
}
432379

@@ -435,38 +382,19 @@ mod tests {
435382
let cli =
436383
TestCli::try_parse_from(["test", "status", "selected"]).unwrap();
437384

438-
match cli.command {
439-
Commands::Status(cmd) => match cmd.command {
440-
StatusCmd::Selected {
441-
group,
442-
address_family,
443-
origin,
444-
..
445-
} => {
446-
assert_eq!(group, None);
447-
assert_eq!(address_family, None);
448-
assert_eq!(origin, None);
449-
}
450-
_ => panic!("expected Selected"),
451-
},
452-
_ => panic!("expected Status command"),
453-
}
454-
}
455-
456-
#[test]
457-
fn test_rpf_set_interval() {
458-
let cli =
459-
TestCli::try_parse_from(["test", "rpf", "set-interval", "500"])
460-
.unwrap();
461-
462-
match cli.command {
463-
Commands::Rpf(cmd) => match cmd.command {
464-
RpfCmd::SetInterval { interval_ms } => {
465-
assert_eq!(interval_ms, 500);
466-
}
467-
_ => panic!("expected SetInterval"),
468-
},
469-
_ => panic!("expected Rpf command"),
385+
let Commands::Status(cmd) = cli.command;
386+
match cmd.command {
387+
StatusCmd::Selected {
388+
group,
389+
address_family,
390+
origin,
391+
..
392+
} => {
393+
assert_eq!(group, None);
394+
assert_eq!(address_family, None);
395+
assert_eq!(origin, None);
396+
}
397+
_ => panic!("expected Selected"),
470398
}
471399
}
472400
}

0 commit comments

Comments
 (0)