use std::cmp::Ord;
use std::cmp::Ordering;
use std::fmt;
use std::net::IpAddr;
use super::{process_output, resolve_to_ip, run_command, GlusterError};
use regex::Regex;
use uuid::Uuid;
#[test]
fn test_parse_peer_status() {
let test_result = vec![
Peer {
uuid: Uuid::parse_str("afbd338e-881b-4557-8764-52e259885ca3").unwrap(),
hostname: "10.0.3.207".to_string(),
status: State::PeerInCluster,
},
Peer {
uuid: Uuid::parse_str("fa3b031a-c4ef-43c5-892d-4b909bc5cd5d").unwrap(),
hostname: "10.0.3.208".to_string(),
status: State::PeerInCluster,
},
Peer {
uuid: Uuid::parse_str("5f45e89a-23c1-41dd-b0cd-fd9cf37f1520").unwrap(),
hostname: "10.0.3.209".to_string(),
status: State::PeerInCluster,
},
];
let test_line = r#"Number of Peers: 3 Hostname: 10.0.3.207
Uuid: afbd338e-881b-4557-8764-52e259885ca3 State: Peer in Cluster (Connected)
Hostname: 10.0.3.208 Uuid: fa3b031a-c4ef-43c5-892d-4b909bc5cd5d
State: Peer in Cluster (Connected) Hostname: 10.0.3.209
Uuid: 5f45e89a-23c1-41dd-b0cd-fd9cf37f1520 State: Peer in Cluster (Connected)"#
.to_string();
let result = parse_peer_status(&test_line);
println!("Result: {:?}", result);
assert!(result.is_ok());
let result_unwrapped = result.unwrap();
assert_eq!(test_result, result_unwrapped);
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum State {
Connected,
Disconnected,
Unknown,
EstablishingConnection,
ProbeSentToPeer,
ProbeReceivedFromPeer,
PeerInCluster,
AcceptedPeerRequest,
SentAndReceivedPeerRequest,
PeerRejected,
PeerDetachInProgress,
ConnectedToPeer,
PeerIsConnectedAndAccepted,
InvalidState,
}
impl State {
pub fn new(name: &str) -> State {
match name.trim().to_ascii_lowercase().as_ref() {
"connected" => State::Connected,
"disconnected" => State::Disconnected,
"establishing connection" => State::EstablishingConnection,
"probe sent to peer" => State::ProbeSentToPeer,
"probe received from peer" => State::ProbeReceivedFromPeer,
"peer in cluster" => State::PeerInCluster,
"accepted peer request" => State::AcceptedPeerRequest,
"sent and received peer request" => State::SentAndReceivedPeerRequest,
"peer rejected" => State::PeerRejected,
"peer detach in progress" => State::PeerDetachInProgress,
"connected to peer" => State::ConnectedToPeer,
"peer is connected and accepted" => State::PeerIsConnectedAndAccepted,
"invalid state" => State::InvalidState,
_ => State::Unknown,
}
}
pub fn to_string(self) -> String {
match self {
State::Connected => "Connected".to_string(),
State::Disconnected => "Disconnected".to_string(),
State::Unknown => "Unknown".to_string(),
State::EstablishingConnection => "establishing connection".to_string(),
State::ProbeSentToPeer => "probe sent to peer".to_string(),
State::ProbeReceivedFromPeer => "probe received from peer".to_string(),
State::PeerInCluster => "peer in cluster".to_string(),
State::AcceptedPeerRequest => "accepted peer request".to_string(),
State::SentAndReceivedPeerRequest => "sent and received peer request".to_string(),
State::PeerRejected => "peer rejected".to_string(),
State::PeerDetachInProgress => "peer detach in progress".to_string(),
State::ConnectedToPeer => "connected to peer".to_string(),
State::PeerIsConnectedAndAccepted => "peer is connected and accepted".to_string(),
State::InvalidState => "invalid state".to_string(),
}
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct Peer {
pub uuid: Uuid,
pub hostname: String,
pub status: State,
}
impl Ord for Peer {
fn cmp(&self, other: &Peer) -> Ordering {
self.uuid.cmp(&other.uuid)
}
}
impl PartialOrd for Peer {
fn partial_cmp(&self, other: &Peer) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Debug for Peer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"UUID: {} Hostname: {} Status: {}",
self.uuid.to_hyphenated().to_string(),
self.hostname,
self.status.to_string()
)
}
}
pub fn get_peer(hostname: &str) -> Result<Peer, GlusterError> {
if cfg!(test) {
return Ok(Peer {
uuid: Uuid::parse_str("78f68270-201a-4d8a-bad3-7cded6e6b7d8").unwrap(),
hostname: "test_ip".to_string(),
status: State::Connected,
});
}
let peer_list = peer_list()?;
for peer in peer_list {
if peer.hostname == *hostname {
debug!("Found peer: {:?}", peer);
return Ok(peer.clone());
}
}
Err(GlusterError::new(format!(
"Unable to find peer by hostname: {}",
hostname
)))
}
fn parse_peer_status(line: &str) -> Result<Vec<Peer>, GlusterError> {
let mut peers: Vec<Peer> = Vec::new();
let regex_str = r#"Hostname:\s+(?P<hostname>[a-zA-Z0-9.]+)\s+
Uuid:\s+(?P<uuid>\w+-\w+-\w+-\w+-\w+)\s+
State:\s+(?P<state_detail>[a-zA-z ]+)\s+\((?P<state>\w+)\)"#;
let peer_regex = Regex::new(®ex_str.replace("\n", ""))?;
for cap in peer_regex.captures_iter(line) {
let hostname = cap
.name("hostname")
.ok_or_else(|| GlusterError::new(format!("Invalid hostname for peer: {}", line)))?;
let uuid = cap
.name("uuid")
.ok_or_else(|| GlusterError::new(format!("Invalid uuid for peer: {}", line)))?;
let uuid_parsed = Uuid::parse_str(uuid.as_str())?;
let state_details = cap
.name("state_detail")
.ok_or_else(|| GlusterError::new(format!("Invalid state for peer: {}", line)))?;
let check_for_ip = hostname.as_str().parse::<IpAddr>();
if check_for_ip.is_err() {
match resolve_to_ip(hostname.as_str()) {
Ok(ip_addr) => {
peers.push(Peer {
uuid: uuid_parsed,
hostname: ip_addr,
status: State::new(state_details.as_str()),
});
continue;
}
Err(e) => {
return Err(GlusterError::new(e.to_string()));
}
};
} else {
peers.push(Peer {
uuid: uuid_parsed,
hostname: hostname.as_str().to_string(),
status: State::new(state_details.as_str()),
});
}
}
Ok(peers)
}
pub fn peer_status() -> Result<Vec<Peer>, GlusterError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push("peer".to_string());
arg_list.push("status".to_string());
let output = run_command("gluster", &arg_list, true, false);
let output_str = String::from_utf8(output.stdout)?;
parse_peer_status(&output_str)
}
pub fn peer_list() -> Result<Vec<Peer>, GlusterError> {
let mut peers: Vec<Peer> = Vec::new();
let mut arg_list: Vec<String> = Vec::new();
arg_list.push("pool".to_string());
arg_list.push("list".to_string());
let output = run_command("gluster", &arg_list, true, false);
let output_str = String::from_utf8(output.stdout)?;
for line in output_str.lines() {
if line.contains("State") {
continue;
} else {
let v: Vec<&str> = line.split('\t').collect();
let uuid = Uuid::parse_str(v[0])?;
let mut hostname = v[1].trim().to_string();
let check_for_ip = hostname.parse::<IpAddr>();
if check_for_ip.is_err() {
hostname = match resolve_to_ip(&hostname) {
Ok(ip_addr) => ip_addr,
Err(e) => {
return Err(GlusterError::new(e.to_string()));
}
};
}
debug!("hostname from peer list command is {:?}", &hostname);
peers.push(Peer {
uuid,
hostname,
status: State::new(v[2]),
});
}
}
Ok(peers)
}
pub fn peer_probe(hostname: &str) -> Result<i32, GlusterError> {
let current_peers = peer_list()?;
for peer in current_peers {
if peer.hostname == *hostname {
return Ok(0); }
}
let mut arg_list: Vec<String> = Vec::new();
arg_list.push("peer".to_string());
arg_list.push("probe".to_string());
arg_list.push(hostname.to_string());
process_output(run_command("gluster", &arg_list, true, false))
}
pub fn peer_remove(hostname: &str, force: bool) -> Result<i32, GlusterError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push("peer".to_string());
arg_list.push("detach".to_string());
arg_list.push(hostname.to_string());
if force {
arg_list.push("force".to_string());
}
process_output(run_command("gluster", &arg_list, true, false))
}