Skip to content

Commit ce952e6

Browse files
committed
The neighbor list API should return paginated results #17
1 parent 44e7725 commit ce952e6

File tree

11 files changed

+238
-59
lines changed

11 files changed

+238
-59
lines changed

Cargo.lock

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@ slog-term = "2.6"
4141
structopt = "0.3"
4242
thiserror = "1.0"
4343
tokio = { version = "1.38.1", features = ["full"]}
44+
uuid = { version = "1.10.0" }

adm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = "2021"
88
[dependencies]
99
anyhow.workspace = true
1010
chrono.workspace = true
11+
futures.workspace = true
1112
lldpd-client.workspace = true
1213
structopt.workspace = true
1314
slog.workspace = true

adm/src/main.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::net::IpAddr;
99
use anyhow::Context;
1010
use chrono::DateTime;
1111
use chrono::Utc;
12+
use futures::stream::TryStreamExt;
1213
use structopt::*;
1314

1415
use lldpd_client::default_port;
@@ -466,10 +467,20 @@ async fn main() -> anyhow::Result<()> {
466467
.context("failed to remove interface"),
467468
},
468469
Commands::Neighbors => {
469-
let neighbors = client.get_neighbors().await?;
470-
for n in neighbors.into_inner() {
471-
println!("On interface {}", n.port);
472-
display_neighbor(&n);
470+
for iface in client
471+
.interface_list()
472+
.await
473+
.context("failed to get interface list")?
474+
.iter()
475+
{
476+
let neighbors: Vec<types::Neighbor> = client
477+
.get_neighbors_stream(&iface.port, None)
478+
.try_collect()
479+
.await?;
480+
for n in neighbors {
481+
println!("On interface {}", n.port);
482+
display_neighbor(&n);
483+
}
473484
}
474485
Ok(())
475486
}

lldpd-client/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ description = "Client library for the Oxide LLDP daemon"
77
[dependencies]
88
chrono.workspace = true
99
common.workspace = true
10+
futures.workspace = true
1011
progenitor.workspace = true
1112
reqwest.workspace = true
1213
schemars.workspace = true
1314
serde.workspace = true
1415
serde_json.workspace = true
1516
slog.workspace = true
17+
uuid.workspace = true

lldpd/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ slog.workspace = true
2121
structopt.workspace = true
2222
thiserror.workspace = true
2323
tokio.workspace = true
24+
uuid.workspace = true
2425

2526
[target.'cfg(target_os = "illumos")'.dependencies]
2627
dlpi = { git = "https://github.com/oxidecomputer/dlpi-sys" }

lldpd/src/api_server.rs

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,19 @@ use std::sync::Arc;
1515
use chrono::DateTime;
1616
use chrono::Utc;
1717
use dropshot::endpoint;
18+
use dropshot::EmptyScanParams;
1819
use dropshot::HttpError;
1920
use dropshot::HttpResponseCreated;
2021
use dropshot::HttpResponseDeleted;
2122
use dropshot::HttpResponseOk;
2223
use dropshot::HttpResponseUpdatedNoContent;
24+
use dropshot::PaginationParams;
2325
use dropshot::Path;
26+
use dropshot::Query;
2427
use dropshot::RequestContext;
28+
use dropshot::ResultsPage;
2529
use dropshot::TypedBody;
30+
use dropshot::WhichPage;
2631
use schemars::JsonSchema;
2732
use serde::Deserialize;
2833
use serde::Serialize;
@@ -681,47 +686,81 @@ async fn interface_clear_management_addr(
681686
.map(|_| HttpResponseDeleted())
682687
}
683688

689+
/**
690+
* Represents a cursor into a paginated request for the contents of the neighbor
691+
* list.
692+
*/
693+
#[derive(Deserialize, Serialize, JsonSchema)]
694+
struct NeighborToken {
695+
id: types::NeighborId,
696+
}
697+
684698
/// A remote system that has been discovered on one of our configured interfaces
685699
#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)]
686700
pub struct Neighbor {
701+
/// The port on which the neighbor was seen
687702
pub port: String,
703+
/// An ID that uniquely identifies the neighbor. Note: this ID is assigned
704+
/// when we first see a neighbor we are currently tracking. If a neighbor
705+
/// goes offline long enough to be forgotten, it will be assigned a new ID
706+
/// if and when it comes back online.
707+
pub id: uuid::Uuid,
708+
/// When was the first beacon received from this neighbor.
688709
pub first_seen: DateTime<Utc>,
710+
/// When was the latest beacon received from this neighbor.
689711
pub last_seen: DateTime<Utc>,
712+
/// When was the last time this neighbor's beaconed LLDPDU contents changed.
690713
pub last_changed: DateTime<Utc>,
714+
/// Contents of the neighbor's LLDPDU beacon.
691715
pub system_info: types::SystemInfo,
692716
}
693717
/// Return a list of the active neighbors
694718
#[endpoint {
695719
method = GET,
696-
path = "/neighbors",
720+
path = "/interface/{iface}/neighbors",
697721
}]
698722
async fn get_neighbors(
699723
rqctx: RequestContext<Arc<Global>>,
700-
) -> Result<HttpResponseOk<Vec<Neighbor>>, HttpError> {
724+
path: Path<InterfacePathParams>,
725+
query: Query<PaginationParams<EmptyScanParams, NeighborToken>>,
726+
) -> Result<HttpResponseOk<ResultsPage<Neighbor>>, HttpError> {
701727
let global: &Global = rqctx.context();
702-
Ok(HttpResponseOk(
703-
global
704-
.interfaces
705-
.lock()
706-
.unwrap()
707-
.iter()
708-
.flat_map(|(name, iface)| {
709-
iface
710-
.lock()
711-
.unwrap()
712-
.neighbors
713-
.values()
728+
let pag_params = query.into_inner();
729+
let iface = path.into_inner().iface;
730+
let max = rqctx.page_limit(&pag_params)?.get();
731+
732+
let previous = match &pag_params.page {
733+
WhichPage::First(..) => None,
734+
WhichPage::Next(NeighborToken { id }) => Some(id.clone()),
735+
};
736+
737+
let neighbors: Vec<Neighbor> =
738+
interfaces::get_neighbors(global, &iface, previous, max)
739+
.await
740+
.map_err(HttpError::from)
741+
.map(|neighbors| {
742+
neighbors
743+
.iter()
714744
.map(|n| Neighbor {
715-
port: name.clone(),
745+
port: iface.clone(),
746+
id: n.id,
716747
first_seen: n.first_seen,
717748
last_seen: n.last_seen,
718749
last_changed: n.last_changed,
719750
system_info: (&n.lldpdu).into(),
720751
})
721-
.collect::<Vec<Neighbor>>()
722-
})
723-
.collect::<Vec<Neighbor>>(),
724-
))
752+
.collect()
753+
})?;
754+
755+
ResultsPage::new(neighbors, &EmptyScanParams {}, |n: &Neighbor, _| {
756+
NeighborToken {
757+
id: types::NeighborId {
758+
chassis_id: n.system_info.chassis_id.clone(),
759+
port_id: n.system_info.port_id.clone(),
760+
},
761+
}
762+
})
763+
.map(HttpResponseOk)
725764
}
726765

