node: Fetch missing inventory
Periodically, make sure we try to fetch inventory that is tracked but missing from local storage. We decide not to fetch immediately on `TrackRepo`, as this limits the command's flexibility. For example, when `rad clone` is used, we want to be able to fetch manually after tracking.
This commit is contained in:
parent
b33fb2a33d
commit
4f062ef2c6
|
|
@ -408,7 +408,9 @@ where
|
||||||
if now - self.last_sync >= SYNC_INTERVAL {
|
if now - self.last_sync >= SYNC_INTERVAL {
|
||||||
debug!(target: "service", "Running 'sync' task...");
|
debug!(target: "service", "Running 'sync' task...");
|
||||||
|
|
||||||
// TODO: What do we do here?
|
if let Err(e) = self.fetch_missing_inventory() {
|
||||||
|
error!(target: "service", "Error fetching missing inventory: {e}");
|
||||||
|
}
|
||||||
self.reactor.wakeup(SYNC_INTERVAL);
|
self.reactor.wakeup(SYNC_INTERVAL);
|
||||||
self.last_sync = now;
|
self.last_sync = now;
|
||||||
}
|
}
|
||||||
|
|
@ -419,7 +421,7 @@ where
|
||||||
.inventory()
|
.inventory()
|
||||||
.and_then(|i| self.announce_inventory(i))
|
.and_then(|i| self.announce_inventory(i))
|
||||||
{
|
{
|
||||||
error!("Error announcing inventory: {}", err);
|
error!(target: "service", "Error announcing inventory: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.reactor.wakeup(ANNOUNCE_INTERVAL);
|
self.reactor.wakeup(ANNOUNCE_INTERVAL);
|
||||||
|
|
@ -443,48 +445,14 @@ where
|
||||||
Command::Connect(id, addr) => {
|
Command::Connect(id, addr) => {
|
||||||
self.connect(id, addr);
|
self.connect(id, addr);
|
||||||
}
|
}
|
||||||
Command::Seeds(rid, resp) => {
|
Command::Seeds(rid, resp) => match self.seeds(&rid) {
|
||||||
#[derive(Default)]
|
Ok(seeds) => {
|
||||||
pub struct Stats {
|
resp.send(seeds).ok();
|
||||||
connected: usize,
|
|
||||||
disconnected: usize,
|
|
||||||
fetching: usize,
|
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
let (stats, seeds) = match self.routing.get(&rid) {
|
error!(target: "service", "Error reading routing table for {rid}: {e}");
|
||||||
Ok(seeds) => seeds.into_iter().fold(
|
}
|
||||||
(Stats::default(), Seeds::default()),
|
},
|
||||||
|(mut stats, mut seeds), node| {
|
|
||||||
if node != self.node_id() {
|
|
||||||
if self.sessions.is_fetching(&node) {
|
|
||||||
seeds.insert(Seed::Fetching(node));
|
|
||||||
stats.fetching += 1;
|
|
||||||
} else if self.sessions.is_connected(&node) {
|
|
||||||
seeds.insert(Seed::Connected(node));
|
|
||||||
stats.connected += 1;
|
|
||||||
} else if self.sessions.is_disconnected(&node) {
|
|
||||||
seeds.insert(Seed::Disconnected(node));
|
|
||||||
stats.connected += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(stats, seeds)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Err(err) => {
|
|
||||||
error!(target: "service", "Error reading routing table for {rid}: {err}");
|
|
||||||
drop(resp);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
debug!(
|
|
||||||
target: "service",
|
|
||||||
"Found {} connected seed(s), {} disconnected seed(s), and {} fetching seed(s) for {}",
|
|
||||||
stats.connected, stats.disconnected, stats.fetching, rid
|
|
||||||
);
|
|
||||||
resp.send(seeds).ok();
|
|
||||||
}
|
|
||||||
Command::Fetch(rid, seed, resp) => {
|
Command::Fetch(rid, seed, resp) => {
|
||||||
// TODO: Establish connections to unconnected seeds, and retry.
|
// TODO: Establish connections to unconnected seeds, and retry.
|
||||||
// TODO: Fetch requests should be queued and re-checked to see if they can
|
// TODO: Fetch requests should be queued and re-checked to see if they can
|
||||||
|
|
@ -493,10 +461,10 @@ where
|
||||||
self.fetch(rid, &seed);
|
self.fetch(rid, &seed);
|
||||||
}
|
}
|
||||||
Command::TrackRepo(rid, scope, resp) => {
|
Command::TrackRepo(rid, scope, resp) => {
|
||||||
|
// Update our tracking policy.
|
||||||
let tracked = self
|
let tracked = self
|
||||||
.track_repo(&rid, scope)
|
.track_repo(&rid, scope)
|
||||||
.expect("Service::command: error tracking repository");
|
.expect("Service::command: error tracking repository");
|
||||||
// TODO: Try to fetch project if we weren't tracking it before.
|
|
||||||
resp.send(tracked).ok();
|
resp.send(tracked).ok();
|
||||||
|
|
||||||
// Let all our peers know that we're interested in this repo from now on.
|
// Let all our peers know that we're interested in this repo from now on.
|
||||||
|
|
@ -1287,6 +1255,47 @@ where
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn seeds(&self, rid: &Id) -> Result<Seeds, Error> {
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Stats {
|
||||||
|
connected: usize,
|
||||||
|
disconnected: usize,
|
||||||
|
fetching: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
let (stats, seeds) = match self.routing.get(rid) {
|
||||||
|
Ok(seeds) => seeds.into_iter().fold(
|
||||||
|
(Stats::default(), Seeds::default()),
|
||||||
|
|(mut stats, mut seeds), node| {
|
||||||
|
if node != self.node_id() {
|
||||||
|
if self.sessions.is_fetching(&node) {
|
||||||
|
seeds.insert(Seed::Fetching(node));
|
||||||
|
stats.fetching += 1;
|
||||||
|
} else if self.sessions.is_connected(&node) {
|
||||||
|
seeds.insert(Seed::Connected(node));
|
||||||
|
stats.connected += 1;
|
||||||
|
} else if self.sessions.is_disconnected(&node) {
|
||||||
|
seeds.insert(Seed::Disconnected(node));
|
||||||
|
stats.connected += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(stats, seeds)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Err(err) => {
|
||||||
|
return Err(Error::Routing(err));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
debug!(
|
||||||
|
target: "service",
|
||||||
|
"Found {} connected seed(s), {} disconnected seed(s), and {} fetching seed(s) for {}",
|
||||||
|
stats.connected, stats.disconnected, stats.fetching, rid
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(seeds)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a new filter object, based on our tracking policy.
|
/// Return a new filter object, based on our tracking policy.
|
||||||
fn filter(&self) -> Filter {
|
fn filter(&self) -> Filter {
|
||||||
if self.config.policy == tracking::Policy::Track {
|
if self.config.policy == tracking::Policy::Track {
|
||||||
|
|
@ -1378,6 +1387,43 @@ where
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch all repositories that are tracked but missing from our inventory.
|
||||||
|
fn fetch_missing_inventory(&mut self) -> Result<(), Error> {
|
||||||
|
let inventory = self.storage().inventory()?;
|
||||||
|
let missing = self
|
||||||
|
.tracking
|
||||||
|
.repo_entries()?
|
||||||
|
.filter_map(|t| (t.policy == tracking::Policy::Track).then_some(t.id))
|
||||||
|
.filter(|rid| !inventory.contains(rid));
|
||||||
|
|
||||||
|
for rid in missing {
|
||||||
|
match self.seeds(&rid) {
|
||||||
|
Ok(mut seeds) => {
|
||||||
|
if seeds.has_connections() {
|
||||||
|
for seed in seeds.connected() {
|
||||||
|
self.fetch(rid, seed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: We should make sure that this fetch is retried later, either
|
||||||
|
// when we connect to a seed, or when we discover a new seed.
|
||||||
|
// Since new connections and routing table updates are both conditions for
|
||||||
|
// fetching, we should trigger fetches when those conditions appear.
|
||||||
|
// Another way to handle this would be to update our database, saying
|
||||||
|
// that we're trying to fetch a certain repo. We would then just
|
||||||
|
// iterate over those entries in the above circumstances. This is
|
||||||
|
// merely an optimization though, we can also iterate over all tracked
|
||||||
|
// repos and check which ones are not in our inventory.
|
||||||
|
warn!(target: "service", "No connected seeds found for {rid}..");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(target: "service", "Couldn't fetch missing repo {rid}: failed to lookup seeds: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn maintain_connections(&mut self) {
|
fn maintain_connections(&mut self) {
|
||||||
let addrs = self.choose_addresses();
|
let addrs = self.choose_addresses();
|
||||||
if addrs.is_empty() {
|
if addrs.is_empty() {
|
||||||
|
|
|
||||||
|
|
@ -944,6 +944,53 @@ fn test_track_repo_subscribe() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fetch_missing_inventory() {
|
||||||
|
let rid = arbitrary::gen::<Id>(1);
|
||||||
|
let mut alice = Peer::new("alice", [7, 7, 7, 7]);
|
||||||
|
let bob = Peer::new("bob", [8, 8, 8, 8]);
|
||||||
|
let eve = Peer::new("eve", [9, 9, 9, 9]);
|
||||||
|
let (send, recv) = chan::bounded::<bool>(1);
|
||||||
|
let now = LocalTime::now();
|
||||||
|
|
||||||
|
alice.connect_to(&bob);
|
||||||
|
alice.connect_to(&eve);
|
||||||
|
alice.receive(
|
||||||
|
bob.id(),
|
||||||
|
Message::inventory(
|
||||||
|
InventoryAnnouncement {
|
||||||
|
inventory: vec![rid].try_into().unwrap(),
|
||||||
|
timestamp: now.as_millis(),
|
||||||
|
},
|
||||||
|
bob.signer(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
alice.receive(
|
||||||
|
eve.id(),
|
||||||
|
Message::inventory(
|
||||||
|
InventoryAnnouncement {
|
||||||
|
inventory: vec![rid].try_into().unwrap(),
|
||||||
|
timestamp: now.as_millis(),
|
||||||
|
},
|
||||||
|
eve.signer(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
alice.command(Command::TrackRepo(rid, node::tracking::Scope::All, send));
|
||||||
|
alice.outbox().for_each(drop);
|
||||||
|
|
||||||
|
assert!(recv.recv().unwrap());
|
||||||
|
|
||||||
|
alice.elapse(service::SYNC_INTERVAL);
|
||||||
|
alice
|
||||||
|
.messages(bob.id)
|
||||||
|
.find(|m| matches!(m, Message::Fetch { .. }))
|
||||||
|
.unwrap();
|
||||||
|
alice
|
||||||
|
.messages(eve.id)
|
||||||
|
.find(|m| matches!(m, Message::Fetch { .. }))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_push_and_pull() {
|
fn test_push_and_pull() {
|
||||||
let tempdir = tempfile::tempdir().unwrap();
|
let tempdir = tempfile::tempdir().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ impl Storage {
|
||||||
|
|
||||||
// For performance reasons, we don't do a full repository check here.
|
// For performance reasons, we don't do a full repository check here.
|
||||||
if let Err(e) = repo.head() {
|
if let Err(e) = repo.head() {
|
||||||
log::error!(target: "storage", "Repository {rid} is corrupted: looking up head: {e}");
|
log::warn!(target: "storage", "Repository {rid} is invalid: looking up head: {e}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
repos.push(rid);
|
repos.push(rid);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue