Skip to content

Commit 0371407

Browse files
authored
Merge pull request #1245 from ckyrouac/reinstall-disk-size
install: Error out when not enough disk space to pull image
2 parents b3602a1 + c7492aa commit 0371407

File tree

2 files changed

+135
-53
lines changed

2 files changed

+135
-53
lines changed

lib/src/deploy.rs

+91-38
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use fn_error_context::context;
1414
use ostree::{gio, glib};
1515
use ostree_container::OstreeImageReference;
1616
use ostree_ext::container as ostree_container;
17-
use ostree_ext::container::store::{ImportProgress, PrepareResult};
17+
use ostree_ext::container::store::{ImageImporter, ImportProgress, PrepareResult, PreparedImport};
1818
use ostree_ext::oci_spec::image::{Descriptor, Digest};
1919
use ostree_ext::ostree::Deployment;
2020
use ostree_ext::ostree::{self, Sysroot};
@@ -301,15 +301,45 @@ async fn handle_layer_progress_print(
301301
prog
302302
}
303303

304-
/// Wrapper for pulling a container image, wiring up status output.
305-
#[context("Pulling")]
306-
pub(crate) async fn pull(
304+
/// Gather all bound images in all deployments, then prune the image store,
305+
/// using the gathered images as the roots (that will not be GC'd).
306+
pub(crate) async fn prune_container_store(sysroot: &Storage) -> Result<()> {
307+
let deployments = sysroot.deployments();
308+
let mut all_bound_images = Vec::new();
309+
for deployment in deployments {
310+
let bound = crate::boundimage::query_bound_images_for_deployment(sysroot, &deployment)?;
311+
all_bound_images.extend(bound.into_iter());
312+
}
313+
// Convert to a hashset of just the image names
314+
let image_names = HashSet::from_iter(all_bound_images.iter().map(|img| img.image.as_str()));
315+
let pruned = sysroot
316+
.get_ensure_imgstore()?
317+
.prune_except_roots(&image_names)
318+
.await?;
319+
tracing::debug!("Pruned images: {}", pruned.len());
320+
Ok(())
321+
}
322+
323+
pub(crate) struct PreparedImportMeta {
324+
pub imp: ImageImporter,
325+
pub prep: Box<PreparedImport>,
326+
pub digest: Digest,
327+
pub n_layers_to_fetch: usize,
328+
pub layers_total: usize,
329+
pub bytes_to_fetch: u64,
330+
pub bytes_total: u64,
331+
}
332+
333+
pub(crate) enum PreparedPullResult {
334+
Ready(PreparedImportMeta),
335+
AlreadyPresent(Box<ImageState>),
336+
}
337+
338+
pub(crate) async fn prepare_for_pull(
307339
repo: &ostree::Repo,
308340
imgref: &ImageReference,
309341
target_imgref: Option<&OstreeImageReference>,
310-
quiet: bool,
311-
prog: ProgressWriter,
312-
) -> Result<Box<ImageState>> {
342+
) -> Result<PreparedPullResult> {
313343
let ostree_imgref = &OstreeImageReference::from(imgref.clone());
314344
let mut imp = new_importer(repo, ostree_imgref).await?;
315345
if let Some(target) = target_imgref {
@@ -318,7 +348,7 @@ pub(crate) async fn pull(
318348
let prep = match imp.prepare().await? {
319349
PrepareResult::AlreadyPresent(c) => {
320350
println!("No changes in {imgref:#} => {}", c.manifest_digest);
321-
return Ok(Box::new((*c).into()));
351+
return Ok(PreparedPullResult::AlreadyPresent(Box::new((*c).into())));
322352
}
323353
PrepareResult::Ready(p) => p,
324354
};
@@ -328,30 +358,49 @@ pub(crate) async fn pull(
328358
}
329359
ostree_ext::cli::print_layer_status(&prep);
330360
let layers_to_fetch = prep.layers_to_fetch().collect::<Result<Vec<_>>>()?;
331-
let n_layers_to_fetch = layers_to_fetch.len();
332-
let layers_total = prep.all_layers().count();
333-
let bytes_to_fetch: u64 = layers_to_fetch.iter().map(|(l, _)| l.layer.size()).sum();
334-
let bytes_total: u64 = prep.all_layers().map(|l| l.layer.size()).sum();
335-
336-
let digest = prep.manifest_digest.clone();
337-
let digest_imp = prep.manifest_digest.clone();
338-
let layer_progress = imp.request_progress();
339-
let layer_byte_progress = imp.request_layer_progress();
361+
362+
let prepared_image = PreparedImportMeta {
363+
imp,
364+
n_layers_to_fetch: layers_to_fetch.len(),
365+
layers_total: prep.all_layers().count(),
366+
bytes_to_fetch: layers_to_fetch.iter().map(|(l, _)| l.layer.size()).sum(),
367+
bytes_total: prep.all_layers().map(|l| l.layer.size()).sum(),
368+
digest: prep.manifest_digest.clone(),
369+
prep,
370+
};
371+
372+
Ok(PreparedPullResult::Ready(prepared_image))
373+
}
374+
375+
#[context("Pulling")]
376+
pub(crate) async fn pull_from_prepared(
377+
repo: &ostree::Repo,
378+
imgref: &ImageReference,
379+
target_imgref: Option<&OstreeImageReference>,
380+
quiet: bool,
381+
prog: ProgressWriter,
382+
mut prepared_image: PreparedImportMeta,
383+
) -> Result<Box<ImageState>> {
384+
let layer_progress = prepared_image.imp.request_progress();
385+
let layer_byte_progress = prepared_image.imp.request_layer_progress();
386+
let digest = prepared_image.digest.clone();
387+
let digest_imp = prepared_image.digest.clone();
388+
340389
let printer = tokio::task::spawn(async move {
341390
handle_layer_progress_print(
342391
layer_progress,
343392
layer_byte_progress,
344393
digest.as_ref().into(),
345-
n_layers_to_fetch,
346-
layers_total,
347-
bytes_to_fetch,
348-
bytes_total,
394+
prepared_image.n_layers_to_fetch,
395+
prepared_image.layers_total,
396+
prepared_image.bytes_to_fetch,
397+
prepared_image.bytes_total,
349398
prog,
350399
quiet,
351400
)
352401
.await
353402
});
354-
let import = imp.import(prep).await;
403+
let import = prepared_image.imp.import(prepared_image.prep).await;
355404
let prog = printer.await?;
356405
// Both the progress and the import are done, so import is done as well
357406
prog.send(Event::ProgressSteps {
@@ -371,6 +420,7 @@ pub(crate) async fn pull(
371420
})
372421
.await;
373422
let import = import?;
423+
let ostree_imgref = &OstreeImageReference::from(imgref.clone());
374424
let wrote_imgref = target_imgref.as_ref().unwrap_or(&ostree_imgref);
375425

376426
if let Some(msg) =
@@ -382,23 +432,26 @@ pub(crate) async fn pull(
382432
Ok(Box::new((*import).into()))
383433
}
384434

385-
/// Gather all bound images in all deployments, then prune the image store,
386-
/// using the gathered images as the roots (that will not be GC'd).
387-
pub(crate) async fn prune_container_store(sysroot: &Storage) -> Result<()> {
388-
let deployments = sysroot.deployments();
389-
let mut all_bound_images = Vec::new();
390-
for deployment in deployments {
391-
let bound = crate::boundimage::query_bound_images_for_deployment(sysroot, &deployment)?;
392-
all_bound_images.extend(bound.into_iter());
435+
/// Wrapper for pulling a container image, wiring up status output.
436+
pub(crate) async fn pull(
437+
repo: &ostree::Repo,
438+
imgref: &ImageReference,
439+
target_imgref: Option<&OstreeImageReference>,
440+
quiet: bool,
441+
prog: ProgressWriter,
442+
) -> Result<Box<ImageState>> {
443+
match prepare_for_pull(repo, imgref, target_imgref).await? {
444+
PreparedPullResult::AlreadyPresent(existing) => Ok(existing),
445+
PreparedPullResult::Ready(prepared_image_meta) => Ok(pull_from_prepared(
446+
repo,
447+
imgref,
448+
target_imgref,
449+
quiet,
450+
prog,
451+
prepared_image_meta,
452+
)
453+
.await?),
393454
}
394-
// Convert to a hashset of just the image names
395-
let image_names = HashSet::from_iter(all_bound_images.iter().map(|img| img.image.as_str()));
396-
let pruned = sysroot
397-
.get_ensure_imgstore()?
398-
.prune_except_roots(&image_names)
399-
.await?;
400-
tracing::debug!("Pruned images: {}", pruned.len());
401-
Ok(())
402455
}
403456

404457
pub(crate) async fn wipe_ostree(sysroot: Sysroot) -> Result<()> {

lib/src/install.rs

+44-15
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ use serde::{Deserialize, Serialize};
5353
use self::baseline::InstallBlockDeviceOpts;
5454
use crate::boundimage::{BoundImage, ResolvedBoundImage};
5555
use crate::containerenv::ContainerExecutionInfo;
56+
use crate::deploy::{prepare_for_pull, pull_from_prepared, PreparedImportMeta, PreparedPullResult};
5657
use crate::lsm;
5758
use crate::mount::Filesystem;
5859
use crate::progress_jsonl::ProgressWriter;
@@ -711,6 +712,27 @@ async fn initialize_ostree_root(
711712
Ok((Storage::new(sysroot, &temp_run)?, has_ostree, imgstore))
712713
}
713714

715+
fn check_disk_space(
716+
repo_fd: impl AsFd,
717+
image_meta: &PreparedImportMeta,
718+
imgref: &ImageReference,
719+
) -> Result<()> {
720+
let stat = rustix::fs::fstatvfs(repo_fd)?;
721+
let bytes_avail: u64 = stat.f_bsize * stat.f_bavail;
722+
tracing::trace!("bytes_avail: {bytes_avail}");
723+
724+
if image_meta.bytes_to_fetch > bytes_avail {
725+
anyhow::bail!(
726+
"Insufficient free space for {image} (available: {bytes_avail} required: {bytes_to_fetch})",
727+
bytes_avail = ostree_ext::glib::format_size(bytes_avail),
728+
bytes_to_fetch = ostree_ext::glib::format_size(image_meta.bytes_to_fetch),
729+
image = imgref.image,
730+
);
731+
}
732+
733+
Ok(())
734+
}
735+
714736
#[context("Creating ostree deployment")]
715737
async fn install_container(
716738
state: &State,
@@ -751,21 +773,28 @@ async fn install_container(
751773

752774
// Pull the container image into the target root filesystem. Since this is
753775
// an install path, we don't need to fsync() individual layers.
754-
let pulled_image = {
755-
let spec_imgref = ImageReference::from(src_imageref.clone());
756-
let repo = &sysroot.repo();
757-
repo.set_disable_fsync(true);
758-
let r = crate::deploy::pull(
759-
repo,
760-
&spec_imgref,
761-
Some(&state.target_imgref),
762-
false,
763-
ProgressWriter::default(),
764-
)
765-
.await?;
766-
repo.set_disable_fsync(false);
767-
r
768-
};
776+
let spec_imgref = ImageReference::from(src_imageref.clone());
777+
let repo = &sysroot.repo();
778+
repo.set_disable_fsync(true);
779+
780+
let pulled_image =
781+
match prepare_for_pull(repo, &spec_imgref, Some(&state.target_imgref)).await? {
782+
PreparedPullResult::AlreadyPresent(existing) => existing,
783+
PreparedPullResult::Ready(image_meta) => {
784+
check_disk_space(root_setup.physical_root.as_fd(), &image_meta, &spec_imgref)?;
785+
pull_from_prepared(
786+
repo,
787+
&spec_imgref,
788+
Some(&state.target_imgref),
789+
false,
790+
ProgressWriter::default(),
791+
image_meta,
792+
)
793+
.await?
794+
}
795+
};
796+
797+
repo.set_disable_fsync(false);
769798

770799
// We need to read the kargs from the target merged ostree commit before
771800
// we do the deployment.

0 commit comments

Comments
 (0)