From 2ad235c038b8b67fcf2311c1c665e7859be80a31 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Thu, 23 May 2024 20:27:31 +0200 Subject: [PATCH] Feat: Allow to set `base_dir` (#219) This pr introduce the ability to set the `base_dir` used by the `namespace`. This allow users to reuse the same node's directories (config and database) and re-start the network from the latest state in sequential runs. Thx! fix #218 --- crates/configuration/src/global_settings.rs | 41 ++++++++++++++++++- crates/examples/examples/common/lib.rs | 33 +++++++++++++++ ...all_network.rs => small_network_config.rs} | 0 .../examples/small_network_with_base_dir.rs | 17 ++++++++ crates/orchestrator/src/lib.rs | 16 +++++++- crates/provider/src/docker/namespace.rs | 22 ++++++++-- crates/provider/src/docker/node.rs | 8 ++-- crates/provider/src/docker/provider.rs | 25 ++++++++++- crates/provider/src/kubernetes/namespace.rs | 22 ++++++++-- crates/provider/src/kubernetes/provider.rs | 25 ++++++++++- crates/provider/src/lib.rs | 5 +++ crates/provider/src/native/namespace.rs | 22 ++++++++-- crates/provider/src/native/provider.rs | 24 ++++++++++- 13 files changed, 238 insertions(+), 22 deletions(-) create mode 100644 crates/examples/examples/common/lib.rs rename crates/examples/examples/{small_network.rs => small_network_config.rs} (100%) create mode 100644 crates/examples/examples/small_network_with_base_dir.rs diff --git a/crates/configuration/src/global_settings.rs b/crates/configuration/src/global_settings.rs index f584206..95cee31 100644 --- a/crates/configuration/src/global_settings.rs +++ b/crates/configuration/src/global_settings.rs @@ -1,4 +1,10 @@ -use std::{error::Error, fmt::Display, net::IpAddr, str::FromStr}; +use std::{ + error::Error, + fmt::Display, + net::IpAddr, + path::{Path, PathBuf}, + str::FromStr, +}; use multiaddr::Multiaddr; use serde::{Deserialize, Serialize}; @@ -15,14 +21,24 @@ use crate::{ /// Global settings applied to an entire network. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct GlobalSettings { + /// Global bootnodes to use (we will then add more) #[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)] bootnodes_addresses: Vec<Multiaddr>, // TODO: parse both case in zombienet node version to avoid renamed ? + /// Glocal spawn timeout #[serde(rename = "timeout")] network_spawn_timeout: Duration, + // TODO: not used yet + /// Node spawn timeout #[serde(default = "default_node_spawn_timeout")] node_spawn_timeout: Duration, + // TODO: not used yet + /// Local ip to use for construct the direct links local_ip: Option<IpAddr>, + /// Directory to use as base dir + /// Used to reuse the same files (database) from a previous run, + /// also note that we will override the content of some of those files. + base_dir: Option<PathBuf>, } impl GlobalSettings { @@ -45,6 +61,12 @@ impl GlobalSettings { pub fn local_ip(&self) -> Option<&IpAddr> { self.local_ip.as_ref() } + + /// Base directory to use (instead a random tmp one) + /// All the artifacts will be created in this directory. + pub fn base_dir(&self) -> Option<&Path> { + self.base_dir.as_deref() + } } /// A global settings builder, used to build [`GlobalSettings`] declaratively with fields validation. @@ -61,6 +83,7 @@ impl Default for GlobalSettingsBuilder { network_spawn_timeout: 1000, node_spawn_timeout: 300, local_ip: None, + base_dir: None, }, errors: vec![], } @@ -143,6 +166,17 @@ impl GlobalSettingsBuilder { } } + /// Set the directory to use as base (instead of a random tmp one). + pub fn with_base_dir(self, base_dir: impl Into<PathBuf>) -> Self { + Self::transition( + GlobalSettings { + base_dir: Some(base_dir.into()), + ..self.config + }, + self.errors, + ) + } + /// Seals the builder and returns a [`GlobalSettings`] if there are no validation errors, else returns errors. pub fn build(self) -> Result<GlobalSettings, Vec<anyhow::Error>> { if !self.errors.is_empty() { @@ -171,6 +205,7 @@ mod tests { .with_network_spawn_timeout(600) .with_node_spawn_timeout(120) .with_local_ip("10.0.0.1") + .with_base_dir("/home/nonroot/mynetwork") .build() .unwrap(); @@ -192,6 +227,10 @@ mod tests { .as_str(), "10.0.0.1" ); + assert_eq!( + global_settings_config.base_dir().unwrap(), + Path::new("/home/nonroot/mynetwork") + ); } #[test] diff --git a/crates/examples/examples/common/lib.rs b/crates/examples/examples/common/lib.rs new file mode 100644 index 0000000..3fb2359 --- /dev/null +++ b/crates/examples/examples/common/lib.rs @@ -0,0 +1,33 @@ +use std::path::Path; + +use zombienet_sdk::{NetworkConfig, NetworkConfigBuilder}; + +#[allow(dead_code)] +pub fn small_network_config( + custom_base_dir: Option<&Path>, +) -> Result<NetworkConfig, Vec<anyhow::Error>> { + // let config = + let builder = NetworkConfigBuilder::new() + .with_relaychain(|r| { + r.with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image("docker.io/parity/polkadot:latest") + .with_node(|node| node.with_name("alice")) + .with_node(|node| node.with_name("bob")) + }) + .with_parachain(|p| { + p.with_id(2000).cumulus_based(true).with_collator(|n| { + n.with_name("collator") + .with_command("polkadot-parachain") + .with_image("docker.io/parity/polkadot-parachain:latest") + }) + }); + + if let Some(base_dir) = custom_base_dir { + builder + .with_global_settings(|g| g.with_base_dir(base_dir)) + .build() + } else { + builder.build() + } +} diff --git a/crates/examples/examples/small_network.rs b/crates/examples/examples/small_network_config.rs similarity index 100% rename from crates/examples/examples/small_network.rs rename to crates/examples/examples/small_network_config.rs diff --git a/crates/examples/examples/small_network_with_base_dir.rs b/crates/examples/examples/small_network_with_base_dir.rs new file mode 100644 index 0000000..296d61a --- /dev/null +++ b/crates/examples/examples/small_network_with_base_dir.rs @@ -0,0 +1,17 @@ +use std::path::Path; + +use zombienet_sdk::NetworkConfigExt; + +#[path = "./common/lib.rs"] +mod common; + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt::init(); + let config = common::small_network_config(Some(Path::new("/tmp/zombie-1"))).unwrap(); + let _network = config.spawn_docker().await.unwrap(); + + // For now let just loop.... + #[allow(clippy::empty_loop)] + loop {} +} diff --git a/crates/orchestrator/src/lib.rs b/crates/orchestrator/src/lib.rs index 999d4ce..14468f7 100644 --- a/crates/orchestrator/src/lib.rs +++ b/crates/orchestrator/src/lib.rs @@ -85,7 +85,14 @@ where })?; // create namespace - let ns = self.provider.create_namespace().await?; + let ns = if let Some(base_dir) = network_spec.global_settings.base_dir() { + self.provider + .create_namespace_with_base_dir(base_dir) + .await? + } else { + self.provider.create_namespace().await? + }; + info!("🧰 ns: {}", ns.name()); info!("🧰 base_dir: {:?}", ns.base_dir()); @@ -118,7 +125,12 @@ where debug!("parachain chain-spec built!"); // TODO: this need to be abstracted in a single call to generate_files. - scoped_fs.create_dir(para.id.to_string()).await?; + if network_spec.global_settings.base_dir().is_some() { + scoped_fs.create_dir_all(para.id.to_string()).await?; + } else { + scoped_fs.create_dir(para.id.to_string()).await?; + }; + // create wasm/state para.genesis_state .build( diff --git a/crates/provider/src/docker/namespace.rs b/crates/provider/src/docker/namespace.rs index 06c0306..ddea343 100644 --- a/crates/provider/src/docker/namespace.rs +++ b/crates/provider/src/docker/namespace.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, - path::PathBuf, + path::{Path, PathBuf}, sync::{Arc, Weak}, }; @@ -8,7 +8,7 @@ use anyhow::anyhow; use async_trait::async_trait; use support::{constants::THIS_IS_A_BUG, fs::FileSystem}; use tokio::sync::{Mutex, RwLock}; -use tracing::{debug, trace}; +use tracing::{debug, trace, warn}; use uuid::Uuid; use super::{ @@ -52,10 +52,24 @@ where capabilities: &ProviderCapabilities, docker_client: &DockerClient, filesystem: &FS, + custom_base_dir: Option<&Path>, ) -> Result<Arc<Self>, ProviderError> { let name = format!("{}{}", NAMESPACE_PREFIX, Uuid::new_v4()); - let base_dir = PathBuf::from_iter([tmp_dir, &PathBuf::from(&name)]); - filesystem.create_dir(&base_dir).await?; + let base_dir = if let Some(custom_base_dir) = custom_base_dir { + if !filesystem.exists(custom_base_dir).await { + filesystem.create_dir(custom_base_dir).await?; + } else { + warn!( + "âš ï¸ Using and existing directory {} as base dir", + custom_base_dir.to_string_lossy() + ); + } + PathBuf::from(custom_base_dir) + } else { + let base_dir = PathBuf::from_iter([tmp_dir, &PathBuf::from(&name)]); + filesystem.create_dir(&base_dir).await?; + base_dir + }; let namespace = Arc::new_cyclic(|weak| DockerNamespace { weak: weak.clone(), diff --git a/crates/provider/src/docker/node.rs b/crates/provider/src/docker/node.rs index 42af95f..4828b6e 100644 --- a/crates/provider/src/docker/node.rs +++ b/crates/provider/src/docker/node.rs @@ -91,10 +91,10 @@ where let log_path = base_dir.join("node.log"); try_join!( - filesystem.create_dir(&config_dir), - filesystem.create_dir(&data_dir), - filesystem.create_dir(&relay_data_dir), - filesystem.create_dir(&scripts_dir), + filesystem.create_dir_all(&config_dir), + filesystem.create_dir_all(&data_dir), + filesystem.create_dir_all(&relay_data_dir), + filesystem.create_dir_all(&scripts_dir), )?; let node = Arc::new(DockerNode { diff --git a/crates/provider/src/docker/provider.rs b/crates/provider/src/docker/provider.rs index 5acd7c2..6f72276 100644 --- a/crates/provider/src/docker/provider.rs +++ b/crates/provider/src/docker/provider.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, - path::PathBuf, + path::{Path, PathBuf}, sync::{Arc, Weak}, }; @@ -84,6 +84,29 @@ where &self.capabilities, &self.docker_client, &self.filesystem, + None, + ) + .await?; + + self.namespaces + .write() + .await + .insert(namespace.name().to_string(), namespace.clone()); + + Ok(namespace) + } + + async fn create_namespace_with_base_dir( + &self, + base_dir: &Path, + ) -> Result<DynNamespace, ProviderError> { + let namespace = DockerNamespace::new( + &self.weak, + &self.tmp_dir, + &self.capabilities, + &self.docker_client, + &self.filesystem, + Some(base_dir), ) .await?; diff --git a/crates/provider/src/kubernetes/namespace.rs b/crates/provider/src/kubernetes/namespace.rs index ac43623..c879255 100644 --- a/crates/provider/src/kubernetes/namespace.rs +++ b/crates/provider/src/kubernetes/namespace.rs @@ -1,7 +1,7 @@ use std::{ collections::{BTreeMap, HashMap}, env, - path::PathBuf, + path::{Path, PathBuf}, sync::{Arc, Weak}, }; @@ -15,7 +15,7 @@ use k8s_openapi::{ }; use support::{constants::THIS_IS_A_BUG, fs::FileSystem, replacer::apply_replacements}; use tokio::sync::{Mutex, RwLock}; -use tracing::{debug, trace}; +use tracing::{debug, trace, warn}; use uuid::Uuid; use super::{client::KubernetesClient, node::KubernetesNode}; @@ -59,10 +59,24 @@ where capabilities: &ProviderCapabilities, k8s_client: &KubernetesClient, filesystem: &FS, + custom_base_dir: Option<&Path>, ) -> Result<Arc<Self>, ProviderError> { let name = format!("{}{}", NAMESPACE_PREFIX, Uuid::new_v4()); - let base_dir = PathBuf::from_iter([tmp_dir, &PathBuf::from(&name)]); - filesystem.create_dir(&base_dir).await?; + let base_dir = if let Some(custom_base_dir) = custom_base_dir { + if !filesystem.exists(custom_base_dir).await { + filesystem.create_dir(custom_base_dir).await?; + } else { + warn!( + "âš ï¸ Using and existing directory {} as base dir", + custom_base_dir.to_string_lossy() + ); + } + PathBuf::from(custom_base_dir) + } else { + let base_dir = PathBuf::from_iter([tmp_dir, &PathBuf::from(&name)]); + filesystem.create_dir(&base_dir).await?; + base_dir + }; let namespace = Arc::new_cyclic(|weak| KubernetesNamespace { weak: weak.clone(), diff --git a/crates/provider/src/kubernetes/provider.rs b/crates/provider/src/kubernetes/provider.rs index 29cba80..d54f949 100644 --- a/crates/provider/src/kubernetes/provider.rs +++ b/crates/provider/src/kubernetes/provider.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, - path::PathBuf, + path::{Path, PathBuf}, sync::{Arc, Weak}, }; @@ -84,6 +84,29 @@ where &self.capabilities, &self.k8s_client, &self.filesystem, + None, + ) + .await?; + + self.namespaces + .write() + .await + .insert(namespace.name().to_string(), namespace.clone()); + + Ok(namespace) + } + + async fn create_namespace_with_base_dir( + &self, + base_dir: &Path, + ) -> Result<DynNamespace, ProviderError> { + let namespace = KubernetesNamespace::new( + &self.weak, + &self.tmp_dir, + &self.capabilities, + &self.k8s_client, + &self.filesystem, + Some(base_dir), ) .await?; diff --git a/crates/provider/src/lib.rs b/crates/provider/src/lib.rs index 0e0abec..68563b8 100644 --- a/crates/provider/src/lib.rs +++ b/crates/provider/src/lib.rs @@ -122,6 +122,11 @@ pub trait Provider { async fn namespaces(&self) -> HashMap<String, DynNamespace>; async fn create_namespace(&self) -> Result<DynNamespace, ProviderError>; + + async fn create_namespace_with_base_dir( + &self, + base_dir: &Path, + ) -> Result<DynNamespace, ProviderError>; } pub type DynProvider = Arc<dyn Provider + Send + Sync>; diff --git a/crates/provider/src/native/namespace.rs b/crates/provider/src/native/namespace.rs index ddefcc1..e8f82d6 100644 --- a/crates/provider/src/native/namespace.rs +++ b/crates/provider/src/native/namespace.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, - path::PathBuf, + path::{Path, PathBuf}, sync::{Arc, Weak}, }; @@ -8,7 +8,7 @@ use anyhow::anyhow; use async_trait::async_trait; use support::fs::FileSystem; use tokio::sync::RwLock; -use tracing::trace; +use tracing::{trace, warn}; use uuid::Uuid; use super::node::{NativeNode, NativeNodeOptions}; @@ -43,10 +43,24 @@ where tmp_dir: &PathBuf, capabilities: &ProviderCapabilities, filesystem: &FS, + custom_base_dir: Option<&Path>, ) -> Result<Arc<Self>, ProviderError> { let name = format!("{}{}", NAMESPACE_PREFIX, Uuid::new_v4()); - let base_dir = PathBuf::from_iter([tmp_dir, &PathBuf::from(&name)]); - filesystem.create_dir(&base_dir).await?; + let base_dir = if let Some(custom_base_dir) = custom_base_dir { + if !filesystem.exists(custom_base_dir).await { + filesystem.create_dir(custom_base_dir).await?; + } else { + warn!( + "âš ï¸ Using and existing directory {} as base dir", + custom_base_dir.to_string_lossy() + ); + } + PathBuf::from(custom_base_dir) + } else { + let base_dir = PathBuf::from_iter([tmp_dir, &PathBuf::from(&name)]); + filesystem.create_dir(&base_dir).await?; + base_dir + }; Ok(Arc::new_cyclic(|weak| NativeNamespace { weak: weak.clone(), diff --git a/crates/provider/src/native/provider.rs b/crates/provider/src/native/provider.rs index 799c379..5e7c528 100644 --- a/crates/provider/src/native/provider.rs +++ b/crates/provider/src/native/provider.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, - path::PathBuf, + path::{Path, PathBuf}, sync::{Arc, Weak}, }; @@ -83,6 +83,28 @@ where &self.tmp_dir, &self.capabilities, &self.filesystem, + None, + ) + .await?; + + self.namespaces + .write() + .await + .insert(namespace.name().to_string(), namespace.clone()); + + Ok(namespace) + } + + async fn create_namespace_with_base_dir( + &self, + base_dir: &Path, + ) -> Result<DynNamespace, ProviderError> { + let namespace = NativeNamespace::new( + &self.weak, + &self.tmp_dir, + &self.capabilities, + &self.filesystem, + Some(base_dir), ) .await?; -- GitLab