use std::net::SocketAddr;
use std::sync::Arc;
use aion_store_haematite::HaematiteStore;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NodeRef {
pub node_id: String,
pub grpc_addr: Option<SocketAddr>,
pub epoch: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OwnerView {
Local,
Remote(NodeRef),
Unknown,
}
pub trait ShardDirectory: Send + Sync {
fn owner_of(&self, shard: usize) -> OwnerView;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DirectoryPeer {
pub name: String,
pub owned_shards: Vec<usize>,
pub grpc_addr: Option<SocketAddr>,
}
pub struct StaticShardDirectory {
store: Arc<HaematiteStore>,
peers: Vec<DirectoryPeer>,
self_node_id: Option<String>,
}
impl StaticShardDirectory {
#[must_use]
pub fn new(
store: Arc<HaematiteStore>,
peers: Vec<DirectoryPeer>,
self_node_id: Option<String>,
) -> Self {
Self {
store,
peers,
self_node_id,
}
}
fn owns_locally(&self, shard: usize) -> bool {
self.store
.owned_shards()
.is_none_or(|owned| owned.contains(&shard))
}
fn resolve_from_record(&self, shard: usize) -> Option<OwnerView> {
let owner = self.store.read_shard_owner(shard).ok().flatten()?;
if self.self_node_id.as_deref() == Some(owner.as_str()) {
return Some(OwnerView::Local);
}
let peer = self.peers.iter().find(|peer| peer.name == owner)?;
if self.store.peer_connected(&peer.name) {
Some(OwnerView::Remote(NodeRef {
node_id: peer.name.clone(),
grpc_addr: peer.grpc_addr,
epoch: 0,
}))
} else {
None
}
}
}
impl ShardDirectory for StaticShardDirectory {
fn owner_of(&self, shard: usize) -> OwnerView {
if self.owns_locally(shard) {
return OwnerView::Local;
}
if let Some(view) = self.resolve_from_record(shard) {
return view;
}
let Some(peer) = self
.peers
.iter()
.find(|peer| peer.owned_shards.contains(&shard))
else {
return OwnerView::Unknown;
};
if self.store.peer_connected(&peer.name) {
OwnerView::Remote(NodeRef {
node_id: peer.name.clone(),
grpc_addr: peer.grpc_addr,
epoch: 0,
})
} else {
OwnerView::Unknown
}
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use super::{DirectoryPeer, OwnerView, ShardDirectory, StaticShardDirectory};
use aion_store::StoreError;
use aion_store_haematite::HaematiteStore;
type TestResult = Result<(), StoreError>;
fn unique_dir(name: &str) -> PathBuf {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |duration| duration.as_nanos());
let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!(
"aion-routing-dir-{name}-{}-{nanos}-{counter}",
std::process::id()
))
}
fn store(
name: &str,
shard_count: usize,
owned: &[usize],
) -> Result<HaematiteStore, StoreError> {
let store = HaematiteStore::create_with_shard_count(unique_dir(name), shard_count)?;
store.set_owned_shards(owned.iter().copied());
Ok(store)
}
#[test]
fn owned_shards_resolve_local() -> TestResult {
let store = std::sync::Arc::new(store("local", 4, &[0, 1])?);
let directory = StaticShardDirectory::new(store, Vec::new(), None);
assert_eq!(directory.owner_of(0), OwnerView::Local);
assert_eq!(directory.owner_of(1), OwnerView::Local);
Ok(())
}
#[test]
fn down_peer_shard_resolves_unknown() -> TestResult {
let store = std::sync::Arc::new(store("downpeer", 4, &[0])?);
let directory = StaticShardDirectory::new(
store,
vec![DirectoryPeer {
name: "peer-1".to_owned(),
owned_shards: vec![2, 3],
grpc_addr: Some(
"127.0.0.1:6001"
.parse()
.map_err(|error| StoreError::Backend(format!("bad addr: {error}")))?,
),
}],
None,
);
assert_eq!(directory.owner_of(2), OwnerView::Unknown);
Ok(())
}
#[test]
fn undeclared_shard_resolves_unknown() -> TestResult {
let store = std::sync::Arc::new(store("undeclared", 4, &[0])?);
let directory = StaticShardDirectory::new(
store,
vec![DirectoryPeer {
name: "peer-1".to_owned(),
owned_shards: vec![1],
grpc_addr: None,
}],
None,
);
assert_eq!(directory.owner_of(3), OwnerView::Unknown);
Ok(())
}
}