burp: Add client/backup info retrieval methods

The `get_clients`, `get_client`, `get_backup`, `get_backup_log`, and
`get_backup_stats` methods fetch information from the BURP server using
its JSON stats interface.  These will be used to populate the metrics we
want to export to Prometheus.
master
Dustin 2022-02-10 19:17:33 -06:00
parent cfdb33d4a3
commit 96849e6367
6 changed files with 256 additions and 0 deletions

45
Cargo.lock generated
View File

@ -28,6 +28,8 @@ name = "burp_exporter"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"openssl", "openssl",
"serde",
"serde_json",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tracing", "tracing",
@ -101,6 +103,12 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -297,6 +305,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "ryu"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]] [[package]]
name = "schannel" name = "schannel"
version = "0.1.19" version = "0.1.19"
@ -330,6 +344,37 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "serde"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.4" version = "0.1.4"

View File

@ -7,9 +7,14 @@ edition = "2021"
[dependencies] [dependencies]
openssl = "^0.10.38" openssl = "^0.10.38"
serde_json = "1.0.78"
tokio-native-tls = "^0.3.0" tokio-native-tls = "^0.3.0"
tracing = "^0.1.30" tracing = "^0.1.30"
[dependencies.serde]
version = "^1.0.136"
features = ["derive"]
[dependencies.tokio] [dependencies.tokio]
version = "^1.16.1" version = "^1.16.1"
features = ["io-util", "macros", "net", "rt", "signal"] features = ["io-util", "macros", "net", "rt", "signal"]

View File

@ -5,6 +5,7 @@ use std::io::prelude::*;
use std::path::Path; use std::path::Path;
use std::str; use std::str;
use serde_json;
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio_native_tls::native_tls::TlsConnector as NativeTlsConnector; use tokio_native_tls::native_tls::TlsConnector as NativeTlsConnector;
@ -14,6 +15,7 @@ use tokio_native_tls::TlsStream;
use tracing::{debug, info, trace}; use tracing::{debug, info, trace};
use super::error::Error; use super::error::Error;
use super::model::{BackupInfo, BackupStats, ClientInfo, ClientList};
/// The BURP client version emulated by this implementation /// The BURP client version emulated by this implementation
const CLIENT_VERSION: &str = "2.1.32"; const CLIENT_VERSION: &str = "2.1.32";
@ -298,4 +300,148 @@ impl Client {
} }
} }
} }
/// Retrieve information about all known clients
///
/// This method returns a list of clients known to the BURP server.
/// Usually, a client can only retrieve information about itself;
/// additional configuration is required in order for a client to
/// view other clients' information. Specifically, the client must
/// be named as a `restore_client` in the BURP server configuration
/// AND it must have *at least* `client_can_list` set to `1` for at
/// least one other client (or globally).
pub async fn get_clients(&mut self) -> Result<ClientList, Error> {
let res = self.command('c', "c:").await?;
Ok(serde_json::from_str(&res)?)
}
/// Retrieve information about a specific client
///
/// This method returns information about a specific client, if that
/// client is known to the BURP server. The same caveat applies
/// about client permissions as for [`get_clients`].
pub async fn get_client<S: AsRef<str>>(
&mut self,
name: S,
) -> Result<ClientInfo, Error> {
let res = self.command('c', &format!("c:{}", name.as_ref())).await?;
let mut clientlist: ClientList = serde_json::from_str(&res)?;
match clientlist.clients.pop() {
Some(c) => Ok(c),
None => Err(Error::Protocol(
"Invalid response from server: no client listed".into(),
)),
}
}
/// Retrieve information about a client backup
///
/// This method returns details about a specific backup for a
/// particular client.
///
/// * `name`: The client name
/// * `number`: The backup number
pub async fn get_backup<S: AsRef<str>>(
&mut self,
name: S,
number: u64,
) -> Result<BackupInfo, Error> {
let res = self
.command('c', &format!("c:{}:b:{}", name.as_ref(), number))
.await?;
let mut clientlist: ClientList = serde_json::from_str(&res)?;
let mut client = match clientlist.clients.pop() {
Some(c) => c,
None => {
return Err(Error::Protocol(
"Invalid response from server: no client listed".into(),
))
}
};
match client.backups.pop() {
Some(b) => Ok(b),
None => Err(Error::Protocol(
"Invalid response from server: no backup listed".into(),
)),
}
}
/// Retrieve the log for a client backup
///
/// This method returns the log for a specific backup for a
/// particular client, as a string.
///
/// * `name`: The client name
/// * `number`: The backup number
pub async fn get_backup_log<S: AsRef<str>>(
&mut self,
name: S,
number: u64,
) -> Result<Option<Vec<String>>, Error> {
let res = self
.command(
'c',
&format!("c:{}:b:{}:l:backup", name.as_ref(), number),
)
.await?;
let mut clientlist: ClientList = serde_json::from_str(&res)?;
let mut client = match clientlist.clients.pop() {
Some(c) => c,
None => {
return Err(Error::Protocol(
"Invalid response from server: no client listed".into(),
))
}
};
let backup = match client.backups.pop() {
Some(b) => b,
None => {
return Err(Error::Protocol(
"Invalid response from server: no backup listed".into(),
))
}
};
Ok(backup.logs.backup)
}
/// Retrieve the statistics for a client backup
///
/// This method returns the statistics of a specific backup for a
/// particular client.
///
/// * `name`: The client name
/// * `number`: The backup number
pub async fn get_backup_stats<S: AsRef<str>>(
&mut self,
name: S,
number: u64,
) -> Result<Option<BackupStats>, Error> {
let res = self
.command(
'c',
&format!("c:{}:b:{}:l:backup_stats", name.as_ref(), number),
)
.await?;
let mut clientlist: ClientList = serde_json::from_str(&res)?;
let mut client = match clientlist.clients.pop() {
Some(c) => c,
None => {
return Err(Error::Protocol(
"Invalid response from server: no client listed".into(),
))
}
};
let backup = match client.backups.pop() {
Some(b) => b,
None => {
return Err(Error::Protocol(
"Invalid response from server: no backup listed".into(),
))
}
};
match backup.logs.backup_stats {
Some(lines) => Ok(Some(serde_json::from_str(&lines.join("\n"))?)),
None => Ok(None),
}
}
} }