727766
/// Return detailed build information about the `dpd` server itself.

lldpd/src/interfaces.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::collections::btree_map::Entry;
88
use std::collections::BTreeMap;
99
use std::collections::BTreeSet;
1010
use std::net::IpAddr;
11+
use std::ops::Bound;
1112
use std::sync::Arc;
1213
use std::sync::Mutex;
1314
use std::time::Duration;
@@ -347,7 +348,6 @@ fn handle_packet(
347348
return error_accounting(iface, e);
348349
}
349350
};
350-
let id = types::NeighborId::new(&lldpdu);
351351

352352
// If one of our own LLDPDUs was reflected back to us, drop it
353353
if match &iface.chassis_id {
@@ -357,12 +357,13 @@ fn handle_packet(
357357
return;
358358
}
359359

360+
let id = types::NeighborId::new(&lldpdu);
360361
// Is this is new neighbor, an update from an old neighbor, or just a
361362
// periodic re-advertisement of the same old stuff?
362363
match iface.neighbors.entry(id.clone()) {
363364
Entry::Vacant(e) => {
364365
let sysinfo: types::SystemInfo = (&lldpdu).into();
365-
let neighbor = types::Neighbor::from_lldpdu(&lldpdu);
366+
let neighbor = types::Neighbor::from_lldpdu(&lldpdu, None);
366367
info!(iface.log, "new neighbor {:?}: {}", id, &sysinfo);
367368
e.insert(neighbor);
368369
}
@@ -385,7 +386,7 @@ fn handle_packet(
385386
trace!(iface.log, "refresh neighbor {:?}", id);
386387
}
387388
}
388-
};
389+
}
389390
}
390391

