2019-04-15 13:36:14 -07:00
|
|
|
//! A command-line executable for monitoring a cluster's gossip plane.
|
2019-04-01 16:12:30 -07:00
|
|
|
|
2019-04-30 16:42:56 -07:00
|
|
|
#[macro_use]
|
2019-08-21 10:23:33 -07:00
|
|
|
extern crate solana_core;
|
2019-04-30 16:42:56 -07:00
|
|
|
|
2019-08-13 10:49:48 -07:00
|
|
|
use clap::{
|
|
|
|
crate_description, crate_name, crate_version, value_t_or_exit, App, AppSettings, Arg,
|
|
|
|
SubCommand,
|
|
|
|
};
|
2019-04-22 14:51:20 -07:00
|
|
|
use solana_client::rpc_client::RpcClient;
|
2019-08-21 10:23:33 -07:00
|
|
|
use solana_core::contact_info::ContactInfo;
|
|
|
|
use solana_core::gossip_service::discover;
|
2019-04-01 16:12:30 -07:00
|
|
|
use solana_sdk::pubkey::Pubkey;
|
|
|
|
use std::error;
|
|
|
|
use std::net::SocketAddr;
|
|
|
|
use std::process::exit;
|
|
|
|
|
2019-08-30 09:27:35 -07:00
|
|
|
fn is_pubkey(pubkey: String) -> Result<(), String> {
|
2019-04-01 16:12:30 -07:00
|
|
|
match pubkey.parse::<Pubkey>() {
|
|
|
|
Ok(_) => Ok(()),
|
|
|
|
Err(err) => Err(format!("{:?}", err)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() -> Result<(), Box<dyn error::Error>> {
|
2019-07-29 10:57:00 -07:00
|
|
|
solana_logger::setup_with_filter("solana=info");
|
2019-04-13 18:43:59 -07:00
|
|
|
|
2019-05-03 15:00:19 -07:00
|
|
|
let mut entrypoint_addr = SocketAddr::from(([127, 0, 0, 1], 8001));
|
|
|
|
let entrypoint_string = entrypoint_addr.to_string();
|
2019-04-01 16:12:30 -07:00
|
|
|
let matches = App::new(crate_name!())
|
|
|
|
.about(crate_description!())
|
|
|
|
.version(crate_version!())
|
2019-04-22 14:51:20 -07:00
|
|
|
.setting(AppSettings::SubcommandRequiredElseHelp)
|
2019-04-01 16:12:30 -07:00
|
|
|
.arg(
|
2019-05-03 15:00:19 -07:00
|
|
|
Arg::with_name("entrypoint")
|
2019-04-01 16:12:30 -07:00
|
|
|
.short("n")
|
2019-05-03 15:00:19 -07:00
|
|
|
.long("entrypoint")
|
2019-04-01 16:12:30 -07:00
|
|
|
.value_name("HOST:PORT")
|
|
|
|
.takes_value(true)
|
2019-05-03 15:00:19 -07:00
|
|
|
.default_value(&entrypoint_string)
|
2019-08-30 09:27:35 -07:00
|
|
|
.validator(solana_netutil::is_host_port)
|
2019-08-12 16:08:39 -07:00
|
|
|
.global(true)
|
2019-05-03 15:00:19 -07:00
|
|
|
.help("Rendezvous with the cluster at this entry point"),
|
2019-04-01 16:12:30 -07:00
|
|
|
)
|
2019-08-13 10:49:48 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("get-rpc-url")
|
|
|
|
.about("Get an RPC URL for the cluster")
|
2019-10-24 10:44:05 -07:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("all")
|
|
|
|
.long("all")
|
|
|
|
.takes_value(false)
|
|
|
|
.help("Return all RPC URLs"),
|
|
|
|
)
|
2019-08-13 10:49:48 -07:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("timeout")
|
|
|
|
.long("timeout")
|
|
|
|
.value_name("SECONDS")
|
|
|
|
.takes_value(true)
|
|
|
|
.default_value("5")
|
|
|
|
.help("Timeout in seconds"),
|
|
|
|
)
|
|
|
|
.setting(AppSettings::DisableVersion),
|
|
|
|
)
|
2019-04-22 14:51:20 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("spy")
|
2019-05-03 15:00:19 -07:00
|
|
|
.about("Monitor the gossip entrypoint")
|
2019-04-22 14:51:20 -07:00
|
|
|
.setting(AppSettings::DisableVersion)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("num_nodes")
|
|
|
|
.short("N")
|
|
|
|
.long("num-nodes")
|
|
|
|
.value_name("NUM")
|
|
|
|
.takes_value(true)
|
|
|
|
.conflicts_with("num_nodes_exactly")
|
2019-08-13 10:49:48 -07:00
|
|
|
.help("Wait for at least NUM nodes to be visible"),
|
2019-04-22 14:51:20 -07:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("num_nodes_exactly")
|
|
|
|
.short("E")
|
|
|
|
.long("num-nodes-exactly")
|
|
|
|
.value_name("NUM")
|
|
|
|
.takes_value(true)
|
2019-08-13 10:49:48 -07:00
|
|
|
.help("Wait for exactly NUM nodes to be visible"),
|
2019-04-22 14:51:20 -07:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("node_pubkey")
|
|
|
|
.short("p")
|
|
|
|
.long("pubkey")
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
2019-08-30 09:27:35 -07:00
|
|
|
.validator(is_pubkey)
|
2019-04-22 14:51:20 -07:00
|
|
|
.help("Public key of a specific node to wait for"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("timeout")
|
|
|
|
.long("timeout")
|
2019-08-13 10:49:48 -07:00
|
|
|
.value_name("SECONDS")
|
2019-04-22 14:51:20 -07:00
|
|
|
.takes_value(true)
|
2019-08-13 10:49:48 -07:00
|
|
|
.help("Maximum time to wait in seconds [default: wait forever]"),
|
2019-04-22 14:51:20 -07:00
|
|
|
),
|
2019-04-01 16:12:30 -07:00
|
|
|
)
|
2019-04-22 14:51:20 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("stop")
|
|
|
|
.about("Send stop request to a node")
|
|
|
|
.setting(AppSettings::DisableVersion)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("node_pubkey")
|
|
|
|
.index(1)
|
|
|
|
.required(true)
|
|
|
|
.value_name("PUBKEY")
|
2019-08-30 09:27:35 -07:00
|
|
|
.validator(is_pubkey)
|
2019-04-22 14:51:20 -07:00
|
|
|
.help("Public key of a specific node to stop"),
|
|
|
|
),
|
2019-04-01 16:12:30 -07:00
|
|
|
)
|
|
|
|
.get_matches();
|
|
|
|
|
2019-05-03 15:00:19 -07:00
|
|
|
if let Some(addr) = matches.value_of("entrypoint") {
|
|
|
|
entrypoint_addr = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| {
|
|
|
|
eprintln!("failed to parse entrypoint address: {}", e);
|
2019-10-01 12:30:11 -07:00
|
|
|
exit(1);
|
2019-04-01 16:12:30 -07:00
|
|
|
});
|
|
|
|
}
|
2019-08-13 10:49:48 -07:00
|
|
|
|
|
|
|
let gossip_addr = {
|
|
|
|
let mut addr = socketaddr_any!();
|
|
|
|
addr.set_ip(
|
|
|
|
solana_netutil::get_public_ip_addr(&entrypoint_addr).unwrap_or_else(|err| {
|
|
|
|
eprintln!("failed to contact {}: {}", entrypoint_addr, err);
|
2019-10-01 12:30:11 -07:00
|
|
|
exit(1);
|
2019-08-13 10:49:48 -07:00
|
|
|
}),
|
|
|
|
);
|
|
|
|
Some(addr)
|
|
|
|
};
|
|
|
|
|
2019-04-22 14:51:20 -07:00
|
|
|
match matches.subcommand() {
|
|
|
|
("spy", Some(matches)) => {
|
|
|
|
let num_nodes_exactly = matches
|
|
|
|
.value_of("num_nodes_exactly")
|
|
|
|
.map(|num| num.to_string().parse().unwrap());
|
|
|
|
let num_nodes = matches
|
|
|
|
.value_of("num_nodes")
|
|
|
|
.map(|num| num.to_string().parse().unwrap())
|
|
|
|
.or(num_nodes_exactly);
|
|
|
|
let timeout = matches
|
|
|
|
.value_of("timeout")
|
|
|
|
.map(|secs| secs.to_string().parse().unwrap());
|
|
|
|
let pubkey = matches
|
|
|
|
.value_of("node_pubkey")
|
|
|
|
.map(|pubkey_str| pubkey_str.parse::<Pubkey>().unwrap());
|
2019-04-01 16:12:30 -07:00
|
|
|
|
2019-10-21 10:29:37 -07:00
|
|
|
let (nodes, _archivers) = discover(
|
2019-05-03 15:00:19 -07:00
|
|
|
&entrypoint_addr,
|
2019-04-30 16:42:56 -07:00
|
|
|
num_nodes,
|
|
|
|
timeout,
|
|
|
|
pubkey,
|
2019-08-30 16:12:58 -07:00
|
|
|
None,
|
2019-04-30 16:42:56 -07:00
|
|
|
gossip_addr.as_ref(),
|
|
|
|
)?;
|
2019-04-01 16:12:30 -07:00
|
|
|
|
2019-04-22 14:51:20 -07:00
|
|
|
if timeout.is_some() {
|
|
|
|
if let Some(num) = num_nodes {
|
|
|
|
if nodes.len() < num {
|
|
|
|
let add = if num_nodes_exactly.is_some() {
|
|
|
|
""
|
|
|
|
} else {
|
|
|
|
" or more"
|
|
|
|
};
|
|
|
|
eprintln!(
|
|
|
|
"Error: Insufficient nodes discovered. Expecting {}{}",
|
|
|
|
num, add,
|
|
|
|
);
|
2019-10-01 12:30:11 -07:00
|
|
|
exit(1);
|
2019-04-22 14:51:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(node) = pubkey {
|
|
|
|
if nodes.iter().find(|x| x.id == node).is_none() {
|
|
|
|
eprintln!("Error: Could not find node {:?}", node);
|
2019-10-01 12:30:11 -07:00
|
|
|
exit(1);
|
2019-04-22 14:51:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-02 18:33:01 -07:00
|
|
|
if let Some(num_nodes_exactly) = num_nodes_exactly {
|
|
|
|
if nodes.len() > num_nodes_exactly {
|
|
|
|
eprintln!(
|
|
|
|
"Error: Extra nodes discovered. Expecting exactly {}",
|
|
|
|
num_nodes_exactly
|
|
|
|
);
|
|
|
|
exit(1);
|
|
|
|
}
|
2019-04-01 16:12:30 -07:00
|
|
|
}
|
|
|
|
}
|
2019-08-13 10:49:48 -07:00
|
|
|
("get-rpc-url", Some(matches)) => {
|
|
|
|
let timeout = value_t_or_exit!(matches, "timeout", u64);
|
2019-10-21 10:29:37 -07:00
|
|
|
let (nodes, _archivers) = discover(
|
2019-08-13 10:49:48 -07:00
|
|
|
&entrypoint_addr,
|
|
|
|
Some(1),
|
|
|
|
Some(timeout),
|
|
|
|
None,
|
2019-08-30 16:12:58 -07:00
|
|
|
Some(entrypoint_addr.ip()),
|
2019-08-13 10:49:48 -07:00
|
|
|
gossip_addr.as_ref(),
|
|
|
|
)?;
|
|
|
|
|
2019-10-24 10:44:05 -07:00
|
|
|
let rpc_addrs: Vec<_> = nodes
|
2019-08-13 10:49:48 -07:00
|
|
|
.iter()
|
|
|
|
.filter_map(ContactInfo::valid_client_facing_addr)
|
|
|
|
.map(|addrs| addrs.0)
|
2019-10-24 10:44:05 -07:00
|
|
|
.filter(|rpc_addr| {
|
|
|
|
matches.is_present("all") || rpc_addr.ip() == entrypoint_addr.ip()
|
|
|
|
})
|
|
|
|
.collect();
|
2019-08-13 10:49:48 -07:00
|
|
|
|
2019-10-24 10:44:05 -07:00
|
|
|
if rpc_addrs.is_empty() {
|
2019-08-13 10:49:48 -07:00
|
|
|
eprintln!("No RPC URL found");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2019-10-24 10:44:05 -07:00
|
|
|
for rpc_addr in rpc_addrs {
|
|
|
|
println!("http://{}", rpc_addr);
|
|
|
|
}
|
2019-08-13 10:49:48 -07:00
|
|
|
}
|
2019-04-22 14:51:20 -07:00
|
|
|
("stop", Some(matches)) => {
|
|
|
|
let pubkey = matches
|
|
|
|
.value_of("node_pubkey")
|
|
|
|
.unwrap()
|
|
|
|
.parse::<Pubkey>()
|
|
|
|
.unwrap();
|
2019-10-21 10:29:37 -07:00
|
|
|
let (nodes, _archivers) = discover(
|
2019-08-13 10:49:48 -07:00
|
|
|
&entrypoint_addr,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
Some(pubkey),
|
2019-08-30 16:12:58 -07:00
|
|
|
None,
|
2019-08-13 10:49:48 -07:00
|
|
|
gossip_addr.as_ref(),
|
|
|
|
)?;
|
2019-04-22 14:51:20 -07:00
|
|
|
let node = nodes.iter().find(|x| x.id == pubkey).unwrap();
|
|
|
|
|
|
|
|
if !ContactInfo::is_valid_address(&node.rpc) {
|
|
|
|
eprintln!("Error: RPC service is not enabled on node {:?}", pubkey);
|
2019-10-01 12:30:11 -07:00
|
|
|
exit(1);
|
2019-04-22 14:51:20 -07:00
|
|
|
}
|
|
|
|
println!("\nSending stop request to node {:?}", pubkey);
|
|
|
|
|
2019-10-11 12:30:52 -07:00
|
|
|
let result = RpcClient::new_socket(node.rpc).validator_exit()?;
|
2019-04-22 14:51:20 -07:00
|
|
|
if result {
|
|
|
|
println!("Stop signal accepted");
|
|
|
|
} else {
|
|
|
|
eprintln!("Error: Stop signal ignored");
|
2019-04-01 16:12:30 -07:00
|
|
|
}
|
|
|
|
}
|
2019-04-22 14:51:20 -07:00
|
|
|
_ => unreachable!(),
|
2019-04-01 16:12:30 -07:00
|
|
|
}
|
2019-04-22 14:51:20 -07:00
|
|
|
|
2019-04-01 16:12:30 -07:00
|
|
|
Ok(())
|
|
|
|
}
|