View File

@ -1,5 +1,7 @@
//! Client error handling //! Client error handling
use std::io; use std::io;
use serde_json;
use tokio_native_tls::native_tls; use tokio_native_tls::native_tls;
/// BURP client errors /// BURP client errors
@ -32,3 +34,9 @@ impl From<native_tls::Error> for Error {
Self::Tls(error) Self::Tls(error)
} }
} }
impl From<serde_json::Error> for Error {
fn from(error: serde_json::Error) -> Self {
Self::Protocol(error.to_string())
}
}

View File

@ -8,3 +8,4 @@
#![warn(missing_docs)] #![warn(missing_docs)]
pub mod client; pub mod client;
pub mod error; pub mod error;
pub mod model;

51
src/burp/model.rs Normal file
View File

@ -0,0 +1,51 @@
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Logs {
pub list: Vec<String>,
#[serde(default)]
pub backup: Option<Vec<String>>,
#[serde(default)]
pub backup_stats: Option<Vec<String>>,
}
#[derive(Debug, Deserialize)]
pub struct BackupInfo {
pub number: u64,
pub timestamp: u64,
pub flags: Vec<String>,
pub logs: Logs,
}
#[derive(Debug, Deserialize)]
pub struct ClientInfo {
pub name: String,
pub run_status: String,
pub protocol: u64,
pub backups: Vec<BackupInfo>,
}
#[derive(Debug, Deserialize)]
pub struct ClientList {
pub clients: Vec<ClientInfo>,
}
#[derive(Debug, Deserialize)]
pub struct Counter {
pub name: String,
pub r#type: String,
pub count: u64,
#[serde(default)]
pub changed: u64,
#[serde(default)]
pub same: u64,
#[serde(default)]
pub deleted: u64,
#[serde(default)]
pub scanned: u64,
}
#[derive(Debug, Deserialize)]
pub struct BackupStats {
pub counters: Vec<Counter>,
}