Show progress while downloading sandbox images
This commit is contained in:
parent
e3e21fff45
commit
aaee63a6e3
3 changed files with 197 additions and 12 deletions
|
|
@ -22,6 +22,7 @@ harness = false
|
|||
[dependencies]
|
||||
ed25519-dalek.workspace = true
|
||||
getrandom.workspace = true
|
||||
indicatif.workspace = true
|
||||
lazaret-control-client = { version = "=0.0.0", path = "../lazaret-control-client" }
|
||||
lazaret-core = { version = "=0.0.0", path = "../lazaret-core" }
|
||||
lazaret-domain-state = { version = "=0.0.0", path = "../lazaret-domain-state" }
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use std::os::unix::fs::MetadataExt;
|
|||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use lazaret_core::configuration::{
|
||||
ConfigurationLoadRequest, ResolvedDomainConfiguration, load_trusted_configuration,
|
||||
};
|
||||
|
|
@ -17,6 +18,7 @@ use lazaret_core::identity::{
|
|||
};
|
||||
use lazaret_core::records::{Domain, Session, SessionPhase};
|
||||
use lazaret_linux::filesystem::try_exclusive_flock;
|
||||
use lazaret_prepared_lower::OciDownloadProgress;
|
||||
use lazaret_runtime_supervisor::sandbox::{
|
||||
DOMAIN_OPEN_FILE_LIMIT, RUNSC_EXECUTABLE_NAME, default_sandbox_layout,
|
||||
};
|
||||
|
|
@ -34,6 +36,45 @@ use crate::waiter::BootstrapWaiterFace;
|
|||
pub use lazaret_core::protocol::PRIVATE_CLIENT_BOOTSTRAP_ROLE;
|
||||
const MAX_CONCURRENT_CONNECTIONS: usize = 32;
|
||||
|
||||
#[derive(Default)]
|
||||
struct ImageDownloadProgress {
|
||||
bar: Option<ProgressBar>,
|
||||
}
|
||||
|
||||
impl ImageDownloadProgress {
|
||||
fn update(&mut self, progress: OciDownloadProgress) {
|
||||
match progress {
|
||||
OciDownloadProgress::Started { total_bytes: 0 } => {}
|
||||
OciDownloadProgress::Started { total_bytes } => {
|
||||
let style = ProgressStyle::with_template(
|
||||
"Lazaret is downloading sandbox image [{wide_bar}] {bytes}/{total_bytes} ({percent}%)",
|
||||
)
|
||||
.expect("static progress template must be valid")
|
||||
.progress_chars("=>-");
|
||||
self.bar = Some(ProgressBar::new(total_bytes).with_style(style));
|
||||
}
|
||||
OciDownloadProgress::Advanced { downloaded_bytes } => {
|
||||
if let Some(bar) = &self.bar {
|
||||
bar.set_position(downloaded_bytes);
|
||||
}
|
||||
}
|
||||
OciDownloadProgress::Finished => self.clear(),
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
if let Some(bar) = self.bar.take() {
|
||||
bar.finish_and_clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ImageDownloadProgress {
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PrivateClientBootstrapError(String);
|
||||
|
||||
|
|
@ -140,6 +181,7 @@ fn run_private_client_bootstrap(arguments: &[OsString]) -> Result<(), PrivateCli
|
|||
.enable_all()
|
||||
.build()
|
||||
.map_err(failure)?;
|
||||
let mut image_progress = ImageDownloadProgress::default();
|
||||
let mut selection = runtime
|
||||
.block_on(select_runtime(
|
||||
&configuration,
|
||||
|
|
@ -149,6 +191,7 @@ fn run_private_client_bootstrap(arguments: &[OsString]) -> Result<(), PrivateCli
|
|||
sandbox_init: &installed.join("lazaret-sandbox-init"),
|
||||
runsc: &installed.join(RUNSC_EXECUTABLE_NAME),
|
||||
},
|
||||
|progress| image_progress.update(progress),
|
||||
))
|
||||
.map_err(failure)
|
||||
.inspect_err(|_| signal(b'E'))?;
|
||||
|
|
@ -552,6 +595,18 @@ mod tests {
|
|||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn image_download_progress_accepts_the_exact_event_lifecycle() {
|
||||
let mut progress = ImageDownloadProgress::default();
|
||||
progress.update(OciDownloadProgress::Started { total_bytes: 1_024 });
|
||||
assert!(progress.bar.is_some());
|
||||
progress.update(OciDownloadProgress::Advanced {
|
||||
downloaded_bytes: 1_024,
|
||||
});
|
||||
progress.update(OciDownloadProgress::Finished);
|
||||
assert!(progress.bar.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_coordinator_plans_a_bounded_raise_from_a_low_soft_nofile_limit() {
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ use lazaret_linux::filesystem::SharedFileLock;
|
|||
use lazaret_linux::platform::{PlatformObservation, check_platform};
|
||||
use lazaret_linux::user_namespace::UserNamespaceMap;
|
||||
use lazaret_prepared_lower::{
|
||||
OciRegistryTransport, Platform, PreparedDestination, PreparedDestinationKind,
|
||||
PreparedLowerError, PreparedLowerStore, RegistryCredentials,
|
||||
OciDownloadProgress, OciRegistryTransport, Platform, PreparedDestination,
|
||||
PreparedDestinationKind, PreparedLowerError, PreparedLowerStore, RegistryCredentials,
|
||||
};
|
||||
use lazaret_runtime_supervisor::sandbox::{
|
||||
CREDENTIALS_DESTINATION, DEV_DESTINATION, ESCALATION_CLIENT_DESTINATION,
|
||||
|
|
@ -150,6 +150,7 @@ pub async fn select_runtime(
|
|||
configuration: &ResolvedDomainConfiguration,
|
||||
store: Arc<HostCoordinatorDomainStore>,
|
||||
product_paths: InstalledProductPaths<'_>,
|
||||
progress: impl FnMut(OciDownloadProgress),
|
||||
) -> Result<RuntimeSelection, RuntimeSelectionError> {
|
||||
let kernel_release = std::fs::read_to_string("/proc/sys/kernel/osrelease").map_err(failure)?;
|
||||
select_runtime_for_platform(
|
||||
|
|
@ -161,6 +162,7 @@ pub async fn select_runtime(
|
|||
architecture: std::env::consts::ARCH,
|
||||
kernel_release: kernel_release.trim(),
|
||||
},
|
||||
progress,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
|
@ -170,6 +172,7 @@ async fn select_runtime_for_platform(
|
|||
store: Arc<HostCoordinatorDomainStore>,
|
||||
product_paths: InstalledProductPaths<'_>,
|
||||
observation: &PlatformObservation<'_>,
|
||||
progress: impl FnMut(OciDownloadProgress),
|
||||
) -> Result<RuntimeSelection, RuntimeSelectionError> {
|
||||
check_platform(observation).map_err(failure)?;
|
||||
let user_namespace_map = UserNamespaceMap::resolve_current().map_err(failure)?;
|
||||
|
|
@ -186,12 +189,13 @@ async fn select_runtime_for_platform(
|
|||
PreparedLowerStore::under_storage_root(configuration.storage_root(), catalog.clone())?;
|
||||
recover_before_cold_start(configuration, &store, &catalog, &prepared).map_err(failure)?;
|
||||
let image = OciRegistryTransport
|
||||
.pull_confined(
|
||||
.pull_confined_with_progress(
|
||||
&configuration.fingerprint_record().image,
|
||||
&RegistryCredentials::Anonymous,
|
||||
&Platform::linux("amd64"),
|
||||
configuration.storage_root(),
|
||||
random_id()?,
|
||||
progress,
|
||||
)
|
||||
.await
|
||||
.map_err(RuntimeSelectionError::PreparedLower)?;
|
||||
|
|
@ -493,6 +497,7 @@ mod tests {
|
|||
runsc: Path::new("/missing-runsc"),
|
||||
},
|
||||
&observation,
|
||||
|_| {},
|
||||
));
|
||||
let mut context = Context::from_waker(Waker::noop());
|
||||
let Poll::Ready(result) = future.as_mut().poll(&mut context) else {
|
||||
|
|
|
|||
Loading…
Reference in a new issue