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.
This commit is contained in:
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"
dependencies = [
"openssl",
"serde",
"serde_json",
"tokio",
"tokio-native-tls",
"tracing",
@@ -101,6 +103,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -297,6 +305,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "ryu"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "schannel"
version = "0.1.19"
@@ -330,6 +344,37 @@ dependencies = [
"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]]
name = "sharded-slab"
version = "0.1.4"

View File

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

View File

@@ -5,6 +5,7 @@ use std::io::prelude::*;
use std::path::Path;
use std::str;
use serde_json;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio_native_tls::native_tls::TlsConnector as NativeTlsConnector;
@@ -14,6 +15,7 @@ use tokio_native_tls::TlsStream;
use tracing::{debug, info, trace};
use super::error::Error;
use super::model::{BackupInfo, BackupStats, ClientInfo, ClientList};
/// The BURP client version emulated by this implementation
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
use std::io;
use serde_json;
use tokio_native_tls::native_tls;
/// BURP client errors
@@ -32,3 +34,9 @@ impl From<native_tls::Error> for 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)]
pub mod client;
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>,
}