node: Implement inventory sync command
This commit is contained in:
parent
8c7c2242cc
commit
7087288cfb
|
|
@ -62,8 +62,6 @@ enum CommandError {
|
||||||
InvalidCommandArg(String, Box<dyn std::error::Error>),
|
InvalidCommandArg(String, Box<dyn std::error::Error>),
|
||||||
#[error("invalid command arguments `{0:?}`")]
|
#[error("invalid command arguments `{0:?}`")]
|
||||||
InvalidCommandArgs(Vec<String>),
|
InvalidCommandArgs(Vec<String>),
|
||||||
#[error("unknown command `{0}`")]
|
|
||||||
UnknownCommand(String),
|
|
||||||
#[error("serialization failed: {0}")]
|
#[error("serialization failed: {0}")]
|
||||||
Serialization(#[from] json::Error),
|
Serialization(#[from] json::Error),
|
||||||
#[error("runtime error: {0}")]
|
#[error("runtime error: {0}")]
|
||||||
|
|
@ -89,6 +87,9 @@ fn command<H: Handle<Error = runtime::HandleError, FetchResult = FetchResult>>(
|
||||||
let cmd: Command = json::from_str(input)?;
|
let cmd: Command = json::from_str(input)?;
|
||||||
|
|
||||||
match cmd.name {
|
match cmd.name {
|
||||||
|
CommandName::Connect => {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
CommandName::Fetch => {
|
CommandName::Fetch => {
|
||||||
let (rid, nid): (Id, NodeId) = parse::args(cmd)?;
|
let (rid, nid): (Id, NodeId) = parse::args(cmd)?;
|
||||||
fetch(rid, nid, LineWriter::new(stream), handle)?;
|
fetch(rid, nid, LineWriter::new(stream), handle)?;
|
||||||
|
|
@ -162,6 +163,14 @@ fn command<H: Handle<Error = runtime::HandleError, FetchResult = FetchResult>>(
|
||||||
}
|
}
|
||||||
CommandResult::ok().to_writer(writer).ok();
|
CommandResult::ok().to_writer(writer).ok();
|
||||||
}
|
}
|
||||||
|
CommandName::SyncInventory => match handle.sync_inventory() {
|
||||||
|
Ok(updated) => {
|
||||||
|
CommandResult::Okay { updated }.to_writer(writer)?;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(CommandError::Runtime(e));
|
||||||
|
}
|
||||||
|
},
|
||||||
CommandName::Status => {
|
CommandName::Status => {
|
||||||
CommandResult::ok().to_writer(writer).ok();
|
CommandResult::ok().to_writer(writer).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -184,9 +193,6 @@ fn command<H: Handle<Error = runtime::HandleError, FetchResult = FetchResult>>(
|
||||||
CommandName::Shutdown => {
|
CommandName::Shutdown => {
|
||||||
return Err(CommandError::Shutdown);
|
return Err(CommandError::Shutdown);
|
||||||
}
|
}
|
||||||
_ => {
|
|
||||||
return Err(CommandError::UnknownCommand(line));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,12 @@ impl<G: Signer + EcSign + 'static> radicle::node::Handle for Handle<G> {
|
||||||
self.command(service::Command::AnnounceRefs(id))
|
self.command(service::Command::AnnounceRefs(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sync_inventory(&mut self) -> Result<bool, Error> {
|
||||||
|
let (sender, receiver) = chan::bounded(1);
|
||||||
|
self.command(service::Command::SyncInventory(sender))?;
|
||||||
|
receiver.recv().map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
fn routing(&self) -> Result<chan::Receiver<(Id, NodeId)>, Error> {
|
fn routing(&self) -> Result<chan::Receiver<(Id, NodeId)>, Error> {
|
||||||
let (sender, receiver) = chan::unbounded();
|
let (sender, receiver) = chan::unbounded();
|
||||||
let query: Arc<QueryState> = Arc::new(move |state| {
|
let query: Arc<QueryState> = Arc::new(move |state| {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
#![allow(clippy::collapsible_match)]
|
#![allow(clippy::collapsible_match)]
|
||||||
|
#![allow(clippy::collapsible_if)]
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
|
@ -103,6 +104,8 @@ pub type QueryState = dyn Fn(&dyn ServiceState) -> Result<(), CommandError> + Se
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// Announce repository references for given repository to peers.
|
/// Announce repository references for given repository to peers.
|
||||||
AnnounceRefs(Id),
|
AnnounceRefs(Id),
|
||||||
|
/// Announce local inventory to peers.
|
||||||
|
SyncInventory(chan::Sender<bool>),
|
||||||
/// Connect to node with the given address.
|
/// Connect to node with the given address.
|
||||||
Connect(NodeId, Address),
|
Connect(NodeId, Address),
|
||||||
/// Lookup seeds for the given repository in the routing table.
|
/// Lookup seeds for the given repository in the routing table.
|
||||||
|
|
@ -125,6 +128,7 @@ impl fmt::Debug for Command {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::AnnounceRefs(id) => write!(f, "AnnounceRefs({id})"),
|
Self::AnnounceRefs(id) => write!(f, "AnnounceRefs({id})"),
|
||||||
|
Self::SyncInventory(_) => write!(f, "SyncInventory(..)"),
|
||||||
Self::Connect(id, addr) => write!(f, "Connect({id}, {addr})"),
|
Self::Connect(id, addr) => write!(f, "Connect({id}, {addr})"),
|
||||||
Self::Seeds(id, _) => write!(f, "Seeds({id})"),
|
Self::Seeds(id, _) => write!(f, "Seeds({id})"),
|
||||||
Self::Fetch(id, node, _) => write!(f, "Fetch({id}, {node})"),
|
Self::Fetch(id, node, _) => write!(f, "Fetch({id}, {node})"),
|
||||||
|
|
@ -396,7 +400,11 @@ where
|
||||||
}
|
}
|
||||||
if now - self.last_announce >= ANNOUNCE_INTERVAL {
|
if now - self.last_announce >= ANNOUNCE_INTERVAL {
|
||||||
if self.out_of_sync {
|
if self.out_of_sync {
|
||||||
if let Err(err) = self.announce_inventory() {
|
if let Err(err) = self
|
||||||
|
.storage
|
||||||
|
.inventory()
|
||||||
|
.and_then(|i| self.announce_inventory(i))
|
||||||
|
{
|
||||||
error!("Error announcing inventory: {}", err);
|
error!("Error announcing inventory: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -480,6 +488,12 @@ where
|
||||||
error!("Error announcing refs: {}", err);
|
error!("Error announcing refs: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Command::SyncInventory(resp) => {
|
||||||
|
let updated = self
|
||||||
|
.sync_and_announce_inventory()
|
||||||
|
.expect("Service::command: error syncing and announcing inventory");
|
||||||
|
resp.send(updated).ok();
|
||||||
|
}
|
||||||
Command::QueryState(query, sender) => {
|
Command::QueryState(query, sender) => {
|
||||||
sender.send(query(self)).ok();
|
sender.send(query(self)).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -735,16 +749,16 @@ where
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = self.sync_inventory(
|
match self.sync_routing(&message.inventory, *announcer, message.timestamp) {
|
||||||
message.inventory.as_slice(),
|
Ok(updated) => {
|
||||||
*announcer,
|
if !updated {
|
||||||
&message.timestamp,
|
return Ok(false);
|
||||||
) {
|
}
|
||||||
error!("Error processing inventory from {}: {}", announcer, err);
|
}
|
||||||
|
Err(e) => {
|
||||||
// There's not much we can do if the peer sending us this message isn't the
|
error!("Error processing inventory from {}: {}", announcer, e);
|
||||||
// origin of it.
|
return Ok(false);
|
||||||
return Ok(false);
|
}
|
||||||
}
|
}
|
||||||
return Ok(relay);
|
return Ok(relay);
|
||||||
}
|
}
|
||||||
|
|
@ -995,19 +1009,32 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sync, and if needed, announce our local inventory.
|
||||||
|
fn sync_and_announce_inventory(&mut self) -> Result<bool, Error> {
|
||||||
|
let inventory = self.storage.inventory()?;
|
||||||
|
let updated = self.sync_routing(&inventory, self.node_id(), self.clock.as_secs())?;
|
||||||
|
|
||||||
|
if updated {
|
||||||
|
self.announce_inventory(inventory)?;
|
||||||
|
}
|
||||||
|
Ok(updated)
|
||||||
|
}
|
||||||
|
|
||||||
/// Process a peer inventory announcement by updating our routing table.
|
/// Process a peer inventory announcement by updating our routing table.
|
||||||
/// This function expects the peer's full inventory, and prunes entries that are not in the
|
/// This function expects the peer's full inventory, and prunes entries that are not in the
|
||||||
/// given inventory.
|
/// given inventory.
|
||||||
fn sync_inventory(
|
fn sync_routing(
|
||||||
&mut self,
|
&mut self,
|
||||||
inventory: &[Id],
|
inventory: &[Id],
|
||||||
from: NodeId,
|
from: NodeId,
|
||||||
timestamp: &Timestamp,
|
timestamp: Timestamp,
|
||||||
) -> Result<(), Error> {
|
) -> Result<bool, Error> {
|
||||||
|
let mut updated = false;
|
||||||
let mut included = HashSet::new();
|
let mut included = HashSet::new();
|
||||||
|
|
||||||
for proj_id in inventory {
|
for proj_id in inventory {
|
||||||
included.insert(proj_id);
|
included.insert(proj_id);
|
||||||
if self.routing.insert(*proj_id, from, *timestamp)? {
|
if self.routing.insert(*proj_id, from, timestamp)? {
|
||||||
info!(target: "service", "Routing table updated for {proj_id} with seed {from}");
|
info!(target: "service", "Routing table updated for {proj_id} with seed {from}");
|
||||||
|
|
||||||
if self
|
if self
|
||||||
|
|
@ -1018,14 +1045,17 @@ where
|
||||||
// TODO: We should fetch here if we're already connected, case this seed has
|
// TODO: We should fetch here if we're already connected, case this seed has
|
||||||
// refs we don't have.
|
// refs we don't have.
|
||||||
}
|
}
|
||||||
|
updated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for id in self.routing.get_resources(&from)?.into_iter() {
|
for id in self.routing.get_resources(&from)?.into_iter() {
|
||||||
if !included.contains(&id) {
|
if !included.contains(&id) {
|
||||||
self.routing.remove(&id, &from)?;
|
if self.routing.remove(&id, &from)? {
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(updated)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Announce local refs for given id.
|
/// Announce local refs for given id.
|
||||||
|
|
@ -1088,13 +1118,9 @@ where
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/// Announce our inventory to all connected peers.
|
/// Announce our inventory to all connected peers.
|
||||||
fn announce_inventory(&mut self) -> Result<(), storage::Error> {
|
fn announce_inventory(&mut self, inventory: Vec<Id>) -> Result<(), storage::Error> {
|
||||||
let inventory = self.storage().inventory()?;
|
let time = self.clock.as_secs();
|
||||||
let inv = Message::inventory(
|
let inv = Message::inventory(gossip::inventory(time, inventory), &self.signer);
|
||||||
gossip::inventory(self.clock.as_secs(), inventory),
|
|
||||||
&self.signer,
|
|
||||||
);
|
|
||||||
|
|
||||||
for id in self.sessions.negotiated().map(|(id, _)| id) {
|
for id in self.sessions.negotiated().map(|(id, _)| id) {
|
||||||
self.reactor.write(*id, inv.clone());
|
self.reactor.write(*id, inv.clone());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ pub fn messages(count: usize, now: LocalTime, delta: LocalDuration) -> Vec<Messa
|
||||||
|
|
||||||
msgs.push(Message::inventory(
|
msgs.push(Message::inventory(
|
||||||
InventoryAnnouncement {
|
InventoryAnnouncement {
|
||||||
inventory: arbitrary::gen(3),
|
inventory: arbitrary::vec(3).try_into().unwrap(),
|
||||||
timestamp: time.as_secs(),
|
timestamp: time.as_secs(),
|
||||||
},
|
},
|
||||||
&signer,
|
&signer,
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,10 @@ impl radicle::node::Handle for Handle {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sync_inventory(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
fn routing(&self) -> Result<chan::Receiver<(Id, service::NodeId)>, Self::Error> {
|
fn routing(&self) -> Result<chan::Receiver<(Id, service::NodeId)>, Self::Error> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -598,7 +598,7 @@ fn test_inventory_relay() {
|
||||||
let mut alice = Peer::new("alice", [7, 7, 7, 7]);
|
let mut alice = Peer::new("alice", [7, 7, 7, 7]);
|
||||||
let bob = Peer::new("bob", [8, 8, 8, 8]);
|
let bob = Peer::new("bob", [8, 8, 8, 8]);
|
||||||
let eve = Peer::new("eve", [9, 9, 9, 9]);
|
let eve = Peer::new("eve", [9, 9, 9, 9]);
|
||||||
let inv = BoundedVec::new();
|
let inv = BoundedVec::try_from(arbitrary::vec(1)).unwrap();
|
||||||
let now = LocalTime::now().as_secs();
|
let now = LocalTime::now().as_secs();
|
||||||
|
|
||||||
// Inventory from Bob relayed to Eve.
|
// Inventory from Bob relayed to Eve.
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,8 @@ impl From<net::SocketAddr> for Address {
|
||||||
pub enum CommandName {
|
pub enum CommandName {
|
||||||
/// Announce repository references for given repository to peers.
|
/// Announce repository references for given repository to peers.
|
||||||
AnnounceRefs,
|
AnnounceRefs,
|
||||||
|
/// Sync local inventory with node.
|
||||||
|
SyncInventory,
|
||||||
/// Connect to node with the given address.
|
/// Connect to node with the given address.
|
||||||
Connect,
|
Connect,
|
||||||
/// Lookup seeds for the given repository in the routing table.
|
/// Lookup seeds for the given repository in the routing table.
|
||||||
|
|
@ -261,9 +263,11 @@ pub trait Handle {
|
||||||
fn untrack_repo(&mut self, id: Id) -> Result<bool, Self::Error>;
|
fn untrack_repo(&mut self, id: Id) -> Result<bool, Self::Error>;
|
||||||
/// Untrack the given node.
|
/// Untrack the given node.
|
||||||
fn untrack_node(&mut self, id: NodeId) -> Result<bool, Self::Error>;
|
fn untrack_node(&mut self, id: NodeId) -> Result<bool, Self::Error>;
|
||||||
/// Notify the client that a project has been updated.
|
/// Notify the service that a project has been updated.
|
||||||
fn announce_refs(&mut self, id: Id) -> Result<(), Self::Error>;
|
fn announce_refs(&mut self, id: Id) -> Result<(), Self::Error>;
|
||||||
/// Ask the client to shutdown.
|
/// Notify the service that our inventory was updated.
|
||||||
|
fn sync_inventory(&mut self) -> Result<bool, Self::Error>;
|
||||||
|
/// Ask the service to shutdown.
|
||||||
fn shutdown(self) -> Result<(), Self::Error>;
|
fn shutdown(self) -> Result<(), Self::Error>;
|
||||||
/// Query the routing table entries.
|
/// Query the routing table entries.
|
||||||
fn routing(&self) -> Result<chan::Receiver<(Id, NodeId)>, Self::Error>;
|
fn routing(&self) -> Result<chan::Receiver<(Id, NodeId)>, Self::Error>;
|
||||||
|
|
@ -403,6 +407,15 @@ impl Handle for Node {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sync_inventory(&mut self) -> Result<bool, Error> {
|
||||||
|
let mut line = self.call::<&str, _>(CommandName::SyncInventory, [])?;
|
||||||
|
let response: CommandResult = line.next().ok_or(Error::EmptyResponse {
|
||||||
|
cmd: CommandName::SyncInventory,
|
||||||
|
})??;
|
||||||
|
|
||||||
|
response.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn routing(&self) -> Result<chan::Receiver<(Id, NodeId)>, Error> {
|
fn routing(&self) -> Result<chan::Receiver<(Id, NodeId)>, Error> {
|
||||||
todo!();
|
todo!();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue