Skip to content

Commit 7c65b03

Browse files
committed
semantic parsing for show status command
1 parent 721953f commit 7c65b03

File tree

5 files changed

+167
-1
lines changed

5 files changed

+167
-1
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ edition = "2021"
1313
resolver = "2"
1414

1515
[dependencies]
16+
chrono = "0.4"
1617
log = "0.4"
1718
tokio = { version = "1.17", features = ["net"] }
1819
tokio-stream = "0.1"

src/connection.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use tokio::net::UnixStream;
77

88
use crate::{
99
Error, Interface, InterfaceAddress, InterfaceProperties, InterfaceSummary, Message, Protocol,
10-
ProtocolDetail, Result, ShowInterfacesMessage, ShowProtocolDetailsMessage,
10+
ProtocolDetail, Result, ShowInterfacesMessage, ShowProtocolDetailsMessage, ShowStatusMessage,
1111
};
1212

1313
/// An active connection, on which requests can be executed, and responses
@@ -291,6 +291,17 @@ impl Connection {
291291
}
292292
}
293293

294+
/// Sends a `show status` request, and returns a semantically parsed response
295+
/// in the form of [ShowStatusMessage]
296+
pub async fn show_status(&mut self) -> Result<ShowStatusMessage> {
297+
let messages = self.send_request("show status").await?;
298+
299+
match ShowStatusMessage::from_messages(&messages) {
300+
Some(ssm) => Ok(ssm),
301+
None => Err(Error::ParseError(messages)),
302+
}
303+
}
304+
294305
/// Reads a full [Message] from the server, and returns it
295306
async fn next_message(&mut self) -> Result<Message> {
296307
// if we have pending messages, return the first one

src/models/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ pub use interface::*;
66
mod protocol;
77
pub use protocol::*;
88

9+
mod status;
10+
pub use status::*;
11+
912
/// A composite entry in the `show interfaces` command
1013
#[derive(Debug)]
1114
pub struct ShowInterfacesMessage {

src/models/status.rs

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
use chrono::NaiveDateTime;
2+
3+
use crate::Message;
4+
5+
pub struct ShowStatusMessage {
6+
/// Server version
7+
pub version_line: String,
8+
/// Router ID configured on this BIRD instance
9+
pub router_id: String,
10+
/// Current server time
11+
pub server_time: NaiveDateTime,
12+
/// Last reboot time
13+
pub last_reboot_on: NaiveDateTime,
14+
/// Last reconfiguration time
15+
pub last_reconfigured_on: NaiveDateTime,
16+
/// Status message
17+
pub status: String,
18+
}
19+
20+
impl ShowStatusMessage {
21+
/// Parses `messages` to create a [ShowStatusMessage] object. Returns `None` if
22+
/// the parsing failed for any reason
23+
pub(crate) fn from_messages(messages: &Vec<Message>) -> Option<ShowStatusMessage> {
24+
let mut version_line: Option<String> = None;
25+
let mut router_id: Option<String> = None;
26+
let mut server_time: Option<NaiveDateTime> = None;
27+
let mut last_reboot_on: Option<NaiveDateTime> = None;
28+
let mut last_reconfigured_on: Option<NaiveDateTime> = None;
29+
let mut status: Option<String> = None;
30+
for msg in messages {
31+
match msg {
32+
Message::BirdVersion(v) => version_line = Some(v.clone()),
33+
Message::StatusReport(s) => status = Some(s.clone()),
34+
Message::Uptime(s) => {
35+
let tfmt = "%Y-%m-%d %H:%M:%S%.3f";
36+
for line in s.lines() {
37+
if let Some(x) = line.strip_prefix("Router ID is ") {
38+
router_id = Some(String::from(x));
39+
} else if let Some(x) = line.strip_prefix("Current server time is ") {
40+
if let Ok(dt) = NaiveDateTime::parse_from_str(x.trim(), tfmt) {
41+
server_time = Some(dt);
42+
} else {
43+
log::error!("failed to parse timestamp {}", x);
44+
return None;
45+
}
46+
} else if let Some(x) = line.strip_prefix("Last reboot on ") {
47+
if let Ok(dt) = NaiveDateTime::parse_from_str(x.trim(), tfmt) {
48+
last_reboot_on = Some(dt);
49+
} else {
50+
log::error!("failed to parse timestamp {}", x);
51+
return None;
52+
}
53+
} else if let Some(x) = line.strip_prefix("Last reconfiguration on ") {
54+
if let Ok(dt) = NaiveDateTime::parse_from_str(x.trim(), tfmt) {
55+
last_reconfigured_on = Some(dt);
56+
} else {
57+
log::error!("failed to parse timestamp {}", x);
58+
return None;
59+
}
60+
}
61+
}
62+
}
63+
_ => continue,
64+
}
65+
}
66+
if let Some(version_line) = version_line {
67+
if let Some(router_id) = router_id {
68+
if let Some(server_time) = server_time {
69+
if let Some(last_reboot_on) = last_reboot_on {
70+
if let Some(last_reconfigured_on) = last_reconfigured_on {
71+
if let Some(status) = status {
72+
return Some(ShowStatusMessage {
73+
version_line,
74+
router_id,
75+
server_time,
76+
last_reboot_on,
77+
last_reconfigured_on,
78+
status,
79+
});
80+
}
81+
}
82+
}
83+
}
84+
}
85+
}
86+
None
87+
}
88+
}
89+
90+
#[cfg(test)]
91+
mod tests {
92+
use super::*;
93+
94+
#[test]
95+
fn test_parse_status() {
96+
let _ = env_logger::try_init();
97+
let messages = vec![
98+
Message::BirdVersion("BIRD 2.0.7".into()),
99+
Message::Uptime("Router ID is 172.29.0.12\nCurrent server time is 2022-05-08 10:14:23.381\nLast reboot on 2022-04-14 22:23:28.096\nLast reconfiguration on 2022-04-15 00:00:46.707".into()),
100+
Message::StatusReport("Daemon is up and running".into()),
101+
];
102+
if let Some(status) = ShowStatusMessage::from_messages(&messages) {
103+
assert_eq!(status.version_line, "BIRD 2.0.7");
104+
assert_eq!(status.router_id, "172.29.0.12");
105+
assert_eq!(status.server_time.to_string(), "2022-05-08 10:14:23.381");
106+
assert_eq!(status.last_reboot_on.to_string(), "2022-04-14 22:23:28.096");
107+
assert_eq!(
108+
status.last_reconfigured_on.to_string(),
109+
"2022-04-15 00:00:46.707",
110+
);
111+
assert_eq!(status.status, "Daemon is up and running");
112+
} else {
113+
panic!("failed to parse status");
114+
}
115+
}
116+
}

tests/test_birdc.rs

+35
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,29 @@ async fn test_show_protocols_all() {
351351
assert_eq!(route_stats.exported, 0);
352352
}
353353

354+
#[tokio::test]
355+
async fn test_show_status() {
356+
let _ = env_logger::try_init();
357+
let server = MockServer::start_server(&get_show_status(), 0)
358+
.await
359+
.expect("failed to start server");
360+
let client = Client::for_unix_socket(&server.unix_socket);
361+
let mut connection = client.connect().await.expect("failed to connect client");
362+
let status = connection
363+
.show_status()
364+
.await
365+
.expect("failed to parse ShowStatusMessage");
366+
assert_eq!(status.version_line, "BIRD 2.0.7");
367+
assert_eq!(status.router_id, "172.29.0.12");
368+
assert_eq!(status.server_time.to_string(), "2022-05-08 10:14:23.381");
369+
assert_eq!(status.last_reboot_on.to_string(), "2022-04-14 22:23:28.096");
370+
assert_eq!(
371+
status.last_reconfigured_on.to_string(),
372+
"2022-04-15 00:00:46.707",
373+
);
374+
assert_eq!(status.status, "Daemon is up and running");
375+
}
376+
354377
/// Validates response of `show interfaces` command
355378
fn validate_show_interfaces_response(response: &[Message]) {
356379
// for device lo
@@ -440,6 +463,18 @@ fn get_interfaces_summary() -> String {
440463
)
441464
}
442465

466+
fn get_show_status() -> String {
467+
heredoc(
468+
"1000-BIRD 2.0.7
469+
1011-Router ID is 172.29.0.12
470+
Current server time is 2022-05-08 10:14:23.381
471+
Last reboot on 2022-04-14 22:23:28.096
472+
Last reconfiguration on 2022-04-15 00:00:46.707
473+
0013 Daemon is up and running
474+
",
475+
)
476+
}
477+
443478
fn get_protocols() -> String {
444479
heredoc(
445480
"2002-Name Proto Table State Since Info

0 commit comments

Comments
 (0)