391392
// Construct and transmit an LLDPDU on this interface. Return the number of
@@ -943,3 +944,28 @@ pub async fn addr_delete_all(g: &Global, name: &String) -> LldpdResult<()> {
943944
})
944945
.await
945946
}
947+
948+
pub async fn get_neighbors(
949+
g: &Global,
950+
iface_name: &str,
951+
prev: Option<types::NeighborId>,
952+
max: u32,
953+
) -> LldpdResult<Vec<types::Neighbor>> {
954+
let iface_lock = get_interface(g, iface_name)?;
955+
let iface = iface_lock.lock().unwrap();
956+
957+
let max = usize::try_from(max)
958+
.map_err(|e| LldpdError::Invalid(format!("invalid max size: {e:?}")))?;
959+
960+
let lower_bound = match prev {
961+
Some(p) => Bound::Excluded(p),
962+
None => Bound::Unbounded,
963+
};
964+
965+
Ok(iface
966+
.neighbors
967+
.range((lower_bound, Bound::Unbounded))
968+
.take(max)
969+
.map(|(_key, value)| value.clone())
970+
.collect())
971+
}

lldpd/src/types.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ impl From<&protocol::Lldpdu> for SystemInfo {
8989
/// advertisements we have received.
9090
#[derive(Clone, Debug)]
9191
pub struct Neighbor {
92+
/// Uuid assigned when the neighbor was first seen
93+
pub id: uuid::Uuid,
9294
/// When the neighbor was first seen. Note: this is reset if the
9395
/// neighbor's TTL expires and we subsequently rediscover it.
9496
pub first_seen: DateTime<Utc>,
@@ -104,11 +106,16 @@ pub struct Neighbor {
104106
}
105107

106108
impl Neighbor {
107-
pub fn from_lldpdu(lldpdu: &protocol::Lldpdu) -> Self {
109+
pub fn from_lldpdu(
110+
lldpdu: &protocol::Lldpdu,
111+
uuid: Option<uuid::Uuid>,
112+
) -> Self {
108113
let now = Utc::now();
109114

115+
let id = uuid.unwrap_or(uuid::Uuid::new_v4());
110116
let ttl = std::time::Duration::from_secs(lldpdu.ttl as u64);
111117
Neighbor {
118+
id,
112119
first_seen: now,
113120
last_seen: now,
114121
last_changed: now,
@@ -118,7 +125,18 @@ impl Neighbor {
118125
}
119126
}
120127

121-
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
128+
#[derive(
129+
Debug,
130+
Clone,
131+
Deserialize,
132+
JsonSchema,
133+
Serialize,
134+
Hash,
135+
PartialEq,
136+
Eq,
137+
PartialOrd,
138+
Ord,
139+
)]
122140
pub struct NeighborId {
123141
pub chassis_id: protocol::ChassisId,
124142
pub port_id: protocol::PortId,

0 commit comments

Comments
 (0)