cli: Try to connect to seeds specified as options
When fetching with a `--seed` specified on the CLI, try to connect to it if not already connected.
This commit is contained in:
parent
11a6ec5dc6
commit
f6aa46a283
|
|
@ -43,6 +43,6 @@ seed succeeds.
|
||||||
``` ~bob
|
``` ~bob
|
||||||
$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --seed z6MkwPUeUS2fJMfc2HZN1RQTQcTTuhw4HhPySB8JeUg2mVvx
|
$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --seed z6MkwPUeUS2fJMfc2HZN1RQTQcTTuhw4HhPySB8JeUg2mVvx
|
||||||
✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi..
|
✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi..
|
||||||
✗ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MkwPU…Ug2mVvx.. error: peer is not connected; cannot initiate fetch
|
! Warning: no addresses found for z6MkwPUeUS2fJMfc2HZN1RQTQcTTuhw4HhPySB8JeUg2mVvx, skipping..
|
||||||
✓ Fetched repository from 1 seed(s)
|
✓ Fetched repository from 1 seed(s)
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -111,10 +111,10 @@ $ rad sync rad:z39mP9rQAaGmERfUMPULfPUi473tY
|
||||||
✗ Error: nothing to announce, repository rad:z39mP9rQAaGmERfUMPULfPUi473tY is not available locally
|
✗ Error: nothing to announce, repository rad:z39mP9rQAaGmERfUMPULfPUi473tY is not available locally
|
||||||
```
|
```
|
||||||
|
|
||||||
Or when trying to fetch from a seed you aren't connected to, using `--seed`:
|
Or when trying to fetch from an unknown seed, using `--seed`:
|
||||||
```
|
```
|
||||||
$ rad sync --fetch rad:z39mP9rQAaGmERfUMPULfPUi473tY --seed z6MkjM3HpqNVV4ZsL5s3RAd8ThVG3VG98YsDCjHBNnGMq5o7
|
$ rad sync --fetch rad:z39mP9rQAaGmERfUMPULfPUi473tY --seed z6MkjM3HpqNVV4ZsL5s3RAd8ThVG3VG98YsDCjHBNnGMq5o7
|
||||||
✗ Fetching rad:z39mP9rQAaGmERfUMPULfPUi473tY from z6MkjM3…nGMq5o7.. error: peer is not connected; cannot initiate fetch
|
! Warning: no addresses found for z6MkjM3HpqNVV4ZsL5s3RAd8ThVG3VG98YsDCjHBNnGMq5o7, skipping..
|
||||||
✗ Error: repository fetch from 1 seed(s) failed
|
✗ Error: repository fetch from 1 seed(s) failed
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ use radicle::node::{Handle as _, Node};
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::rad;
|
use radicle::rad;
|
||||||
use radicle::storage;
|
use radicle::storage;
|
||||||
use radicle::storage::git::Storage;
|
|
||||||
use radicle::storage::RepositoryError;
|
use radicle::storage::RepositoryError;
|
||||||
|
|
||||||
use crate::commands::rad_checkout as checkout;
|
use crate::commands::rad_checkout as checkout;
|
||||||
|
|
@ -150,7 +149,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
options.sync.with_profile(&profile),
|
options.sync.with_profile(&profile),
|
||||||
&mut node,
|
&mut node,
|
||||||
&signer,
|
&signer,
|
||||||
&profile.storage,
|
&profile,
|
||||||
)?;
|
)?;
|
||||||
let delegates = doc
|
let delegates = doc
|
||||||
.delegates()
|
.delegates()
|
||||||
|
|
@ -226,6 +225,8 @@ pub enum CloneError {
|
||||||
NotFound(RepoId),
|
NotFound(RepoId),
|
||||||
#[error("no seeds found for {0}")]
|
#[error("no seeds found for {0}")]
|
||||||
NoSeeds(RepoId),
|
NoSeeds(RepoId),
|
||||||
|
#[error("fetch: {0}")]
|
||||||
|
Fetch(#[from] sync::FetchError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone<G: Signer>(
|
pub fn clone<G: Signer>(
|
||||||
|
|
@ -235,7 +236,7 @@ pub fn clone<G: Signer>(
|
||||||
settings: SyncSettings,
|
settings: SyncSettings,
|
||||||
node: &mut Node,
|
node: &mut Node,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
storage: &Storage,
|
profile: &Profile,
|
||||||
) -> Result<(raw::Repository, storage::git::Repository, Doc, Project), CloneError> {
|
) -> Result<(raw::Repository, storage::git::Repository, Doc, Project), CloneError> {
|
||||||
let me = *signer.public_key();
|
let me = *signer.public_key();
|
||||||
|
|
||||||
|
|
@ -247,8 +248,8 @@ pub fn clone<G: Signer>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let results = sync::fetch(id, settings, node)?;
|
let results = sync::fetch(id, settings, node, profile)?;
|
||||||
let Ok(repository) = storage.repository(id) else {
|
let Ok(repository) = profile.storage.repository(id) else {
|
||||||
// If we don't have the repository locally, even after attempting to fetch,
|
// If we don't have the repository locally, even after attempting to fetch,
|
||||||
// there's nothing we can do.
|
// there's nothing we can do.
|
||||||
if results.is_empty() {
|
if results.is_empty() {
|
||||||
|
|
@ -282,7 +283,7 @@ pub fn clone<G: Signer>(
|
||||||
"Creating checkout in ./{}..",
|
"Creating checkout in ./{}..",
|
||||||
term::format::tertiary(path.display())
|
term::format::tertiary(path.display())
|
||||||
));
|
));
|
||||||
let working = rad::checkout(id, &me, path, &storage)?;
|
let working = rad::checkout(id, &me, path, &profile.storage)?;
|
||||||
|
|
||||||
spinner.finish();
|
spinner.finish();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ pub fn run(
|
||||||
rid,
|
rid,
|
||||||
SyncSettings::default().with_profile(profile),
|
SyncSettings::default().with_profile(profile),
|
||||||
&mut node,
|
&mut node,
|
||||||
|
profile,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
.timeout(timeout)
|
.timeout(timeout)
|
||||||
.with_profile(&profile),
|
.with_profile(&profile),
|
||||||
&mut node,
|
&mut node,
|
||||||
|
&profile,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use std::time;
|
||||||
use anyhow::{anyhow, Context as _};
|
use anyhow::{anyhow, Context as _};
|
||||||
|
|
||||||
use radicle::node;
|
use radicle::node;
|
||||||
|
use radicle::node::address::Store;
|
||||||
use radicle::node::AliasStore;
|
use radicle::node::AliasStore;
|
||||||
use radicle::node::Seed;
|
use radicle::node::Seed;
|
||||||
use radicle::node::{FetchResult, FetchResults, Handle as _, Node, SyncStatus};
|
use radicle::node::{FetchResult, FetchResults, Handle as _, Node, SyncStatus};
|
||||||
|
|
@ -286,7 +287,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
if !profile.policies()?.is_seeding(&rid)? {
|
if !profile.policies()?.is_seeding(&rid)? {
|
||||||
anyhow::bail!("repository {rid} is not seeded");
|
anyhow::bail!("repository {rid} is not seeded");
|
||||||
}
|
}
|
||||||
let results = fetch(rid, settings.clone(), &mut node)?;
|
let results = fetch(rid, settings.clone(), &mut node, &profile)?;
|
||||||
let success = results.success().count();
|
let success = results.success().count();
|
||||||
let failed = results.failed().count();
|
let failed = results.failed().count();
|
||||||
|
|
||||||
|
|
@ -437,21 +438,50 @@ pub fn announce_inventory(mut node: Node) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum FetchError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Node(#[from] node::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Db(#[from] node::db::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Address(#[from] node::address::Error),
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fetch(
|
pub fn fetch(
|
||||||
rid: RepoId,
|
rid: RepoId,
|
||||||
settings: SyncSettings,
|
settings: SyncSettings,
|
||||||
node: &mut Node,
|
node: &mut Node,
|
||||||
) -> Result<FetchResults, node::Error> {
|
profile: &Profile,
|
||||||
|
) -> Result<FetchResults, FetchError> {
|
||||||
let local = node.nid()?;
|
let local = node.nid()?;
|
||||||
// Get seeds. This consults the local routing table only.
|
|
||||||
let seeds = node.seeds(rid)?;
|
|
||||||
let replicas = settings.replicas;
|
let replicas = settings.replicas;
|
||||||
let mut results = FetchResults::default();
|
let mut results = FetchResults::default();
|
||||||
let (connected, mut disconnected) = seeds.partition();
|
let db = profile.database()?;
|
||||||
|
|
||||||
// Fetch from specified seeds.
|
// Fetch from specified seeds, connecting to them if necessary.
|
||||||
for nid in &settings.seeds {
|
for nid in &settings.seeds {
|
||||||
|
if node.session(*nid)?.is_some_and(|s| s.is_connected()) {
|
||||||
fetch_from(rid, nid, settings.timeout, &mut results, node)?;
|
fetch_from(rid, nid, settings.timeout, &mut results, node)?;
|
||||||
|
} else {
|
||||||
|
let addrs = db.addresses_of(nid)?;
|
||||||
|
if addrs.is_empty() {
|
||||||
|
results.push(
|
||||||
|
*nid,
|
||||||
|
FetchResult::Failed {
|
||||||
|
reason: format!("no addresses found in routing table for {nid}"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
term::warning(format!("no addresses found for {nid}, skipping.."));
|
||||||
|
} else if connect(
|
||||||
|
*nid,
|
||||||
|
addrs.into_iter().map(|ka| ka.addr),
|
||||||
|
settings.timeout,
|
||||||
|
node,
|
||||||
|
) {
|
||||||
|
fetch_from(rid, nid, settings.timeout, &mut results, node)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
// We are done when we either hit our replica count,
|
// We are done when we either hit our replica count,
|
||||||
// or fetch from all the specified seeds.
|
// or fetch from all the specified seeds.
|
||||||
if results.success().count() >= replicas {
|
if results.success().count() >= replicas {
|
||||||
|
|
@ -466,6 +496,11 @@ pub fn fetch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're here, we haven't met our sync targets, so consult the routing table
|
||||||
|
// for more seeds to fetch from.
|
||||||
|
let seeds = node.seeds(rid)?;
|
||||||
|
let (connected, mut disconnected) = seeds.partition();
|
||||||
|
|
||||||
// Fetch from connected seeds.
|
// Fetch from connected seeds.
|
||||||
let mut connected = connected
|
let mut connected = connected
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,11 @@ where
|
||||||
|
|
||||||
CommandResult::Okay(sessions).to_writer(writer)?;
|
CommandResult::Okay(sessions).to_writer(writer)?;
|
||||||
}
|
}
|
||||||
|
Command::Session { nid } => {
|
||||||
|
let session = handle.session(nid)?;
|
||||||
|
|
||||||
|
CommandResult::Okay(session).to_writer(writer)?;
|
||||||
|
}
|
||||||
Command::Seed { rid, scope } => match handle.seed(rid, scope) {
|
Command::Seed { rid, scope } => match handle.seed(rid, scope) {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
CommandResult::updated(result).to_writer(writer)?;
|
CommandResult::updated(result).to_writer(writer)?;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::sync::Arc;
|
||||||
use std::{fmt, io, time};
|
use std::{fmt, io, time};
|
||||||
|
|
||||||
use crossbeam_channel as chan;
|
use crossbeam_channel as chan;
|
||||||
use radicle::node::{ConnectOptions, ConnectResult, Link, Seeds};
|
use radicle::node::{ConnectOptions, ConnectResult, Seeds};
|
||||||
use radicle::storage::refs::RefsAt;
|
use radicle::storage::refs::RefsAt;
|
||||||
use reactor::poller::popol::PopolWaker;
|
use reactor::poller::popol::PopolWaker;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
@ -276,16 +276,7 @@ impl radicle::node::Handle for Handle {
|
||||||
let sessions = state
|
let sessions = state
|
||||||
.sessions()
|
.sessions()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(nid, s)| radicle::node::Session {
|
.map(|(_, s)| radicle::node::Session::from(s))
|
||||||
nid: *nid,
|
|
||||||
link: if s.link.is_inbound() {
|
|
||||||
Link::Inbound
|
|
||||||
} else {
|
|
||||||
Link::Outbound
|
|
||||||
},
|
|
||||||
addr: s.addr.clone(),
|
|
||||||
state: s.state.clone(),
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
sender.send(sessions).ok();
|
sender.send(sessions).ok();
|
||||||
|
|
||||||
|
|
@ -300,6 +291,23 @@ impl radicle::node::Handle for Handle {
|
||||||
Ok(sessions)
|
Ok(sessions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn session(&self, nid: NodeId) -> Result<Option<radicle::node::Session>, Self::Error> {
|
||||||
|
let (sender, receiver) = chan::bounded(1);
|
||||||
|
let query: Arc<QueryState> = Arc::new(move |state| {
|
||||||
|
let session = state.sessions().get(&nid).map(radicle::node::Session::from);
|
||||||
|
sender.send(session).ok();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
let (err_sender, err_receiver) = chan::bounded(1);
|
||||||
|
self.command(service::Command::QueryState(query, err_sender))?;
|
||||||
|
err_receiver.recv()??;
|
||||||
|
|
||||||
|
let sessions = receiver.recv()?;
|
||||||
|
|
||||||
|
Ok(sessions)
|
||||||
|
}
|
||||||
|
|
||||||
fn shutdown(self) -> Result<(), Error> {
|
fn shutdown(self) -> Result<(), Error> {
|
||||||
// If the current value is `false`, set it to `true`, otherwise error.
|
// If the current value is `false`, set it to `true`, otherwise error.
|
||||||
if self
|
if self
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,21 @@ impl fmt::Display for Session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&Session> for radicle::node::Session {
|
||||||
|
fn from(s: &Session) -> Self {
|
||||||
|
Self {
|
||||||
|
nid: s.id,
|
||||||
|
link: if s.link.is_inbound() {
|
||||||
|
radicle::node::Link::Inbound
|
||||||
|
} else {
|
||||||
|
radicle::node::Link::Outbound
|
||||||
|
},
|
||||||
|
addr: s.addr.clone(),
|
||||||
|
state: s.state.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Session {
|
impl Session {
|
||||||
pub fn outbound(id: NodeId, addr: Address, persistent: bool, rng: Rng, limits: Limits) -> Self {
|
pub fn outbound(id: NodeId, addr: Address, persistent: bool, rng: Rng, limits: Limits) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,10 @@ impl radicle::node::Handle for Handle {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn session(&self, _node: NodeId) -> Result<Option<radicle::node::Session>, Self::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
fn shutdown(self) -> Result<(), Self::Error> {
|
fn shutdown(self) -> Result<(), Self::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -567,6 +567,9 @@ pub enum Command {
|
||||||
/// Get the current peer sessions.
|
/// Get the current peer sessions.
|
||||||
Sessions,
|
Sessions,
|
||||||
|
|
||||||
|
/// Get a specific peer session.
|
||||||
|
Session { nid: NodeId },
|
||||||
|
|
||||||
/// Fetch the given repository from the network.
|
/// Fetch the given repository from the network.
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
Fetch {
|
Fetch {
|
||||||
|
|
@ -1007,6 +1010,8 @@ pub trait Handle: Clone + Sync + Send {
|
||||||
fn shutdown(self) -> Result<(), Self::Error>;
|
fn shutdown(self) -> Result<(), Self::Error>;
|
||||||
/// Query the peer session state.
|
/// Query the peer session state.
|
||||||
fn sessions(&self) -> Result<Self::Sessions, Self::Error>;
|
fn sessions(&self) -> Result<Self::Sessions, Self::Error>;
|
||||||
|
/// Query the state of a peer session. Returns [`None`] if no session was found.
|
||||||
|
fn session(&self, node: NodeId) -> Result<Option<Session>, Self::Error>;
|
||||||
/// Subscribe to node events.
|
/// Subscribe to node events.
|
||||||
fn subscribe(&self, timeout: time::Duration) -> Result<Self::Events, Self::Error>;
|
fn subscribe(&self, timeout: time::Duration) -> Result<Self::Events, Self::Error>;
|
||||||
/// Return debug information as a JSON value.
|
/// Return debug information as a JSON value.
|
||||||
|
|
@ -1322,6 +1327,15 @@ impl Handle for Node {
|
||||||
Ok(sessions)
|
Ok(sessions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn session(&self, nid: NodeId) -> Result<Option<Session>, Error> {
|
||||||
|
let session = self
|
||||||
|
.call::<Option<Session>>(Command::Session { nid }, DEFAULT_TIMEOUT)?
|
||||||
|
.next()
|
||||||
|
.ok_or(Error::EmptyResponse)??;
|
||||||
|
|
||||||
|
Ok(session)
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(&self) -> Result<json::Value, Self::Error> {
|
fn debug(&self) -> Result<json::Value, Self::Error> {
|
||||||
let debug = self
|
let debug = self
|
||||||
.call::<json::Value>(Command::Debug, DEFAULT_TIMEOUT)?
|
.call::<json::Value>(Command::Debug, DEFAULT_TIMEOUT)?
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue