node: Add clone test with seed acting as relay
This commit is contained in:
parent
3e7761740f
commit
3ae826dee2
|
|
@ -1,11 +1,15 @@
|
|||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::{thread, time};
|
||||
|
||||
use radicle::git;
|
||||
use radicle::prelude::Id;
|
||||
use radicle::profile::Home;
|
||||
use radicle::storage::{ReadRepository, WriteStorage};
|
||||
use radicle::test::fixtures;
|
||||
|
||||
use radicle_node::service::tracking::Policy;
|
||||
use radicle_node::test::{
|
||||
environment::{Config, Environment},
|
||||
logger,
|
||||
|
|
@ -267,3 +271,76 @@ fn rad_init_sync_and_clone() {
|
|||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
//
|
||||
// alice -- seed -- bob
|
||||
//
|
||||
fn test_replication_via_seed() {
|
||||
logger::init(log::Level::Debug);
|
||||
|
||||
let mut environment = Environment::new();
|
||||
let alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let seed = environment.node("seed");
|
||||
let working = environment.tmp().join("working");
|
||||
let rid = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
|
||||
|
||||
let mut alice = alice.spawn(Config::default());
|
||||
let mut bob = bob.spawn(Config::default());
|
||||
let seed = seed.spawn(Config {
|
||||
policy: Policy::Track,
|
||||
..Config::default()
|
||||
});
|
||||
|
||||
alice.connect(&seed);
|
||||
bob.connect(&seed);
|
||||
|
||||
// Enough time for the next inventory from Seed to not be considered stale by Bob.
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
|
||||
alice.routes_to(&[]);
|
||||
seed.routes_to(&[]);
|
||||
bob.routes_to(&[]);
|
||||
|
||||
// Initialize a repo as Alice.
|
||||
fixtures::repository(working.join("alice"));
|
||||
alice
|
||||
.rad(
|
||||
"init",
|
||||
&[
|
||||
"--name",
|
||||
"heartwood",
|
||||
"--description",
|
||||
"Radicle Heartwood Protocol & Stack",
|
||||
"--default-branch",
|
||||
"master",
|
||||
],
|
||||
working.join("alice"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
alice.routes_to(&[(rid, alice.id), (rid, seed.id)]);
|
||||
seed.routes_to(&[(rid, alice.id), (rid, seed.id)]);
|
||||
bob.routes_to(&[(rid, alice.id), (rid, seed.id)]);
|
||||
|
||||
bob.rad("clone", &[rid.to_string().as_str()], working.join("bob"))
|
||||
.unwrap();
|
||||
|
||||
alice.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]);
|
||||
seed.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]);
|
||||
bob.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]);
|
||||
|
||||
// Enough time for the Seed to be able to fetch Bob's fork.
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
|
||||
seed.storage
|
||||
.repository(rid)
|
||||
.unwrap()
|
||||
.remote(&bob.id)
|
||||
.unwrap();
|
||||
|
||||
// TODO: Seed should send Bob's ref announcement to Alice, after the fetch.
|
||||
// Currently, it is relayed when received from Bob, before the fetch completes,
|
||||
// which means that Alice fetches too early from Seed, and nothing is fetched.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -754,7 +754,7 @@ where
|
|||
// Discard inventory messages we've already seen, otherwise update
|
||||
// out last seen time.
|
||||
if !peer.inventory_announced(timestamp) {
|
||||
debug!(target: "service", "Ignoring stale inventory announcement from {announcer}");
|
||||
debug!(target: "service", "Ignoring stale inventory announcement from {announcer} (t={})", self.clock.as_secs());
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -162,12 +162,21 @@ impl<G: Signer + cyphernet::EcSign> NodeHandle<G> {
|
|||
}
|
||||
|
||||
/// Wait until this node's routing table contains the given routes.
|
||||
#[track_caller]
|
||||
pub fn routes_to(&self, routes: &[(Id, NodeId)]) {
|
||||
let mut remaining: BTreeSet<_> = routes.iter().collect();
|
||||
loop {
|
||||
let mut remaining: BTreeSet<_> = routes.iter().collect();
|
||||
|
||||
while !remaining.is_empty() {
|
||||
for (rid, nid) in self.handle.routing().unwrap() {
|
||||
remaining.remove(&(rid, nid));
|
||||
if !remaining.remove(&(rid, nid)) {
|
||||
panic!(
|
||||
"Node::routes_to: unexpected route for {}: ({rid}, {nid})",
|
||||
self.id
|
||||
);
|
||||
}
|
||||
}
|
||||
if remaining.is_empty() {
|
||||
break;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
|
|
@ -309,9 +318,12 @@ pub fn converge<'a, G: Signer + cyphernet::EcSign + 'static>(
|
|||
|
||||
// First build the set of all routes.
|
||||
for node in &nodes {
|
||||
let inv = node.storage.inventory().unwrap();
|
||||
|
||||
for rid in inv {
|
||||
// Routes from the routing table.
|
||||
for (rid, seed_id) in node.handle.routing().unwrap() {
|
||||
all_routes.insert((rid, seed_id));
|
||||
}
|
||||
// Routes from the local inventory.
|
||||
for rid in node.storage.inventory().unwrap() {
|
||||
all_routes.insert((rid, node.id));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue