From 78fd9faa404c5fde8a7ea1d9c50fa38cdb7e91c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20Sch=C3=A4fer?= Date: Thu, 14 Sep 2023 12:09:06 +0200 Subject: [PATCH] Add support for --include-path on all pilots Allow to provision files/directories. So far only tar archives could be used to provision arbitrary data to the instance --- common/src/error.rs | 3 + doc/flake-ctl-firecracker-register.rst | 6 ++ doc/flake-ctl-podman-register.rst | 6 ++ firecracker-pilot/src/config.rs | 5 ++ firecracker-pilot/src/firecracker.rs | 43 +++++++++++++- flake-ctl/src/app.rs | 4 ++ flake-ctl/src/app_config.rs | 13 +++++ flake-ctl/src/cli.rs | 12 ++++ flake-ctl/src/main.rs | 9 ++- flake-ctl/template/container-flake.yaml | 1 + flake-ctl/template/firecracker-flake.yaml | 1 + podman-pilot/src/config.rs | 7 ++- podman-pilot/src/podman.rs | 70 ++++++++++++++++++----- 13 files changed, 158 insertions(+), 22 deletions(-) diff --git a/common/src/error.rs b/common/src/error.rs index 65912a9..c6120c9 100644 --- a/common/src/error.rs +++ b/common/src/error.rs @@ -51,6 +51,9 @@ pub enum FlakeError { #[error("Instance in use by another instance, consider @NAME argument")] AlreadyRunning, + #[error("Datasync failed, for details rerun with PILOT_DEBUG=1")] + SyncFailed, + /// OperationError pass through #[error("{}", .0)] OperationError(#[from] OperationError) diff --git a/doc/flake-ctl-firecracker-register.rst b/doc/flake-ctl-firecracker-register.rst index cfd5642..25d09d3 100644 --- a/doc/flake-ctl-firecracker-register.rst +++ b/doc/flake-ctl-firecracker-register.rst @@ -18,6 +18,7 @@ SYNOPSIS OPTIONS: --app --include-tar ... + --include-path ... --no-net --resume --overlay-size @@ -55,6 +56,11 @@ OPTIONS Name of a tar file to be included on top of the VM instance. This option can be specified multiple times +--include-path ... + + Name of a file or directory to be included on top of the VM instance. + This option can be specified multiple times + --no-net Disable networking diff --git a/doc/flake-ctl-podman-register.rst b/doc/flake-ctl-podman-register.rst index 1a4e8f0..47faa12 100644 --- a/doc/flake-ctl-podman-register.rst +++ b/doc/flake-ctl-podman-register.rst @@ -20,6 +20,7 @@ SYNOPSIS --base --container --include-tar ... + --include-path ... --info --layer ... --opt ... @@ -73,6 +74,11 @@ OPTIONS Name of a tar file to be included on top of the container instance. This option can be specified multiple times +--include-path ... + + Name of a file or directory to be included on top of the container + instance. This option can be specified multiple times + --info Print registration information from container if provided diff --git a/firecracker-pilot/src/config.rs b/firecracker-pilot/src/config.rs index b735338..e5430d0 100644 --- a/firecracker-pilot/src/config.rs +++ b/firecracker-pilot/src/config.rs @@ -116,12 +116,17 @@ impl<'a> Config<'a> { pub fn tars(&self) -> Vec<&'a str> { self.include.tar.as_ref().cloned().unwrap_or_default() } + + pub fn paths(&self) -> Vec<&'a str> { + self.include.path.as_ref().cloned().unwrap_or_default() + } } #[derive(Deserialize)] pub struct IncludeSection<'a> { #[serde(borrow)] tar: Option>, + path: Option>, } #[derive(Deserialize)] diff --git a/firecracker-pilot/src/firecracker.rs b/firecracker-pilot/src/firecracker.rs index 46c5b24..24e402b 100644 --- a/firecracker-pilot/src/firecracker.rs +++ b/firecracker-pilot/src/firecracker.rs @@ -172,7 +172,8 @@ pub fn create(program_name: &String) -> Result<(String, String), FlakeError> { // check for includes let tar_includes = config().tars(); - let has_includes = !tar_includes.is_empty(); + let path_includes = config().paths(); + let has_includes = !tar_includes.is_empty() || !path_includes.is_empty(); // Make sure meta dirs exists init_meta_dirs()?; @@ -821,7 +822,6 @@ pub fn delete_file(filename: &String, user: User) -> bool { true } -// Todo: unifiy with podman pub fn sync_includes( target: &String, user: User ) -> Result<(), FlakeError> { @@ -829,9 +829,11 @@ pub fn sync_includes( Sync custom include data to target path !*/ let tar_includes = &config().tars(); + let path_includes = &config().paths(); + for tar in tar_includes { if Lookup::is_debug() { - debug!("Adding tar include: [{}]", tar); + debug!("Provision tar archive: [{}]", tar); } let mut call = user.run("tar"); call.arg("-C").arg(target) @@ -845,6 +847,41 @@ pub fn sync_includes( debug!("{}", &String::from_utf8_lossy(&output.stderr)); } } + for path in path_includes { + if Lookup::is_debug() { + debug!("Provision path: [{}]", path); + } + sync_data( + path, &format!("{}/{}", target, path), + ["--mkpath"].to_vec(), user + )?; + } + Ok(()) +} + +pub fn sync_data( + source: &str, target: &str, options: Vec<&str>, user: User +) -> Result<(), FlakeError> { + /*! + Sync data from source path to target path + !*/ + let mut call = user.run("rsync"); + call.arg("-av"); + for option in options { + call.arg(option); + } + call.arg(source).arg(target); + if Lookup::is_debug() { + debug!("{:?}", call.get_args()); + } + let output = call.output()?; + if Lookup::is_debug() { + debug!("{}", &String::from_utf8_lossy(&output.stdout)); + debug!("{}", &String::from_utf8_lossy(&output.stderr)); + } + if !output.status.success() { + return Err(FlakeError::SyncFailed) + } Ok(()) } diff --git a/flake-ctl/src/app.rs b/flake-ctl/src/app.rs index 59304ae..470e033 100644 --- a/flake-ctl/src/app.rs +++ b/flake-ctl/src/app.rs @@ -95,6 +95,7 @@ pub fn create_container_config( base: Option<&String>, layers: Option>, includes_tar: Option>, + includes_path: Option>, resume: bool, attach: bool, run_as: Option<&String>, @@ -129,6 +130,7 @@ pub fn create_container_config( base, layers, includes_tar, + includes_path, resume, attach, run_as, @@ -155,6 +157,7 @@ pub fn create_vm_config( no_net: bool, resume: bool, includes_tar: Option>, + includes_path: Option>, ) -> bool { /*! Create app configuration for the firecracker engine. @@ -182,6 +185,7 @@ pub fn create_vm_config( no_net, resume, includes_tar, + includes_path, ) { Ok(_) => true, Err(error) => { diff --git a/flake-ctl/src/app_config.rs b/flake-ctl/src/app_config.rs index a252f50..3cff33d 100644 --- a/flake-ctl/src/app_config.rs +++ b/flake-ctl/src/app_config.rs @@ -56,6 +56,7 @@ pub struct AppContainerRuntime { #[derive(Debug, Serialize, Deserialize)] pub struct AppInclude { pub tar: Option>, + pub path: Option>, } #[derive(Debug, Serialize, Deserialize)] @@ -95,6 +96,7 @@ impl AppConfig { base: Option<&String>, layers: Option>, includes_tar: Option>, + includes_path: Option>, resume: bool, attach: bool, run_as: Option<&String>, @@ -142,6 +144,11 @@ impl AppConfig { includes_tar.as_ref().unwrap().to_vec() ); } + if includes_path.is_some() { + yaml_config.include.path = Some( + includes_path.as_ref().unwrap().to_vec() + ); + } if opts.is_some() { let mut final_opts: Vec = Vec::new(); for opt in opts.as_ref().unwrap() { @@ -176,6 +183,7 @@ impl AppConfig { no_net: bool, resume: bool, includes_tar: Option>, + includes_path: Option>, ) -> Result<(), GenericError> { /*! save stores an AppConfig to the given file @@ -204,6 +212,11 @@ impl AppConfig { includes_tar.as_ref().unwrap().to_vec() ); } + if includes_path.is_some() { + yaml_config.include.path = Some( + includes_path.as_ref().unwrap().to_vec() + ); + } if let Some(overlay_size) = overlay_size { vm_config.runtime.as_mut().unwrap() .firecracker.as_mut().unwrap() diff --git a/flake-ctl/src/cli.rs b/flake-ctl/src/cli.rs index 7eaa6b4..b43c413 100644 --- a/flake-ctl/src/cli.rs +++ b/flake-ctl/src/cli.rs @@ -155,6 +155,12 @@ pub enum Firecracker { /// specified multiple times. #[clap(long, multiple = true, requires = "overlay-size")] include_tar: Option>, + + /// Name of a file or directory to be included on top of + /// the VM instance. This option can be + /// specified multiple times. + #[clap(long, multiple = true, requires = "overlay-size")] + include_path: Option>, }, /// Remove application registration or entire VM #[clap(group( @@ -254,6 +260,12 @@ pub enum Podman { #[clap(long, multiple = true)] include_tar: Option>, + /// Name of a file or directory to be included on top of + /// the VM instance. This option can be + /// specified multiple times. + #[clap(long, multiple = true)] + include_path: Option>, + /// Resume the container from previous execution. /// If the container is still running, the app will be /// executed inside of this container instance. diff --git a/flake-ctl/src/main.rs b/flake-ctl/src/main.rs index a13f0a2..0c6f529 100644 --- a/flake-ctl/src/main.rs +++ b/flake-ctl/src/main.rs @@ -80,7 +80,7 @@ async fn main() -> Result> { // register cli::Firecracker::Register { vm, app, target, run_as, overlay_size, no_net, resume, - include_tar + include_tar, include_path } => { if app::init(Some(app)) { let mut ok = app::register( @@ -96,7 +96,8 @@ async fn main() -> Result> { overlay_size.as_ref(), *no_net, *resume, - include_tar.as_ref().cloned() + include_tar.as_ref().cloned(), + include_path.as_ref().cloned(), ); } if ! ok { @@ -140,7 +141,8 @@ async fn main() -> Result> { // register cli::Podman::Register { container, app, target, base, - layer, include_tar, resume, attach, run_as, opt, info + layer, include_tar, include_path, resume, attach, + run_as, opt, info } => { if *info { podman::print_container_info(container); @@ -157,6 +159,7 @@ async fn main() -> Result> { base.as_ref(), layer.as_ref().cloned(), include_tar.as_ref().cloned(), + include_path.as_ref().cloned(), *resume, *attach, run_as.as_ref(), diff --git a/flake-ctl/template/container-flake.yaml b/flake-ctl/template/container-flake.yaml index 0ee257b..a53c6dc 100644 --- a/flake-ctl/template/container-flake.yaml +++ b/flake-ctl/template/container-flake.yaml @@ -1,5 +1,6 @@ include: tar: ~ + path: ~ container: name: name target_app_path: path/to/program/in/container diff --git a/flake-ctl/template/firecracker-flake.yaml b/flake-ctl/template/firecracker-flake.yaml index daf1a70..2fe1b92 100644 --- a/flake-ctl/template/firecracker-flake.yaml +++ b/flake-ctl/template/firecracker-flake.yaml @@ -1,5 +1,6 @@ include: tar: ~ + path: ~ vm: name: name target_app_path: path/to/program/in/container diff --git a/podman-pilot/src/config.rs b/podman-pilot/src/config.rs index 05d649c..b9a9d84 100644 --- a/podman-pilot/src/config.rs +++ b/podman-pilot/src/config.rs @@ -121,12 +121,17 @@ impl<'a> Config<'a> { pub fn tars(&self) -> Vec<&'a str> { self.include.tar.as_ref().cloned().unwrap_or_default() } + + pub fn paths(&self) -> Vec<&'a str> { + self.include.path.as_ref().cloned().unwrap_or_default() + } } #[derive(Deserialize)] pub struct IncludeSection<'a> { #[serde(borrow)] - tar: Option> + tar: Option>, + path: Option>, } #[derive(Deserialize)] diff --git a/podman-pilot/src/podman.rs b/podman-pilot/src/podman.rs index 70eb10a..97d6d53 100644 --- a/podman-pilot/src/podman.rs +++ b/podman-pilot/src/podman.rs @@ -113,6 +113,8 @@ pub fn create( include: tar: - tar-archive-file-name-to-include + path: + - file-or-directory-to-include !*/ // Read optional @NAME pilot argument to differentiate // simultaneous instances of the same container application @@ -227,13 +229,23 @@ fn run_podman_creation(mut app: Command) -> Result { let runas = config().runtime().runas; let is_delta_container = config().container.base_container.is_some(); - let has_includes = !config().tars().is_empty(); + let has_includes = !config().tars().is_empty() || !config().paths().is_empty(); - let instance_mount_point = if is_delta_container || has_includes { + let instance_mount_point; + + if is_delta_container || has_includes { if Lookup::is_debug() { debug!("Mounting instance for provisioning workload"); } - mount_container(&cid, runas, false)? + match mount_container(&cid, runas, false) { + Ok(mount_point) => { + instance_mount_point = mount_point; + }, + Err(error) => { + call_instance("rm", &cid, "none", runas)?; + return Err(error); + } + } } else { return Ok(cid); }; @@ -261,7 +273,12 @@ fn run_podman_creation(mut app: Command) -> Result { } let app_mount_point = mount_container(layer, runas, true)?; update_removed_files(&app_mount_point, &removed_files)?; - sync_delta(&app_mount_point, &instance_mount_point, runas)?; + sync_data( + &format!("{}/", app_mount_point), + &format!("{}/", instance_mount_point), + [].to_vec(), + runas + )?; let _ = umount_container(layer, runas, true); } @@ -277,7 +294,13 @@ fn run_podman_creation(mut app: Command) -> Result { if Lookup::is_debug() { debug!("Syncing includes..."); } - sync_includes(&instance_mount_point, runas)?; + match sync_includes(&instance_mount_point, runas) { + Ok(_) => { }, + Err(error) => { + call_instance("rm", &cid, "none", runas)?; + return Err(error); + } + } } Ok(cid) } @@ -417,10 +440,11 @@ pub fn sync_includes( Sync custom include data to target path !*/ let tar_includes = &config().tars(); + let path_includes = &config().paths(); for tar in tar_includes { if Lookup::is_debug() { - debug!("Adding tar include: [{}]", tar); + debug!("Provision tar archive: [{}]", tar); } let mut call = user.run("tar"); call.arg("-C").arg(target) @@ -434,25 +458,41 @@ pub fn sync_includes( debug!("{}", &String::from_utf8_lossy(&output.stderr)); } } + for path in path_includes { + if Lookup::is_debug() { + debug!("Provision path: [{}]", path); + } + sync_data( + path, &format!("{}/{}", target, path), + ["--mkpath"].to_vec(), user + )?; + } Ok(()) } -pub fn sync_delta( - source: &String, target: &String, user: User -) -> Result<(), CommandError> { +pub fn sync_data( + source: &str, target: &str, options: Vec<&str>, user: User +) -> Result<(), FlakeError> { /*! Sync data from source path to target path !*/ let mut call = user.run("rsync"); - call.arg("-av") - .arg(format!("{}/", &source)) - .arg(format!("{}/", &target)); + call.arg("-av"); + for option in options { + call.arg(option); + } + call.arg(source).arg(target); if Lookup::is_debug() { debug!("{:?}", call.get_args()); } - - call.perform()?; - + let output = call.output()?; + if Lookup::is_debug() { + debug!("{}", &String::from_utf8_lossy(&output.stdout)); + debug!("{}", &String::from_utf8_lossy(&output.stderr)); + } + if !output.status.success() { + return Err(FlakeError::SyncFailed) + } Ok(()) }