Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

image-rs: Support to reuse meta_store #623

Merged
merged 3 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions image-rs/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::path::{Path, PathBuf};

use crate::snapshots::SnapshotType;

const DEFAULT_WORK_DIR: &str = "/var/lib/image-rs/";
pub const DEFAULT_WORK_DIR: &str = "/var/lib/image-rs/";

/// Default policy file path.
pub const POLICY_FILE_PATH: &str = "kbs:///default/security-policy/test";
Expand All @@ -33,7 +33,7 @@ pub const AUTH_FILE_PATH: &str = "kbs:///default/credential/test";
pub const DEFAULT_MAX_CONCURRENT_DOWNLOAD: usize = 3;

/// Path to the configuration file to generate ImageConfiguration
pub const CONFIGURATION_FILE_PATH: &str = "/var/lib/image-rs/config.json";
pub const CONFIGURATION_FILE_NAME: &str = "config.json";

/// `image-rs` configuration information.
#[derive(Clone, Debug, Deserialize)]
Expand Down
4 changes: 2 additions & 2 deletions image-rs/src/decoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ use std::io;
use anyhow::{bail, Result};
use oci_distribution::manifest;
use oci_spec::image::MediaType;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncRead, BufReader};

/// Error message for unhandled media type.
pub const ERR_BAD_MEDIA_TYPE: &str = "unhandled media type";

/// Represents the layer compression algorithm type,
/// and allows to decompress corresponding compressed data.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Compression {
Uncompressed,
#[default]
Expand Down
64 changes: 55 additions & 9 deletions image-rs/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ use oci_distribution::manifest::{OciDescriptor, OciImageManifest};
use oci_distribution::secrets::RegistryAuth;
use oci_distribution::Reference;
use oci_spec::image::{ImageConfiguration, Os};
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeSet, HashMap};
use std::path::{Path, PathBuf};
use std::sync::Arc;

use tokio::sync::Mutex;

use crate::bundle::{create_runtime_config, BUNDLE_ROOTFS};
use crate::config::{ImageConfig, CONFIGURATION_FILE_PATH};
use crate::config::{ImageConfig, CONFIGURATION_FILE_NAME, DEFAULT_WORK_DIR};
use crate::decoder::Compression;
use crate::meta_store::{MetaStore, METAFILE};
use crate::pull::PullClient;
Expand All @@ -38,7 +38,7 @@ use crate::nydus::{service, utils};
pub const IMAGE_SECURITY_CONFIG_DIR: &str = "/run/image-security";

/// The metadata info for container image layer.
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq)]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct LayerMeta {
/// Image layer compression algorithm type.
pub decoder: Compression,
Expand All @@ -57,7 +57,7 @@ pub struct LayerMeta {
}

/// The metadata info for container image.
#[derive(Clone, Debug, Default, Deserialize)]
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ImageMeta {
/// The digest of the image configuration.
pub id: String,
Expand Down Expand Up @@ -95,8 +95,10 @@ pub struct ImageClient {
impl Default for ImageClient {
// construct a default instance of `ImageClient`
fn default() -> ImageClient {
let config = ImageConfig::try_from(Path::new(CONFIGURATION_FILE_PATH)).unwrap_or_default();
let meta_store = MetaStore::try_from(Path::new(METAFILE)).unwrap_or_default();
let work_dir = Path::new(DEFAULT_WORK_DIR);
let config = ImageConfig::try_from(work_dir.join(CONFIGURATION_FILE_NAME).as_path())
.unwrap_or_default();
let meta_store = MetaStore::try_from(work_dir.join(METAFILE).as_path()).unwrap_or_default();
let snapshots = Self::init_snapshots(&config, &meta_store);

ImageClient {
Expand Down Expand Up @@ -153,8 +155,10 @@ impl ImageClient {

/// Create an ImageClient instance with specific work directory.
pub fn new(image_work_dir: PathBuf) -> Self {
let config = ImageConfig::new(image_work_dir);
let meta_store = MetaStore::try_from(Path::new(METAFILE)).unwrap_or_default();
let work_dir = image_work_dir.as_path();
let config = ImageConfig::try_from(work_dir.join(CONFIGURATION_FILE_NAME).as_path())
.unwrap_or_else(|_| ImageConfig::new(image_work_dir.clone()));
let meta_store = MetaStore::try_from(work_dir.join(METAFILE).as_path()).unwrap_or_default();
let snapshots = Self::init_snapshots(&config, &meta_store);

Self {
Expand Down Expand Up @@ -510,7 +514,7 @@ fn create_bundle(
#[cfg(test)]
mod tests {
use super::*;

use std::fs;
use test_utils::assert_retry;

#[tokio::test]
Expand Down Expand Up @@ -628,4 +632,46 @@ mod tests {
// Assert that image is pulled only once.
assert_eq!(image_client.meta_store.lock().await.image_db.len(), 1);
}

#[tokio::test]
async fn test_meta_store_reuse() {
let work_dir = tempfile::tempdir().unwrap();

let image = "mcr.microsoft.com/hello-world";

let mut image_client = ImageClient::new(work_dir.path().to_path_buf());

let bundle_dir = tempfile::tempdir().unwrap();
if let Err(e) = image_client
.pull_image(image, bundle_dir.path(), &None, &None)
.await
{
panic!("failed to download image: {}", e);
}

// Create a second temporary directory for the second image client
let work_dir_2 = tempfile::tempdir().unwrap();
fs::create_dir_all(work_dir_2.path()).unwrap();

// Lock the meta store and write its data to a file in the second work directory
// This allows the second image client to reuse the meta store and layers from the first image client
let store = image_client.meta_store.lock().await;
let meta_store_path = work_dir_2.path().to_str().unwrap().to_owned() + "/meta_store.json";
store.write_to_file(&meta_store_path).unwrap();

// Initialize the second image client with the second temporary directory
let mut image_client_2 = ImageClient::new(work_dir_2.path().to_path_buf());

let bundle_dir_2 = tempfile::tempdir().unwrap();
if let Err(e) = image_client_2
.pull_image(image, bundle_dir_2.path(), &None, &None)
.await
{
panic!("failed to download image: {}", e);
}

// Verify that the "layers" directory does not exist in the second work directory
// This confirms that the second image client reused the meta store and layers from the first image client
assert!(!work_dir_2.path().join("layers").exists());
}
}
13 changes: 11 additions & 2 deletions image-rs/src/meta_store.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::{anyhow, Result};
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::File;
use std::path::Path;
Expand All @@ -9,7 +9,7 @@ use crate::image::{ImageMeta, LayerMeta};
pub const METAFILE: &str = "meta_store.json";

/// `image-rs` container metadata storage database.
#[derive(Clone, Default, Deserialize, Debug)]
#[derive(Clone, Default, Serialize, Deserialize, Debug)]
pub struct MetaStore {
// image_db holds map of image ID with image data.
pub image_db: HashMap<String, ImageMeta>,
Expand All @@ -31,3 +31,12 @@ impl TryFrom<&Path> for MetaStore {
.map_err(|e| anyhow!("failed to parse metastore file {}", e.to_string()))
}
}

impl MetaStore {
pub fn write_to_file(&self, path: &str) -> Result<()> {
let file = File::create(path)
.map_err(|e| anyhow!("failed to create metastore file: {}", e.to_string()))?;
serde_json::to_writer(file, &self)
.map_err(|e| anyhow!("failed to write metastore to file: {}", e.to_string()))
}
}
Loading