Skip to content

Commit

Permalink
semantic parsing for show status command
Browse files Browse the repository at this point in the history
  • Loading branch information
amodm committed May 8, 2022
1 parent 721953f commit 7c65b03
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ edition = "2021"
resolver = "2"

[dependencies]
chrono = "0.4"
log = "0.4"
tokio = { version = "1.17", features = ["net"] }
tokio-stream = "0.1"
Expand Down
13 changes: 12 additions & 1 deletion src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use tokio::net::UnixStream;

use crate::{
Error, Interface, InterfaceAddress, InterfaceProperties, InterfaceSummary, Message, Protocol,
ProtocolDetail, Result, ShowInterfacesMessage, ShowProtocolDetailsMessage,
ProtocolDetail, Result, ShowInterfacesMessage, ShowProtocolDetailsMessage, ShowStatusMessage,
};

/// An active connection, on which requests can be executed, and responses
Expand Down Expand Up @@ -291,6 +291,17 @@ impl Connection {
}
}

/// Sends a `show status` request, and returns a semantically parsed response
/// in the form of [ShowStatusMessage]
pub async fn show_status(&mut self) -> Result<ShowStatusMessage> {
let messages = self.send_request("show status").await?;

match ShowStatusMessage::from_messages(&messages) {
Some(ssm) => Ok(ssm),
None => Err(Error::ParseError(messages)),
}
}

/// Reads a full [Message] from the server, and returns it
async fn next_message(&mut self) -> Result<Message> {
// if we have pending messages, return the first one
Expand Down
3 changes: 3 additions & 0 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ pub use interface::*;
mod protocol;
pub use protocol::*;

mod status;
pub use status::*;

/// A composite entry in the `show interfaces` command
#[derive(Debug)]
pub struct ShowInterfacesMessage {
Expand Down
116 changes: 116 additions & 0 deletions src/models/status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use chrono::NaiveDateTime;

use crate::Message;

pub struct ShowStatusMessage {
/// Server version
pub version_line: String,
/// Router ID configured on this BIRD instance
pub router_id: String,
/// Current server time
pub server_time: NaiveDateTime,
/// Last reboot time
pub last_reboot_on: NaiveDateTime,
/// Last reconfiguration time
pub last_reconfigured_on: NaiveDateTime,
/// Status message
pub status: String,
}

impl ShowStatusMessage {
/// Parses `messages` to create a [ShowStatusMessage] object. Returns `None` if
/// the parsing failed for any reason
pub(crate) fn from_messages(messages: &Vec<Message>) -> Option<ShowStatusMessage> {
let mut version_line: Option<String> = None;
let mut router_id: Option<String> = None;
let mut server_time: Option<NaiveDateTime> = None;
let mut last_reboot_on: Option<NaiveDateTime> = None;
let mut last_reconfigured_on: Option<NaiveDateTime> = None;
let mut status: Option<String> = None;
for msg in messages {
match msg {
Message::BirdVersion(v) => version_line = Some(v.clone()),
Message::StatusReport(s) => status = Some(s.clone()),
Message::Uptime(s) => {
let tfmt = "%Y-%m-%d %H:%M:%S%.3f";
for line in s.lines() {
if let Some(x) = line.strip_prefix("Router ID is ") {
router_id = Some(String::from(x));
} else if let Some(x) = line.strip_prefix("Current server time is ") {
if let Ok(dt) = NaiveDateTime::parse_from_str(x.trim(), tfmt) {
server_time = Some(dt);
} else {
log::error!("failed to parse timestamp {}", x);
return None;
}
} else if let Some(x) = line.strip_prefix("Last reboot on ") {
if let Ok(dt) = NaiveDateTime::parse_from_str(x.trim(), tfmt) {
last_reboot_on = Some(dt);
} else {
log::error!("failed to parse timestamp {}", x);
return None;
}
} else if let Some(x) = line.strip_prefix("Last reconfiguration on ") {
if let Ok(dt) = NaiveDateTime::parse_from_str(x.trim(), tfmt) {
last_reconfigured_on = Some(dt);
} else {
log::error!("failed to parse timestamp {}", x);
return None;
}
}
}
}
_ => continue,
}
}
if let Some(version_line) = version_line {
if let Some(router_id) = router_id {
if let Some(server_time) = server_time {
if let Some(last_reboot_on) = last_reboot_on {
if let Some(last_reconfigured_on) = last_reconfigured_on {
if let Some(status) = status {
return Some(ShowStatusMessage {
version_line,
router_id,
server_time,
last_reboot_on,
last_reconfigured_on,
status,
});
}
}
}
}
}
}
None
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse_status() {
let _ = env_logger::try_init();
let messages = vec![
Message::BirdVersion("BIRD 2.0.7".into()),
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()),
Message::StatusReport("Daemon is up and running".into()),
];
if let Some(status) = ShowStatusMessage::from_messages(&messages) {
assert_eq!(status.version_line, "BIRD 2.0.7");
assert_eq!(status.router_id, "172.29.0.12");
assert_eq!(status.server_time.to_string(), "2022-05-08 10:14:23.381");
assert_eq!(status.last_reboot_on.to_string(), "2022-04-14 22:23:28.096");
assert_eq!(
status.last_reconfigured_on.to_string(),
"2022-04-15 00:00:46.707",
);
assert_eq!(status.status, "Daemon is up and running");
} else {
panic!("failed to parse status");
}
}
}
35 changes: 35 additions & 0 deletions tests/test_birdc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,29 @@ async fn test_show_protocols_all() {
assert_eq!(route_stats.exported, 0);
}

#[tokio::test]
async fn test_show_status() {
let _ = env_logger::try_init();
let server = MockServer::start_server(&get_show_status(), 0)
.await
.expect("failed to start server");
let client = Client::for_unix_socket(&server.unix_socket);
let mut connection = client.connect().await.expect("failed to connect client");
let status = connection
.show_status()
.await
.expect("failed to parse ShowStatusMessage");
assert_eq!(status.version_line, "BIRD 2.0.7");
assert_eq!(status.router_id, "172.29.0.12");
assert_eq!(status.server_time.to_string(), "2022-05-08 10:14:23.381");
assert_eq!(status.last_reboot_on.to_string(), "2022-04-14 22:23:28.096");
assert_eq!(
status.last_reconfigured_on.to_string(),
"2022-04-15 00:00:46.707",
);
assert_eq!(status.status, "Daemon is up and running");
}

/// Validates response of `show interfaces` command
fn validate_show_interfaces_response(response: &[Message]) {
// for device lo
Expand Down Expand Up @@ -440,6 +463,18 @@ fn get_interfaces_summary() -> String {
)
}

fn get_show_status() -> String {
heredoc(
"1000-BIRD 2.0.7
1011-Router ID is 172.29.0.12
Current server time is 2022-05-08 10:14:23.381
Last reboot on 2022-04-14 22:23:28.096
Last reconfiguration on 2022-04-15 00:00:46.707
0013 Daemon is up and running
",
)
}

fn get_protocols() -> String {
heredoc(
"2002-Name Proto Table State Since Info
Expand Down

0 comments on commit 7c65b03

Please sign in to comment.