use crate::cli::EtcdConfig;
use crate::etcdpb::etcdserverpb::kv_server::{Kv, KvServer};
use crate::etcdpb::etcdserverpb::ResponseHeader;
use crate::queue::{Queue};
use crate::{EtcdEvents, EtcdMgmtEvent, KvEvent};
use rust_i18n::t;
use slog::{warn, error, info, Logger};
use std::collections::{HashMap};
use std::net::SocketAddr;
use std::str::FromStr;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::sync::mpsc::Sender;
use tokio::sync::{mpsc, Mutex, RwLock};
use tonic::Status;
use tonic::transport::Server;
use tonic::transport::server::Router;
use uuid::Uuid;
use crate::etcdpb::etcdserverpb::auth_server::AuthServer;
use crate::etcdpb::etcdserverpb::cluster_server::ClusterServer;
use crate::etcdpb::etcdserverpb::lease_server::LeaseServer;
use crate::etcdpb::etcdserverpb::maintenance_server::MaintenanceServer;
use crate::etcdpb::etcdserverpb::watch_server::{ WatchServer};
use crate::etcdpb::etcdserverpb::WatchResponse;
use crate::etcdpb::v3electionpb::election_server::ElectionServer;
use crate::etcdpb::v3lockpb::lock_server::LockServer;
use crate::peer::EtcdCluster;
use crate::LP;
use std::collections::HashSet;
use std::time::Duration;
const PEER_RETRY_SECS: u64 = 20;
const EMPTY_PEERS_EVERY: u32 = 30;
pub(crate) use crate::peer::EtcdPeerNode;
impl EtcdNode {
pub async fn init(cfg: EtcdConfig, log: Logger,
#[cfg(feature = "tracer")] tracer: Option<opentelemetry_sdk::trace::SdkTracer>,
) -> Result<Self, String> {
let node_id = parse_uuid(&cfg.node)?;
let cluster_id = if cfg.cluster.trim().is_empty() {
let id = parse_uuid(&cfg.cluster)?;
error!(log, "{}no cluster uuid configured: this node invented {} for itself, \
and every other node invents its own - so NO peer will ever be accepted \
(peering compares cluster ids) and this kv is local to this node. Set the \
cluster uuid in the configuration to make the etcd surface cluster-wide.",
LP, id);
id
} else {
parse_uuid(&cfg.cluster)?
};
let cluster = EtcdCluster::connect(&cfg, node_id, cluster_id, &log).await?;
let (event, mut rsvr) = mpsc::channel(10);
let (watcher, watcher_rv) = mpsc::channel(10);
let term = Arc::new(AtomicU64::new(cfg.term));
let policy = crate::prefix::PrefixPolicy::new(
&cfg.local_prefix, &cfg.local_prefix_except, &cfg.local_only_prefix,
&cfg.zone_prefix)
.with_local_only_except(&cfg.local_only_prefix_except)
.with_readonly(&cfg.readonly_prefix);
let c = EtcdNode {
cfg: Arc::new(RwLock::new(cfg)),
vault: Arc::new(Default::default()),
queues: Arc::new(Default::default()),
observers: Arc::new(Default::default()),
observer_ranges: Arc::new(Default::default()),
watchers: Arc::new(Default::default()),
watch_notify: watcher,
peers: Arc::new(RwLock::new(cluster)),
policy: Arc::new(RwLock::new(policy)),
node_id,
cluster_id,
term,
event,
log: log.clone(),
#[cfg(feature = "tracer")] tracer: Arc::new(RwLock::new(tracer)),
#[cfg(feature = "tracer")] meter: Arc::new(RwLock::new(None)),
};
{
let cfg = c.cfg.read().await;
let my_urls: Vec<String> = cfg.listen_client_urls
.split(',')
.filter(|u| !u.is_empty())
.map(|u| {
if u.starts_with("http") { u.to_string() }
else { format!("http://{}", u) }
})
.collect();
drop(cfg);
let pol = c.policy.read().await.clone();
match c.peers.read().await.announce_and_sync(my_urls, &c.vault, &pol, &log).await {
Ok(n) => if n > 0 {
info!(log, "{}startup sync: loaded {} keys", LP, n);
},
Err(e) => info!(log, "{}startup sync skipped: {}", LP, e),
}
}
c.watch_notify(watcher_rv).await;
c.spawn_peer_retry();
let grpc_client = c.clone();
tokio::spawn(async move {
while let Some(r) = rsvr.recv().await {
match r {
EtcdEvents::Data(kv) => match kv {
KvEvent::Put(kv) => {
let _ = grpc_client.put(tonic::Request::new(kv)).await;
},
KvEvent::Delete(kv) => {
let _ = grpc_client.delete_range(tonic::Request::new(kv)).await;
}
KvEvent::Txn(kv) => {
let _ = grpc_client.txn(tonic::Request::new(kv)).await;
}
}
EtcdEvents::Mgmt(e) => match e {
EtcdMgmtEvent::Config(c) => {
let _ = grpc_client.reconfigure(c).await;
}
#[cfg(feature = "tracer")]
EtcdMgmtEvent::Tracer(c) => {
*grpc_client.tracer.write().await = c;
}
#[cfg(feature = "tracer")]
EtcdMgmtEvent::Meter(m) => {
*grpc_client.meter.write().await = m.map(crate::metrics::Instruments::new);
}
EtcdMgmtEvent::Stop => {
info!(grpc_client.log, "{}received Stop event, shutting down event loop", LP);
break;
}
EtcdMgmtEvent::Restart => {
let new_term = grpc_client.term.fetch_add(1, Ordering::Relaxed) + 1;
info!(grpc_client.log, "{}restart: term incremented to {}", LP, new_term);
let cfg = grpc_client.cfg.read().await;
let my_urls: Vec<String> = cfg.listen_client_urls
.split(',')
.filter(|u| !u.is_empty())
.map(|u| {
if u.starts_with("http") { u.to_string() }
else { format!("http://{}", u) }
})
.collect();
drop(cfg);
let pol = grpc_client.policy.read().await.clone();
match grpc_client.peers.read().await
.announce_and_sync(my_urls, &grpc_client.vault, &pol, &grpc_client.log).await {
Ok(n) => if n > 0 {
info!(grpc_client.log, "{}restart sync: loaded {} keys", LP, n);
},
Err(e) => info!(grpc_client.log, "{}restart sync skipped: {}", LP, e),
}
}
EtcdMgmtEvent::Pause(secs) => {
info!(grpc_client.log, "{}pausing for {} seconds", LP, secs);
tokio::time::sleep(tokio::time::Duration::from_secs(secs as u64)).await;
info!(grpc_client.log, "{}resumed after pause", LP);
}
}
}
}
});
Ok(c)
}
fn spawn_peer_retry(&self) {
let node = self.clone();
tokio::spawn(async move {
let mut quiet: u32 = 0;
loop {
tokio::time::sleep(Duration::from_secs(PEER_RETRY_SECS)).await;
let urls: Vec<String> = {
let cfg = node.cfg.read().await;
cfg.peers().iter().map(|s| s.to_string()).collect()
};
let have = node.peers.read().await.connected();
if urls.is_empty() {
quiet = quiet.saturating_add(1);
if quiet == 1 || quiet % EMPTY_PEERS_EVERY == 0 {
let cfg = node.cfg.read().await;
warn!(node.log, "{}no etcd peers to connect to, so every key written \
here stays here. peers() takes initial_advertise_peer_urls \
[{}] minus listen_client_urls [{}] and keeps only http:// \
entries.", LP, cfg.initial_advertise_peer_urls, cfg.listen_client_urls);
}
continue;
}
quiet = 0;
if have >= urls.len() {
continue; }
let set: HashSet<&str> = urls.iter().map(|s| s.as_str()).collect();
match node.add_peers(set).await {
n if n > 0 => info!(node.log, "{}peer retry: connected {} more peer(s), \
{} of {} total", LP, n, node.peers.read().await.connected(), urls.len()),
_ => warn!(node.log, "{}peer retry: still {} of {} peer(s) connected, \
none of {:?} answered", LP, have, urls.len(), urls),
}
}
});
}
pub async fn serve(&self) -> Result<(), String> {
let mut srv = Server::builder();
let srv = self.add_services(srv.add_service(AuthServer::new(self.clone())), false);
let addrs = self.cfg.read().await.listen_client_urls.clone();
let addrsv: Vec<&str> = addrs.split(",").collect();
let adr: Vec<&str> = if addrsv[0].starts_with("http") {
let url: Vec<&str> = addrsv[0].split("//").collect();
url[1].split(":").collect()
} else {
addrsv[0].split(":").collect()
};
let bind = adr[0];
let adr = if bind.chars().next().unwrap_or(' ').is_numeric() {
addrsv[0].parse::<SocketAddr>().map_err(|e| format!("parsing {}: {}", addrs, e))?
} else {
let port = adr[1].parse::<u16>().map_err(|e| format!("expected port in {}: {}", addrs, e))?;
let ips: Vec<std::net::IpAddr> = dns_lookup::lookup_host(bind).expect(format!("Binding to {}", bind).as_str()).collect();
if ips.len() == 0 {
return Err(format!("No IpAddr found {}", bind));
}
SocketAddr::new(ips[0], port)
};
let name = self.cfg.read().await.name.clone();
info!(self.log, "{}Starting server [{}] at: {}", LP, name, addrs);
tokio::spawn(async move {
match srv.serve(adr) .await {
Ok(()) => {
println!("bye");
}
Err(e) => {
eprintln!("{} {}", t!("error"), e);
println!();
std::process::exit(10);
}
}
});
Ok(())
}
pub fn add_all_services(&self, srv: Router) -> Router {
self.add_services(srv, true)
}
fn add_services(&self, srv: Router, all: bool) -> Router {
let srv = if all {
srv.add_service(AuthServer::new(self.clone()))
} else {
srv
};
srv.add_service(KvServer::new(self.clone()))
.add_service(WatchServer::new(self.clone()))
.add_service(LockServer::new(self.clone()))
.add_service(ElectionServer::new(self.clone()))
.add_service(MaintenanceServer::new(self.clone()))
.add_service(ClusterServer::new(self.clone()))
.add_service(LeaseServer::new(self.clone()))
}
async fn reconfigure(&self, cfg: EtcdConfig) {
let peers = cfg.peers();
let cnt = self.add_peers(peers).await;
if cnt > 0 {
info!(self.log, "{}reconfigure: added {} peer(s)", LP, cnt);
}
if cfg.term > 0 {
self.term.store(cfg.term, Ordering::Relaxed);
}
let mut current = self.cfg.write().await;
current.initial_advertise_peer_urls = cfg.initial_advertise_peer_urls;
current.listen_client_urls = cfg.listen_client_urls;
current.listen_peer_urls = cfg.listen_peer_urls;
if !cfg.clickhouse_url.is_empty() {
current.clickhouse_url = cfg.clickhouse_url;
current.clickhouse_db = cfg.clickhouse_db;
}
current.initial_cluster = cfg.initial_cluster;
current.initial_cluster_token = cfg.initial_cluster_token;
current.name = cfg.name;
current.max_txn_ops = cfg.max_txn_ops;
current.max_request_bytes = cfg.max_request_bytes;
current.log_level = cfg.log_level;
current.term = cfg.term;
current.local_prefix = cfg.local_prefix;
current.local_prefix_except = cfg.local_prefix_except;
current.local_only_prefix = cfg.local_only_prefix;
current.local_only_prefix_except = cfg.local_only_prefix_except;
current.readonly_prefix = cfg.readonly_prefix;
current.zone_prefix = cfg.zone_prefix;
current.zone = cfg.zone;
current.peer_zones = cfg.peer_zones;
*self.policy.write().await = crate::prefix::PrefixPolicy::new(
¤t.local_prefix, ¤t.local_prefix_except, ¤t.local_only_prefix,
¤t.zone_prefix)
.with_local_only_except(¤t.local_only_prefix_except)
.with_readonly(¤t.readonly_prefix);
self.peers.write().await.apply_zones(¤t.zone, ¤t.peer_zones);
}
pub(crate) async fn broadcast_scoped(&self, request: crate::peer::BroadcastRequest, zone_scoped: Option<&str>)
-> Result<(), Status>
{
let plan = self.peers.read().await.plan(zone_scoped).await;
plan.send(request).await
}
pub(crate) async fn broadcast(&self, request: crate::peer::BroadcastRequest) -> Result<(), Status> {
self.broadcast_scoped(request, None).await
}
pub(crate) async fn unicast(&self, request: crate::peer::BroadcastRequest, peer_id: NodeId,
zone_scoped: Option<&str>) -> bool
{
let (target, me) = {
let peers = self.peers.read().await;
(peers.unicast_target(peer_id, zone_scoped).await, peers.me())
};
let Some(target) = target else { return false };
let id = tonic::metadata::MetadataValue::from_str(me.to_string().as_str()).ok();
EtcdCluster::peer_request(request, target, id).await
}
pub(crate) async fn add_peers(&self, clients: HashSet<&str>) -> usize {
let (urls, (cluster_id, timeout_ms, log)) = {
let peers = self.peers.read().await;
(peers.not_held(clients).await, peers.dial_params())
};
if urls.is_empty() {
return 0;
}
let dialled = EtcdCluster::dial(urls, cluster_id, timeout_ms, &log).await;
self.peers.write().await.admit(dialled).await
}
pub(crate) fn response_header(&self) -> ResponseHeader {
ResponseHeader {
cluster_id: self.cluster_id,
member_id: self.node_id,
revision: 0,
raft_term: self.term.load(Ordering::Relaxed),
}
}
pub async fn kv_put(&self, key: Vec<u8>, value: Vec<u8>) -> Result<(), tonic::Status> {
let req = crate::etcdpb::etcdserverpb::PutRequest {
key, value, lease: 0, prev_kv: false, ignore_value: false, ignore_lease: false,
};
self.put_impl(tonic::Request::new(req)).await.map(|_| ())
}
pub async fn kv_get(&self, key: &[u8]) -> Option<Vec<u8>> {
self.vault.read().await.get(key).map(|kv| kv.value.clone())
}
pub async fn queue_link(&self, request_q: &str, reply_q: &str) {
let qn = crate::queue::QueueNameKey::new(request_q.to_string());
let q = self.queues.read().await.get(&qn.queue_name).cloned();
if let Some(q) = q {
q.link_reply_to(reply_q, &self.log).await;
}
}
pub async fn queue_dispatcher(&self, q_name: &str) -> Option<NodeId> {
let qn = crate::queue::QueueNameKey::new(q_name.to_string());
let key = crate::route::dispatcher_key(&qn.name());
self.kv_get(key.as_bytes()).await
.and_then(|v| crate::route::DispatchRecord::parse(&String::from_utf8_lossy(&v)))
.map(|r| r.node)
}
pub async fn kv_delete(&self, key: Vec<u8>) -> Result<(), tonic::Status> {
let req = crate::etcdpb::etcdserverpb::DeleteRangeRequest {
key, range_end: vec![], prev_kv: false,
};
self.delete_impl(tonic::Request::new(req)).await.map(|_| ())
}
pub async fn kv_prefix(&self, prefix: &[u8]) -> Vec<(Vec<u8>, Vec<u8>)> {
self.vault.read().await.iter()
.filter(|(k, _)| k.starts_with(prefix))
.map(|(k, v)| (k.clone(), v.value.clone()))
.collect()
}
pub async fn get_peer_urls(&self) -> String {
self.peers.read().await.peer_urls().await
}
}
pub type KvKey = Vec<u8>;
pub type NodeId = u64;
pub type ClientId = Uuid;
pub type WatcherId = i64;
#[derive(Clone)]
pub struct EtcdNode {
pub(crate) node_id: NodeId,
pub(crate) cluster_id: NodeId,
pub(crate) term: Arc<AtomicU64>,
pub(crate) cfg: Arc<RwLock<EtcdConfig>>,
pub(crate) vault: Arc<RwLock<HashMap<KvKey, crate::kv::Kv>>>,
pub(crate) queues: Arc<RwLock<HashMap<String, Queue>>>,
pub(crate) observers: Arc<RwLock<HashMap<KvKey, EtcdObserverType>>>,
pub(crate) observer_ranges: Arc<RwLock<HashMap<KvKey, Vec<u8>>>>,
pub(crate) watchers: Arc<RwLock<HashMap<ClientId, EtcdClientType>>>,
pub(crate) watch_notify: Sender<crate::kv::Kv>,
pub(crate) peers: Arc<RwLock<EtcdCluster>>,
pub(crate) policy: Arc<RwLock<crate::prefix::PrefixPolicy>>,
pub event: Sender<EtcdEvents>,
pub(crate) log: Logger,
#[cfg(feature = "tracer")] pub(crate) tracer: Arc<RwLock<Option<opentelemetry_sdk::trace::SdkTracer>>>,
#[cfg(feature = "tracer")] pub(crate) meter: Arc<RwLock<Option<crate::metrics::Instruments>>>,
}
pub(crate) type EtcdObserverType = Vec<(ClientId, WatcherId)>;
pub(crate) type EtcdPeerNodeType = Arc<Mutex<EtcdPeerNode>>;
pub(crate) type EtcdClientType = Arc<RwLock<EtcdClientNode>>;
pub struct EtcdClientNode {
#[allow(dead_code)]
pub(crate) client_id: ClientId,
pub(crate) watchers: HashMap<WatcherId, WatcherConsumer>,
}
pub struct WatcherConsumer {
pub(crate) key: KvKey,
pub(crate) client: Sender<Result<WatchResponse, Status>>,
}
#[inline]
fn parse_uuid(value: &String) -> Result<u64, String> {
let (a, b) = if value.len() > 0 {
Uuid::from_str(value.as_str())
.map_err(|e| format!("parsing to uuid: [{}]: {}", value, e))?
} else {
Uuid::new_v4()
}.as_u64_pair();
Ok(a^b)
}
#[cfg(test)]
mod header_tests {
use super::*;
async fn node(cluster: &str, node: &str) -> EtcdNode {
let mut cfg = crate::cli::EtcdConfig::with_defaults();
cfg.cluster = cluster.to_string();
cfg.node = node.to_string();
cfg.listen_client_urls = "localhost:0".to_string();
EtcdNode::init(cfg, slog::Logger::root(slog::Discard, slog::o!()),
#[cfg(feature = "tracer")] None).await.unwrap()
}
#[tokio::test]
async fn the_header_reports_this_node_s_cluster() {
let n = node("11111111-1111-5111-8111-111111111111",
"22222222-2222-5222-8222-222222222222").await;
let h = n.response_header();
assert_ne!(h.cluster_id, 0, "a zero cluster id is what made every peer refuse every peer");
assert_eq!(h.cluster_id, n.cluster_id);
assert_eq!(h.member_id, n.node_id, "and the member id stays this node's own");
}
#[tokio::test]
async fn two_nodes_of_one_cluster_advertise_the_same_id() {
let a = node("11111111-1111-5111-8111-111111111111",
"22222222-2222-5222-8222-222222222222").await;
let b = node("11111111-1111-5111-8111-111111111111",
"33333333-3333-5333-8333-333333333333").await;
assert_eq!(a.response_header().cluster_id, b.response_header().cluster_id,
"same cluster -> the peer check must pass");
assert_ne!(a.response_header().member_id, b.response_header().member_id,
"...while still being distinguishable nodes");
}
#[tokio::test]
async fn a_different_cluster_still_reports_a_different_id() {
let a = node("11111111-1111-5111-8111-111111111111",
"22222222-2222-5222-8222-222222222222").await;
let c = node("44444444-4444-5444-8444-444444444444",
"55555555-5555-5555-8555-555555555555").await;
assert_ne!(a.response_header().cluster_id, c.response_header().cluster_id);
}
}
#[cfg(test)]
mod peering_tests {
use super::*;
use crate::etcdpb::etcdserverpb::PutRequest;
use crate::peer::PeerState;
const CLUSTER: &str = "11111111-1111-5111-8111-111111111111";
struct Print;
impl slog::Drain for Print {
type Ok = ();
type Err = slog::Never;
fn log(&self, r: &slog::Record, _: &slog::OwnedKVList) -> Result<(), slog::Never> {
eprintln!("[{}] {}", r.level(), r.msg());
Ok(())
}
}
fn test_log() -> slog::Logger {
slog::Logger::root(Print, slog::o!())
}
fn free_port() -> u16 {
let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let p = l.local_addr().unwrap().port();
drop(l);
p
}
async fn node(node_uuid: &str, listen: &str, peers: &str) -> EtcdNode {
let mut cfg = crate::cli::EtcdConfig::with_defaults();
cfg.cluster = CLUSTER.to_string();
cfg.node = node_uuid.to_string();
cfg.listen_client_urls = listen.to_string();
cfg.initial_advertise_peer_urls = peers.to_string();
EtcdNode::init(cfg, test_log(),
#[cfg(feature = "tracer")] None).await.unwrap()
}
async fn pair() -> (EtcdNode, EtcdNode, String) {
let port = free_port();
let a_url = format!("http://127.0.0.1:{}", port);
let a = node("22222222-2222-5222-8222-222222222222",
&format!("127.0.0.1:{}", port), "").await;
let served = a.clone();
tokio::spawn(async move { let _ = served.serve().await; });
for _ in 0..50 {
if std::net::TcpStream::connect(("127.0.0.1", port)).is_ok() { break; }
tokio::time::sleep(Duration::from_millis(20)).await;
}
let b = node("33333333-3333-5333-8333-333333333333",
"127.0.0.1:0", &a_url).await;
let _ = b.peers.write().await
.add_connections(HashSet::from([a_url.as_str()])).await.unwrap();
let held = b.peers.read().await.peer_info().await;
assert_eq!(held.len(), 1, "B must hold the served A as a peer (same cluster uuid)");
(a, b, a_url)
}
#[tokio::test]
async fn a_connected_peer_is_not_left_in_a_state_nothing_sends_to() {
let (_a, b, _) = pair().await;
let states: Vec<PeerState> =
b.peers.read().await.peer_info().await.into_iter().map(|(_, _, s)| s).collect();
assert_eq!(states.len(), 1);
assert_ne!(states[0], PeerState::Spare,
"a verified, connected peer left Spare receives nothing from \
broadcast_scoped, forever - this is the bug");
}
#[tokio::test]
async fn a_key_written_on_one_node_reaches_the_other() {
let (a, b, _) = pair().await;
b.put(tonic::Request::new(PutRequest {
key: b"/peering/k".to_vec(),
value: b"v".to_vec(),
..Default::default()
})).await.unwrap();
let mut seen = None;
for _ in 0..50 {
if let Some(kv) = a.vault.read().await.get(&b"/peering/k".to_vec()) {
seen = Some(kv.value.clone());
break;
}
tokio::time::sleep(Duration::from_millis(20)).await;
}
assert_eq!(seen.as_deref(), Some(&b"v"[..]),
"a put on B must replicate to A - it did not, so broadcast_scoped \
found nothing to send to");
}
#[tokio::test]
async fn replicating_successfully_promotes_the_peer_to_online() {
let (_a, b, _) = pair().await;
b.put(tonic::Request::new(PutRequest {
key: b"/peering/p".to_vec(),
value: b"v".to_vec(),
..Default::default()
})).await.unwrap();
let mut state = None;
for _ in 0..50 {
let s = b.peers.read().await.peer_info().await
.into_iter().map(|(_, _, s)| s).next();
if s == Some(PeerState::Online) { state = s; break; }
tokio::time::sleep(Duration::from_millis(20)).await;
}
assert_eq!(state, Some(PeerState::Online),
"a peer that replicated a message has proven what Online means");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn three_nodes_peering_at_once_stay_live() {
use crate::{EtcdEvents, EtcdMgmtEvent, KvEvent};
let ports: Vec<u16> = (0..3).map(|_| free_port()).collect();
let urls: Vec<String> = ports.iter().map(|p| format!("http://127.0.0.1:{}", p)).collect();
let ids = ["44444444-4444-5444-8444-444444444441", "44444444-4444-5444-8444-444444444442",
"44444444-4444-5444-8444-444444444443"];
let mut nodes = Vec::new();
for i in 0..3 {
let n = node(ids[i], &format!("127.0.0.1:{}", ports[i]), "").await;
let served = n.clone();
tokio::spawn(async move { let _ = served.serve().await; });
nodes.push(n);
}
for p in &ports {
for _ in 0..50 {
if std::net::TcpStream::connect(("127.0.0.1", *p)).is_ok() { break; }
tokio::time::sleep(Duration::from_millis(20)).await;
}
}
let all = urls.join(",");
for round in 0..2 {
let mut sends = Vec::new();
for (i, n) in nodes.iter().enumerate() {
let ev = n.event.clone();
let (me, all, urls) = (urls[i].clone(), all.clone(), urls.clone());
sends.push(tokio::spawn(async move {
let mut c = crate::cli::EtcdConfig::default();
c.listen_client_urls = me;
c.initial_advertise_peer_urls = all.clone();
c.listen_peer_urls = all;
ev.send(EtcdEvents::Mgmt(EtcdMgmtEvent::Config(c))).await.unwrap();
for (j, u) in urls.iter().enumerate() {
ev.send(EtcdEvents::Data(KvEvent::Put(PutRequest {
key: format!("/cluster/peers/{}", j).into_bytes(),
value: format!("{} r{}", u, round).into_bytes(),
..Default::default()
}))).await.unwrap();
}
}));
}
for s in sends { s.await.unwrap(); }
}
tokio::time::sleep(Duration::from_millis(500)).await;
for (i, n) in nodes.iter().enumerate() {
let held = tokio::time::timeout(Duration::from_secs(3), async {
n.peers.read().await.peer_info().await.len()
}).await;
assert!(held.is_ok(), "node {} no longer answers: its peer set stays locked", i);
}
for (i, n) in nodes.iter().enumerate() {
let key = format!("/live/{}", i).into_bytes();
let put = tokio::time::timeout(Duration::from_secs(5), n.put(tonic::Request::new(PutRequest {
key: key.clone(), value: b"v".to_vec(), ..Default::default()
}))).await;
assert!(put.is_ok(), "a put on node {} hangs", i);
for (j, m) in nodes.iter().enumerate() {
let mut seen = false;
for _ in 0..100 {
if m.vault.read().await.contains_key(&key) { seen = true; break; }
tokio::time::sleep(Duration::from_millis(20)).await;
}
assert!(seen, "the key written on node {} never reached node {}", i, j);
}
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn three_nodes_with_queue_consumers_stay_live() {
use crate::{EtcdEvents, EtcdMgmtEvent, KvEvent};
use crate::etcdpb::etcdserverpb::{WatchRequest, WatchCreateRequest, watch_request};
let ports: Vec<u16> = (0..3).map(|_| free_port()).collect();
let urls: Vec<String> = ports.iter().map(|p| format!("http://127.0.0.1:{}", p)).collect();
let ids = ["55555555-5555-5555-8555-555555555551", "55555555-5555-5555-8555-555555555552",
"55555555-5555-5555-8555-555555555553"];
let mut nodes = Vec::new();
for i in 0..3 {
let n = node(ids[i], &format!("127.0.0.1:{}", ports[i]), "").await;
let served = n.clone();
tokio::spawn(async move { let _ = served.serve().await; });
nodes.push(n);
}
for p in &ports {
for _ in 0..50 {
if std::net::TcpStream::connect(("127.0.0.1", *p)).is_ok() { break; }
tokio::time::sleep(Duration::from_millis(20)).await;
}
}
let mut keep = Vec::new();
for (i, n) in nodes.iter().enumerate() {
let client = uuid::Uuid::parse_str(&format!("66666666-6666-5666-8666-66666666666{}", i)).unwrap();
let (req_tx, req_rx) = tokio::sync::mpsc::channel(100);
let (resp_tx, mut resp_rx) = tokio::sync::mpsc::channel(100);
n.new_watcher(req_rx, resp_tx, &client).await;
req_tx.send(WatchRequest { request_union: Some(watch_request::RequestUnion::CreateRequest(
WatchCreateRequest { key: format!("/q/analytics/consumer/{}", client).into_bytes(), ..Default::default() }
)) }).await.unwrap();
tokio::spawn(async move { while resp_rx.recv().await.is_some() {} });
keep.push(req_tx);
}
let all = urls.join(",");
let mut sends = Vec::new();
for (i, n) in nodes.iter().enumerate() {
let ev = n.event.clone();
let (me, all, urls) = (urls[i].clone(), all.clone(), urls.clone());
sends.push(tokio::spawn(async move {
for round in 0..2 {
let mut c = crate::cli::EtcdConfig::default();
c.listen_client_urls = me.clone();
c.initial_advertise_peer_urls = all.clone();
c.listen_peer_urls = all.clone();
ev.send(EtcdEvents::Mgmt(EtcdMgmtEvent::Config(c))).await.unwrap();
for (j, u) in urls.iter().enumerate() {
ev.send(EtcdEvents::Data(KvEvent::Put(PutRequest {
key: format!("/cluster/peers/{}", j).into_bytes(),
value: format!("{} r{}", u, round).into_bytes(), ..Default::default()
}))).await.unwrap();
ev.send(EtcdEvents::Data(KvEvent::Put(PutRequest {
key: format!("/q/analytics/p/m{}-{}-{}", i, j, round).into_bytes(),
value: b"msg".to_vec(), ..Default::default()
}))).await.unwrap();
}
}
}));
}
for s in sends {
tokio::time::timeout(Duration::from_secs(10), s).await
.expect("sending the peering events blocked: an event loop is stuck").unwrap();
}
tokio::time::sleep(Duration::from_millis(500)).await;
for (i, n) in nodes.iter().enumerate() {
let held = tokio::time::timeout(Duration::from_secs(3), async {
n.peers.read().await.peer_info().await.len()
}).await;
assert!(held.is_ok(), "node {} no longer answers: its peer set stays locked", i);
}
for (i, n) in nodes.iter().enumerate() {
let key = format!("/live2/{}", i).into_bytes();
let put = tokio::time::timeout(Duration::from_secs(5), n.put(tonic::Request::new(PutRequest {
key: key.clone(), value: b"v".to_vec(), ..Default::default()
}))).await;
assert!(put.is_ok(), "a put on node {} hangs", i);
for (j, m) in nodes.iter().enumerate() {
let mut seen = false;
for _ in 0..100 {
if m.vault.read().await.contains_key(&key) { seen = true; break; }
tokio::time::sleep(Duration::from_millis(20)).await;
}
assert!(seen, "the key written on node {} never reached node {}", i, j);
}
}
drop(keep);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_peer_that_does_not_answer_does_not_stop_this_node() {
let (a, b, _) = pair().await;
let stuck = a.vault.write().await;
let b2 = b.clone();
let put = tokio::spawn(async move {
b2.put(tonic::Request::new(PutRequest {
key: b"/lockup/k".to_vec(), value: b"v".to_vec(), ..Default::default()
})).await
});
tokio::time::sleep(Duration::from_millis(300)).await; let b3 = b.clone();
let writer = tokio::spawn(async move { let _w = b3.peers.write().await; });
tokio::time::sleep(Duration::from_millis(100)).await; let read = tokio::time::timeout(Duration::from_secs(2), async {
b.peers.read().await.connected()
}).await;
assert!(read.is_ok(), "B's peer set stayed locked while a broadcast waited on a silent peer");
assert!(tokio::time::timeout(Duration::from_secs(2), writer).await.is_ok(),
"the writer never got B's peer set");
let done = tokio::time::timeout(crate::peer::PEER_RPC_TIMEOUT + Duration::from_secs(3), put).await;
assert!(done.is_ok(), "the put waited on the silent peer past PEER_RPC_TIMEOUT");
drop(stuck);
}
}