fix: migrate vendor rustfmt.toml to stable-only features
- Update vendor/pezkuwi-zombienet-sdk/rustfmt.toml to stable-only - Reformat 74 vendor files with stable rustfmt - Remove nightly-only features causing CI failures
This commit is contained in:
@@ -302,7 +302,9 @@ impl<T: Config> ExtrinsicEvents<T> {
|
|||||||
///
|
///
|
||||||
/// This works in the same way that [`events::Events::find()`] does, with the
|
/// This works in the same way that [`events::Events::find()`] does, with the
|
||||||
/// exception that it filters out events not related to the submitted extrinsic.
|
/// exception that it filters out events not related to the submitted extrinsic.
|
||||||
pub fn find<'a, Ev: events::StaticEvent + 'a>(&'a self) -> impl Iterator<Item = Result<Ev, EventsError>> + 'a {
|
pub fn find<'a, Ev: events::StaticEvent + 'a>(
|
||||||
|
&'a self,
|
||||||
|
) -> impl Iterator<Item = Result<Ev, EventsError>> + 'a {
|
||||||
self.iter().filter_map(|ev| ev.and_then(|ev| ev.as_event::<Ev>()).transpose())
|
self.iter().filter_map(|ev| ev.and_then(|ev| ev.as_event::<Ev>()).transpose())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+263
-307
@@ -1,383 +1,339 @@
|
|||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
net::IpAddr,
|
net::IpAddr,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use multiaddr::Multiaddr;
|
use multiaddr::Multiaddr;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
shared::{
|
shared::{
|
||||||
errors::{ConfigError, FieldError},
|
errors::{ConfigError, FieldError},
|
||||||
helpers::{merge_errors, merge_errors_vecs},
|
helpers::{merge_errors, merge_errors_vecs},
|
||||||
types::Duration,
|
types::Duration,
|
||||||
},
|
},
|
||||||
utils::{default_as_true, default_node_spawn_timeout, default_timeout},
|
utils::{default_as_true, default_node_spawn_timeout, default_timeout},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Global settings applied to an entire network.
|
/// Global settings applied to an entire network.
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct GlobalSettings {
|
pub struct GlobalSettings {
|
||||||
/// Global bootnodes to use (we will then add more)
|
/// Global bootnodes to use (we will then add more)
|
||||||
#[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
|
#[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
|
||||||
bootnodes_addresses: Vec<Multiaddr>,
|
bootnodes_addresses: Vec<Multiaddr>,
|
||||||
// TODO: parse both case in zombienet node version to avoid renamed ?
|
// TODO: parse both case in zombienet node version to avoid renamed ?
|
||||||
/// Global spawn timeout
|
/// Global spawn timeout
|
||||||
#[serde(rename = "timeout", default = "default_timeout")]
|
#[serde(rename = "timeout", default = "default_timeout")]
|
||||||
network_spawn_timeout: Duration,
|
network_spawn_timeout: Duration,
|
||||||
// TODO: not used yet
|
// TODO: not used yet
|
||||||
/// Node spawn timeout
|
/// Node spawn timeout
|
||||||
#[serde(default = "default_node_spawn_timeout")]
|
#[serde(default = "default_node_spawn_timeout")]
|
||||||
node_spawn_timeout: Duration,
|
node_spawn_timeout: Duration,
|
||||||
// TODO: not used yet
|
// TODO: not used yet
|
||||||
/// Local ip to use for construct the direct links
|
/// Local ip to use for construct the direct links
|
||||||
local_ip: Option<IpAddr>,
|
local_ip: Option<IpAddr>,
|
||||||
/// Directory to use as base dir
|
/// Directory to use as base dir
|
||||||
/// Used to reuse the same files (database) from a previous run,
|
/// Used to reuse the same files (database) from a previous run,
|
||||||
/// also note that we will override the content of some of those files.
|
/// also note that we will override the content of some of those files.
|
||||||
base_dir: Option<PathBuf>,
|
base_dir: Option<PathBuf>,
|
||||||
/// Number of concurrent spawning process to launch, None means try to spawn all at the same time.
|
/// Number of concurrent spawning process to launch, None means try to spawn all at the same time.
|
||||||
spawn_concurrency: Option<usize>,
|
spawn_concurrency: Option<usize>,
|
||||||
/// If enabled, will launch a task to monitor nodes' liveness and tear down the network if there are any.
|
/// If enabled, will launch a task to monitor nodes' liveness and tear down the network if there are any.
|
||||||
#[serde(default = "default_as_true")]
|
#[serde(default = "default_as_true")]
|
||||||
tear_down_on_failure: bool,
|
tear_down_on_failure: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalSettings {
|
impl GlobalSettings {
|
||||||
/// External bootnode address.
|
/// External bootnode address.
|
||||||
pub fn bootnodes_addresses(&self) -> Vec<&Multiaddr> {
|
pub fn bootnodes_addresses(&self) -> Vec<&Multiaddr> {
|
||||||
self.bootnodes_addresses.iter().collect()
|
self.bootnodes_addresses.iter().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Global spawn timeout in seconds.
|
/// Global spawn timeout in seconds.
|
||||||
pub fn network_spawn_timeout(&self) -> Duration {
|
pub fn network_spawn_timeout(&self) -> Duration {
|
||||||
self.network_spawn_timeout
|
self.network_spawn_timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Individual node spawn timeout in seconds.
|
/// Individual node spawn timeout in seconds.
|
||||||
pub fn node_spawn_timeout(&self) -> Duration {
|
pub fn node_spawn_timeout(&self) -> Duration {
|
||||||
self.node_spawn_timeout
|
self.node_spawn_timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Local IP used to expose local services (including RPC, metrics and monitoring).
|
/// Local IP used to expose local services (including RPC, metrics and monitoring).
|
||||||
pub fn local_ip(&self) -> Option<&IpAddr> {
|
pub fn local_ip(&self) -> Option<&IpAddr> {
|
||||||
self.local_ip.as_ref()
|
self.local_ip.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Base directory to use (instead a random tmp one)
|
/// Base directory to use (instead a random tmp one)
|
||||||
/// All the artifacts will be created in this directory.
|
/// All the artifacts will be created in this directory.
|
||||||
pub fn base_dir(&self) -> Option<&Path> {
|
pub fn base_dir(&self) -> Option<&Path> {
|
||||||
self.base_dir.as_deref()
|
self.base_dir.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Number of concurrent spawning process to launch
|
/// Number of concurrent spawning process to launch
|
||||||
pub fn spawn_concurrency(&self) -> Option<usize> {
|
pub fn spawn_concurrency(&self) -> Option<usize> {
|
||||||
self.spawn_concurrency
|
self.spawn_concurrency
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A flag to tear down the network if there are any unresponsive nodes detected.
|
/// A flag to tear down the network if there are any unresponsive nodes detected.
|
||||||
pub fn tear_down_on_failure(&self) -> bool {
|
pub fn tear_down_on_failure(&self) -> bool {
|
||||||
self.tear_down_on_failure
|
self.tear_down_on_failure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GlobalSettings {
|
impl Default for GlobalSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
bootnodes_addresses: Default::default(),
|
bootnodes_addresses: Default::default(),
|
||||||
network_spawn_timeout: default_timeout(),
|
network_spawn_timeout: default_timeout(),
|
||||||
node_spawn_timeout: default_node_spawn_timeout(),
|
node_spawn_timeout: default_node_spawn_timeout(),
|
||||||
local_ip: Default::default(),
|
local_ip: Default::default(),
|
||||||
base_dir: Default::default(),
|
base_dir: Default::default(),
|
||||||
spawn_concurrency: Default::default(),
|
spawn_concurrency: Default::default(),
|
||||||
tear_down_on_failure: true,
|
tear_down_on_failure: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A global settings builder, used to build [`GlobalSettings`] declaratively with fields validation.
|
/// A global settings builder, used to build [`GlobalSettings`] declaratively with fields validation.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct GlobalSettingsBuilder {
|
pub struct GlobalSettingsBuilder {
|
||||||
config: GlobalSettings,
|
config: GlobalSettings,
|
||||||
errors: Vec<anyhow::Error>,
|
errors: Vec<anyhow::Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalSettingsBuilder {
|
impl GlobalSettingsBuilder {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transition to the next state of the builder.
|
// Transition to the next state of the builder.
|
||||||
fn transition(config: GlobalSettings, errors: Vec<anyhow::Error>) -> Self {
|
fn transition(config: GlobalSettings, errors: Vec<anyhow::Error>) -> Self {
|
||||||
Self { config, errors }
|
Self { config, errors }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the external bootnode address.
|
/// Set the external bootnode address.
|
||||||
///
|
///
|
||||||
/// Note: Bootnode address replacements are NOT supported here.
|
/// Note: Bootnode address replacements are NOT supported here.
|
||||||
/// Only arguments (`args`) support dynamic replacements. Bootnode addresses must be a valid address.
|
/// Only arguments (`args`) support dynamic replacements. Bootnode addresses must be a valid address.
|
||||||
pub fn with_raw_bootnodes_addresses<T>(self, bootnodes_addresses: Vec<T>) -> Self
|
pub fn with_raw_bootnodes_addresses<T>(self, bootnodes_addresses: Vec<T>) -> Self
|
||||||
where
|
where
|
||||||
T: TryInto<Multiaddr> + Display + Copy,
|
T: TryInto<Multiaddr> + Display + Copy,
|
||||||
T::Error: Error + Send + Sync + 'static,
|
T::Error: Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let mut addrs = vec![];
|
let mut addrs = vec![];
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
|
|
||||||
for (index, addr) in bootnodes_addresses.into_iter().enumerate() {
|
for (index, addr) in bootnodes_addresses.into_iter().enumerate() {
|
||||||
match addr.try_into() {
|
match addr.try_into() {
|
||||||
Ok(addr) => addrs.push(addr),
|
Ok(addr) => addrs.push(addr),
|
||||||
Err(error) => errors.push(
|
Err(error) => errors.push(
|
||||||
FieldError::BootnodesAddress(index, addr.to_string(), error.into()).into(),
|
FieldError::BootnodesAddress(index, addr.to_string(), error.into()).into(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::transition(
|
Self::transition(
|
||||||
GlobalSettings {
|
GlobalSettings { bootnodes_addresses: addrs, ..self.config },
|
||||||
bootnodes_addresses: addrs,
|
merge_errors_vecs(self.errors, errors),
|
||||||
..self.config
|
)
|
||||||
},
|
}
|
||||||
merge_errors_vecs(self.errors, errors),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set global spawn timeout in seconds.
|
/// Set global spawn timeout in seconds.
|
||||||
pub fn with_network_spawn_timeout(self, timeout: Duration) -> Self {
|
pub fn with_network_spawn_timeout(self, timeout: Duration) -> Self {
|
||||||
Self::transition(
|
Self::transition(
|
||||||
GlobalSettings {
|
GlobalSettings { network_spawn_timeout: timeout, ..self.config },
|
||||||
network_spawn_timeout: timeout,
|
self.errors,
|
||||||
..self.config
|
)
|
||||||
},
|
}
|
||||||
self.errors,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set individual node spawn timeout in seconds.
|
/// Set individual node spawn timeout in seconds.
|
||||||
pub fn with_node_spawn_timeout(self, timeout: Duration) -> Self {
|
pub fn with_node_spawn_timeout(self, timeout: Duration) -> Self {
|
||||||
Self::transition(
|
Self::transition(GlobalSettings { node_spawn_timeout: timeout, ..self.config }, self.errors)
|
||||||
GlobalSettings {
|
}
|
||||||
node_spawn_timeout: timeout,
|
|
||||||
..self.config
|
|
||||||
},
|
|
||||||
self.errors,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set local IP used to expose local services (including RPC, metrics and monitoring).
|
/// Set local IP used to expose local services (including RPC, metrics and monitoring).
|
||||||
pub fn with_local_ip(self, local_ip: &str) -> Self {
|
pub fn with_local_ip(self, local_ip: &str) -> Self {
|
||||||
match IpAddr::from_str(local_ip) {
|
match IpAddr::from_str(local_ip) {
|
||||||
Ok(local_ip) => Self::transition(
|
Ok(local_ip) => Self::transition(
|
||||||
GlobalSettings {
|
GlobalSettings { local_ip: Some(local_ip), ..self.config },
|
||||||
local_ip: Some(local_ip),
|
self.errors,
|
||||||
..self.config
|
),
|
||||||
},
|
Err(error) => Self::transition(
|
||||||
self.errors,
|
self.config,
|
||||||
),
|
merge_errors(self.errors, FieldError::LocalIp(error.into()).into()),
|
||||||
Err(error) => Self::transition(
|
),
|
||||||
self.config,
|
}
|
||||||
merge_errors(self.errors, FieldError::LocalIp(error.into()).into()),
|
}
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the directory to use as base (instead of a random tmp one).
|
/// 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 {
|
pub fn with_base_dir(self, base_dir: impl Into<PathBuf>) -> Self {
|
||||||
Self::transition(
|
Self::transition(
|
||||||
GlobalSettings {
|
GlobalSettings { base_dir: Some(base_dir.into()), ..self.config },
|
||||||
base_dir: Some(base_dir.into()),
|
self.errors,
|
||||||
..self.config
|
)
|
||||||
},
|
}
|
||||||
self.errors,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the spawn concurrency
|
/// Set the spawn concurrency
|
||||||
pub fn with_spawn_concurrency(self, spawn_concurrency: usize) -> Self {
|
pub fn with_spawn_concurrency(self, spawn_concurrency: usize) -> Self {
|
||||||
Self::transition(
|
Self::transition(
|
||||||
GlobalSettings {
|
GlobalSettings { spawn_concurrency: Some(spawn_concurrency), ..self.config },
|
||||||
spawn_concurrency: Some(spawn_concurrency),
|
self.errors,
|
||||||
..self.config
|
)
|
||||||
},
|
}
|
||||||
self.errors,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the `tear_down_on_failure` flag
|
/// Set the `tear_down_on_failure` flag
|
||||||
pub fn with_tear_down_on_failure(self, tear_down_on_failure: bool) -> Self {
|
pub fn with_tear_down_on_failure(self, tear_down_on_failure: bool) -> Self {
|
||||||
Self::transition(
|
Self::transition(GlobalSettings { tear_down_on_failure, ..self.config }, self.errors)
|
||||||
GlobalSettings {
|
}
|
||||||
tear_down_on_failure,
|
|
||||||
..self.config
|
|
||||||
},
|
|
||||||
self.errors,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Seals the builder and returns a [`GlobalSettings`] if there are no validation errors, else returns 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>> {
|
pub fn build(self) -> Result<GlobalSettings, Vec<anyhow::Error>> {
|
||||||
if !self.errors.is_empty() {
|
if !self.errors.is_empty() {
|
||||||
return Err(self
|
return Err(self
|
||||||
.errors
|
.errors
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|error| ConfigError::GlobalSettings(error).into())
|
.map(|error| ConfigError::GlobalSettings(error).into())
|
||||||
.collect::<Vec<_>>());
|
.collect::<Vec<_>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(self.config)
|
Ok(self.config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn global_settings_config_builder_should_succeeds_and_returns_a_global_settings_config() {
|
fn global_settings_config_builder_should_succeeds_and_returns_a_global_settings_config() {
|
||||||
let global_settings_config = GlobalSettingsBuilder::new()
|
let global_settings_config = GlobalSettingsBuilder::new()
|
||||||
.with_raw_bootnodes_addresses(vec![
|
.with_raw_bootnodes_addresses(vec![
|
||||||
"/ip4/10.41.122.55/tcp/45421",
|
"/ip4/10.41.122.55/tcp/45421",
|
||||||
"/ip4/51.144.222.10/tcp/2333",
|
"/ip4/51.144.222.10/tcp/2333",
|
||||||
])
|
])
|
||||||
.with_network_spawn_timeout(600)
|
.with_network_spawn_timeout(600)
|
||||||
.with_node_spawn_timeout(120)
|
.with_node_spawn_timeout(120)
|
||||||
.with_local_ip("10.0.0.1")
|
.with_local_ip("10.0.0.1")
|
||||||
.with_base_dir("/home/nonroot/mynetwork")
|
.with_base_dir("/home/nonroot/mynetwork")
|
||||||
.with_spawn_concurrency(5)
|
.with_spawn_concurrency(5)
|
||||||
.with_tear_down_on_failure(true)
|
.with_tear_down_on_failure(true)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let bootnodes_addresses: Vec<Multiaddr> = vec![
|
let bootnodes_addresses: Vec<Multiaddr> = vec![
|
||||||
"/ip4/10.41.122.55/tcp/45421".try_into().unwrap(),
|
"/ip4/10.41.122.55/tcp/45421".try_into().unwrap(),
|
||||||
"/ip4/51.144.222.10/tcp/2333".try_into().unwrap(),
|
"/ip4/51.144.222.10/tcp/2333".try_into().unwrap(),
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
global_settings_config.bootnodes_addresses(),
|
global_settings_config.bootnodes_addresses(),
|
||||||
bootnodes_addresses.iter().collect::<Vec<_>>()
|
bootnodes_addresses.iter().collect::<Vec<_>>()
|
||||||
);
|
);
|
||||||
assert_eq!(global_settings_config.network_spawn_timeout(), 600);
|
assert_eq!(global_settings_config.network_spawn_timeout(), 600);
|
||||||
assert_eq!(global_settings_config.node_spawn_timeout(), 120);
|
assert_eq!(global_settings_config.node_spawn_timeout(), 120);
|
||||||
assert_eq!(
|
assert_eq!(global_settings_config.local_ip().unwrap().to_string().as_str(), "10.0.0.1");
|
||||||
global_settings_config
|
assert_eq!(
|
||||||
.local_ip()
|
global_settings_config.base_dir().unwrap(),
|
||||||
.unwrap()
|
Path::new("/home/nonroot/mynetwork")
|
||||||
.to_string()
|
);
|
||||||
.as_str(),
|
assert_eq!(global_settings_config.spawn_concurrency().unwrap(), 5);
|
||||||
"10.0.0.1"
|
assert!(global_settings_config.tear_down_on_failure());
|
||||||
);
|
}
|
||||||
assert_eq!(
|
|
||||||
global_settings_config.base_dir().unwrap(),
|
|
||||||
Path::new("/home/nonroot/mynetwork")
|
|
||||||
);
|
|
||||||
assert_eq!(global_settings_config.spawn_concurrency().unwrap(), 5);
|
|
||||||
assert!(global_settings_config.tear_down_on_failure());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn global_settings_config_builder_should_succeeds_when_node_spawn_timeout_is_missing() {
|
fn global_settings_config_builder_should_succeeds_when_node_spawn_timeout_is_missing() {
|
||||||
let global_settings_config = GlobalSettingsBuilder::new()
|
let global_settings_config = GlobalSettingsBuilder::new()
|
||||||
.with_raw_bootnodes_addresses(vec![
|
.with_raw_bootnodes_addresses(vec![
|
||||||
"/ip4/10.41.122.55/tcp/45421",
|
"/ip4/10.41.122.55/tcp/45421",
|
||||||
"/ip4/51.144.222.10/tcp/2333",
|
"/ip4/51.144.222.10/tcp/2333",
|
||||||
])
|
])
|
||||||
.with_network_spawn_timeout(600)
|
.with_network_spawn_timeout(600)
|
||||||
.with_local_ip("10.0.0.1")
|
.with_local_ip("10.0.0.1")
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let bootnodes_addresses: Vec<Multiaddr> = vec![
|
let bootnodes_addresses: Vec<Multiaddr> = vec![
|
||||||
"/ip4/10.41.122.55/tcp/45421".try_into().unwrap(),
|
"/ip4/10.41.122.55/tcp/45421".try_into().unwrap(),
|
||||||
"/ip4/51.144.222.10/tcp/2333".try_into().unwrap(),
|
"/ip4/51.144.222.10/tcp/2333".try_into().unwrap(),
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
global_settings_config.bootnodes_addresses(),
|
global_settings_config.bootnodes_addresses(),
|
||||||
bootnodes_addresses.iter().collect::<Vec<_>>()
|
bootnodes_addresses.iter().collect::<Vec<_>>()
|
||||||
);
|
);
|
||||||
assert_eq!(global_settings_config.network_spawn_timeout(), 600);
|
assert_eq!(global_settings_config.network_spawn_timeout(), 600);
|
||||||
assert_eq!(global_settings_config.node_spawn_timeout(), 600);
|
assert_eq!(global_settings_config.node_spawn_timeout(), 600);
|
||||||
assert_eq!(
|
assert_eq!(global_settings_config.local_ip().unwrap().to_string().as_str(), "10.0.0.1");
|
||||||
global_settings_config
|
}
|
||||||
.local_ip()
|
|
||||||
.unwrap()
|
|
||||||
.to_string()
|
|
||||||
.as_str(),
|
|
||||||
"10.0.0.1"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn global_settings_builder_should_fails_and_returns_an_error_if_one_bootnode_address_is_invalid(
|
fn global_settings_builder_should_fails_and_returns_an_error_if_one_bootnode_address_is_invalid(
|
||||||
) {
|
) {
|
||||||
let errors = GlobalSettingsBuilder::new()
|
let errors = GlobalSettingsBuilder::new()
|
||||||
.with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421"])
|
.with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421"])
|
||||||
.build()
|
.build()
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(errors.len(), 1);
|
assert_eq!(errors.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors.first().unwrap().to_string(),
|
errors.first().unwrap().to_string(),
|
||||||
"global_settings.bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
|
"global_settings.bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn global_settings_builder_should_fails_and_returns_multiple_errors_if_multiple_bootnodes_addresses_are_invalid(
|
fn global_settings_builder_should_fails_and_returns_multiple_errors_if_multiple_bootnodes_addresses_are_invalid(
|
||||||
) {
|
) {
|
||||||
let errors = GlobalSettingsBuilder::new()
|
let errors = GlobalSettingsBuilder::new()
|
||||||
.with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421", "//10.42.153.10/tcp/43111"])
|
.with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421", "//10.42.153.10/tcp/43111"])
|
||||||
.build()
|
.build()
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(errors.len(), 2);
|
assert_eq!(errors.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors.first().unwrap().to_string(),
|
errors.first().unwrap().to_string(),
|
||||||
"global_settings.bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
|
"global_settings.bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors.get(1).unwrap().to_string(),
|
errors.get(1).unwrap().to_string(),
|
||||||
"global_settings.bootnodes_addresses[1]: '//10.42.153.10/tcp/43111' unknown protocol string: "
|
"global_settings.bootnodes_addresses[1]: '//10.42.153.10/tcp/43111' unknown protocol string: "
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn global_settings_builder_should_fails_and_returns_an_error_if_local_ip_is_invalid() {
|
fn global_settings_builder_should_fails_and_returns_an_error_if_local_ip_is_invalid() {
|
||||||
let errors = GlobalSettingsBuilder::new()
|
let errors = GlobalSettingsBuilder::new().with_local_ip("invalid").build().unwrap_err();
|
||||||
.with_local_ip("invalid")
|
|
||||||
.build()
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert_eq!(errors.len(), 1);
|
assert_eq!(errors.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors.first().unwrap().to_string(),
|
errors.first().unwrap().to_string(),
|
||||||
"global_settings.local_ip: invalid IP address syntax"
|
"global_settings.local_ip: invalid IP address syntax"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn global_settings_builder_should_fails_and_returns_multiple_errors_if_multiple_fields_are_invalid(
|
fn global_settings_builder_should_fails_and_returns_multiple_errors_if_multiple_fields_are_invalid(
|
||||||
) {
|
) {
|
||||||
let errors = GlobalSettingsBuilder::new()
|
let errors = GlobalSettingsBuilder::new()
|
||||||
.with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421", "//10.42.153.10/tcp/43111"])
|
.with_raw_bootnodes_addresses(vec!["/ip4//tcp/45421", "//10.42.153.10/tcp/43111"])
|
||||||
.with_local_ip("invalid")
|
.with_local_ip("invalid")
|
||||||
.build()
|
.build()
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(errors.len(), 3);
|
assert_eq!(errors.len(), 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors.first().unwrap().to_string(),
|
errors.first().unwrap().to_string(),
|
||||||
"global_settings.bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
|
"global_settings.bootnodes_addresses[0]: '/ip4//tcp/45421' failed to parse: invalid IPv4 address syntax"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors.get(1).unwrap().to_string(),
|
errors.get(1).unwrap().to_string(),
|
||||||
"global_settings.bootnodes_addresses[1]: '//10.42.153.10/tcp/43111' unknown protocol string: "
|
"global_settings.bootnodes_addresses[1]: '//10.42.153.10/tcp/43111' unknown protocol string: "
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors.get(2).unwrap().to_string(),
|
errors.get(2).unwrap().to_string(),
|
||||||
"global_settings.local_ip: invalid IP address syntax"
|
"global_settings.local_ip: invalid IP address syntax"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,131 +7,116 @@ use crate::shared::{macros::states, types::ParaId};
|
|||||||
/// HRMP channel configuration, with fine-grained configuration options.
|
/// HRMP channel configuration, with fine-grained configuration options.
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct HrmpChannelConfig {
|
pub struct HrmpChannelConfig {
|
||||||
sender: ParaId,
|
sender: ParaId,
|
||||||
recipient: ParaId,
|
recipient: ParaId,
|
||||||
max_capacity: u32,
|
max_capacity: u32,
|
||||||
max_message_size: u32,
|
max_message_size: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HrmpChannelConfig {
|
impl HrmpChannelConfig {
|
||||||
/// The sending parachain ID.
|
/// The sending parachain ID.
|
||||||
pub fn sender(&self) -> ParaId {
|
pub fn sender(&self) -> ParaId {
|
||||||
self.sender
|
self.sender
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The receiving parachain ID.
|
/// The receiving parachain ID.
|
||||||
pub fn recipient(&self) -> ParaId {
|
pub fn recipient(&self) -> ParaId {
|
||||||
self.recipient
|
self.recipient
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The maximum capacity of messages in the channel.
|
/// The maximum capacity of messages in the channel.
|
||||||
pub fn max_capacity(&self) -> u32 {
|
pub fn max_capacity(&self) -> u32 {
|
||||||
self.max_capacity
|
self.max_capacity
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The maximum size of a message in the channel.
|
/// The maximum size of a message in the channel.
|
||||||
pub fn max_message_size(&self) -> u32 {
|
pub fn max_message_size(&self) -> u32 {
|
||||||
self.max_message_size
|
self.max_message_size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
states! {
|
states! {
|
||||||
Initial,
|
Initial,
|
||||||
WithSender,
|
WithSender,
|
||||||
WithRecipient
|
WithRecipient
|
||||||
}
|
}
|
||||||
|
|
||||||
/// HRMP channel configuration builder, used to build an [`HrmpChannelConfig`] declaratively with fields validation.
|
/// HRMP channel configuration builder, used to build an [`HrmpChannelConfig`] declaratively with fields validation.
|
||||||
pub struct HrmpChannelConfigBuilder<State> {
|
pub struct HrmpChannelConfigBuilder<State> {
|
||||||
config: HrmpChannelConfig,
|
config: HrmpChannelConfig,
|
||||||
_state: PhantomData<State>,
|
_state: PhantomData<State>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HrmpChannelConfigBuilder<Initial> {
|
impl Default for HrmpChannelConfigBuilder<Initial> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
config: HrmpChannelConfig {
|
config: HrmpChannelConfig {
|
||||||
sender: 0,
|
sender: 0,
|
||||||
recipient: 0,
|
recipient: 0,
|
||||||
max_capacity: 8,
|
max_capacity: 8,
|
||||||
max_message_size: 512,
|
max_message_size: 512,
|
||||||
},
|
},
|
||||||
_state: PhantomData,
|
_state: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A> HrmpChannelConfigBuilder<A> {
|
impl<A> HrmpChannelConfigBuilder<A> {
|
||||||
fn transition<B>(&self, config: HrmpChannelConfig) -> HrmpChannelConfigBuilder<B> {
|
fn transition<B>(&self, config: HrmpChannelConfig) -> HrmpChannelConfigBuilder<B> {
|
||||||
HrmpChannelConfigBuilder {
|
HrmpChannelConfigBuilder { config, _state: PhantomData }
|
||||||
config,
|
}
|
||||||
_state: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HrmpChannelConfigBuilder<Initial> {
|
impl HrmpChannelConfigBuilder<Initial> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the sending parachain ID.
|
/// Set the sending parachain ID.
|
||||||
pub fn with_sender(self, sender: ParaId) -> HrmpChannelConfigBuilder<WithSender> {
|
pub fn with_sender(self, sender: ParaId) -> HrmpChannelConfigBuilder<WithSender> {
|
||||||
self.transition(HrmpChannelConfig {
|
self.transition(HrmpChannelConfig { sender, ..self.config })
|
||||||
sender,
|
}
|
||||||
..self.config
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HrmpChannelConfigBuilder<WithSender> {
|
impl HrmpChannelConfigBuilder<WithSender> {
|
||||||
/// Set the receiving parachain ID.
|
/// Set the receiving parachain ID.
|
||||||
pub fn with_recipient(self, recipient: ParaId) -> HrmpChannelConfigBuilder<WithRecipient> {
|
pub fn with_recipient(self, recipient: ParaId) -> HrmpChannelConfigBuilder<WithRecipient> {
|
||||||
self.transition(HrmpChannelConfig {
|
self.transition(HrmpChannelConfig { recipient, ..self.config })
|
||||||
recipient,
|
}
|
||||||
..self.config
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HrmpChannelConfigBuilder<WithRecipient> {
|
impl HrmpChannelConfigBuilder<WithRecipient> {
|
||||||
/// Set the max capacity of messages in the channel.
|
/// Set the max capacity of messages in the channel.
|
||||||
pub fn with_max_capacity(self, max_capacity: u32) -> Self {
|
pub fn with_max_capacity(self, max_capacity: u32) -> Self {
|
||||||
self.transition(HrmpChannelConfig {
|
self.transition(HrmpChannelConfig { max_capacity, ..self.config })
|
||||||
max_capacity,
|
}
|
||||||
..self.config
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the maximum size of a message in the channel.
|
/// Set the maximum size of a message in the channel.
|
||||||
pub fn with_max_message_size(self, max_message_size: u32) -> Self {
|
pub fn with_max_message_size(self, max_message_size: u32) -> Self {
|
||||||
self.transition(HrmpChannelConfig {
|
self.transition(HrmpChannelConfig { max_message_size, ..self.config })
|
||||||
max_message_size,
|
}
|
||||||
..self.config
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> HrmpChannelConfig {
|
pub fn build(self) -> HrmpChannelConfig {
|
||||||
self.config
|
self.config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hrmp_channel_config_builder_should_build_a_new_hrmp_channel_config_correctly() {
|
fn hrmp_channel_config_builder_should_build_a_new_hrmp_channel_config_correctly() {
|
||||||
let hrmp_channel_config = HrmpChannelConfigBuilder::new()
|
let hrmp_channel_config = HrmpChannelConfigBuilder::new()
|
||||||
.with_sender(1000)
|
.with_sender(1000)
|
||||||
.with_recipient(2000)
|
.with_recipient(2000)
|
||||||
.with_max_capacity(50)
|
.with_max_capacity(50)
|
||||||
.with_max_message_size(100)
|
.with_max_message_size(100)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assert_eq!(hrmp_channel_config.sender(), 1000);
|
assert_eq!(hrmp_channel_config.sender(), 1000);
|
||||||
assert_eq!(hrmp_channel_config.recipient(), 2000);
|
assert_eq!(hrmp_channel_config.recipient(), 2000);
|
||||||
assert_eq!(hrmp_channel_config.max_capacity(), 50);
|
assert_eq!(hrmp_channel_config.max_capacity(), 50);
|
||||||
assert_eq!(hrmp_channel_config.max_message_size(), 100);
|
assert_eq!(hrmp_channel_config.max_message_size(), 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ pub use relaychain::{RelaychainConfig, RelaychainConfigBuilder};
|
|||||||
// re-export shared
|
// re-export shared
|
||||||
pub use shared::{node::NodeConfig, types};
|
pub use shared::{node::NodeConfig, types};
|
||||||
pub use teyrchain::{
|
pub use teyrchain::{
|
||||||
states as para_states, RegistrationStrategy, TeyrchainConfig, TeyrchainConfigBuilder,
|
states as para_states, RegistrationStrategy, TeyrchainConfig, TeyrchainConfigBuilder,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Backward compatibility aliases for external crates that use Polkadot SDK terminology
|
// Backward compatibility aliases for external crates that use Polkadot SDK terminology
|
||||||
|
|||||||
+1525
-1643
File diff suppressed because it is too large
Load Diff
+873
-977
File diff suppressed because it is too large
Load Diff
@@ -3,114 +3,114 @@ use super::types::{ParaId, Port};
|
|||||||
/// An error at the configuration level.
|
/// An error at the configuration level.
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum ConfigError {
|
pub enum ConfigError {
|
||||||
#[error("relaychain.{0}")]
|
#[error("relaychain.{0}")]
|
||||||
Relaychain(anyhow::Error),
|
Relaychain(anyhow::Error),
|
||||||
|
|
||||||
#[error("teyrchain[{0}].{1}")]
|
#[error("teyrchain[{0}].{1}")]
|
||||||
Teyrchain(ParaId, anyhow::Error),
|
Teyrchain(ParaId, anyhow::Error),
|
||||||
|
|
||||||
#[error("global_settings.{0}")]
|
#[error("global_settings.{0}")]
|
||||||
GlobalSettings(anyhow::Error),
|
GlobalSettings(anyhow::Error),
|
||||||
|
|
||||||
#[error("nodes['{0}'].{1}")]
|
#[error("nodes['{0}'].{1}")]
|
||||||
Node(String, anyhow::Error),
|
Node(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("collators['{0}'].{1}")]
|
#[error("collators['{0}'].{1}")]
|
||||||
Collator(String, anyhow::Error),
|
Collator(String, anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error at the field level.
|
/// An error at the field level.
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum FieldError {
|
pub enum FieldError {
|
||||||
#[error("name: {0}")]
|
#[error("name: {0}")]
|
||||||
Name(anyhow::Error),
|
Name(anyhow::Error),
|
||||||
|
|
||||||
#[error("chain: {0}")]
|
#[error("chain: {0}")]
|
||||||
Chain(anyhow::Error),
|
Chain(anyhow::Error),
|
||||||
|
|
||||||
#[error("image: {0}")]
|
#[error("image: {0}")]
|
||||||
Image(anyhow::Error),
|
Image(anyhow::Error),
|
||||||
|
|
||||||
#[error("default_image: {0}")]
|
#[error("default_image: {0}")]
|
||||||
DefaultImage(anyhow::Error),
|
DefaultImage(anyhow::Error),
|
||||||
|
|
||||||
#[error("command: {0}")]
|
#[error("command: {0}")]
|
||||||
Command(anyhow::Error),
|
Command(anyhow::Error),
|
||||||
|
|
||||||
#[error("default_command: {0}")]
|
#[error("default_command: {0}")]
|
||||||
DefaultCommand(anyhow::Error),
|
DefaultCommand(anyhow::Error),
|
||||||
|
|
||||||
#[error("bootnodes_addresses[{0}]: '{1}' {2}")]
|
#[error("bootnodes_addresses[{0}]: '{1}' {2}")]
|
||||||
BootnodesAddress(usize, String, anyhow::Error),
|
BootnodesAddress(usize, String, anyhow::Error),
|
||||||
|
|
||||||
#[error("genesis_wasm_generator: {0}")]
|
#[error("genesis_wasm_generator: {0}")]
|
||||||
GenesisWasmGenerator(anyhow::Error),
|
GenesisWasmGenerator(anyhow::Error),
|
||||||
|
|
||||||
#[error("genesis_state_generator: {0}")]
|
#[error("genesis_state_generator: {0}")]
|
||||||
GenesisStateGenerator(anyhow::Error),
|
GenesisStateGenerator(anyhow::Error),
|
||||||
|
|
||||||
#[error("local_ip: {0}")]
|
#[error("local_ip: {0}")]
|
||||||
LocalIp(anyhow::Error),
|
LocalIp(anyhow::Error),
|
||||||
|
|
||||||
#[error("default_resources.{0}")]
|
#[error("default_resources.{0}")]
|
||||||
DefaultResources(anyhow::Error),
|
DefaultResources(anyhow::Error),
|
||||||
|
|
||||||
#[error("resources.{0}")]
|
#[error("resources.{0}")]
|
||||||
Resources(anyhow::Error),
|
Resources(anyhow::Error),
|
||||||
|
|
||||||
#[error("request_memory: {0}")]
|
#[error("request_memory: {0}")]
|
||||||
RequestMemory(anyhow::Error),
|
RequestMemory(anyhow::Error),
|
||||||
|
|
||||||
#[error("request_cpu: {0}")]
|
#[error("request_cpu: {0}")]
|
||||||
RequestCpu(anyhow::Error),
|
RequestCpu(anyhow::Error),
|
||||||
|
|
||||||
#[error("limit_memory: {0}")]
|
#[error("limit_memory: {0}")]
|
||||||
LimitMemory(anyhow::Error),
|
LimitMemory(anyhow::Error),
|
||||||
|
|
||||||
#[error("limit_cpu: {0}")]
|
#[error("limit_cpu: {0}")]
|
||||||
LimitCpu(anyhow::Error),
|
LimitCpu(anyhow::Error),
|
||||||
|
|
||||||
#[error("ws_port: {0}")]
|
#[error("ws_port: {0}")]
|
||||||
WsPort(anyhow::Error),
|
WsPort(anyhow::Error),
|
||||||
|
|
||||||
#[error("rpc_port: {0}")]
|
#[error("rpc_port: {0}")]
|
||||||
RpcPort(anyhow::Error),
|
RpcPort(anyhow::Error),
|
||||||
|
|
||||||
#[error("prometheus_port: {0}")]
|
#[error("prometheus_port: {0}")]
|
||||||
PrometheusPort(anyhow::Error),
|
PrometheusPort(anyhow::Error),
|
||||||
|
|
||||||
#[error("p2p_port: {0}")]
|
#[error("p2p_port: {0}")]
|
||||||
P2pPort(anyhow::Error),
|
P2pPort(anyhow::Error),
|
||||||
|
|
||||||
#[error("session_key: {0}")]
|
#[error("session_key: {0}")]
|
||||||
SessionKey(anyhow::Error),
|
SessionKey(anyhow::Error),
|
||||||
|
|
||||||
#[error("registration_strategy: {0}")]
|
#[error("registration_strategy: {0}")]
|
||||||
RegistrationStrategy(anyhow::Error),
|
RegistrationStrategy(anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A conversion error for shared types across fields.
|
/// A conversion error for shared types across fields.
|
||||||
#[derive(thiserror::Error, Debug, Clone)]
|
#[derive(thiserror::Error, Debug, Clone)]
|
||||||
pub enum ConversionError {
|
pub enum ConversionError {
|
||||||
#[error("'{0}' shouldn't contains whitespace")]
|
#[error("'{0}' shouldn't contains whitespace")]
|
||||||
ContainsWhitespaces(String),
|
ContainsWhitespaces(String),
|
||||||
|
|
||||||
#[error("'{}' doesn't match regex '{}'", .value, .regex)]
|
#[error("'{}' doesn't match regex '{}'", .value, .regex)]
|
||||||
DoesntMatchRegex { value: String, regex: String },
|
DoesntMatchRegex { value: String, regex: String },
|
||||||
|
|
||||||
#[error("can't be empty")]
|
#[error("can't be empty")]
|
||||||
CantBeEmpty,
|
CantBeEmpty,
|
||||||
|
|
||||||
#[error("deserialize error")]
|
#[error("deserialize error")]
|
||||||
DeserializeError(String),
|
DeserializeError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A validation error for shared types across fields.
|
/// A validation error for shared types across fields.
|
||||||
#[derive(thiserror::Error, Debug, Clone)]
|
#[derive(thiserror::Error, Debug, Clone)]
|
||||||
pub enum ValidationError {
|
pub enum ValidationError {
|
||||||
#[error("'{0}' is already used across config")]
|
#[error("'{0}' is already used across config")]
|
||||||
PortAlreadyUsed(Port),
|
PortAlreadyUsed(Port),
|
||||||
|
|
||||||
#[error("can't be empty")]
|
#[error("can't be empty")]
|
||||||
CantBeEmpty(),
|
CantBeEmpty(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,28 +4,28 @@ use support::constants::{BORROWABLE, THIS_IS_A_BUG};
|
|||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
errors::ValidationError,
|
errors::ValidationError,
|
||||||
types::{ParaId, Port, ValidationContext},
|
types::{ParaId, Port, ValidationContext},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn merge_errors(errors: Vec<anyhow::Error>, new_error: anyhow::Error) -> Vec<anyhow::Error> {
|
pub fn merge_errors(errors: Vec<anyhow::Error>, new_error: anyhow::Error) -> Vec<anyhow::Error> {
|
||||||
let mut errors = errors;
|
let mut errors = errors;
|
||||||
errors.push(new_error);
|
errors.push(new_error);
|
||||||
|
|
||||||
errors
|
errors
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn merge_errors_vecs(
|
pub fn merge_errors_vecs(
|
||||||
errors: Vec<anyhow::Error>,
|
errors: Vec<anyhow::Error>,
|
||||||
new_errors: Vec<anyhow::Error>,
|
new_errors: Vec<anyhow::Error>,
|
||||||
) -> Vec<anyhow::Error> {
|
) -> Vec<anyhow::Error> {
|
||||||
let mut errors = errors;
|
let mut errors = errors;
|
||||||
|
|
||||||
for new_error in new_errors.into_iter() {
|
for new_error in new_errors.into_iter() {
|
||||||
errors.push(new_error);
|
errors.push(new_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
errors
|
errors
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a unique name from a base name and the names already present in a
|
/// Generates a unique name from a base name and the names already present in a
|
||||||
@@ -34,14 +34,13 @@ pub fn merge_errors_vecs(
|
|||||||
/// Uses [`generate_unique_node_name_from_names()`] internally to ensure uniqueness.
|
/// Uses [`generate_unique_node_name_from_names()`] internally to ensure uniqueness.
|
||||||
/// Logs a warning if the generated name differs from the original due to duplicates.
|
/// Logs a warning if the generated name differs from the original due to duplicates.
|
||||||
pub fn generate_unique_node_name(
|
pub fn generate_unique_node_name(
|
||||||
node_name: impl Into<String>,
|
node_name: impl Into<String>,
|
||||||
validation_context: Rc<RefCell<ValidationContext>>,
|
validation_context: Rc<RefCell<ValidationContext>>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut context = validation_context
|
let mut context =
|
||||||
.try_borrow_mut()
|
validation_context.try_borrow_mut().expect(&format!("{BORROWABLE}, {THIS_IS_A_BUG}"));
|
||||||
.expect(&format!("{BORROWABLE}, {THIS_IS_A_BUG}"));
|
|
||||||
|
|
||||||
generate_unique_node_name_from_names(node_name, &mut context.used_nodes_names)
|
generate_unique_node_name_from_names(node_name, &mut context.used_nodes_names)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `node_name` if it is not already in `names`.
|
/// Returns `node_name` if it is not already in `names`.
|
||||||
@@ -49,70 +48,68 @@ pub fn generate_unique_node_name(
|
|||||||
/// Otherwise, appends an incrementing `-{counter}` suffix until a unique name is found,
|
/// Otherwise, appends an incrementing `-{counter}` suffix until a unique name is found,
|
||||||
/// then returns it. Logs a warning when a duplicate is detected.
|
/// then returns it. Logs a warning when a duplicate is detected.
|
||||||
pub fn generate_unique_node_name_from_names(
|
pub fn generate_unique_node_name_from_names(
|
||||||
node_name: impl Into<String>,
|
node_name: impl Into<String>,
|
||||||
names: &mut HashSet<String>,
|
names: &mut HashSet<String>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let node_name = node_name.into();
|
let node_name = node_name.into();
|
||||||
|
|
||||||
if names.insert(node_name.clone()) {
|
if names.insert(node_name.clone()) {
|
||||||
return node_name;
|
return node_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut counter = 1;
|
let mut counter = 1;
|
||||||
let mut candidate = node_name.clone();
|
let mut candidate = node_name.clone();
|
||||||
while names.contains(&candidate) {
|
while names.contains(&candidate) {
|
||||||
candidate = format!("{node_name}-{counter}");
|
candidate = format!("{node_name}-{counter}");
|
||||||
counter += 1;
|
counter += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
warn!(
|
warn!(
|
||||||
original = %node_name,
|
original = %node_name,
|
||||||
adjusted = %candidate,
|
adjusted = %candidate,
|
||||||
"Duplicate node name detected."
|
"Duplicate node name detected."
|
||||||
);
|
);
|
||||||
|
|
||||||
names.insert(candidate.clone());
|
names.insert(candidate.clone());
|
||||||
candidate
|
candidate
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_value_is_not_empty(value: &str) -> Result<(), anyhow::Error> {
|
pub fn ensure_value_is_not_empty(value: &str) -> Result<(), anyhow::Error> {
|
||||||
if value.is_empty() {
|
if value.is_empty() {
|
||||||
Err(ValidationError::CantBeEmpty().into())
|
Err(ValidationError::CantBeEmpty().into())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_port_unique(
|
pub fn ensure_port_unique(
|
||||||
port: Port,
|
port: Port,
|
||||||
validation_context: Rc<RefCell<ValidationContext>>,
|
validation_context: Rc<RefCell<ValidationContext>>,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
let mut context = validation_context
|
let mut context =
|
||||||
.try_borrow_mut()
|
validation_context.try_borrow_mut().expect(&format!("{BORROWABLE}, {THIS_IS_A_BUG}"));
|
||||||
.expect(&format!("{BORROWABLE}, {THIS_IS_A_BUG}"));
|
|
||||||
|
|
||||||
if !context.used_ports.contains(&port) {
|
if !context.used_ports.contains(&port) {
|
||||||
context.used_ports.push(port);
|
context.used_ports.push(port);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(ValidationError::PortAlreadyUsed(port).into())
|
Err(ValidationError::PortAlreadyUsed(port).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_unique_para_id(
|
pub fn generate_unique_para_id(
|
||||||
para_id: ParaId,
|
para_id: ParaId,
|
||||||
validation_context: Rc<RefCell<ValidationContext>>,
|
validation_context: Rc<RefCell<ValidationContext>>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut context = validation_context
|
let mut context =
|
||||||
.try_borrow_mut()
|
validation_context.try_borrow_mut().expect(&format!("{BORROWABLE}, {THIS_IS_A_BUG}"));
|
||||||
.expect(&format!("{BORROWABLE}, {THIS_IS_A_BUG}"));
|
|
||||||
|
|
||||||
if let Some(suffix) = context.used_para_ids.get_mut(¶_id) {
|
if let Some(suffix) = context.used_para_ids.get_mut(¶_id) {
|
||||||
*suffix += 1;
|
*suffix += 1;
|
||||||
format!("{para_id}-{suffix}")
|
format!("{para_id}-{suffix}")
|
||||||
} else {
|
} else {
|
||||||
// insert 0, since will be used next time.
|
// insert 0, since will be used next time.
|
||||||
context.used_para_ids.insert(para_id, 0);
|
context.used_para_ids.insert(para_id, 0);
|
||||||
para_id.to_string()
|
para_id.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1040
-1139
File diff suppressed because it is too large
Load Diff
+339
-358
@@ -3,15 +3,15 @@ use std::error::Error;
|
|||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{
|
use serde::{
|
||||||
de::{self},
|
de::{self},
|
||||||
ser::SerializeStruct,
|
ser::SerializeStruct,
|
||||||
Deserialize, Serialize,
|
Deserialize, Serialize,
|
||||||
};
|
};
|
||||||
use support::constants::{SHOULD_COMPILE, THIS_IS_A_BUG};
|
use support::constants::{SHOULD_COMPILE, THIS_IS_A_BUG};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
errors::{ConversionError, FieldError},
|
errors::{ConversionError, FieldError},
|
||||||
helpers::merge_errors,
|
helpers::merge_errors,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A resource quantity used to define limits (k8s/podman only).
|
/// A resource quantity used to define limits (k8s/podman only).
|
||||||
@@ -37,453 +37,434 @@ use super::{
|
|||||||
pub struct ResourceQuantity(String);
|
pub struct ResourceQuantity(String);
|
||||||
|
|
||||||
impl ResourceQuantity {
|
impl ResourceQuantity {
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for ResourceQuantity {
|
impl TryFrom<&str> for ResourceQuantity {
|
||||||
type Error = ConversionError;
|
type Error = ConversionError;
|
||||||
|
|
||||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref RE: Regex = Regex::new(r"^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$")
|
static ref RE: Regex = Regex::new(r"^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$")
|
||||||
.expect(&format!("{SHOULD_COMPILE}, {THIS_IS_A_BUG}"));
|
.expect(&format!("{SHOULD_COMPILE}, {THIS_IS_A_BUG}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !RE.is_match(value) {
|
if !RE.is_match(value) {
|
||||||
return Err(ConversionError::DoesntMatchRegex {
|
return Err(ConversionError::DoesntMatchRegex {
|
||||||
value: value.to_string(),
|
value: value.to_string(),
|
||||||
regex: r"^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$".to_string(),
|
regex: r"^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$".to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self(value.to_string()))
|
Ok(Self(value.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u64> for ResourceQuantity {
|
impl From<u64> for ResourceQuantity {
|
||||||
fn from(value: u64) -> Self {
|
fn from(value: u64) -> Self {
|
||||||
Self(value.to_string())
|
Self(value.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resources limits used in the context of podman/k8s.
|
/// Resources limits used in the context of podman/k8s.
|
||||||
#[derive(Debug, Default, Clone, PartialEq)]
|
#[derive(Debug, Default, Clone, PartialEq)]
|
||||||
pub struct Resources {
|
pub struct Resources {
|
||||||
request_memory: Option<ResourceQuantity>,
|
request_memory: Option<ResourceQuantity>,
|
||||||
request_cpu: Option<ResourceQuantity>,
|
request_cpu: Option<ResourceQuantity>,
|
||||||
limit_memory: Option<ResourceQuantity>,
|
limit_memory: Option<ResourceQuantity>,
|
||||||
limit_cpu: Option<ResourceQuantity>,
|
limit_cpu: Option<ResourceQuantity>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct ResourcesField {
|
struct ResourcesField {
|
||||||
memory: Option<ResourceQuantity>,
|
memory: Option<ResourceQuantity>,
|
||||||
cpu: Option<ResourceQuantity>,
|
cpu: Option<ResourceQuantity>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Resources {
|
impl Serialize for Resources {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
let mut state = serializer.serialize_struct("Resources", 2)?;
|
let mut state = serializer.serialize_struct("Resources", 2)?;
|
||||||
|
|
||||||
if self.request_memory.is_some() || self.request_memory.is_some() {
|
if self.request_memory.is_some() || self.request_memory.is_some() {
|
||||||
state.serialize_field(
|
state.serialize_field(
|
||||||
"requests",
|
"requests",
|
||||||
&ResourcesField {
|
&ResourcesField {
|
||||||
memory: self.request_memory.clone(),
|
memory: self.request_memory.clone(),
|
||||||
cpu: self.request_cpu.clone(),
|
cpu: self.request_cpu.clone(),
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
state.skip_field("requests")?;
|
state.skip_field("requests")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.limit_memory.is_some() || self.limit_memory.is_some() {
|
if self.limit_memory.is_some() || self.limit_memory.is_some() {
|
||||||
state.serialize_field(
|
state.serialize_field(
|
||||||
"limits",
|
"limits",
|
||||||
&ResourcesField {
|
&ResourcesField { memory: self.limit_memory.clone(), cpu: self.limit_cpu.clone() },
|
||||||
memory: self.limit_memory.clone(),
|
)?;
|
||||||
cpu: self.limit_cpu.clone(),
|
} else {
|
||||||
},
|
state.skip_field("limits")?;
|
||||||
)?;
|
}
|
||||||
} else {
|
|
||||||
state.skip_field("limits")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.end()
|
state.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ResourcesVisitor;
|
struct ResourcesVisitor;
|
||||||
|
|
||||||
impl<'de> de::Visitor<'de> for ResourcesVisitor {
|
impl<'de> de::Visitor<'de> for ResourcesVisitor {
|
||||||
type Value = Resources;
|
type Value = Resources;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
formatter.write_str("a resources object")
|
formatter.write_str("a resources object")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||||
where
|
where
|
||||||
A: de::MapAccess<'de>,
|
A: de::MapAccess<'de>,
|
||||||
{
|
{
|
||||||
let mut resources: Resources = Resources::default();
|
let mut resources: Resources = Resources::default();
|
||||||
|
|
||||||
while let Some((key, value)) = map.next_entry::<String, ResourcesField>()? {
|
while let Some((key, value)) = map.next_entry::<String, ResourcesField>()? {
|
||||||
match key.as_str() {
|
match key.as_str() {
|
||||||
"requests" => {
|
"requests" => {
|
||||||
resources.request_memory = value.memory;
|
resources.request_memory = value.memory;
|
||||||
resources.request_cpu = value.cpu;
|
resources.request_cpu = value.cpu;
|
||||||
},
|
},
|
||||||
"limits" => {
|
"limits" => {
|
||||||
resources.limit_memory = value.memory;
|
resources.limit_memory = value.memory;
|
||||||
resources.limit_cpu = value.cpu;
|
resources.limit_cpu = value.cpu;
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
return Err(de::Error::unknown_field(
|
return Err(de::Error::unknown_field(
|
||||||
&key,
|
&key,
|
||||||
&["requests", "limits", "cpu", "memory"],
|
&["requests", "limits", "cpu", "memory"],
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(resources)
|
Ok(resources)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Resources {
|
impl<'de> Deserialize<'de> for Resources {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
deserializer.deserialize_any(ResourcesVisitor)
|
deserializer.deserialize_any(ResourcesVisitor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resources {
|
impl Resources {
|
||||||
/// Memory limit applied to requests.
|
/// Memory limit applied to requests.
|
||||||
pub fn request_memory(&self) -> Option<&ResourceQuantity> {
|
pub fn request_memory(&self) -> Option<&ResourceQuantity> {
|
||||||
self.request_memory.as_ref()
|
self.request_memory.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// CPU limit applied to requests.
|
/// CPU limit applied to requests.
|
||||||
pub fn request_cpu(&self) -> Option<&ResourceQuantity> {
|
pub fn request_cpu(&self) -> Option<&ResourceQuantity> {
|
||||||
self.request_cpu.as_ref()
|
self.request_cpu.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Overall memory limit applied.
|
/// Overall memory limit applied.
|
||||||
pub fn limit_memory(&self) -> Option<&ResourceQuantity> {
|
pub fn limit_memory(&self) -> Option<&ResourceQuantity> {
|
||||||
self.limit_memory.as_ref()
|
self.limit_memory.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Overall CPU limit applied.
|
/// Overall CPU limit applied.
|
||||||
pub fn limit_cpu(&self) -> Option<&ResourceQuantity> {
|
pub fn limit_cpu(&self) -> Option<&ResourceQuantity> {
|
||||||
self.limit_cpu.as_ref()
|
self.limit_cpu.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A resources builder, used to build a [`Resources`] declaratively with fields validation.
|
/// A resources builder, used to build a [`Resources`] declaratively with fields validation.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct ResourcesBuilder {
|
pub struct ResourcesBuilder {
|
||||||
config: Resources,
|
config: Resources,
|
||||||
errors: Vec<anyhow::Error>,
|
errors: Vec<anyhow::Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourcesBuilder {
|
impl ResourcesBuilder {
|
||||||
pub fn new() -> ResourcesBuilder {
|
pub fn new() -> ResourcesBuilder {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transition(config: Resources, errors: Vec<anyhow::Error>) -> Self {
|
fn transition(config: Resources, errors: Vec<anyhow::Error>) -> Self {
|
||||||
Self { config, errors }
|
Self { config, errors }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the requested memory for a pod. This is the minimum memory allocated for a pod.
|
/// Set the requested memory for a pod. This is the minimum memory allocated for a pod.
|
||||||
pub fn with_request_memory<T>(self, quantity: T) -> Self
|
pub fn with_request_memory<T>(self, quantity: T) -> Self
|
||||||
where
|
where
|
||||||
T: TryInto<ResourceQuantity>,
|
T: TryInto<ResourceQuantity>,
|
||||||
T::Error: Error + Send + Sync + 'static,
|
T::Error: Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
match quantity.try_into() {
|
match quantity.try_into() {
|
||||||
Ok(quantity) => Self::transition(
|
Ok(quantity) => Self::transition(
|
||||||
Resources {
|
Resources { request_memory: Some(quantity), ..self.config },
|
||||||
request_memory: Some(quantity),
|
self.errors,
|
||||||
..self.config
|
),
|
||||||
},
|
Err(error) => Self::transition(
|
||||||
self.errors,
|
self.config,
|
||||||
),
|
merge_errors(self.errors, FieldError::RequestMemory(error.into()).into()),
|
||||||
Err(error) => Self::transition(
|
),
|
||||||
self.config,
|
}
|
||||||
merge_errors(self.errors, FieldError::RequestMemory(error.into()).into()),
|
}
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the requested CPU limit for a pod. This is the minimum CPU allocated for a pod.
|
/// Set the requested CPU limit for a pod. This is the minimum CPU allocated for a pod.
|
||||||
pub fn with_request_cpu<T>(self, quantity: T) -> Self
|
pub fn with_request_cpu<T>(self, quantity: T) -> Self
|
||||||
where
|
where
|
||||||
T: TryInto<ResourceQuantity>,
|
T: TryInto<ResourceQuantity>,
|
||||||
T::Error: Error + Send + Sync + 'static,
|
T::Error: Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
match quantity.try_into() {
|
match quantity.try_into() {
|
||||||
Ok(quantity) => Self::transition(
|
Ok(quantity) => Self::transition(
|
||||||
Resources {
|
Resources { request_cpu: Some(quantity), ..self.config },
|
||||||
request_cpu: Some(quantity),
|
self.errors,
|
||||||
..self.config
|
),
|
||||||
},
|
Err(error) => Self::transition(
|
||||||
self.errors,
|
self.config,
|
||||||
),
|
merge_errors(self.errors, FieldError::RequestCpu(error.into()).into()),
|
||||||
Err(error) => Self::transition(
|
),
|
||||||
self.config,
|
}
|
||||||
merge_errors(self.errors, FieldError::RequestCpu(error.into()).into()),
|
}
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the overall memory limit for a pod. This is the maximum memory threshold for a pod.
|
/// Set the overall memory limit for a pod. This is the maximum memory threshold for a pod.
|
||||||
pub fn with_limit_memory<T>(self, quantity: T) -> Self
|
pub fn with_limit_memory<T>(self, quantity: T) -> Self
|
||||||
where
|
where
|
||||||
T: TryInto<ResourceQuantity>,
|
T: TryInto<ResourceQuantity>,
|
||||||
T::Error: Error + Send + Sync + 'static,
|
T::Error: Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
match quantity.try_into() {
|
match quantity.try_into() {
|
||||||
Ok(quantity) => Self::transition(
|
Ok(quantity) => Self::transition(
|
||||||
Resources {
|
Resources { limit_memory: Some(quantity), ..self.config },
|
||||||
limit_memory: Some(quantity),
|
self.errors,
|
||||||
..self.config
|
),
|
||||||
},
|
Err(error) => Self::transition(
|
||||||
self.errors,
|
self.config,
|
||||||
),
|
merge_errors(self.errors, FieldError::LimitMemory(error.into()).into()),
|
||||||
Err(error) => Self::transition(
|
),
|
||||||
self.config,
|
}
|
||||||
merge_errors(self.errors, FieldError::LimitMemory(error.into()).into()),
|
}
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the overall CPU limit for a pod. This is the maximum CPU threshold for a pod.
|
/// Set the overall CPU limit for a pod. This is the maximum CPU threshold for a pod.
|
||||||
pub fn with_limit_cpu<T>(self, quantity: T) -> Self
|
pub fn with_limit_cpu<T>(self, quantity: T) -> Self
|
||||||
where
|
where
|
||||||
T: TryInto<ResourceQuantity>,
|
T: TryInto<ResourceQuantity>,
|
||||||
T::Error: Error + Send + Sync + 'static,
|
T::Error: Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
match quantity.try_into() {
|
match quantity.try_into() {
|
||||||
Ok(quantity) => Self::transition(
|
Ok(quantity) => Self::transition(
|
||||||
Resources {
|
Resources { limit_cpu: Some(quantity), ..self.config },
|
||||||
limit_cpu: Some(quantity),
|
self.errors,
|
||||||
..self.config
|
),
|
||||||
},
|
Err(error) => Self::transition(
|
||||||
self.errors,
|
self.config,
|
||||||
),
|
merge_errors(self.errors, FieldError::LimitCpu(error.into()).into()),
|
||||||
Err(error) => Self::transition(
|
),
|
||||||
self.config,
|
}
|
||||||
merge_errors(self.errors, FieldError::LimitCpu(error.into()).into()),
|
}
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Seals the builder and returns a [`Resources`] if there are no validation errors, else returns errors.
|
/// Seals the builder and returns a [`Resources`] if there are no validation errors, else returns errors.
|
||||||
pub fn build(self) -> Result<Resources, Vec<anyhow::Error>> {
|
pub fn build(self) -> Result<Resources, Vec<anyhow::Error>> {
|
||||||
if !self.errors.is_empty() {
|
if !self.errors.is_empty() {
|
||||||
return Err(self.errors);
|
return Err(self.errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(self.config)
|
Ok(self.config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::NetworkConfig;
|
use crate::NetworkConfig;
|
||||||
|
|
||||||
macro_rules! impl_resources_quantity_unit_test {
|
macro_rules! impl_resources_quantity_unit_test {
|
||||||
($val:literal) => {{
|
($val:literal) => {{
|
||||||
let resources = ResourcesBuilder::new()
|
let resources = ResourcesBuilder::new().with_request_memory($val).build().unwrap();
|
||||||
.with_request_memory($val)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(resources.request_memory().unwrap().as_str(), $val);
|
assert_eq!(resources.request_memory().unwrap().as_str(), $val);
|
||||||
assert_eq!(resources.request_cpu(), None);
|
assert_eq!(resources.request_cpu(), None);
|
||||||
assert_eq!(resources.limit_cpu(), None);
|
assert_eq!(resources.limit_cpu(), None);
|
||||||
assert_eq!(resources.limit_memory(), None);
|
assert_eq!(resources.limit_memory(), None);
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_string_a_resource_quantity_without_unit_should_succeeds() {
|
fn converting_a_string_a_resource_quantity_without_unit_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("1000");
|
impl_resources_quantity_unit_test!("1000");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_str_with_m_unit_into_a_resource_quantity_should_succeeds() {
|
fn converting_a_str_with_m_unit_into_a_resource_quantity_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("100m");
|
impl_resources_quantity_unit_test!("100m");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_str_with_K_unit_into_a_resource_quantity_should_succeeds() {
|
fn converting_a_str_with_K_unit_into_a_resource_quantity_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("50K");
|
impl_resources_quantity_unit_test!("50K");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_str_with_M_unit_into_a_resource_quantity_should_succeeds() {
|
fn converting_a_str_with_M_unit_into_a_resource_quantity_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("100M");
|
impl_resources_quantity_unit_test!("100M");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_str_with_G_unit_into_a_resource_quantity_should_succeeds() {
|
fn converting_a_str_with_G_unit_into_a_resource_quantity_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("1G");
|
impl_resources_quantity_unit_test!("1G");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_str_with_T_unit_into_a_resource_quantity_should_succeeds() {
|
fn converting_a_str_with_T_unit_into_a_resource_quantity_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("0.01T");
|
impl_resources_quantity_unit_test!("0.01T");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_str_with_P_unit_into_a_resource_quantity_should_succeeds() {
|
fn converting_a_str_with_P_unit_into_a_resource_quantity_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("0.00001P");
|
impl_resources_quantity_unit_test!("0.00001P");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_str_with_E_unit_into_a_resource_quantity_should_succeeds() {
|
fn converting_a_str_with_E_unit_into_a_resource_quantity_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("0.000000001E");
|
impl_resources_quantity_unit_test!("0.000000001E");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_str_with_Ki_unit_into_a_resource_quantity_should_succeeds() {
|
fn converting_a_str_with_Ki_unit_into_a_resource_quantity_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("50Ki");
|
impl_resources_quantity_unit_test!("50Ki");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_str_with_Mi_unit_into_a_resource_quantity_should_succeeds() {
|
fn converting_a_str_with_Mi_unit_into_a_resource_quantity_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("100Mi");
|
impl_resources_quantity_unit_test!("100Mi");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_str_with_Gi_unit_into_a_resource_quantity_should_succeeds() {
|
fn converting_a_str_with_Gi_unit_into_a_resource_quantity_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("1Gi");
|
impl_resources_quantity_unit_test!("1Gi");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_str_with_Ti_unit_into_a_resource_quantity_should_succeeds() {
|
fn converting_a_str_with_Ti_unit_into_a_resource_quantity_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("0.01Ti");
|
impl_resources_quantity_unit_test!("0.01Ti");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_str_with_Pi_unit_into_a_resource_quantity_should_succeeds() {
|
fn converting_a_str_with_Pi_unit_into_a_resource_quantity_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("0.00001Pi");
|
impl_resources_quantity_unit_test!("0.00001Pi");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converting_a_str_with_Ei_unit_into_a_resource_quantity_should_succeeds() {
|
fn converting_a_str_with_Ei_unit_into_a_resource_quantity_should_succeeds() {
|
||||||
impl_resources_quantity_unit_test!("0.000000001Ei");
|
impl_resources_quantity_unit_test!("0.000000001Ei");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resources_config_builder_should_succeeds_and_returns_a_resources_config() {
|
fn resources_config_builder_should_succeeds_and_returns_a_resources_config() {
|
||||||
let resources = ResourcesBuilder::new()
|
let resources = ResourcesBuilder::new()
|
||||||
.with_request_memory("200M")
|
.with_request_memory("200M")
|
||||||
.with_request_cpu("1G")
|
.with_request_cpu("1G")
|
||||||
.with_limit_cpu("500M")
|
.with_limit_cpu("500M")
|
||||||
.with_limit_memory("2G")
|
.with_limit_memory("2G")
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(resources.request_memory().unwrap().as_str(), "200M");
|
assert_eq!(resources.request_memory().unwrap().as_str(), "200M");
|
||||||
assert_eq!(resources.request_cpu().unwrap().as_str(), "1G");
|
assert_eq!(resources.request_cpu().unwrap().as_str(), "1G");
|
||||||
assert_eq!(resources.limit_cpu().unwrap().as_str(), "500M");
|
assert_eq!(resources.limit_cpu().unwrap().as_str(), "500M");
|
||||||
assert_eq!(resources.limit_memory().unwrap().as_str(), "2G");
|
assert_eq!(resources.limit_memory().unwrap().as_str(), "2G");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resources_config_toml_import_should_succeeds_and_returns_a_resources_config() {
|
fn resources_config_toml_import_should_succeeds_and_returns_a_resources_config() {
|
||||||
let load_from_toml =
|
let load_from_toml =
|
||||||
NetworkConfig::load_from_toml("./testing/snapshots/0001-big-network.toml").unwrap();
|
NetworkConfig::load_from_toml("./testing/snapshots/0001-big-network.toml").unwrap();
|
||||||
|
|
||||||
let resources = load_from_toml.relaychain().default_resources().unwrap();
|
let resources = load_from_toml.relaychain().default_resources().unwrap();
|
||||||
assert_eq!(resources.request_memory().unwrap().as_str(), "500M");
|
assert_eq!(resources.request_memory().unwrap().as_str(), "500M");
|
||||||
assert_eq!(resources.request_cpu().unwrap().as_str(), "100000");
|
assert_eq!(resources.request_cpu().unwrap().as_str(), "100000");
|
||||||
assert_eq!(resources.limit_cpu().unwrap().as_str(), "10Gi");
|
assert_eq!(resources.limit_cpu().unwrap().as_str(), "10Gi");
|
||||||
assert_eq!(resources.limit_memory().unwrap().as_str(), "4000M");
|
assert_eq!(resources.limit_memory().unwrap().as_str(), "4000M");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resources_config_builder_should_fails_and_returns_an_error_if_couldnt_parse_request_memory()
|
fn resources_config_builder_should_fails_and_returns_an_error_if_couldnt_parse_request_memory()
|
||||||
{
|
{
|
||||||
let resources_builder = ResourcesBuilder::new().with_request_memory("invalid");
|
let resources_builder = ResourcesBuilder::new().with_request_memory("invalid");
|
||||||
|
|
||||||
let errors = resources_builder.build().err().unwrap();
|
let errors = resources_builder.build().err().unwrap();
|
||||||
|
|
||||||
assert_eq!(errors.len(), 1);
|
assert_eq!(errors.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors.first().unwrap().to_string(),
|
errors.first().unwrap().to_string(),
|
||||||
r"request_memory: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
|
r"request_memory: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resources_config_builder_should_fails_and_returns_an_error_if_couldnt_parse_request_cpu() {
|
fn resources_config_builder_should_fails_and_returns_an_error_if_couldnt_parse_request_cpu() {
|
||||||
let resources_builder = ResourcesBuilder::new().with_request_cpu("invalid");
|
let resources_builder = ResourcesBuilder::new().with_request_cpu("invalid");
|
||||||
|
|
||||||
let errors = resources_builder.build().err().unwrap();
|
let errors = resources_builder.build().err().unwrap();
|
||||||
|
|
||||||
assert_eq!(errors.len(), 1);
|
assert_eq!(errors.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors.first().unwrap().to_string(),
|
errors.first().unwrap().to_string(),
|
||||||
r"request_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
|
r"request_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resources_config_builder_should_fails_and_returns_an_error_if_couldnt_parse_limit_memory() {
|
fn resources_config_builder_should_fails_and_returns_an_error_if_couldnt_parse_limit_memory() {
|
||||||
let resources_builder = ResourcesBuilder::new().with_limit_memory("invalid");
|
let resources_builder = ResourcesBuilder::new().with_limit_memory("invalid");
|
||||||
|
|
||||||
let errors = resources_builder.build().err().unwrap();
|
let errors = resources_builder.build().err().unwrap();
|
||||||
|
|
||||||
assert_eq!(errors.len(), 1);
|
assert_eq!(errors.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors.first().unwrap().to_string(),
|
errors.first().unwrap().to_string(),
|
||||||
r"limit_memory: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
|
r"limit_memory: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resources_config_builder_should_fails_and_returns_an_error_if_couldnt_parse_limit_cpu() {
|
fn resources_config_builder_should_fails_and_returns_an_error_if_couldnt_parse_limit_cpu() {
|
||||||
let resources_builder = ResourcesBuilder::new().with_limit_cpu("invalid");
|
let resources_builder = ResourcesBuilder::new().with_limit_cpu("invalid");
|
||||||
|
|
||||||
let errors = resources_builder.build().err().unwrap();
|
let errors = resources_builder.build().err().unwrap();
|
||||||
|
|
||||||
assert_eq!(errors.len(), 1);
|
assert_eq!(errors.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors.first().unwrap().to_string(),
|
errors.first().unwrap().to_string(),
|
||||||
r"limit_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
|
r"limit_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resources_config_builder_should_fails_and_returns_multiple_error_if_couldnt_parse_multiple_fields(
|
fn resources_config_builder_should_fails_and_returns_multiple_error_if_couldnt_parse_multiple_fields(
|
||||||
) {
|
) {
|
||||||
let resources_builder = ResourcesBuilder::new()
|
let resources_builder =
|
||||||
.with_limit_cpu("invalid")
|
ResourcesBuilder::new().with_limit_cpu("invalid").with_request_memory("invalid");
|
||||||
.with_request_memory("invalid");
|
|
||||||
|
|
||||||
let errors = resources_builder.build().err().unwrap();
|
let errors = resources_builder.build().err().unwrap();
|
||||||
|
|
||||||
assert_eq!(errors.len(), 2);
|
assert_eq!(errors.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors.first().unwrap().to_string(),
|
errors.first().unwrap().to_string(),
|
||||||
r"limit_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
|
r"limit_cpu: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
errors.get(1).unwrap().to_string(),
|
errors.get(1).unwrap().to_string(),
|
||||||
r"request_memory: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
|
r"request_memory: 'invalid' doesn't match regex '^\d+(.\d+)?(m|K|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$'"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+525
-561
File diff suppressed because it is too large
Load Diff
+1448
-1644
File diff suppressed because it is too large
Load Diff
+24
-24
@@ -5,61 +5,61 @@ use support::constants::ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS;
|
|||||||
use crate::types::{Chain, Command, Duration};
|
use crate::types::{Chain, Command, Duration};
|
||||||
|
|
||||||
pub(crate) fn is_true(value: &bool) -> bool {
|
pub(crate) fn is_true(value: &bool) -> bool {
|
||||||
*value
|
*value
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_false(value: &bool) -> bool {
|
pub(crate) fn is_false(value: &bool) -> bool {
|
||||||
!(*value)
|
!(*value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn default_as_true() -> bool {
|
pub(crate) fn default_as_true() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn default_as_false() -> bool {
|
pub(crate) fn default_as_false() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn default_initial_balance() -> crate::types::U128 {
|
pub(crate) fn default_initial_balance() -> crate::types::U128 {
|
||||||
2_000_000_000_000.into()
|
2_000_000_000_000.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default timeout for spawning a node (10mins)
|
/// Default timeout for spawning a node (10mins)
|
||||||
pub(crate) fn default_node_spawn_timeout() -> Duration {
|
pub(crate) fn default_node_spawn_timeout() -> Duration {
|
||||||
env::var(ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS)
|
env::var(ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|s| s.parse::<u32>().ok())
|
.and_then(|s| s.parse::<u32>().ok())
|
||||||
.unwrap_or(600)
|
.unwrap_or(600)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default timeout for spawning the whole network (1hr)
|
/// Default timeout for spawning the whole network (1hr)
|
||||||
pub(crate) fn default_timeout() -> Duration {
|
pub(crate) fn default_timeout() -> Duration {
|
||||||
3600
|
3600
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn default_command_polkadot() -> Option<Command> {
|
pub(crate) fn default_command_polkadot() -> Option<Command> {
|
||||||
TryInto::<Command>::try_into("polkadot").ok()
|
TryInto::<Command>::try_into("polkadot").ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn default_relaychain_chain() -> Chain {
|
pub(crate) fn default_relaychain_chain() -> Chain {
|
||||||
TryInto::<Chain>::try_into("rococo-local").expect("'rococo-local' should be a valid chain")
|
TryInto::<Chain>::try_into("rococo-local").expect("'rococo-local' should be a valid chain")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_node_spawn_timeout_works_before_and_after_env_is_set() {
|
fn default_node_spawn_timeout_works_before_and_after_env_is_set() {
|
||||||
// The default should be 600 seconds if the env var is not set
|
// The default should be 600 seconds if the env var is not set
|
||||||
assert_eq!(default_node_spawn_timeout(), 600);
|
assert_eq!(default_node_spawn_timeout(), 600);
|
||||||
|
|
||||||
// If env var is set to a valid number, it should return that number
|
// If env var is set to a valid number, it should return that number
|
||||||
env::set_var(ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS, "123");
|
env::set_var(ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS, "123");
|
||||||
assert_eq!(default_node_spawn_timeout(), 123);
|
assert_eq!(default_node_spawn_timeout(), 123);
|
||||||
|
|
||||||
// If env var is set to a NOT valid number, it should return 600
|
// If env var is set to a NOT valid number, it should return 600
|
||||||
env::set_var(ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS, "NOT_A_NUMBER");
|
env::set_var(ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS, "NOT_A_NUMBER");
|
||||||
assert_eq!(default_node_spawn_timeout(), 600);
|
assert_eq!(default_node_spawn_timeout(), 600);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-21
@@ -7,25 +7,25 @@ use crate::generators;
|
|||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum OrchestratorError {
|
pub enum OrchestratorError {
|
||||||
// TODO: improve invalid config reporting
|
// TODO: improve invalid config reporting
|
||||||
#[error("Invalid network configuration: {0}")]
|
#[error("Invalid network configuration: {0}")]
|
||||||
InvalidConfig(String),
|
InvalidConfig(String),
|
||||||
#[error("Invalid network config to use provider {0}: {1}")]
|
#[error("Invalid network config to use provider {0}: {1}")]
|
||||||
InvalidConfigForProvider(String, String),
|
InvalidConfigForProvider(String, String),
|
||||||
#[error("Invalid configuration for node: {0}, field: {1}")]
|
#[error("Invalid configuration for node: {0}, field: {1}")]
|
||||||
InvalidNodeConfig(String, String),
|
InvalidNodeConfig(String, String),
|
||||||
#[error("Invariant not fulfilled {0}")]
|
#[error("Invariant not fulfilled {0}")]
|
||||||
InvariantError(&'static str),
|
InvariantError(&'static str),
|
||||||
#[error("Global network spawn timeout: {0} secs")]
|
#[error("Global network spawn timeout: {0} secs")]
|
||||||
GlobalTimeOut(u32),
|
GlobalTimeOut(u32),
|
||||||
#[error("Generator error: {0}")]
|
#[error("Generator error: {0}")]
|
||||||
GeneratorError(#[from] generators::errors::GeneratorError),
|
GeneratorError(#[from] generators::errors::GeneratorError),
|
||||||
#[error("Provider error")]
|
#[error("Provider error")]
|
||||||
ProviderError(#[from] ProviderError),
|
ProviderError(#[from] ProviderError),
|
||||||
#[error("FileSystem error")]
|
#[error("FileSystem error")]
|
||||||
FileSystemError(#[from] FileSystemError),
|
FileSystemError(#[from] FileSystemError),
|
||||||
#[error("Serialization error")]
|
#[error("Serialization error")]
|
||||||
SerializationError(#[from] serde_json::Error),
|
SerializationError(#[from] serde_json::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SpawnerError(#[from] anyhow::Error),
|
SpawnerError(#[from] anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ mod port;
|
|||||||
|
|
||||||
pub use bootnode_addr::generate as generate_node_bootnode_addr;
|
pub use bootnode_addr::generate as generate_node_bootnode_addr;
|
||||||
pub use command::{
|
pub use command::{
|
||||||
generate_for_cumulus_node as generate_node_command_cumulus,
|
generate_for_cumulus_node as generate_node_command_cumulus,
|
||||||
generate_for_node as generate_node_command, GenCmdOptions,
|
generate_for_node as generate_node_command, GenCmdOptions,
|
||||||
};
|
};
|
||||||
pub use identity::generate as generate_node_identity;
|
pub use identity::generate as generate_node_identity;
|
||||||
pub use key::generate as generate_node_keys;
|
pub use key::generate as generate_node_keys;
|
||||||
|
|||||||
+95
-95
@@ -8,21 +8,21 @@ use configuration::types::Arg;
|
|||||||
/// - `-:insecure-validator` -> removes `--insecure-validator` (normalized)
|
/// - `-:insecure-validator` -> removes `--insecure-validator` (normalized)
|
||||||
/// - `-:--prometheus-port` -> removes `--prometheus-port`
|
/// - `-:--prometheus-port` -> removes `--prometheus-port`
|
||||||
pub fn parse_removal_args(args: &[Arg]) -> Vec<String> {
|
pub fn parse_removal_args(args: &[Arg]) -> Vec<String> {
|
||||||
args.iter()
|
args.iter()
|
||||||
.filter_map(|arg| match arg {
|
.filter_map(|arg| match arg {
|
||||||
Arg::Flag(flag) if flag.starts_with("-:") => {
|
Arg::Flag(flag) if flag.starts_with("-:") => {
|
||||||
let mut flag_to_exclude = flag[2..].to_string();
|
let mut flag_to_exclude = flag[2..].to_string();
|
||||||
|
|
||||||
// Normalize flag format - ensure it starts with --
|
// Normalize flag format - ensure it starts with --
|
||||||
if !flag_to_exclude.starts_with("--") {
|
if !flag_to_exclude.starts_with("--") {
|
||||||
flag_to_exclude = format!("--{flag_to_exclude}");
|
flag_to_exclude = format!("--{flag_to_exclude}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(flag_to_exclude)
|
Some(flag_to_exclude)
|
||||||
},
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply arg removals to a vector of string arguments.
|
/// Apply arg removals to a vector of string arguments.
|
||||||
@@ -35,104 +35,104 @@ pub fn parse_removal_args(args: &[Arg]) -> Vec<String> {
|
|||||||
/// # Returns
|
/// # Returns
|
||||||
/// Filtered vector with specified args removed
|
/// Filtered vector with specified args removed
|
||||||
pub fn apply_arg_removals(args: Vec<String>, removals: &[String]) -> Vec<String> {
|
pub fn apply_arg_removals(args: Vec<String>, removals: &[String]) -> Vec<String> {
|
||||||
if removals.is_empty() {
|
if removals.is_empty() {
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
let mut skip_next = false;
|
let mut skip_next = false;
|
||||||
|
|
||||||
for (i, arg) in args.iter().enumerate() {
|
for (i, arg) in args.iter().enumerate() {
|
||||||
if skip_next {
|
if skip_next {
|
||||||
skip_next = false;
|
skip_next = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let should_remove = removals
|
let should_remove = removals
|
||||||
.iter()
|
.iter()
|
||||||
.any(|removal| arg == removal || arg.starts_with(&format!("{removal}=")));
|
.any(|removal| arg == removal || arg.starts_with(&format!("{removal}=")));
|
||||||
|
|
||||||
if should_remove {
|
if should_remove {
|
||||||
// Only skip next if this looks like an option (starts with --) and next arg doesn't start with --
|
// Only skip next if this looks like an option (starts with --) and next arg doesn't start with --
|
||||||
if !arg.contains("=") && i + 1 < args.len() {
|
if !arg.contains("=") && i + 1 < args.len() {
|
||||||
let next_arg = &args[i + 1];
|
let next_arg = &args[i + 1];
|
||||||
if !next_arg.starts_with("-") {
|
if !next_arg.starts_with("-") {
|
||||||
skip_next = true;
|
skip_next = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.push(arg.clone());
|
res.push(arg.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_removal_args() {
|
fn test_parse_removal_args() {
|
||||||
let args = vec![
|
let args = vec![
|
||||||
Arg::Flag("-:--insecure-validator-i-know-what-i-do".to_string()),
|
Arg::Flag("-:--insecure-validator-i-know-what-i-do".to_string()),
|
||||||
Arg::Flag("--validator".to_string()),
|
Arg::Flag("--validator".to_string()),
|
||||||
Arg::Flag("-:--no-telemetry".to_string()),
|
Arg::Flag("-:--no-telemetry".to_string()),
|
||||||
];
|
];
|
||||||
|
|
||||||
let removals = parse_removal_args(&args);
|
let removals = parse_removal_args(&args);
|
||||||
assert_eq!(removals.len(), 2);
|
assert_eq!(removals.len(), 2);
|
||||||
assert!(removals.contains(&"--insecure-validator-i-know-what-i-do".to_string()));
|
assert!(removals.contains(&"--insecure-validator-i-know-what-i-do".to_string()));
|
||||||
assert!(removals.contains(&"--no-telemetry".to_string()));
|
assert!(removals.contains(&"--no-telemetry".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_arg_removals_flag() {
|
fn test_apply_arg_removals_flag() {
|
||||||
let args = vec![
|
let args = vec![
|
||||||
"--validator".to_string(),
|
"--validator".to_string(),
|
||||||
"--insecure-validator-i-know-what-i-do".to_string(),
|
"--insecure-validator-i-know-what-i-do".to_string(),
|
||||||
"--no-telemetry".to_string(),
|
"--no-telemetry".to_string(),
|
||||||
];
|
];
|
||||||
let removals = vec!["--insecure-validator-i-know-what-i-do".to_string()];
|
let removals = vec!["--insecure-validator-i-know-what-i-do".to_string()];
|
||||||
let res = apply_arg_removals(args, &removals);
|
let res = apply_arg_removals(args, &removals);
|
||||||
assert_eq!(res.len(), 2);
|
assert_eq!(res.len(), 2);
|
||||||
assert!(res.contains(&"--validator".to_string()));
|
assert!(res.contains(&"--validator".to_string()));
|
||||||
assert!(res.contains(&"--no-telemetry".to_string()));
|
assert!(res.contains(&"--no-telemetry".to_string()));
|
||||||
assert!(!res.contains(&"--insecure-validator-i-know-what-i-do".to_string()));
|
assert!(!res.contains(&"--insecure-validator-i-know-what-i-do".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_arg_removals_option_with_equals() {
|
fn test_apply_arg_removals_option_with_equals() {
|
||||||
let args = vec!["--name=alice".to_string(), "--port=30333".to_string()];
|
let args = vec!["--name=alice".to_string(), "--port=30333".to_string()];
|
||||||
let removals = vec!["--port".to_string()];
|
let removals = vec!["--port".to_string()];
|
||||||
let res = apply_arg_removals(args, &removals);
|
let res = apply_arg_removals(args, &removals);
|
||||||
assert_eq!(res.len(), 1);
|
assert_eq!(res.len(), 1);
|
||||||
assert_eq!(res[0], "--name=alice");
|
assert_eq!(res[0], "--name=alice");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_arg_removals_option_with_space() {
|
fn test_apply_arg_removals_option_with_space() {
|
||||||
let args = vec![
|
let args = vec![
|
||||||
"--name".to_string(),
|
"--name".to_string(),
|
||||||
"alice".to_string(),
|
"alice".to_string(),
|
||||||
"--port".to_string(),
|
"--port".to_string(),
|
||||||
"30333".to_string(),
|
"30333".to_string(),
|
||||||
];
|
];
|
||||||
let removals = vec!["--port".to_string()];
|
let removals = vec!["--port".to_string()];
|
||||||
|
|
||||||
let res = apply_arg_removals(args, &removals);
|
let res = apply_arg_removals(args, &removals);
|
||||||
assert_eq!(res.len(), 2);
|
assert_eq!(res.len(), 2);
|
||||||
assert_eq!(res[0], "--name");
|
assert_eq!(res[0], "--name");
|
||||||
assert_eq!(res[1], "alice");
|
assert_eq!(res[1], "alice");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_arg_removals_empty() {
|
fn test_apply_arg_removals_empty() {
|
||||||
let args = vec!["--validator".to_string()];
|
let args = vec!["--validator".to_string()];
|
||||||
let removals = vec![];
|
let removals = vec![];
|
||||||
|
|
||||||
let res = apply_arg_removals(args, &removals);
|
let res = apply_arg_removals(args, &removals);
|
||||||
assert_eq!(res, vec!["--validator".to_string()]);
|
assert_eq!(res, vec!["--validator".to_string()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-89
@@ -3,109 +3,94 @@ use std::{fmt::Display, net::IpAddr};
|
|||||||
use super::errors::GeneratorError;
|
use super::errors::GeneratorError;
|
||||||
|
|
||||||
pub fn generate<T: AsRef<str> + Display>(
|
pub fn generate<T: AsRef<str> + Display>(
|
||||||
peer_id: &str,
|
peer_id: &str,
|
||||||
ip: &IpAddr,
|
ip: &IpAddr,
|
||||||
port: u16,
|
port: u16,
|
||||||
args: &[T],
|
args: &[T],
|
||||||
p2p_cert: &Option<String>,
|
p2p_cert: &Option<String>,
|
||||||
) -> Result<String, GeneratorError> {
|
) -> Result<String, GeneratorError> {
|
||||||
let addr = if let Some(index) = args.iter().position(|arg| arg.as_ref().eq("--listen-addr")) {
|
let addr = if let Some(index) = args.iter().position(|arg| arg.as_ref().eq("--listen-addr")) {
|
||||||
let listen_value = args
|
let listen_value = args
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.get(index + 1)
|
.get(index + 1)
|
||||||
.ok_or(GeneratorError::BootnodeAddrGeneration(
|
.ok_or(GeneratorError::BootnodeAddrGeneration(
|
||||||
"can not generate bootnode address from args".into(),
|
"can not generate bootnode address from args".into(),
|
||||||
))?
|
))?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let ip_str = ip.to_string();
|
let ip_str = ip.to_string();
|
||||||
let port_str = port.to_string();
|
let port_str = port.to_string();
|
||||||
let mut parts = listen_value.split('/').collect::<Vec<&str>>();
|
let mut parts = listen_value.split('/').collect::<Vec<&str>>();
|
||||||
parts[2] = &ip_str;
|
parts[2] = &ip_str;
|
||||||
parts[4] = port_str.as_str();
|
parts[4] = port_str.as_str();
|
||||||
parts.join("/")
|
parts.join("/")
|
||||||
} else {
|
} else {
|
||||||
format!("/ip4/{ip}/tcp/{port}/ws")
|
format!("/ip4/{ip}/tcp/{port}/ws")
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut addr_with_peer = format!("{addr}/p2p/{peer_id}");
|
let mut addr_with_peer = format!("{addr}/p2p/{peer_id}");
|
||||||
if let Some(p2p_cert) = p2p_cert {
|
if let Some(p2p_cert) = p2p_cert {
|
||||||
addr_with_peer.push_str("/certhash/");
|
addr_with_peer.push_str("/certhash/");
|
||||||
addr_with_peer.push_str(p2p_cert)
|
addr_with_peer.push_str(p2p_cert)
|
||||||
}
|
}
|
||||||
Ok(addr_with_peer)
|
Ok(addr_with_peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use provider::constants::LOCALHOST;
|
use provider::constants::LOCALHOST;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_for_alice_without_args() {
|
fn generate_for_alice_without_args() {
|
||||||
let peer_id = "12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"; // from alice as seed
|
let peer_id = "12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"; // from alice as seed
|
||||||
let args: Vec<&str> = vec![];
|
let args: Vec<&str> = vec![];
|
||||||
let bootnode_addr = generate(peer_id, &LOCALHOST, 5678, &args, &None).unwrap();
|
let bootnode_addr = generate(peer_id, &LOCALHOST, 5678, &args, &None).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&bootnode_addr,
|
&bootnode_addr,
|
||||||
"/ip4/127.0.0.1/tcp/5678/ws/p2p/12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"
|
"/ip4/127.0.0.1/tcp/5678/ws/p2p/12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_for_alice_with_listen_addr() {
|
fn generate_for_alice_with_listen_addr() {
|
||||||
// Should override the ip/port
|
// Should override the ip/port
|
||||||
let peer_id = "12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"; // from alice as seed
|
let peer_id = "12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"; // from alice as seed
|
||||||
let args: Vec<String> = [
|
let args: Vec<String> =
|
||||||
"--some",
|
["--some", "other", "--listen-addr", "/ip4/192.168.100.1/tcp/30333/ws"]
|
||||||
"other",
|
.iter()
|
||||||
"--listen-addr",
|
.map(|x| x.to_string())
|
||||||
"/ip4/192.168.100.1/tcp/30333/ws",
|
.collect();
|
||||||
]
|
let bootnode_addr =
|
||||||
.iter()
|
generate(peer_id, &LOCALHOST, 5678, args.iter().as_ref(), &None).unwrap();
|
||||||
.map(|x| x.to_string())
|
assert_eq!(
|
||||||
.collect();
|
&bootnode_addr,
|
||||||
let bootnode_addr =
|
"/ip4/127.0.0.1/tcp/5678/ws/p2p/12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"
|
||||||
generate(peer_id, &LOCALHOST, 5678, args.iter().as_ref(), &None).unwrap();
|
);
|
||||||
assert_eq!(
|
}
|
||||||
&bootnode_addr,
|
|
||||||
"/ip4/127.0.0.1/tcp/5678/ws/p2p/12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_for_alice_with_listen_addr_without_value_must_fail() {
|
fn generate_for_alice_with_listen_addr_without_value_must_fail() {
|
||||||
// Should override the ip/port
|
// Should override the ip/port
|
||||||
let peer_id = "12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"; // from alice as seed
|
let peer_id = "12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"; // from alice as seed
|
||||||
let args: Vec<String> = ["--some", "other", "--listen-addr"]
|
let args: Vec<String> =
|
||||||
.iter()
|
["--some", "other", "--listen-addr"].iter().map(|x| x.to_string()).collect();
|
||||||
.map(|x| x.to_string())
|
let bootnode_addr = generate(peer_id, &LOCALHOST, 5678, args.iter().as_ref(), &None);
|
||||||
.collect();
|
|
||||||
let bootnode_addr = generate(peer_id, &LOCALHOST, 5678, args.iter().as_ref(), &None);
|
|
||||||
|
|
||||||
assert!(bootnode_addr.is_err());
|
assert!(bootnode_addr.is_err());
|
||||||
assert!(matches!(
|
assert!(matches!(bootnode_addr, Err(GeneratorError::BootnodeAddrGeneration(_))));
|
||||||
bootnode_addr,
|
}
|
||||||
Err(GeneratorError::BootnodeAddrGeneration(_))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_for_alice_withcert() {
|
fn generate_for_alice_withcert() {
|
||||||
let peer_id = "12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"; // from alice as seed
|
let peer_id = "12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"; // from alice as seed
|
||||||
let args: Vec<&str> = vec![];
|
let args: Vec<&str> = vec![];
|
||||||
let bootnode_addr = generate(
|
let bootnode_addr =
|
||||||
peer_id,
|
generate(peer_id, &LOCALHOST, 5678, &args, &Some(String::from("data"))).unwrap();
|
||||||
&LOCALHOST,
|
assert_eq!(
|
||||||
5678,
|
|
||||||
&args,
|
|
||||||
&Some(String::from("data")),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
&bootnode_addr,
|
&bootnode_addr,
|
||||||
"/ip4/127.0.0.1/tcp/5678/ws/p2p/12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm/certhash/data"
|
"/ip4/127.0.0.1/tcp/5678/ws/p2p/12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm/certhash/data"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1624
-1790
File diff suppressed because it is too large
Load Diff
+448
-536
File diff suppressed because it is too large
Load Diff
+18
-18
@@ -3,22 +3,22 @@ use support::fs::FileSystemError;
|
|||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum GeneratorError {
|
pub enum GeneratorError {
|
||||||
#[error("Generating key {0} with input {1}")]
|
#[error("Generating key {0} with input {1}")]
|
||||||
KeyGeneration(String, String),
|
KeyGeneration(String, String),
|
||||||
#[error("Generating port {0}, err {1}")]
|
#[error("Generating port {0}, err {1}")]
|
||||||
PortGeneration(u16, String),
|
PortGeneration(u16, String),
|
||||||
#[error("Chain-spec build error: {0}")]
|
#[error("Chain-spec build error: {0}")]
|
||||||
ChainSpecGeneration(String),
|
ChainSpecGeneration(String),
|
||||||
#[error("Provider error: {0}")]
|
#[error("Provider error: {0}")]
|
||||||
ProviderError(#[from] ProviderError),
|
ProviderError(#[from] ProviderError),
|
||||||
#[error("FileSystem error")]
|
#[error("FileSystem error")]
|
||||||
FileSystemError(#[from] FileSystemError),
|
FileSystemError(#[from] FileSystemError),
|
||||||
#[error("Generating identity, err {0}")]
|
#[error("Generating identity, err {0}")]
|
||||||
IdentityGeneration(String),
|
IdentityGeneration(String),
|
||||||
#[error("Generating bootnode address, err {0}")]
|
#[error("Generating bootnode address, err {0}")]
|
||||||
BootnodeAddrGeneration(String),
|
BootnodeAddrGeneration(String),
|
||||||
#[error("Error overriding wasm on raw chain-spec, err {0}")]
|
#[error("Error overriding wasm on raw chain-spec, err {0}")]
|
||||||
OverridingWasm(String),
|
OverridingWasm(String),
|
||||||
#[error("Error overriding raw chain-spec, err {0}")]
|
#[error("Error overriding raw chain-spec, err {0}")]
|
||||||
OverridingRawSpec(String),
|
OverridingRawSpec(String),
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-24
@@ -7,35 +7,29 @@ use super::errors::GeneratorError;
|
|||||||
// Generate p2p identity for node
|
// Generate p2p identity for node
|
||||||
// return `node-key` and `peerId`
|
// return `node-key` and `peerId`
|
||||||
pub fn generate(node_name: &str) -> Result<(String, String), GeneratorError> {
|
pub fn generate(node_name: &str) -> Result<(String, String), GeneratorError> {
|
||||||
let key = hex::encode(sha2::Sha256::digest(node_name));
|
let key = hex::encode(sha2::Sha256::digest(node_name));
|
||||||
|
|
||||||
let bytes = <[u8; 32]>::from_hex(key.clone()).map_err(|_| {
|
let bytes = <[u8; 32]>::from_hex(key.clone()).map_err(|_| {
|
||||||
GeneratorError::IdentityGeneration("can not transform hex to [u8;32]".into())
|
GeneratorError::IdentityGeneration("can not transform hex to [u8;32]".into())
|
||||||
})?;
|
})?;
|
||||||
let sk = ed25519::SecretKey::try_from_bytes(bytes)
|
let sk = ed25519::SecretKey::try_from_bytes(bytes)
|
||||||
.map_err(|_| GeneratorError::IdentityGeneration("can not create sk from bytes".into()))?;
|
.map_err(|_| GeneratorError::IdentityGeneration("can not create sk from bytes".into()))?;
|
||||||
let local_identity: Keypair = ed25519::Keypair::from(sk).into();
|
let local_identity: Keypair = ed25519::Keypair::from(sk).into();
|
||||||
let local_public = local_identity.public();
|
let local_public = local_identity.public();
|
||||||
let local_peer_id = local_public.to_peer_id();
|
let local_peer_id = local_public.to_peer_id();
|
||||||
|
|
||||||
Ok((key, local_peer_id.to_base58()))
|
Ok((key, local_peer_id.to_base58()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_for_alice() {
|
fn generate_for_alice() {
|
||||||
let s = "alice";
|
let s = "alice";
|
||||||
let (key, peer_id) = generate(s).unwrap();
|
let (key, peer_id) = generate(s).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(&key, "2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90");
|
||||||
&key,
|
assert_eq!(&peer_id, "12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm");
|
||||||
"2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90"
|
}
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
&peer_id,
|
|
||||||
"12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-121
@@ -1,151 +1,141 @@
|
|||||||
use pezsp_core::{crypto::SecretStringError, ecdsa, ed25519, keccak_256, sr25519, Pair, H160, H256};
|
use pezsp_core::{
|
||||||
|
crypto::SecretStringError, ecdsa, ed25519, keccak_256, sr25519, Pair, H160, H256,
|
||||||
|
};
|
||||||
|
|
||||||
use super::errors::GeneratorError;
|
use super::errors::GeneratorError;
|
||||||
use crate::shared::types::{Accounts, NodeAccount};
|
use crate::shared::types::{Accounts, NodeAccount};
|
||||||
const KEYS: [&str; 5] = ["sr", "sr_stash", "ed", "ec", "eth"];
|
const KEYS: [&str; 5] = ["sr", "sr_stash", "ed", "ec", "eth"];
|
||||||
|
|
||||||
pub fn generate_pair<T: Pair>(seed: &str) -> Result<T::Pair, SecretStringError> {
|
pub fn generate_pair<T: Pair>(seed: &str) -> Result<T::Pair, SecretStringError> {
|
||||||
let pair = T::Pair::from_string(seed, None)?;
|
let pair = T::Pair::from_string(seed, None)?;
|
||||||
Ok(pair)
|
Ok(pair)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate(seed: &str) -> Result<Accounts, GeneratorError> {
|
pub fn generate(seed: &str) -> Result<Accounts, GeneratorError> {
|
||||||
let mut accounts: Accounts = Default::default();
|
let mut accounts: Accounts = Default::default();
|
||||||
for k in KEYS {
|
for k in KEYS {
|
||||||
let (address, public_key) = match k {
|
let (address, public_key) = match k {
|
||||||
"sr" => {
|
"sr" => {
|
||||||
let pair = generate_pair::<sr25519::Pair>(seed)
|
let pair = generate_pair::<sr25519::Pair>(seed)
|
||||||
.map_err(|_| GeneratorError::KeyGeneration(k.into(), seed.into()))?;
|
.map_err(|_| GeneratorError::KeyGeneration(k.into(), seed.into()))?;
|
||||||
(pair.public().to_string(), hex::encode(pair.public()))
|
(pair.public().to_string(), hex::encode(pair.public()))
|
||||||
},
|
},
|
||||||
"sr_stash" => {
|
"sr_stash" => {
|
||||||
let pair = generate_pair::<sr25519::Pair>(&format!("{seed}//stash"))
|
let pair = generate_pair::<sr25519::Pair>(&format!("{seed}//stash"))
|
||||||
.map_err(|_| GeneratorError::KeyGeneration(k.into(), seed.into()))?;
|
.map_err(|_| GeneratorError::KeyGeneration(k.into(), seed.into()))?;
|
||||||
(pair.public().to_string(), hex::encode(pair.public()))
|
(pair.public().to_string(), hex::encode(pair.public()))
|
||||||
},
|
},
|
||||||
"ed" => {
|
"ed" => {
|
||||||
let pair = generate_pair::<ed25519::Pair>(seed)
|
let pair = generate_pair::<ed25519::Pair>(seed)
|
||||||
.map_err(|_| GeneratorError::KeyGeneration(k.into(), seed.into()))?;
|
.map_err(|_| GeneratorError::KeyGeneration(k.into(), seed.into()))?;
|
||||||
(pair.public().to_string(), hex::encode(pair.public()))
|
(pair.public().to_string(), hex::encode(pair.public()))
|
||||||
},
|
},
|
||||||
"ec" => {
|
"ec" => {
|
||||||
let pair = generate_pair::<ecdsa::Pair>(seed)
|
let pair = generate_pair::<ecdsa::Pair>(seed)
|
||||||
.map_err(|_| GeneratorError::KeyGeneration(k.into(), seed.into()))?;
|
.map_err(|_| GeneratorError::KeyGeneration(k.into(), seed.into()))?;
|
||||||
(pair.public().to_string(), hex::encode(pair.public()))
|
(pair.public().to_string(), hex::encode(pair.public()))
|
||||||
},
|
},
|
||||||
"eth" => {
|
"eth" => {
|
||||||
let pair = generate_pair::<ecdsa::Pair>(seed)
|
let pair = generate_pair::<ecdsa::Pair>(seed)
|
||||||
.map_err(|_| GeneratorError::KeyGeneration(k.into(), seed.into()))?;
|
.map_err(|_| GeneratorError::KeyGeneration(k.into(), seed.into()))?;
|
||||||
|
|
||||||
let decompressed = libsecp256k1::PublicKey::parse_compressed(&pair.public().0)
|
let decompressed = libsecp256k1::PublicKey::parse_compressed(&pair.public().0)
|
||||||
.map_err(|_| GeneratorError::KeyGeneration(k.into(), seed.into()))?
|
.map_err(|_| GeneratorError::KeyGeneration(k.into(), seed.into()))?
|
||||||
.serialize();
|
.serialize();
|
||||||
let mut m = [0u8; 64];
|
let mut m = [0u8; 64];
|
||||||
m.copy_from_slice(&decompressed[1..65]);
|
m.copy_from_slice(&decompressed[1..65]);
|
||||||
let account = H160::from(H256::from(keccak_256(&m)));
|
let account = H160::from(H256::from(keccak_256(&m)));
|
||||||
|
|
||||||
(hex::encode(account), hex::encode(account))
|
(hex::encode(account), hex::encode(account))
|
||||||
},
|
},
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
accounts.insert(k.into(), NodeAccount::new(address, public_key));
|
accounts.insert(k.into(), NodeAccount::new(address, public_key));
|
||||||
}
|
}
|
||||||
Ok(accounts)
|
Ok(accounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_for_alice() {
|
fn generate_for_alice() {
|
||||||
use pezsp_core::crypto::Ss58Codec;
|
use pezsp_core::crypto::Ss58Codec;
|
||||||
let s = "Alice";
|
let s = "Alice";
|
||||||
let seed = format!("//{s}");
|
let seed = format!("//{s}");
|
||||||
|
|
||||||
let pair = generate_pair::<sr25519::Pair>(&seed).unwrap();
|
let pair = generate_pair::<sr25519::Pair>(&seed).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
|
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
|
||||||
pair.public().to_ss58check()
|
pair.public().to_ss58check()
|
||||||
);
|
);
|
||||||
|
|
||||||
let pair = generate_pair::<ecdsa::Pair>(&seed).unwrap();
|
let pair = generate_pair::<ecdsa::Pair>(&seed).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"0x020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1",
|
"0x020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1",
|
||||||
format!("0x{}", hex::encode(pair.public()))
|
format!("0x{}", hex::encode(pair.public()))
|
||||||
);
|
);
|
||||||
|
|
||||||
let pair = generate_pair::<ed25519::Pair>(&seed).unwrap();
|
let pair = generate_pair::<ed25519::Pair>(&seed).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu",
|
"5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu",
|
||||||
pair.public().to_ss58check()
|
pair.public().to_ss58check()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_for_zombie() {
|
fn generate_for_zombie() {
|
||||||
use pezsp_core::crypto::Ss58Codec;
|
use pezsp_core::crypto::Ss58Codec;
|
||||||
let s = "Zombie";
|
let s = "Zombie";
|
||||||
let seed = format!("//{s}");
|
let seed = format!("//{s}");
|
||||||
|
|
||||||
let pair = generate_pair::<sr25519::Pair>(&seed).unwrap();
|
let pair = generate_pair::<sr25519::Pair>(&seed).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"5FTcLfwFc7ctvqp3RhbEig6UuHLHcHVRujuUm8r21wy4dAR8",
|
"5FTcLfwFc7ctvqp3RhbEig6UuHLHcHVRujuUm8r21wy4dAR8",
|
||||||
pair.public().to_ss58check()
|
pair.public().to_ss58check()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_pair_invalid_should_fail() {
|
fn generate_pair_invalid_should_fail() {
|
||||||
let s = "Alice";
|
let s = "Alice";
|
||||||
let seed = s.to_string();
|
let seed = s.to_string();
|
||||||
|
|
||||||
let pair = generate_pair::<sr25519::Pair>(&seed);
|
let pair = generate_pair::<sr25519::Pair>(&seed);
|
||||||
assert!(pair.is_err());
|
assert!(pair.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_invalid_should_fail() {
|
fn generate_invalid_should_fail() {
|
||||||
let s = "Alice";
|
let s = "Alice";
|
||||||
let seed = s.to_string();
|
let seed = s.to_string();
|
||||||
|
|
||||||
let pair = generate(&seed);
|
let pair = generate(&seed);
|
||||||
assert!(pair.is_err());
|
assert!(pair.is_err());
|
||||||
assert!(matches!(pair, Err(GeneratorError::KeyGeneration(_, _))));
|
assert!(matches!(pair, Err(GeneratorError::KeyGeneration(_, _))));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_work() {
|
fn generate_work() {
|
||||||
let s = "Alice";
|
let s = "Alice";
|
||||||
let seed = format!("//{s}");
|
let seed = format!("//{s}");
|
||||||
|
|
||||||
let pair = generate(&seed).unwrap();
|
let pair = generate(&seed).unwrap();
|
||||||
let sr = pair.get("sr").unwrap();
|
let sr = pair.get("sr").unwrap();
|
||||||
let sr_stash = pair.get("sr_stash").unwrap();
|
let sr_stash = pair.get("sr_stash").unwrap();
|
||||||
let ed = pair.get("ed").unwrap();
|
let ed = pair.get("ed").unwrap();
|
||||||
let ec = pair.get("ec").unwrap();
|
let ec = pair.get("ec").unwrap();
|
||||||
let eth = pair.get("eth").unwrap();
|
let eth = pair.get("eth").unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(sr.address, "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY");
|
||||||
sr.address,
|
assert_eq!(sr_stash.address, "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY");
|
||||||
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
|
assert_eq!(ed.address, "5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu");
|
||||||
);
|
assert_eq!(
|
||||||
assert_eq!(
|
format!("0x{}", ec.public_key),
|
||||||
sr_stash.address,
|
"0x020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1"
|
||||||
"5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY"
|
);
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ed.address,
|
|
||||||
"5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
format!("0x{}", ec.public_key),
|
|
||||||
"0x020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(format!("0x{}", eth.public_key), "0xe04cc55ebee1cbce552f250e85c57b70b2e2625b")
|
||||||
format!("0x{}", eth.public_key),
|
}
|
||||||
"0xe04cc55ebee1cbce552f250e85c57b70b2e2625b"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+183
-219
@@ -1,6 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
vec,
|
vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use hex::encode;
|
use hex::encode;
|
||||||
@@ -8,9 +8,9 @@ use support::{constants::THIS_IS_A_BUG, fs::FileSystem};
|
|||||||
|
|
||||||
use super::errors::GeneratorError;
|
use super::errors::GeneratorError;
|
||||||
use crate::{
|
use crate::{
|
||||||
generators::keystore_key_types::{parse_keystore_key_types, KeystoreKeyType},
|
generators::keystore_key_types::{parse_keystore_key_types, KeystoreKeyType},
|
||||||
shared::types::NodeAccounts,
|
shared::types::NodeAccounts,
|
||||||
ScopedFilesystem,
|
ScopedFilesystem,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Generates keystore files for a node.
|
/// Generates keystore files for a node.
|
||||||
@@ -25,266 +25,230 @@ use crate::{
|
|||||||
/// If `keystore_key_types` is empty, all default key types will be generated.
|
/// If `keystore_key_types` is empty, all default key types will be generated.
|
||||||
/// Otherwise, only the specified key types will be generated.
|
/// Otherwise, only the specified key types will be generated.
|
||||||
pub async fn generate<'a, T>(
|
pub async fn generate<'a, T>(
|
||||||
acc: &NodeAccounts,
|
acc: &NodeAccounts,
|
||||||
node_files_path: impl AsRef<Path>,
|
node_files_path: impl AsRef<Path>,
|
||||||
scoped_fs: &ScopedFilesystem<'a, T>,
|
scoped_fs: &ScopedFilesystem<'a, T>,
|
||||||
asset_hub_polkadot: bool,
|
asset_hub_polkadot: bool,
|
||||||
keystore_key_types: Vec<&str>,
|
keystore_key_types: Vec<&str>,
|
||||||
) -> Result<Vec<PathBuf>, GeneratorError>
|
) -> Result<Vec<PathBuf>, GeneratorError>
|
||||||
where
|
where
|
||||||
T: FileSystem,
|
T: FileSystem,
|
||||||
{
|
{
|
||||||
// Create local keystore
|
// Create local keystore
|
||||||
scoped_fs.create_dir_all(node_files_path.as_ref()).await?;
|
scoped_fs.create_dir_all(node_files_path.as_ref()).await?;
|
||||||
let mut filenames = vec![];
|
let mut filenames = vec![];
|
||||||
|
|
||||||
// Parse the key type specifications
|
// Parse the key type specifications
|
||||||
let key_types = parse_keystore_key_types(&keystore_key_types, asset_hub_polkadot);
|
let key_types = parse_keystore_key_types(&keystore_key_types, asset_hub_polkadot);
|
||||||
|
|
||||||
let futures: Vec<_> = key_types
|
let futures: Vec<_> = key_types
|
||||||
.iter()
|
.iter()
|
||||||
.map(|key_type| {
|
.map(|key_type| {
|
||||||
let filename = generate_keystore_filename(key_type, acc);
|
let filename = generate_keystore_filename(key_type, acc);
|
||||||
let file_path = PathBuf::from(format!(
|
let file_path = PathBuf::from(format!(
|
||||||
"{}/{}",
|
"{}/{}",
|
||||||
node_files_path.as_ref().to_string_lossy(),
|
node_files_path.as_ref().to_string_lossy(),
|
||||||
filename
|
filename
|
||||||
));
|
));
|
||||||
let content = format!("\"{}\"", acc.seed);
|
let content = format!("\"{}\"", acc.seed);
|
||||||
(filename, scoped_fs.write(file_path, content))
|
(filename, scoped_fs.write(file_path, content))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for (filename, future) in futures {
|
for (filename, future) in futures {
|
||||||
future.await?;
|
future.await?;
|
||||||
filenames.push(PathBuf::from(filename));
|
filenames.push(PathBuf::from(filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(filenames)
|
Ok(filenames)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates the keystore filename for a given key type.
|
/// Generates the keystore filename for a given key type.
|
||||||
///
|
///
|
||||||
/// The filename format is: `{hex_encoded_key_type}{public_key}`
|
/// The filename format is: `{hex_encoded_key_type}{public_key}`
|
||||||
fn generate_keystore_filename(key_type: &KeystoreKeyType, acc: &NodeAccounts) -> String {
|
fn generate_keystore_filename(key_type: &KeystoreKeyType, acc: &NodeAccounts) -> String {
|
||||||
let account_key = key_type.scheme.account_key();
|
let account_key = key_type.scheme.account_key();
|
||||||
let pk = acc
|
let pk = acc
|
||||||
.accounts
|
.accounts
|
||||||
.get(account_key)
|
.get(account_key)
|
||||||
.expect(&format!(
|
.expect(&format!("Key '{}' should be set for node {THIS_IS_A_BUG}", account_key))
|
||||||
"Key '{}' should be set for node {THIS_IS_A_BUG}",
|
.public_key
|
||||||
account_key
|
.as_str();
|
||||||
))
|
|
||||||
.public_key
|
|
||||||
.as_str();
|
|
||||||
|
|
||||||
format!("{}{}", encode(&key_type.key_type), pk)
|
format!("{}{}", encode(&key_type.key_type), pk)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{collections::HashMap, ffi::OsString, str::FromStr};
|
use std::{collections::HashMap, ffi::OsString, str::FromStr};
|
||||||
|
|
||||||
use support::fs::in_memory::{InMemoryFile, InMemoryFileSystem};
|
use support::fs::in_memory::{InMemoryFile, InMemoryFileSystem};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::shared::types::{NodeAccount, NodeAccounts};
|
use crate::shared::types::{NodeAccount, NodeAccounts};
|
||||||
|
|
||||||
fn create_test_accounts() -> NodeAccounts {
|
fn create_test_accounts() -> NodeAccounts {
|
||||||
let mut accounts = HashMap::new();
|
let mut accounts = HashMap::new();
|
||||||
accounts.insert(
|
accounts.insert("sr".to_string(), NodeAccount::new("sr_address", "sr_public_key"));
|
||||||
"sr".to_string(),
|
accounts.insert("ed".to_string(), NodeAccount::new("ed_address", "ed_public_key"));
|
||||||
NodeAccount::new("sr_address", "sr_public_key"),
|
accounts.insert("ec".to_string(), NodeAccount::new("ec_address", "ec_public_key"));
|
||||||
);
|
NodeAccounts { seed: "//Alice".to_string(), accounts }
|
||||||
accounts.insert(
|
}
|
||||||
"ed".to_string(),
|
|
||||||
NodeAccount::new("ed_address", "ed_public_key"),
|
|
||||||
);
|
|
||||||
accounts.insert(
|
|
||||||
"ec".to_string(),
|
|
||||||
NodeAccount::new("ec_address", "ec_public_key"),
|
|
||||||
);
|
|
||||||
NodeAccounts {
|
|
||||||
seed: "//Alice".to_string(),
|
|
||||||
accounts,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_test_fs() -> InMemoryFileSystem {
|
fn create_test_fs() -> InMemoryFileSystem {
|
||||||
InMemoryFileSystem::new(HashMap::from([(
|
InMemoryFileSystem::new(HashMap::from([(
|
||||||
OsString::from_str("/").unwrap(),
|
OsString::from_str("/").unwrap(),
|
||||||
InMemoryFile::dir(),
|
InMemoryFile::dir(),
|
||||||
)]))
|
)]))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn generate_creates_default_keystore_files_when_no_key_types_specified() {
|
async fn generate_creates_default_keystore_files_when_no_key_types_specified() {
|
||||||
let accounts = create_test_accounts();
|
let accounts = create_test_accounts();
|
||||||
let fs = create_test_fs();
|
let fs = create_test_fs();
|
||||||
let base_dir = "/tmp/test";
|
let base_dir = "/tmp/test";
|
||||||
|
|
||||||
let scoped_fs = ScopedFilesystem { fs: &fs, base_dir };
|
let scoped_fs = ScopedFilesystem { fs: &fs, base_dir };
|
||||||
let key_types: Vec<&str> = vec![];
|
let key_types: Vec<&str> = vec![];
|
||||||
|
|
||||||
let res = generate(&accounts, "node1", &scoped_fs, false, key_types).await;
|
let res = generate(&accounts, "node1", &scoped_fs, false, key_types).await;
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
|
|
||||||
let filenames = res.unwrap();
|
let filenames = res.unwrap();
|
||||||
|
|
||||||
assert!(filenames.len() > 10);
|
assert!(filenames.len() > 10);
|
||||||
|
|
||||||
let filename_strs: Vec<String> = filenames
|
let filename_strs: Vec<String> =
|
||||||
.iter()
|
filenames.iter().map(|p| p.to_string_lossy().to_string()).collect();
|
||||||
.map(|p| p.to_string_lossy().to_string())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Check that aura key is generated (hex of "aura" is 61757261)
|
// Check that aura key is generated (hex of "aura" is 61757261)
|
||||||
assert!(filename_strs.iter().any(|f| f.starts_with("61757261")));
|
assert!(filename_strs.iter().any(|f| f.starts_with("61757261")));
|
||||||
// Check that babe key is generated (hex of "babe" is 62616265)
|
// Check that babe key is generated (hex of "babe" is 62616265)
|
||||||
assert!(filename_strs.iter().any(|f| f.starts_with("62616265")));
|
assert!(filename_strs.iter().any(|f| f.starts_with("62616265")));
|
||||||
// Check that gran key is generated (hex of "gran" is 6772616e)
|
// Check that gran key is generated (hex of "gran" is 6772616e)
|
||||||
assert!(filename_strs.iter().any(|f| f.starts_with("6772616e")));
|
assert!(filename_strs.iter().any(|f| f.starts_with("6772616e")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn generate_creates_only_specified_keystore_files() {
|
async fn generate_creates_only_specified_keystore_files() {
|
||||||
let accounts = create_test_accounts();
|
let accounts = create_test_accounts();
|
||||||
let fs = create_test_fs();
|
let fs = create_test_fs();
|
||||||
let base_dir = "/tmp/test";
|
let base_dir = "/tmp/test";
|
||||||
|
|
||||||
let scoped_fs = ScopedFilesystem { fs: &fs, base_dir };
|
let scoped_fs = ScopedFilesystem { fs: &fs, base_dir };
|
||||||
let key_types = vec!["audi", "gran"];
|
let key_types = vec!["audi", "gran"];
|
||||||
|
|
||||||
let res = generate(&accounts, "node1", &scoped_fs, false, key_types).await;
|
let res = generate(&accounts, "node1", &scoped_fs, false, key_types).await;
|
||||||
|
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
|
|
||||||
let filenames = res.unwrap();
|
let filenames = res.unwrap();
|
||||||
assert_eq!(filenames.len(), 2);
|
assert_eq!(filenames.len(), 2);
|
||||||
|
|
||||||
let filename_strs: Vec<String> = filenames
|
let filename_strs: Vec<String> =
|
||||||
.iter()
|
filenames.iter().map(|p| p.to_string_lossy().to_string()).collect();
|
||||||
.map(|p| p.to_string_lossy().to_string())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// audi uses sr scheme by default
|
// audi uses sr scheme by default
|
||||||
assert!(filename_strs
|
assert!(filename_strs
|
||||||
.iter()
|
.iter()
|
||||||
.any(|f| f.starts_with("61756469") && f.contains("sr_public_key")));
|
.any(|f| f.starts_with("61756469") && f.contains("sr_public_key")));
|
||||||
// gran uses ed scheme by default
|
// gran uses ed scheme by default
|
||||||
assert!(filename_strs
|
assert!(filename_strs
|
||||||
.iter()
|
.iter()
|
||||||
.any(|f| f.starts_with("6772616e") && f.contains("ed_public_key")));
|
.any(|f| f.starts_with("6772616e") && f.contains("ed_public_key")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn generate_produces_correct_keystore_files() {
|
async fn generate_produces_correct_keystore_files() {
|
||||||
struct TestCase {
|
struct TestCase {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
key_types: Vec<&'static str>,
|
key_types: Vec<&'static str>,
|
||||||
asset_hub_polkadot: bool,
|
asset_hub_polkadot: bool,
|
||||||
expected_prefix: &'static str,
|
expected_prefix: &'static str,
|
||||||
expected_public_key: &'static str,
|
expected_public_key: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
let test_cases = vec![
|
let test_cases = vec![
|
||||||
TestCase {
|
TestCase {
|
||||||
name: "explicit scheme override (gran_sr)",
|
name: "explicit scheme override (gran_sr)",
|
||||||
key_types: vec!["gran_sr"],
|
key_types: vec!["gran_sr"],
|
||||||
asset_hub_polkadot: false,
|
asset_hub_polkadot: false,
|
||||||
expected_prefix: "6772616e", // "gran" in hex
|
expected_prefix: "6772616e", // "gran" in hex
|
||||||
expected_public_key: "sr_public_key",
|
expected_public_key: "sr_public_key",
|
||||||
},
|
},
|
||||||
TestCase {
|
TestCase {
|
||||||
name: "aura with asset_hub_polkadot uses ed",
|
name: "aura with asset_hub_polkadot uses ed",
|
||||||
key_types: vec!["aura"],
|
key_types: vec!["aura"],
|
||||||
asset_hub_polkadot: true,
|
asset_hub_polkadot: true,
|
||||||
expected_prefix: "61757261", // "aura" in hex
|
expected_prefix: "61757261", // "aura" in hex
|
||||||
expected_public_key: "ed_public_key",
|
expected_public_key: "ed_public_key",
|
||||||
},
|
},
|
||||||
TestCase {
|
TestCase {
|
||||||
name: "aura without asset_hub_polkadot uses sr",
|
name: "aura without asset_hub_polkadot uses sr",
|
||||||
key_types: vec!["aura"],
|
key_types: vec!["aura"],
|
||||||
asset_hub_polkadot: false,
|
asset_hub_polkadot: false,
|
||||||
expected_prefix: "61757261", // "aura" in hex
|
expected_prefix: "61757261", // "aura" in hex
|
||||||
expected_public_key: "sr_public_key",
|
expected_public_key: "sr_public_key",
|
||||||
},
|
},
|
||||||
TestCase {
|
TestCase {
|
||||||
name: "custom key type with explicit ec scheme",
|
name: "custom key type with explicit ec scheme",
|
||||||
key_types: vec!["cust_ec"],
|
key_types: vec!["cust_ec"],
|
||||||
asset_hub_polkadot: false,
|
asset_hub_polkadot: false,
|
||||||
expected_prefix: "63757374", // "cust" in hex
|
expected_prefix: "63757374", // "cust" in hex
|
||||||
expected_public_key: "ec_public_key",
|
expected_public_key: "ec_public_key",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
for tc in test_cases {
|
for tc in test_cases {
|
||||||
let accounts = create_test_accounts();
|
let accounts = create_test_accounts();
|
||||||
let fs = create_test_fs();
|
let fs = create_test_fs();
|
||||||
let scoped_fs = ScopedFilesystem {
|
let scoped_fs = ScopedFilesystem { fs: &fs, base_dir: "/tmp/test" };
|
||||||
fs: &fs,
|
|
||||||
base_dir: "/tmp/test",
|
|
||||||
};
|
|
||||||
|
|
||||||
let key_types: Vec<&str> = tc.key_types.clone();
|
let key_types: Vec<&str> = tc.key_types.clone();
|
||||||
let res = generate(
|
let res =
|
||||||
&accounts,
|
generate(&accounts, "node1", &scoped_fs, tc.asset_hub_polkadot, key_types).await;
|
||||||
"node1",
|
|
||||||
&scoped_fs,
|
|
||||||
tc.asset_hub_polkadot,
|
|
||||||
key_types,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
assert!(
|
assert!(res.is_ok(), "[{}] Expected Ok but got: {:?}", tc.name, res.err());
|
||||||
res.is_ok(),
|
let filenames = res.unwrap();
|
||||||
"[{}] Expected Ok but got: {:?}",
|
|
||||||
tc.name,
|
|
||||||
res.err()
|
|
||||||
);
|
|
||||||
let filenames = res.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(filenames.len(), 1, "[{}] Expected 1 file", tc.name);
|
assert_eq!(filenames.len(), 1, "[{}] Expected 1 file", tc.name);
|
||||||
|
|
||||||
let filename = filenames[0].to_string_lossy().to_string();
|
let filename = filenames[0].to_string_lossy().to_string();
|
||||||
assert!(
|
assert!(
|
||||||
filename.starts_with(tc.expected_prefix),
|
filename.starts_with(tc.expected_prefix),
|
||||||
"[{}] Expected prefix '{}', got '{}'",
|
"[{}] Expected prefix '{}', got '{}'",
|
||||||
tc.name,
|
tc.name,
|
||||||
tc.expected_prefix,
|
tc.expected_prefix,
|
||||||
filename
|
filename
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
filename.contains(tc.expected_public_key),
|
filename.contains(tc.expected_public_key),
|
||||||
"[{}] Expected public key '{}' in '{}'",
|
"[{}] Expected public key '{}' in '{}'",
|
||||||
tc.name,
|
tc.name,
|
||||||
tc.expected_public_key,
|
tc.expected_public_key,
|
||||||
filename
|
filename
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn generate_ignores_invalid_key_specs_and_uses_defaults() {
|
async fn generate_ignores_invalid_key_specs_and_uses_defaults() {
|
||||||
let accounts = create_test_accounts();
|
let accounts = create_test_accounts();
|
||||||
let fs = create_test_fs();
|
let fs = create_test_fs();
|
||||||
let scoped_fs = ScopedFilesystem {
|
let scoped_fs = ScopedFilesystem { fs: &fs, base_dir: "/tmp/test" };
|
||||||
fs: &fs,
|
|
||||||
base_dir: "/tmp/test",
|
|
||||||
};
|
|
||||||
|
|
||||||
let key_types = vec![
|
let key_types = vec![
|
||||||
"invalid", // Too long
|
"invalid", // Too long
|
||||||
"xxx", // Too short
|
"xxx", // Too short
|
||||||
"audi_xx", // Invalid sceme
|
"audi_xx", // Invalid sceme
|
||||||
];
|
];
|
||||||
|
|
||||||
let res = generate(&accounts, "node1", &scoped_fs, false, key_types).await;
|
let res = generate(&accounts, "node1", &scoped_fs, false, key_types).await;
|
||||||
|
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
let filenames = res.unwrap();
|
let filenames = res.unwrap();
|
||||||
|
|
||||||
// Should fall back to defaults since all specs are invalid
|
// Should fall back to defaults since all specs are invalid
|
||||||
assert!(filenames.len() > 10);
|
assert!(filenames.len() > 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+188
-193
@@ -5,94 +5,91 @@ use serde::{Deserialize, Serialize};
|
|||||||
/// Supported cryptographic schemes for keystore keys.
|
/// Supported cryptographic schemes for keystore keys.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub enum KeyScheme {
|
pub enum KeyScheme {
|
||||||
/// Sr25519 scheme
|
/// Sr25519 scheme
|
||||||
Sr,
|
Sr,
|
||||||
/// Ed25519 scheme
|
/// Ed25519 scheme
|
||||||
Ed,
|
Ed,
|
||||||
/// ECDSA scheme
|
/// ECDSA scheme
|
||||||
Ec,
|
Ec,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyScheme {
|
impl KeyScheme {
|
||||||
/// Returns the account key suffix used in `NodeAccounts` for this scheme.
|
/// Returns the account key suffix used in `NodeAccounts` for this scheme.
|
||||||
pub fn account_key(&self) -> &'static str {
|
pub fn account_key(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
KeyScheme::Sr => "sr",
|
KeyScheme::Sr => "sr",
|
||||||
KeyScheme::Ed => "ed",
|
KeyScheme::Ed => "ed",
|
||||||
KeyScheme::Ec => "ec",
|
KeyScheme::Ec => "ec",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for KeyScheme {
|
impl std::fmt::Display for KeyScheme {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
KeyScheme::Sr => write!(f, "sr"),
|
KeyScheme::Sr => write!(f, "sr"),
|
||||||
KeyScheme::Ed => write!(f, "ed"),
|
KeyScheme::Ed => write!(f, "ed"),
|
||||||
KeyScheme::Ec => write!(f, "ec"),
|
KeyScheme::Ec => write!(f, "ec"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for KeyScheme {
|
impl TryFrom<&str> for KeyScheme {
|
||||||
type Error = String;
|
type Error = String;
|
||||||
|
|
||||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
match value.to_lowercase().as_str() {
|
match value.to_lowercase().as_str() {
|
||||||
"sr" => Ok(KeyScheme::Sr),
|
"sr" => Ok(KeyScheme::Sr),
|
||||||
"ed" => Ok(KeyScheme::Ed),
|
"ed" => Ok(KeyScheme::Ed),
|
||||||
"ec" => Ok(KeyScheme::Ec),
|
"ec" => Ok(KeyScheme::Ec),
|
||||||
_ => Err(format!("Unsupported key scheme: {}", value)),
|
_ => Err(format!("Unsupported key scheme: {}", value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A parsed keystore key type.
|
/// A parsed keystore key type.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct KeystoreKeyType {
|
pub struct KeystoreKeyType {
|
||||||
/// The 4-character key type identifier (e.g., "aura", "babe", "gran").
|
/// The 4-character key type identifier (e.g., "aura", "babe", "gran").
|
||||||
pub key_type: String,
|
pub key_type: String,
|
||||||
/// The cryptographic scheme to use for this key type.
|
/// The cryptographic scheme to use for this key type.
|
||||||
pub scheme: KeyScheme,
|
pub scheme: KeyScheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeystoreKeyType {
|
impl KeystoreKeyType {
|
||||||
pub fn new(key_type: impl Into<String>, scheme: KeyScheme) -> Self {
|
pub fn new(key_type: impl Into<String>, scheme: KeyScheme) -> Self {
|
||||||
Self {
|
Self { key_type: key_type.into(), scheme }
|
||||||
key_type: key_type.into(),
|
}
|
||||||
scheme,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the default predefined key schemes for known key types.
|
/// Returns the default predefined key schemes for known key types.
|
||||||
/// Special handling for `aura` when `is_asset_hub_polkadot` is true.
|
/// Special handling for `aura` when `is_asset_hub_polkadot` is true.
|
||||||
fn get_predefined_schemes(is_asset_hub_polkadot: bool) -> HashMap<&'static str, KeyScheme> {
|
fn get_predefined_schemes(is_asset_hub_polkadot: bool) -> HashMap<&'static str, KeyScheme> {
|
||||||
let mut schemes = HashMap::new();
|
let mut schemes = HashMap::new();
|
||||||
|
|
||||||
// aura has special handling for asset-hub-polkadot
|
// aura has special handling for asset-hub-polkadot
|
||||||
if is_asset_hub_polkadot {
|
if is_asset_hub_polkadot {
|
||||||
schemes.insert("aura", KeyScheme::Ed);
|
schemes.insert("aura", KeyScheme::Ed);
|
||||||
} else {
|
} else {
|
||||||
schemes.insert("aura", KeyScheme::Sr);
|
schemes.insert("aura", KeyScheme::Sr);
|
||||||
}
|
}
|
||||||
|
|
||||||
schemes.insert("babe", KeyScheme::Sr);
|
schemes.insert("babe", KeyScheme::Sr);
|
||||||
schemes.insert("imon", KeyScheme::Sr);
|
schemes.insert("imon", KeyScheme::Sr);
|
||||||
schemes.insert("gran", KeyScheme::Ed);
|
schemes.insert("gran", KeyScheme::Ed);
|
||||||
schemes.insert("audi", KeyScheme::Sr);
|
schemes.insert("audi", KeyScheme::Sr);
|
||||||
schemes.insert("asgn", KeyScheme::Sr);
|
schemes.insert("asgn", KeyScheme::Sr);
|
||||||
schemes.insert("para", KeyScheme::Sr);
|
schemes.insert("para", KeyScheme::Sr);
|
||||||
schemes.insert("beef", KeyScheme::Ec);
|
schemes.insert("beef", KeyScheme::Ec);
|
||||||
schemes.insert("nmbs", KeyScheme::Sr); // Nimbus
|
schemes.insert("nmbs", KeyScheme::Sr); // Nimbus
|
||||||
schemes.insert("rand", KeyScheme::Sr); // Randomness (Moonbeam)
|
schemes.insert("rand", KeyScheme::Sr); // Randomness (Moonbeam)
|
||||||
schemes.insert("rate", KeyScheme::Ed); // Equilibrium rate module
|
schemes.insert("rate", KeyScheme::Ed); // Equilibrium rate module
|
||||||
schemes.insert("acco", KeyScheme::Sr);
|
schemes.insert("acco", KeyScheme::Sr);
|
||||||
schemes.insert("bcsv", KeyScheme::Sr); // BlockchainSrvc (StorageHub)
|
schemes.insert("bcsv", KeyScheme::Sr); // BlockchainSrvc (StorageHub)
|
||||||
schemes.insert("ftsv", KeyScheme::Ed); // FileTransferSrvc (StorageHub)
|
schemes.insert("ftsv", KeyScheme::Ed); // FileTransferSrvc (StorageHub)
|
||||||
schemes.insert("mixn", KeyScheme::Sr); // Mixnet
|
schemes.insert("mixn", KeyScheme::Sr); // Mixnet
|
||||||
|
|
||||||
schemes
|
schemes
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a single keystore key type specification string.
|
/// Parses a single keystore key type specification string.
|
||||||
@@ -103,26 +100,26 @@ fn get_predefined_schemes(is_asset_hub_polkadot: bool) -> HashMap<&'static str,
|
|||||||
///
|
///
|
||||||
/// Returns `None` if the spec is invalid or doesn't match the expected format.
|
/// Returns `None` if the spec is invalid or doesn't match the expected format.
|
||||||
fn parse_key_spec(spec: &str, predefined: &HashMap<&str, KeyScheme>) -> Option<KeystoreKeyType> {
|
fn parse_key_spec(spec: &str, predefined: &HashMap<&str, KeyScheme>) -> Option<KeystoreKeyType> {
|
||||||
let spec = spec.trim();
|
let spec = spec.trim();
|
||||||
|
|
||||||
// Try parsing as long form first: key_type_scheme (e.g., "audi_sr")
|
// Try parsing as long form first: key_type_scheme (e.g., "audi_sr")
|
||||||
if let Some((key_type, scheme_str)) = spec.split_once('_') {
|
if let Some((key_type, scheme_str)) = spec.split_once('_') {
|
||||||
if key_type.len() != 4 {
|
if key_type.len() != 4 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let scheme = KeyScheme::try_from(scheme_str).ok()?;
|
let scheme = KeyScheme::try_from(scheme_str).ok()?;
|
||||||
return Some(KeystoreKeyType::new(key_type, scheme));
|
return Some(KeystoreKeyType::new(key_type, scheme));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try parsing as short form: key_type only (e.g., "audi")
|
// Try parsing as short form: key_type only (e.g., "audi")
|
||||||
if spec.len() == 4 {
|
if spec.len() == 4 {
|
||||||
// Look up predefined scheme; default to Sr if not found
|
// Look up predefined scheme; default to Sr if not found
|
||||||
let scheme = predefined.get(spec).copied().unwrap_or(KeyScheme::Sr);
|
let scheme = predefined.get(spec).copied().unwrap_or(KeyScheme::Sr);
|
||||||
return Some(KeystoreKeyType::new(spec, scheme));
|
return Some(KeystoreKeyType::new(spec, scheme));
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a list of keystore key type specifications.
|
/// Parses a list of keystore key type specifications.
|
||||||
@@ -132,151 +129,149 @@ fn parse_key_spec(spec: &str, predefined: &HashMap<&str, KeyScheme>) -> Option<K
|
|||||||
///
|
///
|
||||||
/// If the resulting list is empty, returns the default keystore key types.
|
/// If the resulting list is empty, returns the default keystore key types.
|
||||||
pub fn parse_keystore_key_types<T: AsRef<str>>(
|
pub fn parse_keystore_key_types<T: AsRef<str>>(
|
||||||
specs: &[T],
|
specs: &[T],
|
||||||
is_asset_hub_polkadot: bool,
|
is_asset_hub_polkadot: bool,
|
||||||
) -> Vec<KeystoreKeyType> {
|
) -> Vec<KeystoreKeyType> {
|
||||||
let predefined_schemes = get_predefined_schemes(is_asset_hub_polkadot);
|
let predefined_schemes = get_predefined_schemes(is_asset_hub_polkadot);
|
||||||
|
|
||||||
let parsed: Vec<KeystoreKeyType> = specs
|
let parsed: Vec<KeystoreKeyType> = specs
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|spec| parse_key_spec(spec.as_ref(), &predefined_schemes))
|
.filter_map(|spec| parse_key_spec(spec.as_ref(), &predefined_schemes))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if parsed.is_empty() {
|
if parsed.is_empty() {
|
||||||
get_default_keystore_key_types(is_asset_hub_polkadot)
|
get_default_keystore_key_types(is_asset_hub_polkadot)
|
||||||
} else {
|
} else {
|
||||||
parsed
|
parsed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the default keystore key types when none are specified.
|
/// Returns the default keystore key types when none are specified.
|
||||||
pub fn get_default_keystore_key_types(is_asset_hub_polkadot: bool) -> Vec<KeystoreKeyType> {
|
pub fn get_default_keystore_key_types(is_asset_hub_polkadot: bool) -> Vec<KeystoreKeyType> {
|
||||||
let predefined_schemes = get_predefined_schemes(is_asset_hub_polkadot);
|
let predefined_schemes = get_predefined_schemes(is_asset_hub_polkadot);
|
||||||
let default_keys = [
|
let default_keys = [
|
||||||
"aura", "babe", "imon", "gran", "audi", "asgn", "para", "beef", "nmbs", "rand", "rate",
|
"aura", "babe", "imon", "gran", "audi", "asgn", "para", "beef", "nmbs", "rand", "rate",
|
||||||
"mixn", "bcsv", "ftsv",
|
"mixn", "bcsv", "ftsv",
|
||||||
];
|
];
|
||||||
|
|
||||||
default_keys
|
default_keys
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|key_type| {
|
.filter_map(|key_type| {
|
||||||
predefined_schemes
|
predefined_schemes.get(*key_type).map(|scheme| KeystoreKeyType::new(*key_type, *scheme))
|
||||||
.get(*key_type)
|
})
|
||||||
.map(|scheme| KeystoreKeyType::new(*key_type, *scheme))
|
.collect()
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_keystore_key_types_ignores_invalid_specs() {
|
fn parse_keystore_key_types_ignores_invalid_specs() {
|
||||||
let specs = vec![
|
let specs = vec![
|
||||||
"audi".to_string(),
|
"audi".to_string(),
|
||||||
"invalid".to_string(), // Too long - ignored
|
"invalid".to_string(), // Too long - ignored
|
||||||
"xxx".to_string(), // Too short - ignored
|
"xxx".to_string(), // Too short - ignored
|
||||||
"xxxx".to_string(), // Unknown key - defaults to sr
|
"xxxx".to_string(), // Unknown key - defaults to sr
|
||||||
"audi_xx".to_string(), // Invalid scheme - ignored
|
"audi_xx".to_string(), // Invalid scheme - ignored
|
||||||
"gran".to_string(),
|
"gran".to_string(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let result = parse_keystore_key_types(&specs, false);
|
let result = parse_keystore_key_types(&specs, false);
|
||||||
assert_eq!(result.len(), 3);
|
assert_eq!(result.len(), 3);
|
||||||
assert_eq!(result[1], KeystoreKeyType::new("xxxx", KeyScheme::Sr)); // Unknown defaults to sr
|
assert_eq!(result[1], KeystoreKeyType::new("xxxx", KeyScheme::Sr)); // Unknown defaults to sr
|
||||||
assert_eq!(result[2], KeystoreKeyType::new("gran", KeyScheme::Ed));
|
assert_eq!(result[2], KeystoreKeyType::new("gran", KeyScheme::Ed));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_keystore_key_types_returns_specified_keys() {
|
fn parse_keystore_key_types_returns_specified_keys() {
|
||||||
let specs = vec!["audi".to_string(), "gran".to_string()];
|
let specs = vec!["audi".to_string(), "gran".to_string()];
|
||||||
let res = parse_keystore_key_types(&specs, false);
|
let res = parse_keystore_key_types(&specs, false);
|
||||||
|
|
||||||
assert_eq!(res.len(), 2);
|
assert_eq!(res.len(), 2);
|
||||||
assert_eq!(res[0], KeystoreKeyType::new("audi", KeyScheme::Sr));
|
assert_eq!(res[0], KeystoreKeyType::new("audi", KeyScheme::Sr));
|
||||||
assert_eq!(res[1], KeystoreKeyType::new("gran", KeyScheme::Ed));
|
assert_eq!(res[1], KeystoreKeyType::new("gran", KeyScheme::Ed));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_keystore_key_types_mixed_short_and_long_forms() {
|
fn parse_keystore_key_types_mixed_short_and_long_forms() {
|
||||||
let specs = vec![
|
let specs = vec![
|
||||||
"audi".to_string(),
|
"audi".to_string(),
|
||||||
"gran_sr".to_string(), // Override gran's default ed to sr
|
"gran_sr".to_string(), // Override gran's default ed to sr
|
||||||
"gran".to_string(),
|
"gran".to_string(),
|
||||||
"beef".to_string(),
|
"beef".to_string(),
|
||||||
];
|
];
|
||||||
let res = parse_keystore_key_types(&specs, false);
|
let res = parse_keystore_key_types(&specs, false);
|
||||||
|
|
||||||
assert_eq!(res.len(), 4);
|
assert_eq!(res.len(), 4);
|
||||||
assert_eq!(res[0], KeystoreKeyType::new("audi", KeyScheme::Sr));
|
assert_eq!(res[0], KeystoreKeyType::new("audi", KeyScheme::Sr));
|
||||||
assert_eq!(res[1], KeystoreKeyType::new("gran", KeyScheme::Sr)); // Overridden
|
assert_eq!(res[1], KeystoreKeyType::new("gran", KeyScheme::Sr)); // Overridden
|
||||||
assert_eq!(res[2], KeystoreKeyType::new("gran", KeyScheme::Ed));
|
assert_eq!(res[2], KeystoreKeyType::new("gran", KeyScheme::Ed));
|
||||||
assert_eq!(res[3], KeystoreKeyType::new("beef", KeyScheme::Ec));
|
assert_eq!(res[3], KeystoreKeyType::new("beef", KeyScheme::Ec));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_keystore_key_types_returns_defaults_when_empty() {
|
fn parse_keystore_key_types_returns_defaults_when_empty() {
|
||||||
let specs: Vec<String> = vec![];
|
let specs: Vec<String> = vec![];
|
||||||
let res = parse_keystore_key_types(&specs, false);
|
let res = parse_keystore_key_types(&specs, false);
|
||||||
|
|
||||||
// Should return all default keys
|
// Should return all default keys
|
||||||
assert!(!res.is_empty());
|
assert!(!res.is_empty());
|
||||||
assert!(res.iter().any(|k| k.key_type == "aura"));
|
assert!(res.iter().any(|k| k.key_type == "aura"));
|
||||||
assert!(res.iter().any(|k| k.key_type == "babe"));
|
assert!(res.iter().any(|k| k.key_type == "babe"));
|
||||||
assert!(res.iter().any(|k| k.key_type == "gran"));
|
assert!(res.iter().any(|k| k.key_type == "gran"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_keystore_key_types_allows_custom_key_with_explicit_scheme() {
|
fn parse_keystore_key_types_allows_custom_key_with_explicit_scheme() {
|
||||||
let specs = vec![
|
let specs = vec![
|
||||||
"cust_sr".to_string(), // Custom key with explicit scheme
|
"cust_sr".to_string(), // Custom key with explicit scheme
|
||||||
"audi".to_string(),
|
"audi".to_string(),
|
||||||
];
|
];
|
||||||
let result = parse_keystore_key_types(&specs, false);
|
let result = parse_keystore_key_types(&specs, false);
|
||||||
|
|
||||||
assert_eq!(result.len(), 2);
|
assert_eq!(result.len(), 2);
|
||||||
assert_eq!(result[0], KeystoreKeyType::new("cust", KeyScheme::Sr));
|
assert_eq!(result[0], KeystoreKeyType::new("cust", KeyScheme::Sr));
|
||||||
assert_eq!(result[1], KeystoreKeyType::new("audi", KeyScheme::Sr));
|
assert_eq!(result[1], KeystoreKeyType::new("audi", KeyScheme::Sr));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn full_workflow_asset_hub_polkadot() {
|
fn full_workflow_asset_hub_polkadot() {
|
||||||
// For asset-hub-polkadot, aura should default to ed
|
// For asset-hub-polkadot, aura should default to ed
|
||||||
let specs = vec!["aura".to_string(), "babe".to_string()];
|
let specs = vec!["aura".to_string(), "babe".to_string()];
|
||||||
|
|
||||||
let res = parse_keystore_key_types(&specs, true);
|
let res = parse_keystore_key_types(&specs, true);
|
||||||
|
|
||||||
assert_eq!(res.len(), 2);
|
assert_eq!(res.len(), 2);
|
||||||
assert_eq!(res[0].key_type, "aura");
|
assert_eq!(res[0].key_type, "aura");
|
||||||
assert_eq!(res[0].scheme, KeyScheme::Ed); // ed for asset-hub-polkadot
|
assert_eq!(res[0].scheme, KeyScheme::Ed); // ed for asset-hub-polkadot
|
||||||
|
|
||||||
assert_eq!(res[1].key_type, "babe");
|
assert_eq!(res[1].key_type, "babe");
|
||||||
assert_eq!(res[1].scheme, KeyScheme::Sr);
|
assert_eq!(res[1].scheme, KeyScheme::Sr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn full_workflow_custom_key_types() {
|
fn full_workflow_custom_key_types() {
|
||||||
let specs = vec![
|
let specs = vec![
|
||||||
"aura".to_string(), // Use default scheme
|
"aura".to_string(), // Use default scheme
|
||||||
"gran_sr".to_string(), // Override gran to use sr instead of ed
|
"gran_sr".to_string(), // Override gran to use sr instead of ed
|
||||||
"cust_ec".to_string(), // Custom key type with ecdsa
|
"cust_ec".to_string(), // Custom key type with ecdsa
|
||||||
];
|
];
|
||||||
|
|
||||||
let res = parse_keystore_key_types(&specs, false);
|
let res = parse_keystore_key_types(&specs, false);
|
||||||
|
|
||||||
assert_eq!(res.len(), 3);
|
assert_eq!(res.len(), 3);
|
||||||
|
|
||||||
// aura uses default sr
|
// aura uses default sr
|
||||||
assert_eq!(res[0].key_type, "aura");
|
assert_eq!(res[0].key_type, "aura");
|
||||||
assert_eq!(res[0].scheme, KeyScheme::Sr);
|
assert_eq!(res[0].scheme, KeyScheme::Sr);
|
||||||
|
|
||||||
// gran overridden to sr
|
// gran overridden to sr
|
||||||
assert_eq!(res[1].key_type, "gran");
|
assert_eq!(res[1].key_type, "gran");
|
||||||
assert_eq!(res[1].scheme, KeyScheme::Sr);
|
assert_eq!(res[1].scheme, KeyScheme::Sr);
|
||||||
|
|
||||||
// custom key with ec
|
// custom key with ec
|
||||||
assert_eq!(res[2].key_type, "cust");
|
assert_eq!(res[2].key_type, "cust");
|
||||||
assert_eq!(res[2].scheme, KeyScheme::Ec);
|
assert_eq!(res[2].scheme, KeyScheme::Ec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+113
-127
@@ -2,9 +2,9 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use configuration::types::CommandWithCustomArgs;
|
use configuration::types::CommandWithCustomArgs;
|
||||||
use provider::{
|
use provider::{
|
||||||
constants::NODE_CONFIG_DIR,
|
constants::NODE_CONFIG_DIR,
|
||||||
types::{GenerateFileCommand, GenerateFilesOptions, TransferedFile},
|
types::{GenerateFileCommand, GenerateFilesOptions, TransferedFile},
|
||||||
DynNamespace,
|
DynNamespace,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use support::fs::FileSystem;
|
use support::fs::FileSystem;
|
||||||
@@ -15,151 +15,137 @@ use crate::ScopedFilesystem;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub(crate) enum ParaArtifactType {
|
pub(crate) enum ParaArtifactType {
|
||||||
Wasm,
|
Wasm,
|
||||||
State,
|
State,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub(crate) enum ParaArtifactBuildOption {
|
pub(crate) enum ParaArtifactBuildOption {
|
||||||
Path(String),
|
Path(String),
|
||||||
Command(String),
|
Command(String),
|
||||||
CommandWithCustomArgs(CommandWithCustomArgs),
|
CommandWithCustomArgs(CommandWithCustomArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parachain artifact (could be either the genesis state or genesis wasm)
|
/// Parachain artifact (could be either the genesis state or genesis wasm)
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ParaArtifact {
|
pub struct ParaArtifact {
|
||||||
artifact_type: ParaArtifactType,
|
artifact_type: ParaArtifactType,
|
||||||
build_option: ParaArtifactBuildOption,
|
build_option: ParaArtifactBuildOption,
|
||||||
artifact_path: Option<PathBuf>,
|
artifact_path: Option<PathBuf>,
|
||||||
// image to use for building the para artifact
|
// image to use for building the para artifact
|
||||||
image: Option<String>,
|
image: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParaArtifact {
|
impl ParaArtifact {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
artifact_type: ParaArtifactType,
|
artifact_type: ParaArtifactType,
|
||||||
build_option: ParaArtifactBuildOption,
|
build_option: ParaArtifactBuildOption,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self { artifact_type, build_option, artifact_path: None, image: None }
|
||||||
artifact_type,
|
}
|
||||||
build_option,
|
|
||||||
artifact_path: None,
|
|
||||||
image: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn image(mut self, image: Option<String>) -> Self {
|
pub(crate) fn image(mut self, image: Option<String>) -> Self {
|
||||||
self.image = image;
|
self.image = image;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn artifact_path(&self) -> Option<&PathBuf> {
|
pub(crate) fn artifact_path(&self) -> Option<&PathBuf> {
|
||||||
self.artifact_path.as_ref()
|
self.artifact_path.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn build<'a, T>(
|
pub(crate) async fn build<'a, T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
chain_spec_path: Option<impl AsRef<Path>>,
|
chain_spec_path: Option<impl AsRef<Path>>,
|
||||||
artifact_path: impl AsRef<Path>,
|
artifact_path: impl AsRef<Path>,
|
||||||
ns: &DynNamespace,
|
ns: &DynNamespace,
|
||||||
scoped_fs: &ScopedFilesystem<'a, T>,
|
scoped_fs: &ScopedFilesystem<'a, T>,
|
||||||
maybe_output_path: Option<PathBuf>,
|
maybe_output_path: Option<PathBuf>,
|
||||||
) -> Result<(), GeneratorError>
|
) -> Result<(), GeneratorError>
|
||||||
where
|
where
|
||||||
T: FileSystem,
|
T: FileSystem,
|
||||||
{
|
{
|
||||||
let (cmd, custom_args) = match &self.build_option {
|
let (cmd, custom_args) = match &self.build_option {
|
||||||
ParaArtifactBuildOption::Path(path) => {
|
ParaArtifactBuildOption::Path(path) => {
|
||||||
let t = TransferedFile::new(PathBuf::from(path), artifact_path.as_ref().into());
|
let t = TransferedFile::new(PathBuf::from(path), artifact_path.as_ref().into());
|
||||||
scoped_fs.copy_files(vec![&t]).await?;
|
scoped_fs.copy_files(vec![&t]).await?;
|
||||||
self.artifact_path = Some(artifact_path.as_ref().into());
|
self.artifact_path = Some(artifact_path.as_ref().into());
|
||||||
return Ok(()); // work done!
|
return Ok(()); // work done!
|
||||||
},
|
},
|
||||||
ParaArtifactBuildOption::Command(cmd) => (cmd, &vec![]),
|
ParaArtifactBuildOption::Command(cmd) => (cmd, &vec![]),
|
||||||
ParaArtifactBuildOption::CommandWithCustomArgs(cmd_with_custom_args) => {
|
ParaArtifactBuildOption::CommandWithCustomArgs(cmd_with_custom_args) => {
|
||||||
(
|
(&cmd_with_custom_args.cmd().as_str().to_string(), cmd_with_custom_args.args())
|
||||||
&cmd_with_custom_args.cmd().as_str().to_string(),
|
// (cmd.cmd_as_str().to_string(), cmd.1)
|
||||||
cmd_with_custom_args.args(),
|
},
|
||||||
)
|
};
|
||||||
// (cmd.cmd_as_str().to_string(), cmd.1)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let generate_subcmd = match self.artifact_type {
|
let generate_subcmd = match self.artifact_type {
|
||||||
ParaArtifactType::Wasm => "export-genesis-wasm",
|
ParaArtifactType::Wasm => "export-genesis-wasm",
|
||||||
ParaArtifactType::State => "export-genesis-state",
|
ParaArtifactType::State => "export-genesis-state",
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: replace uuid with para_id-random
|
// TODO: replace uuid with para_id-random
|
||||||
let temp_name = format!("temp-{}-{}", generate_subcmd, Uuid::new_v4());
|
let temp_name = format!("temp-{}-{}", generate_subcmd, Uuid::new_v4());
|
||||||
let mut args: Vec<String> = vec![generate_subcmd.into()];
|
let mut args: Vec<String> = vec![generate_subcmd.into()];
|
||||||
|
|
||||||
let files_to_inject = if let Some(chain_spec_path) = chain_spec_path {
|
let files_to_inject = if let Some(chain_spec_path) = chain_spec_path {
|
||||||
// TODO: we should get the full path from the scoped filesystem
|
// TODO: we should get the full path from the scoped filesystem
|
||||||
let chain_spec_path_local = format!(
|
let chain_spec_path_local = format!(
|
||||||
"{}/{}",
|
"{}/{}",
|
||||||
ns.base_dir().to_string_lossy(),
|
ns.base_dir().to_string_lossy(),
|
||||||
chain_spec_path.as_ref().to_string_lossy()
|
chain_spec_path.as_ref().to_string_lossy()
|
||||||
);
|
);
|
||||||
// Remote path to be injected
|
// Remote path to be injected
|
||||||
let chain_spec_path_in_pod = format!(
|
let chain_spec_path_in_pod =
|
||||||
"{}/{}",
|
format!("{}/{}", NODE_CONFIG_DIR, chain_spec_path.as_ref().to_string_lossy());
|
||||||
NODE_CONFIG_DIR,
|
// Path in the context of the node, this can be different in the context of the providers (e.g native)
|
||||||
chain_spec_path.as_ref().to_string_lossy()
|
let chain_spec_path_in_args = if ns.capabilities().prefix_with_full_path {
|
||||||
);
|
// In native
|
||||||
// Path in the context of the node, this can be different in the context of the providers (e.g native)
|
format!(
|
||||||
let chain_spec_path_in_args = if ns.capabilities().prefix_with_full_path {
|
"{}/{}{}",
|
||||||
// In native
|
ns.base_dir().to_string_lossy(),
|
||||||
format!(
|
&temp_name,
|
||||||
"{}/{}{}",
|
&chain_spec_path_in_pod
|
||||||
ns.base_dir().to_string_lossy(),
|
)
|
||||||
&temp_name,
|
} else {
|
||||||
&chain_spec_path_in_pod
|
chain_spec_path_in_pod.clone()
|
||||||
)
|
};
|
||||||
} else {
|
|
||||||
chain_spec_path_in_pod.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
args.push("--chain".into());
|
args.push("--chain".into());
|
||||||
args.push(chain_spec_path_in_args);
|
args.push(chain_spec_path_in_args);
|
||||||
|
|
||||||
for custom_arg in custom_args {
|
for custom_arg in custom_args {
|
||||||
match custom_arg {
|
match custom_arg {
|
||||||
configuration::types::Arg::Flag(flag) => {
|
configuration::types::Arg::Flag(flag) => {
|
||||||
args.push(flag.into());
|
args.push(flag.into());
|
||||||
},
|
},
|
||||||
configuration::types::Arg::Option(flag, flag_value) => {
|
configuration::types::Arg::Option(flag, flag_value) => {
|
||||||
args.push(flag.into());
|
args.push(flag.into());
|
||||||
args.push(flag_value.into());
|
args.push(flag_value.into());
|
||||||
},
|
},
|
||||||
configuration::types::Arg::Array(flag, values) => {
|
configuration::types::Arg::Array(flag, values) => {
|
||||||
args.push(flag.into());
|
args.push(flag.into());
|
||||||
values.iter().for_each(|v| args.push(v.into()));
|
values.iter().for_each(|v| args.push(v.into()));
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vec![TransferedFile::new(
|
vec![TransferedFile::new(chain_spec_path_local, chain_spec_path_in_pod)]
|
||||||
chain_spec_path_local,
|
} else {
|
||||||
chain_spec_path_in_pod,
|
vec![]
|
||||||
)]
|
};
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
|
|
||||||
let artifact_path_ref = artifact_path.as_ref();
|
let artifact_path_ref = artifact_path.as_ref();
|
||||||
let generate_command = GenerateFileCommand::new(cmd.as_str(), artifact_path_ref).args(args);
|
let generate_command = GenerateFileCommand::new(cmd.as_str(), artifact_path_ref).args(args);
|
||||||
let options = GenerateFilesOptions::with_files(
|
let options = GenerateFilesOptions::with_files(
|
||||||
vec![generate_command],
|
vec![generate_command],
|
||||||
self.image.clone(),
|
self.image.clone(),
|
||||||
&files_to_inject,
|
&files_to_inject,
|
||||||
maybe_output_path,
|
maybe_output_path,
|
||||||
)
|
)
|
||||||
.temp_name(temp_name);
|
.temp_name(temp_name);
|
||||||
ns.generate_files(options).await?;
|
ns.generate_files(options).await?;
|
||||||
self.artifact_path = Some(artifact_path_ref.into());
|
self.artifact_path = Some(artifact_path_ref.into());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,41 +8,39 @@ use crate::shared::types::ParkedPort;
|
|||||||
|
|
||||||
// TODO: (team), we want to continue support ws_port? No
|
// TODO: (team), we want to continue support ws_port? No
|
||||||
enum PortTypes {
|
enum PortTypes {
|
||||||
Rpc,
|
Rpc,
|
||||||
P2P,
|
P2P,
|
||||||
Prometheus,
|
Prometheus,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate(port: Option<Port>) -> Result<ParkedPort, GeneratorError> {
|
pub fn generate(port: Option<Port>) -> Result<ParkedPort, GeneratorError> {
|
||||||
let port = port.unwrap_or(0);
|
let port = port.unwrap_or(0);
|
||||||
let listener = TcpListener::bind(format!("0.0.0.0:{port}"))
|
let listener = TcpListener::bind(format!("0.0.0.0:{port}"))
|
||||||
.map_err(|_e| GeneratorError::PortGeneration(port, "Can't bind".into()))?;
|
.map_err(|_e| GeneratorError::PortGeneration(port, "Can't bind".into()))?;
|
||||||
let port = listener
|
let port = listener
|
||||||
.local_addr()
|
.local_addr()
|
||||||
.expect(&format!(
|
.expect(&format!("We should always get the local_addr from the listener {THIS_IS_A_BUG}"))
|
||||||
"We should always get the local_addr from the listener {THIS_IS_A_BUG}"
|
.port();
|
||||||
))
|
Ok(ParkedPort::new(port, listener))
|
||||||
.port();
|
|
||||||
Ok(ParkedPort::new(port, listener))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_random() {
|
fn generate_random() {
|
||||||
let port = generate(None).unwrap();
|
let port = generate(None).unwrap();
|
||||||
let listener = port.1.write().unwrap();
|
let listener = port.1.write().unwrap();
|
||||||
|
|
||||||
assert!(listener.is_some());
|
assert!(listener.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_fixed_port() {
|
fn generate_fixed_port() {
|
||||||
let port = generate(Some(33056)).unwrap();
|
let port = generate(Some(33056)).unwrap();
|
||||||
let listener = port.1.write().unwrap();
|
let listener = port.1.write().unwrap();
|
||||||
|
|
||||||
assert!(listener.is_some());
|
assert!(listener.is_some());
|
||||||
assert_eq!(port.0, 33056);
|
assert_eq!(port.0, 33056);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1055
-1138
File diff suppressed because it is too large
Load Diff
+760
-808
File diff suppressed because it is too large
Load Diff
+25
-25
@@ -9,33 +9,33 @@ use crate::{shared::types::RuntimeUpgradeOptions, tx_helper};
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ChainUpgrade {
|
pub trait ChainUpgrade {
|
||||||
/// Perform a runtime upgrade (with sudo)
|
/// Perform a runtime upgrade (with sudo)
|
||||||
///
|
///
|
||||||
/// This call 'System.set_code_without_checks' wrapped in
|
/// This call 'System.set_code_without_checks' wrapped in
|
||||||
/// 'Sudo.sudo_unchecked_weight'
|
/// 'Sudo.sudo_unchecked_weight'
|
||||||
async fn runtime_upgrade(&self, options: RuntimeUpgradeOptions) -> Result<(), anyhow::Error>;
|
async fn runtime_upgrade(&self, options: RuntimeUpgradeOptions) -> Result<(), anyhow::Error>;
|
||||||
|
|
||||||
/// Perform a runtime upgrade (with sudo), inner call with the node pass as arg.
|
/// Perform a runtime upgrade (with sudo), inner call with the node pass as arg.
|
||||||
///
|
///
|
||||||
/// This call 'System.set_code_without_checks' wrapped in
|
/// This call 'System.set_code_without_checks' wrapped in
|
||||||
/// 'Sudo.sudo_unchecked_weight'
|
/// 'Sudo.sudo_unchecked_weight'
|
||||||
async fn perform_runtime_upgrade(
|
async fn perform_runtime_upgrade(
|
||||||
&self,
|
&self,
|
||||||
node: &NetworkNode,
|
node: &NetworkNode,
|
||||||
options: RuntimeUpgradeOptions,
|
options: RuntimeUpgradeOptions,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
let sudo = if let Some(possible_seed) = options.seed {
|
let sudo = if let Some(possible_seed) = options.seed {
|
||||||
Keypair::from_secret_key(possible_seed)
|
Keypair::from_secret_key(possible_seed)
|
||||||
.map_err(|_| anyhow!("seed should return a Keypair"))?
|
.map_err(|_| anyhow!("seed should return a Keypair"))?
|
||||||
} else {
|
} else {
|
||||||
let uri = SecretUri::from_str("//Alice")?;
|
let uri = SecretUri::from_str("//Alice")?;
|
||||||
Keypair::from_uri(&uri).map_err(|_| anyhow!("'//Alice' should return a Keypair"))?
|
Keypair::from_uri(&uri).map_err(|_| anyhow!("'//Alice' should return a Keypair"))?
|
||||||
};
|
};
|
||||||
|
|
||||||
let wasm_data = options.wasm.get_asset().await?;
|
let wasm_data = options.wasm.get_asset().await?;
|
||||||
|
|
||||||
tx_helper::runtime_upgrade::upgrade(node, &wasm_data, &sudo).await?;
|
tx_helper::runtime_upgrade::upgrade(node, &wasm_data, &sudo).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+962
-983
File diff suppressed because it is too large
Load Diff
+39
-48
@@ -6,70 +6,61 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use super::node::NetworkNode;
|
use super::node::NetworkNode;
|
||||||
use crate::{
|
use crate::{
|
||||||
network::chain_upgrade::ChainUpgrade, shared::types::RuntimeUpgradeOptions,
|
network::chain_upgrade::ChainUpgrade, shared::types::RuntimeUpgradeOptions,
|
||||||
utils::default_as_empty_vec,
|
utils::default_as_empty_vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Relaychain {
|
pub struct Relaychain {
|
||||||
pub(crate) chain: String,
|
pub(crate) chain: String,
|
||||||
pub(crate) chain_id: String,
|
pub(crate) chain_id: String,
|
||||||
pub(crate) chain_spec_path: PathBuf,
|
pub(crate) chain_spec_path: PathBuf,
|
||||||
#[serde(default, deserialize_with = "default_as_empty_vec")]
|
#[serde(default, deserialize_with = "default_as_empty_vec")]
|
||||||
pub(crate) nodes: Vec<NetworkNode>,
|
pub(crate) nodes: Vec<NetworkNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub(crate) struct RawRelaychain {
|
pub(crate) struct RawRelaychain {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub(crate) inner: Relaychain,
|
pub(crate) inner: Relaychain,
|
||||||
pub(crate) nodes: serde_json::Value,
|
pub(crate) nodes: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ChainUpgrade for Relaychain {
|
impl ChainUpgrade for Relaychain {
|
||||||
async fn runtime_upgrade(&self, options: RuntimeUpgradeOptions) -> Result<(), anyhow::Error> {
|
async fn runtime_upgrade(&self, options: RuntimeUpgradeOptions) -> Result<(), anyhow::Error> {
|
||||||
// check if the node is valid first
|
// check if the node is valid first
|
||||||
let node = if let Some(node_name) = &options.node_name {
|
let node = if let Some(node_name) = &options.node_name {
|
||||||
if let Some(node) = self
|
if let Some(node) = self.nodes().into_iter().find(|node| node.name() == node_name) {
|
||||||
.nodes()
|
node
|
||||||
.into_iter()
|
} else {
|
||||||
.find(|node| node.name() == node_name)
|
return Err(anyhow!("Node: {node_name} is not part of the set of nodes"));
|
||||||
{
|
}
|
||||||
node
|
} else {
|
||||||
} else {
|
// take the first node
|
||||||
return Err(anyhow!("Node: {node_name} is not part of the set of nodes"));
|
if let Some(node) = self.nodes().first() {
|
||||||
}
|
node
|
||||||
} else {
|
} else {
|
||||||
// take the first node
|
return Err(anyhow!("chain doesn't have any node!"));
|
||||||
if let Some(node) = self.nodes().first() {
|
}
|
||||||
node
|
};
|
||||||
} else {
|
|
||||||
return Err(anyhow!("chain doesn't have any node!"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.perform_runtime_upgrade(node, options).await
|
self.perform_runtime_upgrade(node, options).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Relaychain {
|
impl Relaychain {
|
||||||
pub(crate) fn new(chain: String, chain_id: String, chain_spec_path: PathBuf) -> Self {
|
pub(crate) fn new(chain: String, chain_id: String, chain_spec_path: PathBuf) -> Self {
|
||||||
Self {
|
Self { chain, chain_id, chain_spec_path, nodes: Default::default() }
|
||||||
chain,
|
}
|
||||||
chain_id,
|
|
||||||
chain_spec_path,
|
|
||||||
nodes: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public API
|
// Public API
|
||||||
pub fn nodes(&self) -> Vec<&NetworkNode> {
|
pub fn nodes(&self) -> Vec<&NetworkNode> {
|
||||||
self.nodes.iter().collect()
|
self.nodes.iter().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get chain name
|
/// Get chain name
|
||||||
pub fn chain(&self) -> &str {
|
pub fn chain(&self) -> &str {
|
||||||
&self.chain
|
&self.chain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+229
-264
@@ -1,6 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
@@ -14,317 +14,282 @@ use tracing::info;
|
|||||||
|
|
||||||
use super::{chain_upgrade::ChainUpgrade, node::NetworkNode};
|
use super::{chain_upgrade::ChainUpgrade, node::NetworkNode};
|
||||||
use crate::{
|
use crate::{
|
||||||
network_spec::teyrchain::TeyrchainSpec,
|
network_spec::teyrchain::TeyrchainSpec,
|
||||||
shared::types::{RegisterParachainOptions, RuntimeUpgradeOptions},
|
shared::types::{RegisterParachainOptions, RuntimeUpgradeOptions},
|
||||||
tx_helper::client::get_client_from_url,
|
tx_helper::client::get_client_from_url,
|
||||||
utils::default_as_empty_vec,
|
utils::default_as_empty_vec,
|
||||||
ScopedFilesystem,
|
ScopedFilesystem,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Parachain {
|
pub struct Parachain {
|
||||||
pub(crate) chain: Option<String>,
|
pub(crate) chain: Option<String>,
|
||||||
pub(crate) para_id: u32,
|
pub(crate) para_id: u32,
|
||||||
// unique_id is internally used to allow multiple parachains with the same id
|
// unique_id is internally used to allow multiple parachains with the same id
|
||||||
// See `ParachainConfig` for more details
|
// See `ParachainConfig` for more details
|
||||||
pub(crate) unique_id: String,
|
pub(crate) unique_id: String,
|
||||||
pub(crate) chain_id: Option<String>,
|
pub(crate) chain_id: Option<String>,
|
||||||
pub(crate) chain_spec_path: Option<PathBuf>,
|
pub(crate) chain_spec_path: Option<PathBuf>,
|
||||||
#[serde(default, deserialize_with = "default_as_empty_vec")]
|
#[serde(default, deserialize_with = "default_as_empty_vec")]
|
||||||
pub(crate) collators: Vec<NetworkNode>,
|
pub(crate) collators: Vec<NetworkNode>,
|
||||||
pub(crate) files_to_inject: Vec<TransferedFile>,
|
pub(crate) files_to_inject: Vec<TransferedFile>,
|
||||||
pub(crate) bootnodes_addresses: Vec<multiaddr::Multiaddr>,
|
pub(crate) bootnodes_addresses: Vec<multiaddr::Multiaddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub(crate) struct RawParachain {
|
pub(crate) struct RawParachain {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub(crate) inner: Parachain,
|
pub(crate) inner: Parachain,
|
||||||
pub(crate) collators: serde_json::Value,
|
pub(crate) collators: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ChainUpgrade for Parachain {
|
impl ChainUpgrade for Parachain {
|
||||||
async fn runtime_upgrade(&self, options: RuntimeUpgradeOptions) -> Result<(), anyhow::Error> {
|
async fn runtime_upgrade(&self, options: RuntimeUpgradeOptions) -> Result<(), anyhow::Error> {
|
||||||
// check if the node is valid first
|
// check if the node is valid first
|
||||||
let node = if let Some(node_name) = &options.node_name {
|
let node = if let Some(node_name) = &options.node_name {
|
||||||
if let Some(node) = self
|
if let Some(node) = self.collators().into_iter().find(|node| node.name() == node_name) {
|
||||||
.collators()
|
node
|
||||||
.into_iter()
|
} else {
|
||||||
.find(|node| node.name() == node_name)
|
return Err(anyhow!("Node: {node_name} is not part of the set of nodes"));
|
||||||
{
|
}
|
||||||
node
|
} else {
|
||||||
} else {
|
// take the first node
|
||||||
return Err(anyhow!("Node: {node_name} is not part of the set of nodes"));
|
if let Some(node) = self.collators().first() {
|
||||||
}
|
node
|
||||||
} else {
|
} else {
|
||||||
// take the first node
|
return Err(anyhow!("chain doesn't have any node!"));
|
||||||
if let Some(node) = self.collators().first() {
|
}
|
||||||
node
|
};
|
||||||
} else {
|
|
||||||
return Err(anyhow!("chain doesn't have any node!"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.perform_runtime_upgrade(node, options).await
|
self.perform_runtime_upgrade(node, options).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parachain {
|
impl Parachain {
|
||||||
pub(crate) fn new(para_id: u32, unique_id: impl Into<String>) -> Self {
|
pub(crate) fn new(para_id: u32, unique_id: impl Into<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
chain: None,
|
chain: None,
|
||||||
para_id,
|
para_id,
|
||||||
unique_id: unique_id.into(),
|
unique_id: unique_id.into(),
|
||||||
chain_id: None,
|
chain_id: None,
|
||||||
chain_spec_path: None,
|
chain_spec_path: None,
|
||||||
collators: Default::default(),
|
collators: Default::default(),
|
||||||
files_to_inject: Default::default(),
|
files_to_inject: Default::default(),
|
||||||
bootnodes_addresses: vec![],
|
bootnodes_addresses: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn with_chain_spec(
|
pub(crate) fn with_chain_spec(
|
||||||
para_id: u32,
|
para_id: u32,
|
||||||
unique_id: impl Into<String>,
|
unique_id: impl Into<String>,
|
||||||
chain_id: impl Into<String>,
|
chain_id: impl Into<String>,
|
||||||
chain_spec_path: impl AsRef<Path>,
|
chain_spec_path: impl AsRef<Path>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
para_id,
|
para_id,
|
||||||
unique_id: unique_id.into(),
|
unique_id: unique_id.into(),
|
||||||
chain: None,
|
chain: None,
|
||||||
chain_id: Some(chain_id.into()),
|
chain_id: Some(chain_id.into()),
|
||||||
chain_spec_path: Some(chain_spec_path.as_ref().into()),
|
chain_spec_path: Some(chain_spec_path.as_ref().into()),
|
||||||
collators: Default::default(),
|
collators: Default::default(),
|
||||||
files_to_inject: Default::default(),
|
files_to_inject: Default::default(),
|
||||||
bootnodes_addresses: vec![],
|
bootnodes_addresses: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn from_spec(
|
pub(crate) async fn from_spec(
|
||||||
para: &TeyrchainSpec,
|
para: &TeyrchainSpec,
|
||||||
files_to_inject: &[TransferedFile],
|
files_to_inject: &[TransferedFile],
|
||||||
scoped_fs: &ScopedFilesystem<'_, impl FileSystem>,
|
scoped_fs: &ScopedFilesystem<'_, impl FileSystem>,
|
||||||
) -> Result<Self, anyhow::Error> {
|
) -> Result<Self, anyhow::Error> {
|
||||||
let mut para_files_to_inject = files_to_inject.to_owned();
|
let mut para_files_to_inject = files_to_inject.to_owned();
|
||||||
|
|
||||||
// parachain id is used for the keystore
|
// parachain id is used for the keystore
|
||||||
let mut parachain = if let Some(chain_spec) = para.chain_spec.as_ref() {
|
let mut parachain = if let Some(chain_spec) = para.chain_spec.as_ref() {
|
||||||
let id = chain_spec.read_chain_id(scoped_fs).await?;
|
let id = chain_spec.read_chain_id(scoped_fs).await?;
|
||||||
|
|
||||||
// add the spec to global files to inject
|
// add the spec to global files to inject
|
||||||
let spec_name = chain_spec.chain_spec_name();
|
let spec_name = chain_spec.chain_spec_name();
|
||||||
let base = PathBuf::from_str(scoped_fs.base_dir)?;
|
let base = PathBuf::from_str(scoped_fs.base_dir)?;
|
||||||
para_files_to_inject.push(TransferedFile::new(
|
para_files_to_inject.push(TransferedFile::new(
|
||||||
base.join(format!("{spec_name}.json")),
|
base.join(format!("{spec_name}.json")),
|
||||||
PathBuf::from(format!("/cfg/{}.json", para.id)),
|
PathBuf::from(format!("/cfg/{}.json", para.id)),
|
||||||
));
|
));
|
||||||
|
|
||||||
let raw_path = chain_spec
|
let raw_path = chain_spec
|
||||||
.raw_path()
|
.raw_path()
|
||||||
.ok_or(anyhow::anyhow!("chain-spec path should be set by now.",))?;
|
.ok_or(anyhow::anyhow!("chain-spec path should be set by now.",))?;
|
||||||
let mut running_para =
|
let mut running_para =
|
||||||
Parachain::with_chain_spec(para.id, ¶.unique_id, id, raw_path);
|
Parachain::with_chain_spec(para.id, ¶.unique_id, id, raw_path);
|
||||||
if let Some(chain_name) = chain_spec.chain_name() {
|
if let Some(chain_name) = chain_spec.chain_name() {
|
||||||
running_para.chain = Some(chain_name.to_string());
|
running_para.chain = Some(chain_name.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
running_para
|
running_para
|
||||||
} else {
|
} else {
|
||||||
Parachain::new(para.id, ¶.unique_id)
|
Parachain::new(para.id, ¶.unique_id)
|
||||||
};
|
};
|
||||||
|
|
||||||
parachain.bootnodes_addresses = para.bootnodes_addresses().into_iter().cloned().collect();
|
parachain.bootnodes_addresses = para.bootnodes_addresses().into_iter().cloned().collect();
|
||||||
parachain.files_to_inject = para_files_to_inject;
|
parachain.files_to_inject = para_files_to_inject;
|
||||||
|
|
||||||
Ok(parachain)
|
Ok(parachain)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn register(
|
pub async fn register(
|
||||||
options: RegisterParachainOptions,
|
options: RegisterParachainOptions,
|
||||||
scoped_fs: &ScopedFilesystem<'_, impl FileSystem>,
|
scoped_fs: &ScopedFilesystem<'_, impl FileSystem>,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
info!("Registering parachain: {:?}", options);
|
info!("Registering parachain: {:?}", options);
|
||||||
// get the seed
|
// get the seed
|
||||||
let sudo: Keypair;
|
let sudo: Keypair;
|
||||||
if let Some(possible_seed) = options.seed {
|
if let Some(possible_seed) = options.seed {
|
||||||
sudo = Keypair::from_secret_key(possible_seed)
|
sudo = Keypair::from_secret_key(possible_seed)
|
||||||
.expect(&format!("seed should return a Keypair {THIS_IS_A_BUG}"));
|
.expect(&format!("seed should return a Keypair {THIS_IS_A_BUG}"));
|
||||||
} else {
|
} else {
|
||||||
let uri = SecretUri::from_str("//Alice")?;
|
let uri = SecretUri::from_str("//Alice")?;
|
||||||
sudo = Keypair::from_uri(&uri)?;
|
sudo = Keypair::from_uri(&uri)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let genesis_state = scoped_fs
|
let genesis_state = scoped_fs
|
||||||
.read_to_string(options.state_path)
|
.read_to_string(options.state_path)
|
||||||
.await
|
.await
|
||||||
.expect(&format!(
|
.expect(&format!("State Path should be ok by this point {THIS_IS_A_BUG}"));
|
||||||
"State Path should be ok by this point {THIS_IS_A_BUG}"
|
let wasm_data = scoped_fs
|
||||||
));
|
.read_to_string(options.wasm_path)
|
||||||
let wasm_data = scoped_fs
|
.await
|
||||||
.read_to_string(options.wasm_path)
|
.expect(&format!("Wasm Path should be ok by this point {THIS_IS_A_BUG}"));
|
||||||
.await
|
|
||||||
.expect(&format!(
|
|
||||||
"Wasm Path should be ok by this point {THIS_IS_A_BUG}"
|
|
||||||
));
|
|
||||||
|
|
||||||
wait_ws_ready(options.node_ws_url.as_str())
|
wait_ws_ready(options.node_ws_url.as_str()).await.map_err(|_| {
|
||||||
.await
|
anyhow::anyhow!("Error waiting for ws to be ready, at {}", options.node_ws_url.as_str())
|
||||||
.map_err(|_| {
|
})?;
|
||||||
anyhow::anyhow!(
|
|
||||||
"Error waiting for ws to be ready, at {}",
|
|
||||||
options.node_ws_url.as_str()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let api: OnlineClient<BizinikiwConfig> = get_client_from_url(&options.node_ws_url).await?;
|
let api: OnlineClient<BizinikiwConfig> = get_client_from_url(&options.node_ws_url).await?;
|
||||||
|
|
||||||
let schedule_para = pezkuwi_subxt::dynamic::tx(
|
let schedule_para = pezkuwi_subxt::dynamic::tx(
|
||||||
"ParasSudoWrapper",
|
"ParasSudoWrapper",
|
||||||
"sudo_schedule_para_initialize",
|
"sudo_schedule_para_initialize",
|
||||||
vec![
|
vec![
|
||||||
Value::primitive(options.id.into()),
|
Value::primitive(options.id.into()),
|
||||||
Value::named_composite([
|
Value::named_composite([
|
||||||
(
|
("genesis_head", Value::from_bytes(hex::decode(&genesis_state[2..])?)),
|
||||||
"genesis_head",
|
("validation_code", Value::from_bytes(hex::decode(&wasm_data[2..])?)),
|
||||||
Value::from_bytes(hex::decode(&genesis_state[2..])?),
|
("para_kind", Value::bool(options.onboard_as_para)),
|
||||||
),
|
]),
|
||||||
(
|
],
|
||||||
"validation_code",
|
);
|
||||||
Value::from_bytes(hex::decode(&wasm_data[2..])?),
|
|
||||||
),
|
|
||||||
("para_kind", Value::bool(options.onboard_as_para)),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
let sudo_call =
|
let sudo_call =
|
||||||
pezkuwi_subxt::dynamic::tx("Sudo", "sudo", vec![schedule_para.into_value()]);
|
pezkuwi_subxt::dynamic::tx("Sudo", "sudo", vec![schedule_para.into_value()]);
|
||||||
|
|
||||||
// TODO: uncomment below and fix the sign and submit (and follow afterwards until
|
// TODO: uncomment below and fix the sign and submit (and follow afterwards until
|
||||||
// finalized block) to register the parachain
|
// finalized block) to register the parachain
|
||||||
let mut tx = api
|
let mut tx = api.tx().sign_and_submit_then_watch_default(&sudo_call, &sudo).await?;
|
||||||
.tx()
|
|
||||||
.sign_and_submit_then_watch_default(&sudo_call, &sudo)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Below we use the low level API to replicate the `wait_for_in_block` behaviour
|
// Below we use the low level API to replicate the `wait_for_in_block` behaviour
|
||||||
// which was removed in subxt 0.33.0. See https://github.com/paritytech/subxt/pull/1237.
|
// which was removed in subxt 0.33.0. See https://github.com/paritytech/subxt/pull/1237.
|
||||||
while let Some(status) = tx.next().await {
|
while let Some(status) = tx.next().await {
|
||||||
match status? {
|
match status? {
|
||||||
TxStatus::InBestBlock(tx_in_block) | TxStatus::InFinalizedBlock(tx_in_block) => {
|
TxStatus::InBestBlock(tx_in_block) | TxStatus::InFinalizedBlock(tx_in_block) => {
|
||||||
let _result = tx_in_block.wait_for_success().await?;
|
let _result = tx_in_block.wait_for_success().await?;
|
||||||
info!("In block: {:#?}", tx_in_block.block_hash());
|
info!("In block: {:#?}", tx_in_block.block_hash());
|
||||||
},
|
},
|
||||||
TxStatus::Error { message }
|
TxStatus::Error { message }
|
||||||
| TxStatus::Invalid { message }
|
| TxStatus::Invalid { message }
|
||||||
| TxStatus::Dropped { message } => {
|
| TxStatus::Dropped { message } => {
|
||||||
return Err(anyhow::format_err!("Error submitting tx: {message}"));
|
return Err(anyhow::format_err!("Error submitting tx: {message}"));
|
||||||
},
|
},
|
||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn para_id(&self) -> u32 {
|
pub fn para_id(&self) -> u32 {
|
||||||
self.para_id
|
self.para_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unique_id(&self) -> &str {
|
pub fn unique_id(&self) -> &str {
|
||||||
self.unique_id.as_str()
|
self.unique_id.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chain_id(&self) -> Option<&str> {
|
pub fn chain_id(&self) -> Option<&str> {
|
||||||
self.chain_id.as_deref()
|
self.chain_id.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collators(&self) -> Vec<&NetworkNode> {
|
pub fn collators(&self) -> Vec<&NetworkNode> {
|
||||||
self.collators.iter().collect()
|
self.collators.iter().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bootnodes_addresses(&self) -> Vec<&multiaddr::Multiaddr> {
|
pub fn bootnodes_addresses(&self) -> Vec<&multiaddr::Multiaddr> {
|
||||||
self.bootnodes_addresses.iter().collect()
|
self.bootnodes_addresses.iter().collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_with_is_works() {
|
fn create_with_is_works() {
|
||||||
let para = Parachain::new(100, "100");
|
let para = Parachain::new(100, "100");
|
||||||
// only para_id and unique_id should be set
|
// only para_id and unique_id should be set
|
||||||
assert_eq!(para.para_id, 100);
|
assert_eq!(para.para_id, 100);
|
||||||
assert_eq!(para.unique_id, "100");
|
assert_eq!(para.unique_id, "100");
|
||||||
assert_eq!(para.chain_id, None);
|
assert_eq!(para.chain_id, None);
|
||||||
assert_eq!(para.chain, None);
|
assert_eq!(para.chain, None);
|
||||||
assert_eq!(para.chain_spec_path, None);
|
assert_eq!(para.chain_spec_path, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_with_chain_spec_works() {
|
fn create_with_chain_spec_works() {
|
||||||
let para = Parachain::with_chain_spec(100, "100", "rococo-local", "/tmp/rococo-local.json");
|
let para = Parachain::with_chain_spec(100, "100", "rococo-local", "/tmp/rococo-local.json");
|
||||||
assert_eq!(para.para_id, 100);
|
assert_eq!(para.para_id, 100);
|
||||||
assert_eq!(para.unique_id, "100");
|
assert_eq!(para.unique_id, "100");
|
||||||
assert_eq!(para.chain_id, Some("rococo-local".to_string()));
|
assert_eq!(para.chain_id, Some("rococo-local".to_string()));
|
||||||
assert_eq!(para.chain, None);
|
assert_eq!(para.chain, None);
|
||||||
assert_eq!(
|
assert_eq!(para.chain_spec_path, Some(PathBuf::from("/tmp/rococo-local.json")));
|
||||||
para.chain_spec_path,
|
}
|
||||||
Some(PathBuf::from("/tmp/rococo-local.json"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn create_with_para_spec_works() {
|
async fn create_with_para_spec_works() {
|
||||||
use configuration::ParachainConfigBuilder;
|
use configuration::ParachainConfigBuilder;
|
||||||
|
|
||||||
use crate::network_spec::teyrchain::TeyrchainSpec;
|
use crate::network_spec::teyrchain::TeyrchainSpec;
|
||||||
|
|
||||||
let bootnode_addresses = vec!["/ip4/10.41.122.55/tcp/45421"];
|
let bootnode_addresses = vec!["/ip4/10.41.122.55/tcp/45421"];
|
||||||
|
|
||||||
let para_config = ParachainConfigBuilder::new(Default::default())
|
let para_config = ParachainConfigBuilder::new(Default::default())
|
||||||
.with_id(100)
|
.with_id(100)
|
||||||
.cumulus_based(false)
|
.cumulus_based(false)
|
||||||
.with_default_command("adder-collator")
|
.with_default_command("adder-collator")
|
||||||
.with_raw_bootnodes_addresses(bootnode_addresses.clone())
|
.with_raw_bootnodes_addresses(bootnode_addresses.clone())
|
||||||
.with_collator(|c| c.with_name("col"))
|
.with_collator(|c| c.with_name("col"))
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let para_spec =
|
let para_spec =
|
||||||
TeyrchainSpec::from_config(¶_config, "rococo-local".try_into().unwrap()).unwrap();
|
TeyrchainSpec::from_config(¶_config, "rococo-local".try_into().unwrap()).unwrap();
|
||||||
let fs = support::fs::in_memory::InMemoryFileSystem::new(HashMap::default());
|
let fs = support::fs::in_memory::InMemoryFileSystem::new(HashMap::default());
|
||||||
let scoped_fs = ScopedFilesystem {
|
let scoped_fs = ScopedFilesystem { fs: &fs, base_dir: "/tmp/some" };
|
||||||
fs: &fs,
|
|
||||||
base_dir: "/tmp/some",
|
|
||||||
};
|
|
||||||
|
|
||||||
let files = vec![TransferedFile::new(
|
let files =
|
||||||
PathBuf::from("/tmp/some"),
|
vec![TransferedFile::new(PathBuf::from("/tmp/some"), PathBuf::from("/tmp/some"))];
|
||||||
PathBuf::from("/tmp/some"),
|
let para = Parachain::from_spec(¶_spec, &files, &scoped_fs).await.unwrap();
|
||||||
)];
|
println!("{para:#?}");
|
||||||
let para = Parachain::from_spec(¶_spec, &files, &scoped_fs)
|
assert_eq!(para.para_id, 100);
|
||||||
.await
|
assert_eq!(para.unique_id, "100");
|
||||||
.unwrap();
|
assert_eq!(para.chain_id, None);
|
||||||
println!("{para:#?}");
|
assert_eq!(para.chain, None);
|
||||||
assert_eq!(para.para_id, 100);
|
// one file should be added.
|
||||||
assert_eq!(para.unique_id, "100");
|
assert_eq!(para.files_to_inject.len(), 1);
|
||||||
assert_eq!(para.chain_id, None);
|
assert_eq!(
|
||||||
assert_eq!(para.chain, None);
|
para.bootnodes_addresses().iter().map(|addr| addr.to_string()).collect::<Vec<_>>(),
|
||||||
// one file should be added.
|
bootnode_addresses
|
||||||
assert_eq!(para.files_to_inject.len(), 1);
|
);
|
||||||
assert_eq!(
|
}
|
||||||
para.bootnodes_addresses()
|
|
||||||
.iter()
|
|
||||||
.map(|addr| addr.to_string())
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
bootnode_addresses
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-41
@@ -5,58 +5,56 @@ use reqwest::Url;
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait MetricsHelper {
|
pub trait MetricsHelper {
|
||||||
async fn metric(&self, metric_name: &str) -> Result<f64, anyhow::Error>;
|
async fn metric(&self, metric_name: &str) -> Result<f64, anyhow::Error>;
|
||||||
async fn metric_with_url(
|
async fn metric_with_url(
|
||||||
metric: impl AsRef<str> + Send,
|
metric: impl AsRef<str> + Send,
|
||||||
endpoint: impl Into<Url> + Send,
|
endpoint: impl Into<Url> + Send,
|
||||||
) -> Result<f64, anyhow::Error>;
|
) -> Result<f64, anyhow::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Metrics {
|
pub struct Metrics {
|
||||||
endpoint: Url,
|
endpoint: Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Metrics {
|
impl Metrics {
|
||||||
fn new(endpoint: impl Into<Url>) -> Self {
|
fn new(endpoint: impl Into<Url>) -> Self {
|
||||||
Self {
|
Self { endpoint: endpoint.into() }
|
||||||
endpoint: endpoint.into(),
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch_metrics(
|
async fn fetch_metrics(
|
||||||
endpoint: impl AsRef<str>,
|
endpoint: impl AsRef<str>,
|
||||||
) -> Result<HashMap<String, f64>, anyhow::Error> {
|
) -> Result<HashMap<String, f64>, anyhow::Error> {
|
||||||
let response = reqwest::get(endpoint.as_ref()).await?;
|
let response = reqwest::get(endpoint.as_ref()).await?;
|
||||||
Ok(prom_metrics_parser::parse(&response.text().await?)?)
|
Ok(prom_metrics_parser::parse(&response.text().await?)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_metric(
|
fn get_metric(
|
||||||
metrics_map: HashMap<String, f64>,
|
metrics_map: HashMap<String, f64>,
|
||||||
metric_name: &str,
|
metric_name: &str,
|
||||||
) -> Result<f64, anyhow::Error> {
|
) -> Result<f64, anyhow::Error> {
|
||||||
let treat_not_found_as_zero = true;
|
let treat_not_found_as_zero = true;
|
||||||
if let Some(val) = metrics_map.get(metric_name) {
|
if let Some(val) = metrics_map.get(metric_name) {
|
||||||
Ok(*val)
|
Ok(*val)
|
||||||
} else if treat_not_found_as_zero {
|
} else if treat_not_found_as_zero {
|
||||||
Ok(0_f64)
|
Ok(0_f64)
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow::anyhow!("MetricNotFound: {metric_name}"))
|
Err(anyhow::anyhow!("MetricNotFound: {metric_name}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl MetricsHelper for Metrics {
|
impl MetricsHelper for Metrics {
|
||||||
async fn metric(&self, metric_name: &str) -> Result<f64, anyhow::Error> {
|
async fn metric(&self, metric_name: &str) -> Result<f64, anyhow::Error> {
|
||||||
let metrics_map = Metrics::fetch_metrics(self.endpoint.as_str()).await?;
|
let metrics_map = Metrics::fetch_metrics(self.endpoint.as_str()).await?;
|
||||||
Metrics::get_metric(metrics_map, metric_name)
|
Metrics::get_metric(metrics_map, metric_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn metric_with_url(
|
async fn metric_with_url(
|
||||||
metric_name: impl AsRef<str> + Send,
|
metric_name: impl AsRef<str> + Send,
|
||||||
endpoint: impl Into<Url> + Send,
|
endpoint: impl Into<Url> + Send,
|
||||||
) -> Result<f64, anyhow::Error> {
|
) -> Result<f64, anyhow::Error> {
|
||||||
let metrics_map = Metrics::fetch_metrics(endpoint.into()).await?;
|
let metrics_map = Metrics::fetch_metrics(endpoint.into()).await?;
|
||||||
Metrics::get_metric(metrics_map, metric_name.as_ref())
|
Metrics::get_metric(metrics_map, metric_name.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-17
@@ -6,29 +6,29 @@ use tracing::trace;
|
|||||||
use crate::network::node::NetworkNode;
|
use crate::network::node::NetworkNode;
|
||||||
|
|
||||||
pub(crate) async fn verify_nodes(nodes: &[&NetworkNode]) -> Result<(), anyhow::Error> {
|
pub(crate) async fn verify_nodes(nodes: &[&NetworkNode]) -> Result<(), anyhow::Error> {
|
||||||
timeout(Duration::from_secs(90), check_nodes(nodes))
|
timeout(Duration::from_secs(90), check_nodes(nodes))
|
||||||
.await
|
.await
|
||||||
.map_err(|_| anyhow::anyhow!("one or more nodes are not ready!"))
|
.map_err(|_| anyhow::anyhow!("one or more nodes are not ready!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we should inject in someway the logic to make the request
|
// TODO: we should inject in someway the logic to make the request
|
||||||
// in order to allow us to `mock` and easily test this.
|
// in order to allow us to `mock` and easily test this.
|
||||||
// maybe moved to the provider with a NodeStatus, and some helpers like wait_running, wait_ready, etc... ? to be discussed
|
// maybe moved to the provider with a NodeStatus, and some helpers like wait_running, wait_ready, etc... ? to be discussed
|
||||||
async fn check_nodes(nodes: &[&NetworkNode]) {
|
async fn check_nodes(nodes: &[&NetworkNode]) {
|
||||||
loop {
|
loop {
|
||||||
let tasks: Vec<_> = nodes
|
let tasks: Vec<_> = nodes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|node| {
|
.map(|node| {
|
||||||
trace!("🔎 checking node: {} ", node.name);
|
trace!("🔎 checking node: {} ", node.name);
|
||||||
reqwest::get(node.prometheus_uri.clone())
|
reqwest::get(node.prometheus_uri.clone())
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let all_ready = futures::future::try_join_all(tasks).await;
|
let all_ready = futures::future::try_join_all(tasks).await;
|
||||||
if all_ready.is_ok() {
|
if all_ready.is_ok() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+242
-264
@@ -1,6 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{hash_map::Entry, HashMap},
|
collections::{hash_map::Entry, HashMap},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use configuration::{GlobalSettings, HrmpChannelConfig, NetworkConfig};
|
use configuration::{GlobalSettings, HrmpChannelConfig, NetworkConfig};
|
||||||
@@ -20,311 +20,289 @@ use self::{node::NodeSpec, relaychain::RelaychainSpec, teyrchain::TeyrchainSpec}
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct NetworkSpec {
|
pub struct NetworkSpec {
|
||||||
/// Relaychain configuration.
|
/// Relaychain configuration.
|
||||||
pub(crate) relaychain: RelaychainSpec,
|
pub(crate) relaychain: RelaychainSpec,
|
||||||
|
|
||||||
/// Parachains configurations.
|
/// Parachains configurations.
|
||||||
pub(crate) parachains: Vec<TeyrchainSpec>,
|
pub(crate) parachains: Vec<TeyrchainSpec>,
|
||||||
|
|
||||||
/// HRMP channels configurations.
|
/// HRMP channels configurations.
|
||||||
pub(crate) hrmp_channels: Vec<HrmpChannelConfig>,
|
pub(crate) hrmp_channels: Vec<HrmpChannelConfig>,
|
||||||
|
|
||||||
/// Global settings
|
/// Global settings
|
||||||
pub(crate) global_settings: GlobalSettings,
|
pub(crate) global_settings: GlobalSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetworkSpec {
|
impl NetworkSpec {
|
||||||
pub async fn from_config(
|
pub async fn from_config(
|
||||||
network_config: &NetworkConfig,
|
network_config: &NetworkConfig,
|
||||||
) -> Result<NetworkSpec, OrchestratorError> {
|
) -> Result<NetworkSpec, OrchestratorError> {
|
||||||
let mut errs = vec![];
|
let mut errs = vec![];
|
||||||
let relaychain = RelaychainSpec::from_config(network_config.relaychain())?;
|
let relaychain = RelaychainSpec::from_config(network_config.relaychain())?;
|
||||||
let mut parachains = vec![];
|
let mut parachains = vec![];
|
||||||
|
|
||||||
// TODO: move to `fold` or map+fold
|
// TODO: move to `fold` or map+fold
|
||||||
for para_config in network_config.parachains() {
|
for para_config in network_config.parachains() {
|
||||||
match TeyrchainSpec::from_config(para_config, relaychain.chain.clone()) {
|
match TeyrchainSpec::from_config(para_config, relaychain.chain.clone()) {
|
||||||
Ok(para) => parachains.push(para),
|
Ok(para) => parachains.push(para),
|
||||||
Err(err) => errs.push(err),
|
Err(err) => errs.push(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if errs.is_empty() {
|
if errs.is_empty() {
|
||||||
Ok(NetworkSpec {
|
Ok(NetworkSpec {
|
||||||
relaychain,
|
relaychain,
|
||||||
parachains,
|
parachains,
|
||||||
hrmp_channels: network_config
|
hrmp_channels: network_config.hrmp_channels().into_iter().cloned().collect(),
|
||||||
.hrmp_channels()
|
global_settings: network_config.global_settings().clone(),
|
||||||
.into_iter()
|
})
|
||||||
.cloned()
|
} else {
|
||||||
.collect(),
|
let errs_str =
|
||||||
global_settings: network_config.global_settings().clone(),
|
errs.into_iter().map(|e| e.to_string()).collect::<Vec<String>>().join("\n");
|
||||||
})
|
Err(OrchestratorError::InvalidConfig(errs_str))
|
||||||
} else {
|
}
|
||||||
let errs_str = errs
|
}
|
||||||
.into_iter()
|
|
||||||
.map(|e| e.to_string())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("\n");
|
|
||||||
Err(OrchestratorError::InvalidConfig(errs_str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn populate_nodes_available_args(
|
pub async fn populate_nodes_available_args(
|
||||||
&mut self,
|
&mut self,
|
||||||
ns: Arc<dyn ProviderNamespace + Send + Sync>,
|
ns: Arc<dyn ProviderNamespace + Send + Sync>,
|
||||||
) -> Result<(), OrchestratorError> {
|
) -> Result<(), OrchestratorError> {
|
||||||
let network_nodes = self.collect_network_nodes();
|
let network_nodes = self.collect_network_nodes();
|
||||||
|
|
||||||
let mut image_command_to_nodes_mapping =
|
let mut image_command_to_nodes_mapping =
|
||||||
Self::create_image_command_to_nodes_mapping(network_nodes);
|
Self::create_image_command_to_nodes_mapping(network_nodes);
|
||||||
|
|
||||||
let available_args_outputs =
|
let available_args_outputs =
|
||||||
Self::retrieve_all_nodes_available_args_output(ns, &image_command_to_nodes_mapping)
|
Self::retrieve_all_nodes_available_args_output(ns, &image_command_to_nodes_mapping)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Self::update_nodes_available_args_output(
|
Self::update_nodes_available_args_output(
|
||||||
&mut image_command_to_nodes_mapping,
|
&mut image_command_to_nodes_mapping,
|
||||||
available_args_outputs,
|
available_args_outputs,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
pub async fn node_available_args_output(
|
pub async fn node_available_args_output(
|
||||||
&self,
|
&self,
|
||||||
node_spec: &NodeSpec,
|
node_spec: &NodeSpec,
|
||||||
ns: Arc<dyn ProviderNamespace + Send + Sync>,
|
ns: Arc<dyn ProviderNamespace + Send + Sync>,
|
||||||
) -> Result<String, ProviderError> {
|
) -> Result<String, ProviderError> {
|
||||||
// try to find a node that use the same combination of image/cmd
|
// try to find a node that use the same combination of image/cmd
|
||||||
let cmp_fn = |ad_hoc: &&NodeSpec| -> bool {
|
let cmp_fn = |ad_hoc: &&NodeSpec| -> bool {
|
||||||
ad_hoc.image == node_spec.image && ad_hoc.command == node_spec.command
|
ad_hoc.image == node_spec.image && ad_hoc.command == node_spec.command
|
||||||
};
|
};
|
||||||
|
|
||||||
// check if we already had computed the args output for this cmd/[image]
|
// check if we already had computed the args output for this cmd/[image]
|
||||||
let node = self.relaychain.nodes.iter().find(cmp_fn);
|
let node = self.relaychain.nodes.iter().find(cmp_fn);
|
||||||
let node = if let Some(node) = node {
|
let node = if let Some(node) = node {
|
||||||
Some(node)
|
Some(node)
|
||||||
} else {
|
} else {
|
||||||
let node = self
|
let node = self.parachains.iter().find_map(|para| para.collators.iter().find(cmp_fn));
|
||||||
.parachains
|
|
||||||
.iter()
|
|
||||||
.find_map(|para| para.collators.iter().find(cmp_fn));
|
|
||||||
|
|
||||||
node
|
node
|
||||||
};
|
};
|
||||||
|
|
||||||
let output = if let Some(node) = node {
|
let output = if let Some(node) = node {
|
||||||
node.available_args_output.clone().expect(&format!(
|
node.available_args_output
|
||||||
"args_output should be set for running nodes {THIS_IS_A_BUG}"
|
.clone()
|
||||||
))
|
.expect(&format!("args_output should be set for running nodes {THIS_IS_A_BUG}"))
|
||||||
} else {
|
} else {
|
||||||
// we need to compute the args output
|
// we need to compute the args output
|
||||||
let image = node_spec
|
let image = node_spec.image.as_ref().map(|image| image.as_str().to_string());
|
||||||
.image
|
let command = node_spec.command.as_str().to_string();
|
||||||
.as_ref()
|
|
||||||
.map(|image| image.as_str().to_string());
|
|
||||||
let command = node_spec.command.as_str().to_string();
|
|
||||||
|
|
||||||
ns.get_node_available_args((command, image)).await?
|
ns.get_node_available_args((command, image)).await?
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn relaychain(&self) -> &RelaychainSpec {
|
pub fn relaychain(&self) -> &RelaychainSpec {
|
||||||
&self.relaychain
|
&self.relaychain
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn relaychain_mut(&mut self) -> &mut RelaychainSpec {
|
pub fn relaychain_mut(&mut self) -> &mut RelaychainSpec {
|
||||||
&mut self.relaychain
|
&mut self.relaychain
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parachains_iter(&self) -> impl Iterator<Item = &TeyrchainSpec> {
|
pub fn parachains_iter(&self) -> impl Iterator<Item = &TeyrchainSpec> {
|
||||||
self.parachains.iter()
|
self.parachains.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parachains_iter_mut(&mut self) -> impl Iterator<Item = &mut TeyrchainSpec> {
|
pub fn parachains_iter_mut(&mut self) -> impl Iterator<Item = &mut TeyrchainSpec> {
|
||||||
self.parachains.iter_mut()
|
self.parachains.iter_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_global_settings(&mut self, global_settings: GlobalSettings) {
|
pub fn set_global_settings(&mut self, global_settings: GlobalSettings) {
|
||||||
self.global_settings = global_settings;
|
self.global_settings = global_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn build_parachain_artifacts<'a, T: FileSystem>(
|
pub async fn build_parachain_artifacts<'a, T: FileSystem>(
|
||||||
&mut self,
|
&mut self,
|
||||||
ns: DynNamespace,
|
ns: DynNamespace,
|
||||||
scoped_fs: &ScopedFilesystem<'a, T>,
|
scoped_fs: &ScopedFilesystem<'a, T>,
|
||||||
relaychain_id: &str,
|
relaychain_id: &str,
|
||||||
base_dir_exists: bool,
|
base_dir_exists: bool,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
for para in self.parachains.iter_mut() {
|
for para in self.parachains.iter_mut() {
|
||||||
let chain_spec_raw_path = para.build_chain_spec(relaychain_id, &ns, scoped_fs).await?;
|
let chain_spec_raw_path = para.build_chain_spec(relaychain_id, &ns, scoped_fs).await?;
|
||||||
|
|
||||||
trace!("creating dirs for {}", ¶.unique_id);
|
trace!("creating dirs for {}", ¶.unique_id);
|
||||||
if base_dir_exists {
|
if base_dir_exists {
|
||||||
scoped_fs.create_dir_all(¶.unique_id).await?;
|
scoped_fs.create_dir_all(¶.unique_id).await?;
|
||||||
} else {
|
} else {
|
||||||
scoped_fs.create_dir(¶.unique_id).await?;
|
scoped_fs.create_dir(¶.unique_id).await?;
|
||||||
};
|
};
|
||||||
trace!("created dirs for {}", ¶.unique_id);
|
trace!("created dirs for {}", ¶.unique_id);
|
||||||
|
|
||||||
// create wasm/state
|
// create wasm/state
|
||||||
para.genesis_state
|
para.genesis_state
|
||||||
.build(
|
.build(
|
||||||
chain_spec_raw_path.clone(),
|
chain_spec_raw_path.clone(),
|
||||||
format!("{}/genesis-state", para.unique_id),
|
format!("{}/genesis-state", para.unique_id),
|
||||||
&ns,
|
&ns,
|
||||||
scoped_fs,
|
scoped_fs,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
debug!("parachain genesis state built!");
|
debug!("parachain genesis state built!");
|
||||||
para.genesis_wasm
|
para.genesis_wasm
|
||||||
.build(
|
.build(
|
||||||
chain_spec_raw_path,
|
chain_spec_raw_path,
|
||||||
format!("{}/genesis-wasm", para.unique_id),
|
format!("{}/genesis-wasm", para.unique_id),
|
||||||
&ns,
|
&ns,
|
||||||
scoped_fs,
|
scoped_fs,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
debug!("parachain genesis wasm built!");
|
debug!("parachain genesis wasm built!");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// collect mutable references to all nodes from relaychain and parachains
|
// collect mutable references to all nodes from relaychain and parachains
|
||||||
fn collect_network_nodes(&mut self) -> Vec<&mut NodeSpec> {
|
fn collect_network_nodes(&mut self) -> Vec<&mut NodeSpec> {
|
||||||
vec![
|
vec![
|
||||||
self.relaychain.nodes.iter_mut().collect::<Vec<_>>(),
|
self.relaychain.nodes.iter_mut().collect::<Vec<_>>(),
|
||||||
self.parachains
|
self.parachains.iter_mut().flat_map(|para| para.collators.iter_mut()).collect(),
|
||||||
.iter_mut()
|
]
|
||||||
.flat_map(|para| para.collators.iter_mut())
|
.into_iter()
|
||||||
.collect(),
|
.flatten()
|
||||||
]
|
.collect::<Vec<_>>()
|
||||||
.into_iter()
|
}
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize the mapping of all possible node image/commands to corresponding nodes
|
// initialize the mapping of all possible node image/commands to corresponding nodes
|
||||||
fn create_image_command_to_nodes_mapping(
|
fn create_image_command_to_nodes_mapping(
|
||||||
network_nodes: Vec<&mut NodeSpec>,
|
network_nodes: Vec<&mut NodeSpec>,
|
||||||
) -> HashMap<(Option<String>, String), Vec<&mut NodeSpec>> {
|
) -> HashMap<(Option<String>, String), Vec<&mut NodeSpec>> {
|
||||||
network_nodes.into_iter().fold(
|
network_nodes.into_iter().fold(
|
||||||
HashMap::new(),
|
HashMap::new(),
|
||||||
|mut acc: HashMap<(Option<String>, String), Vec<&mut node::NodeSpec>>, node| {
|
|mut acc: HashMap<(Option<String>, String), Vec<&mut node::NodeSpec>>, node| {
|
||||||
// build mapping key using image and command if image is present or command only
|
// build mapping key using image and command if image is present or command only
|
||||||
let key = node
|
let key = node
|
||||||
.image
|
.image
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|image| {
|
.map(|image| {
|
||||||
(
|
(Some(image.as_str().to_string()), node.command.as_str().to_string())
|
||||||
Some(image.as_str().to_string()),
|
})
|
||||||
node.command.as_str().to_string(),
|
.unwrap_or_else(|| (None, node.command.as_str().to_string()));
|
||||||
)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| (None, node.command.as_str().to_string()));
|
|
||||||
|
|
||||||
// append the node to the vector of nodes for this image/command tuple
|
// append the node to the vector of nodes for this image/command tuple
|
||||||
if let Entry::Vacant(entry) = acc.entry(key.clone()) {
|
if let Entry::Vacant(entry) = acc.entry(key.clone()) {
|
||||||
entry.insert(vec![node]);
|
entry.insert(vec![node]);
|
||||||
} else {
|
} else {
|
||||||
acc.get_mut(&key).unwrap().push(node);
|
acc.get_mut(&key).unwrap().push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
acc
|
acc
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn retrieve_all_nodes_available_args_output(
|
async fn retrieve_all_nodes_available_args_output(
|
||||||
ns: Arc<dyn ProviderNamespace + Send + Sync>,
|
ns: Arc<dyn ProviderNamespace + Send + Sync>,
|
||||||
image_command_to_nodes_mapping: &HashMap<(Option<String>, String), Vec<&mut NodeSpec>>,
|
image_command_to_nodes_mapping: &HashMap<(Option<String>, String), Vec<&mut NodeSpec>>,
|
||||||
) -> Result<Vec<(Option<String>, String, String)>, OrchestratorError> {
|
) -> Result<Vec<(Option<String>, String, String)>, OrchestratorError> {
|
||||||
try_join_all(
|
try_join_all(
|
||||||
image_command_to_nodes_mapping
|
image_command_to_nodes_mapping
|
||||||
.keys()
|
.keys()
|
||||||
.map(|(image, command)| {
|
.map(|(image, command)| {
|
||||||
let ns = ns.clone();
|
let ns = ns.clone();
|
||||||
let image = image.clone();
|
let image = image.clone();
|
||||||
let command = command.clone();
|
let command = command.clone();
|
||||||
async move {
|
async move {
|
||||||
// get node available args output from image/command
|
// get node available args output from image/command
|
||||||
let available_args = ns
|
let available_args =
|
||||||
.get_node_available_args((command.clone(), image.clone()))
|
ns.get_node_available_args((command.clone(), image.clone())).await?;
|
||||||
.await?;
|
debug!(
|
||||||
debug!(
|
"retrieved available args for image: {:?}, command: {}",
|
||||||
"retrieved available args for image: {:?}, command: {}",
|
image, command
|
||||||
image, command
|
);
|
||||||
);
|
|
||||||
|
|
||||||
// map the result to include image and command
|
// map the result to include image and command
|
||||||
Ok::<_, OrchestratorError>((image, command, available_args))
|
Ok::<_, OrchestratorError>((image, command, available_args))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_nodes_available_args_output(
|
fn update_nodes_available_args_output(
|
||||||
image_command_to_nodes_mapping: &mut HashMap<(Option<String>, String), Vec<&mut NodeSpec>>,
|
image_command_to_nodes_mapping: &mut HashMap<(Option<String>, String), Vec<&mut NodeSpec>>,
|
||||||
available_args_outputs: Vec<(Option<String>, String, String)>,
|
available_args_outputs: Vec<(Option<String>, String, String)>,
|
||||||
) {
|
) {
|
||||||
for (image, command, available_args_output) in available_args_outputs {
|
for (image, command, available_args_output) in available_args_outputs {
|
||||||
let nodes = image_command_to_nodes_mapping
|
let nodes = image_command_to_nodes_mapping
|
||||||
.get_mut(&(image, command))
|
.get_mut(&(image, command))
|
||||||
.expect(&format!(
|
.expect(&format!("node image/command key should exist {THIS_IS_A_BUG}"));
|
||||||
"node image/command key should exist {THIS_IS_A_BUG}"
|
|
||||||
));
|
|
||||||
|
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
node.available_args_output = Some(available_args_output.clone());
|
node.available_args_output = Some(available_args_output.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn small_network_config_get_spec() {
|
async fn small_network_config_get_spec() {
|
||||||
use configuration::NetworkConfigBuilder;
|
use configuration::NetworkConfigBuilder;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
let config = NetworkConfigBuilder::new()
|
let config = NetworkConfigBuilder::new()
|
||||||
.with_relaychain(|r| {
|
.with_relaychain(|r| {
|
||||||
r.with_chain("rococo-local")
|
r.with_chain("rococo-local")
|
||||||
.with_default_command("polkadot")
|
.with_default_command("polkadot")
|
||||||
.with_validator(|node| node.with_name("alice"))
|
.with_validator(|node| node.with_name("alice"))
|
||||||
.with_fullnode(|node| node.with_name("bob").with_command("polkadot1"))
|
.with_fullnode(|node| node.with_name("bob").with_command("polkadot1"))
|
||||||
})
|
})
|
||||||
.with_parachain(|p| {
|
.with_parachain(|p| {
|
||||||
p.with_id(100)
|
p.with_id(100)
|
||||||
.with_default_command("adder-collator")
|
.with_default_command("adder-collator")
|
||||||
.with_collator(|c| c.with_name("collator1"))
|
.with_collator(|c| c.with_name("collator1"))
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let network_spec = NetworkSpec::from_config(&config).await.unwrap();
|
let network_spec = NetworkSpec::from_config(&config).await.unwrap();
|
||||||
let alice = network_spec.relaychain.nodes.first().unwrap();
|
let alice = network_spec.relaychain.nodes.first().unwrap();
|
||||||
let bob = network_spec.relaychain.nodes.get(1).unwrap();
|
let bob = network_spec.relaychain.nodes.get(1).unwrap();
|
||||||
assert_eq!(alice.command.as_str(), "polkadot");
|
assert_eq!(alice.command.as_str(), "polkadot");
|
||||||
assert_eq!(bob.command.as_str(), "polkadot1");
|
assert_eq!(bob.command.as_str(), "polkadot1");
|
||||||
assert!(alice.is_validator);
|
assert!(alice.is_validator);
|
||||||
assert!(!bob.is_validator);
|
assert!(!bob.is_validator);
|
||||||
|
|
||||||
// paras
|
// paras
|
||||||
assert_eq!(network_spec.parachains.len(), 1);
|
assert_eq!(network_spec.parachains.len(), 1);
|
||||||
let para_100 = network_spec.parachains.first().unwrap();
|
let para_100 = network_spec.parachains.first().unwrap();
|
||||||
assert_eq!(para_100.id, 100);
|
assert_eq!(para_100.id, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+259
-283
@@ -1,9 +1,9 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use configuration::shared::{
|
use configuration::shared::{
|
||||||
node::{EnvVar, NodeConfig},
|
node::{EnvVar, NodeConfig},
|
||||||
resources::Resources,
|
resources::Resources,
|
||||||
types::{Arg, AssetLocation, Command, Image},
|
types::{Arg, AssetLocation, Command, Image},
|
||||||
};
|
};
|
||||||
use multiaddr::Multiaddr;
|
use multiaddr::Multiaddr;
|
||||||
use provider::types::Port;
|
use provider::types::Port;
|
||||||
@@ -11,39 +11,39 @@ use serde::{Deserialize, Serialize};
|
|||||||
use support::constants::THIS_IS_A_BUG;
|
use support::constants::THIS_IS_A_BUG;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::OrchestratorError,
|
errors::OrchestratorError,
|
||||||
generators,
|
generators,
|
||||||
network::AddNodeOptions,
|
network::AddNodeOptions,
|
||||||
shared::{
|
shared::{
|
||||||
macros,
|
macros,
|
||||||
types::{ChainDefaultContext, NodeAccount, NodeAccounts, ParkedPort},
|
types::{ChainDefaultContext, NodeAccount, NodeAccounts, ParkedPort},
|
||||||
},
|
},
|
||||||
AddCollatorOptions,
|
AddCollatorOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
macros::create_add_options!(AddNodeSpecOpts {
|
macros::create_add_options!(AddNodeSpecOpts {
|
||||||
override_eth_key: Option<String>
|
override_eth_key: Option<String>
|
||||||
});
|
});
|
||||||
|
|
||||||
macro_rules! impl_from_for_add_node_opts {
|
macro_rules! impl_from_for_add_node_opts {
|
||||||
($struct:ident) => {
|
($struct:ident) => {
|
||||||
impl From<$struct> for AddNodeSpecOpts {
|
impl From<$struct> for AddNodeSpecOpts {
|
||||||
fn from(value: $struct) -> Self {
|
fn from(value: $struct) -> Self {
|
||||||
Self {
|
Self {
|
||||||
image: value.image,
|
image: value.image,
|
||||||
command: value.command,
|
command: value.command,
|
||||||
subcommand: value.subcommand,
|
subcommand: value.subcommand,
|
||||||
args: value.args,
|
args: value.args,
|
||||||
env: value.env,
|
env: value.env,
|
||||||
is_validator: value.is_validator,
|
is_validator: value.is_validator,
|
||||||
rpc_port: value.rpc_port,
|
rpc_port: value.rpc_port,
|
||||||
prometheus_port: value.prometheus_port,
|
prometheus_port: value.prometheus_port,
|
||||||
p2p_port: value.p2p_port,
|
p2p_port: value.p2p_port,
|
||||||
override_eth_key: value.override_eth_key,
|
override_eth_key: value.override_eth_key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_from_for_add_node_opts!(AddNodeOptions);
|
impl_from_for_add_node_opts!(AddNodeOptions);
|
||||||
@@ -52,305 +52,281 @@ impl_from_for_add_node_opts!(AddCollatorOptions);
|
|||||||
/// A node configuration, with fine-grained configuration options.
|
/// A node configuration, with fine-grained configuration options.
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct NodeSpec {
|
pub struct NodeSpec {
|
||||||
// Node name (should be unique or an index will be appended).
|
// Node name (should be unique or an index will be appended).
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
|
|
||||||
/// Node key, used for compute the p2p identity.
|
/// Node key, used for compute the p2p identity.
|
||||||
pub(crate) key: String,
|
pub(crate) key: String,
|
||||||
|
|
||||||
// libp2p local identity
|
// libp2p local identity
|
||||||
pub(crate) peer_id: String,
|
pub(crate) peer_id: String,
|
||||||
|
|
||||||
/// Accounts to be injected in the keystore.
|
/// Accounts to be injected in the keystore.
|
||||||
pub(crate) accounts: NodeAccounts,
|
pub(crate) accounts: NodeAccounts,
|
||||||
|
|
||||||
/// Image to run (only podman/k8s). Override the default.
|
/// Image to run (only podman/k8s). Override the default.
|
||||||
pub(crate) image: Option<Image>,
|
pub(crate) image: Option<Image>,
|
||||||
|
|
||||||
/// Command to run the node. Override the default.
|
/// Command to run the node. Override the default.
|
||||||
pub(crate) command: Command,
|
pub(crate) command: Command,
|
||||||
|
|
||||||
/// Optional subcommand for the node.
|
/// Optional subcommand for the node.
|
||||||
pub(crate) subcommand: Option<Command>,
|
pub(crate) subcommand: Option<Command>,
|
||||||
|
|
||||||
/// Arguments to use for node. Appended to default.
|
/// Arguments to use for node. Appended to default.
|
||||||
pub(crate) args: Vec<Arg>,
|
pub(crate) args: Vec<Arg>,
|
||||||
|
|
||||||
// The help command output containing the available arguments.
|
// The help command output containing the available arguments.
|
||||||
pub(crate) available_args_output: Option<String>,
|
pub(crate) available_args_output: Option<String>,
|
||||||
|
|
||||||
/// Wether the node is a validator.
|
/// Wether the node is a validator.
|
||||||
pub(crate) is_validator: bool,
|
pub(crate) is_validator: bool,
|
||||||
|
|
||||||
/// Whether the node keys must be added to invulnerables.
|
/// Whether the node keys must be added to invulnerables.
|
||||||
pub(crate) is_invulnerable: bool,
|
pub(crate) is_invulnerable: bool,
|
||||||
|
|
||||||
/// Whether the node is a bootnode.
|
/// Whether the node is a bootnode.
|
||||||
pub(crate) is_bootnode: bool,
|
pub(crate) is_bootnode: bool,
|
||||||
|
|
||||||
/// Node initial balance present in genesis.
|
/// Node initial balance present in genesis.
|
||||||
pub(crate) initial_balance: u128,
|
pub(crate) initial_balance: u128,
|
||||||
|
|
||||||
/// Environment variables to set (inside pod for podman/k8s, inside shell for native).
|
/// Environment variables to set (inside pod for podman/k8s, inside shell for native).
|
||||||
pub(crate) env: Vec<EnvVar>,
|
pub(crate) env: Vec<EnvVar>,
|
||||||
|
|
||||||
/// List of node's bootnodes addresses to use. Appended to default.
|
/// List of node's bootnodes addresses to use. Appended to default.
|
||||||
pub(crate) bootnodes_addresses: Vec<Multiaddr>,
|
pub(crate) bootnodes_addresses: Vec<Multiaddr>,
|
||||||
|
|
||||||
/// Default resources. Override the default.
|
/// Default resources. Override the default.
|
||||||
pub(crate) resources: Option<Resources>,
|
pub(crate) resources: Option<Resources>,
|
||||||
|
|
||||||
/// Websocket port to use.
|
/// Websocket port to use.
|
||||||
pub(crate) ws_port: ParkedPort,
|
pub(crate) ws_port: ParkedPort,
|
||||||
|
|
||||||
/// RPC port to use.
|
/// RPC port to use.
|
||||||
pub(crate) rpc_port: ParkedPort,
|
pub(crate) rpc_port: ParkedPort,
|
||||||
|
|
||||||
/// Prometheus port to use.
|
/// Prometheus port to use.
|
||||||
pub(crate) prometheus_port: ParkedPort,
|
pub(crate) prometheus_port: ParkedPort,
|
||||||
|
|
||||||
/// P2P port to use.
|
/// P2P port to use.
|
||||||
pub(crate) p2p_port: ParkedPort,
|
pub(crate) p2p_port: ParkedPort,
|
||||||
|
|
||||||
/// libp2p cert hash to use with `webrtc` transport.
|
/// libp2p cert hash to use with `webrtc` transport.
|
||||||
pub(crate) p2p_cert_hash: Option<String>,
|
pub(crate) p2p_cert_hash: Option<String>,
|
||||||
|
|
||||||
/// Database snapshot. Override the default.
|
/// Database snapshot. Override the default.
|
||||||
pub(crate) db_snapshot: Option<AssetLocation>,
|
pub(crate) db_snapshot: Option<AssetLocation>,
|
||||||
|
|
||||||
/// P2P port to use by full node if this is the case
|
/// P2P port to use by full node if this is the case
|
||||||
pub(crate) full_node_p2p_port: Option<ParkedPort>,
|
pub(crate) full_node_p2p_port: Option<ParkedPort>,
|
||||||
/// Prometheus port to use by full node if this is the case
|
/// Prometheus port to use by full node if this is the case
|
||||||
pub(crate) full_node_prometheus_port: Option<ParkedPort>,
|
pub(crate) full_node_prometheus_port: Option<ParkedPort>,
|
||||||
|
|
||||||
/// Optionally specify a log path for the node
|
/// Optionally specify a log path for the node
|
||||||
pub(crate) node_log_path: Option<PathBuf>,
|
pub(crate) node_log_path: Option<PathBuf>,
|
||||||
|
|
||||||
/// Optionally specify a keystore path for the node
|
/// Optionally specify a keystore path for the node
|
||||||
pub(crate) keystore_path: Option<PathBuf>,
|
pub(crate) keystore_path: Option<PathBuf>,
|
||||||
|
|
||||||
/// Keystore key types to generate.
|
/// Keystore key types to generate.
|
||||||
/// Supports short form (e.g., "audi") using predefined schemas,
|
/// Supports short form (e.g., "audi") using predefined schemas,
|
||||||
/// or long form (e.g., "audi_sr") with explicit schema (sr, ed, ec).
|
/// or long form (e.g., "audi_sr") with explicit schema (sr, ed, ec).
|
||||||
pub(crate) keystore_key_types: Vec<String>,
|
pub(crate) keystore_key_types: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeSpec {
|
impl NodeSpec {
|
||||||
pub fn from_config(
|
pub fn from_config(
|
||||||
node_config: &NodeConfig,
|
node_config: &NodeConfig,
|
||||||
chain_context: &ChainDefaultContext,
|
chain_context: &ChainDefaultContext,
|
||||||
full_node_present: bool,
|
full_node_present: bool,
|
||||||
evm_based: bool,
|
evm_based: bool,
|
||||||
) -> Result<Self, OrchestratorError> {
|
) -> Result<Self, OrchestratorError> {
|
||||||
// Check first if the image is set at node level, then try with the default
|
// Check first if the image is set at node level, then try with the default
|
||||||
let image = node_config.image().or(chain_context.default_image).cloned();
|
let image = node_config.image().or(chain_context.default_image).cloned();
|
||||||
|
|
||||||
// Check first if the command is set at node level, then try with the default
|
// Check first if the command is set at node level, then try with the default
|
||||||
let command = if let Some(cmd) = node_config.command() {
|
let command = if let Some(cmd) = node_config.command() {
|
||||||
cmd.clone()
|
cmd.clone()
|
||||||
} else if let Some(cmd) = chain_context.default_command {
|
} else if let Some(cmd) = chain_context.default_command {
|
||||||
cmd.clone()
|
cmd.clone()
|
||||||
} else {
|
} else {
|
||||||
return Err(OrchestratorError::InvalidNodeConfig(
|
return Err(OrchestratorError::InvalidNodeConfig(
|
||||||
node_config.name().into(),
|
node_config.name().into(),
|
||||||
"command".to_string(),
|
"command".to_string(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let subcommand = node_config.subcommand().cloned();
|
let subcommand = node_config.subcommand().cloned();
|
||||||
|
|
||||||
// If `args` is set at `node` level use them
|
// If `args` is set at `node` level use them
|
||||||
// otherwise use the default_args (can be empty).
|
// otherwise use the default_args (can be empty).
|
||||||
let args: Vec<Arg> = if node_config.args().is_empty() {
|
let args: Vec<Arg> = if node_config.args().is_empty() {
|
||||||
chain_context
|
chain_context.default_args.iter().map(|x| x.to_owned().clone()).collect()
|
||||||
.default_args
|
} else {
|
||||||
.iter()
|
node_config.args().into_iter().cloned().collect()
|
||||||
.map(|x| x.to_owned().clone())
|
};
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
node_config.args().into_iter().cloned().collect()
|
|
||||||
};
|
|
||||||
|
|
||||||
let (key, peer_id) = generators::generate_node_identity(node_config.name())?;
|
let (key, peer_id) = generators::generate_node_identity(node_config.name())?;
|
||||||
|
|
||||||
let mut name = node_config.name().to_string();
|
let mut name = node_config.name().to_string();
|
||||||
let seed = format!("//{}{name}", name.remove(0).to_uppercase());
|
let seed = format!("//{}{name}", name.remove(0).to_uppercase());
|
||||||
let accounts = generators::generate_node_keys(&seed)?;
|
let accounts = generators::generate_node_keys(&seed)?;
|
||||||
let mut accounts = NodeAccounts { seed, accounts };
|
let mut accounts = NodeAccounts { seed, accounts };
|
||||||
|
|
||||||
if evm_based {
|
if evm_based {
|
||||||
if let Some(session_key) = node_config.override_eth_key() {
|
if let Some(session_key) = node_config.override_eth_key() {
|
||||||
accounts
|
accounts.accounts.insert("eth".into(), NodeAccount::new(session_key, session_key));
|
||||||
.accounts
|
}
|
||||||
.insert("eth".into(), NodeAccount::new(session_key, session_key));
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let db_snapshot = match (node_config.db_snapshot(), chain_context.default_db_snapshot) {
|
let db_snapshot = match (node_config.db_snapshot(), chain_context.default_db_snapshot) {
|
||||||
(Some(db_snapshot), _) => Some(db_snapshot),
|
(Some(db_snapshot), _) => Some(db_snapshot),
|
||||||
(None, Some(db_snapshot)) => Some(db_snapshot),
|
(None, Some(db_snapshot)) => Some(db_snapshot),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (full_node_p2p_port, full_node_prometheus_port) = if full_node_present {
|
let (full_node_p2p_port, full_node_prometheus_port) = if full_node_present {
|
||||||
(
|
(
|
||||||
Some(generators::generate_node_port(None)?),
|
Some(generators::generate_node_port(None)?),
|
||||||
Some(generators::generate_node_port(None)?),
|
Some(generators::generate_node_port(None)?),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(None, None)
|
(None, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name: node_config.name().to_string(),
|
name: node_config.name().to_string(),
|
||||||
key,
|
key,
|
||||||
peer_id,
|
peer_id,
|
||||||
image,
|
image,
|
||||||
command,
|
command,
|
||||||
subcommand,
|
subcommand,
|
||||||
args,
|
args,
|
||||||
available_args_output: None,
|
available_args_output: None,
|
||||||
is_validator: node_config.is_validator(),
|
is_validator: node_config.is_validator(),
|
||||||
is_invulnerable: node_config.is_invulnerable(),
|
is_invulnerable: node_config.is_invulnerable(),
|
||||||
is_bootnode: node_config.is_bootnode(),
|
is_bootnode: node_config.is_bootnode(),
|
||||||
initial_balance: node_config.initial_balance(),
|
initial_balance: node_config.initial_balance(),
|
||||||
env: node_config.env().into_iter().cloned().collect(),
|
env: node_config.env().into_iter().cloned().collect(),
|
||||||
bootnodes_addresses: node_config
|
bootnodes_addresses: node_config.bootnodes_addresses().into_iter().cloned().collect(),
|
||||||
.bootnodes_addresses()
|
resources: node_config.resources().cloned(),
|
||||||
.into_iter()
|
p2p_cert_hash: node_config.p2p_cert_hash().map(str::to_string),
|
||||||
.cloned()
|
db_snapshot: db_snapshot.cloned(),
|
||||||
.collect(),
|
accounts,
|
||||||
resources: node_config.resources().cloned(),
|
ws_port: generators::generate_node_port(node_config.ws_port())?,
|
||||||
p2p_cert_hash: node_config.p2p_cert_hash().map(str::to_string),
|
rpc_port: generators::generate_node_port(node_config.rpc_port())?,
|
||||||
db_snapshot: db_snapshot.cloned(),
|
prometheus_port: generators::generate_node_port(node_config.prometheus_port())?,
|
||||||
accounts,
|
p2p_port: generators::generate_node_port(node_config.p2p_port())?,
|
||||||
ws_port: generators::generate_node_port(node_config.ws_port())?,
|
full_node_p2p_port,
|
||||||
rpc_port: generators::generate_node_port(node_config.rpc_port())?,
|
full_node_prometheus_port,
|
||||||
prometheus_port: generators::generate_node_port(node_config.prometheus_port())?,
|
node_log_path: node_config.node_log_path().cloned(),
|
||||||
p2p_port: generators::generate_node_port(node_config.p2p_port())?,
|
keystore_path: node_config.keystore_path().cloned(),
|
||||||
full_node_p2p_port,
|
keystore_key_types: node_config
|
||||||
full_node_prometheus_port,
|
.keystore_key_types()
|
||||||
node_log_path: node_config.node_log_path().cloned(),
|
.into_iter()
|
||||||
keystore_path: node_config.keystore_path().cloned(),
|
.map(str::to_string)
|
||||||
keystore_key_types: node_config
|
.collect(),
|
||||||
.keystore_key_types()
|
})
|
||||||
.into_iter()
|
}
|
||||||
.map(str::to_string)
|
|
||||||
.collect(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_ad_hoc(
|
pub fn from_ad_hoc(
|
||||||
name: impl Into<String>,
|
name: impl Into<String>,
|
||||||
options: AddNodeSpecOpts,
|
options: AddNodeSpecOpts,
|
||||||
chain_context: &ChainDefaultContext,
|
chain_context: &ChainDefaultContext,
|
||||||
full_node_present: bool,
|
full_node_present: bool,
|
||||||
evm_based: bool,
|
evm_based: bool,
|
||||||
) -> Result<Self, OrchestratorError> {
|
) -> Result<Self, OrchestratorError> {
|
||||||
// Check first if the image is set at node level, then try with the default
|
// Check first if the image is set at node level, then try with the default
|
||||||
let image = if let Some(img) = options.image {
|
let image = if let Some(img) = options.image {
|
||||||
Some(img.clone())
|
Some(img.clone())
|
||||||
} else {
|
} else {
|
||||||
chain_context.default_image.cloned()
|
chain_context.default_image.cloned()
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = name.into();
|
let name = name.into();
|
||||||
// Check first if the command is set at node level, then try with the default
|
// Check first if the command is set at node level, then try with the default
|
||||||
let command = if let Some(cmd) = options.command {
|
let command = if let Some(cmd) = options.command {
|
||||||
cmd.clone()
|
cmd.clone()
|
||||||
} else if let Some(cmd) = chain_context.default_command {
|
} else if let Some(cmd) = chain_context.default_command {
|
||||||
cmd.clone()
|
cmd.clone()
|
||||||
} else {
|
} else {
|
||||||
return Err(OrchestratorError::InvalidNodeConfig(
|
return Err(OrchestratorError::InvalidNodeConfig(name, "command".to_string()));
|
||||||
name,
|
};
|
||||||
"command".to_string(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let subcommand = options.subcommand.clone();
|
let subcommand = options.subcommand.clone();
|
||||||
|
|
||||||
// If `args` is set at `node` level use them
|
// If `args` is set at `node` level use them
|
||||||
// otherwise use the default_args (can be empty).
|
// otherwise use the default_args (can be empty).
|
||||||
let args: Vec<Arg> = if options.args.is_empty() {
|
let args: Vec<Arg> = if options.args.is_empty() {
|
||||||
chain_context
|
chain_context.default_args.iter().map(|x| x.to_owned().clone()).collect()
|
||||||
.default_args
|
} else {
|
||||||
.iter()
|
options.args
|
||||||
.map(|x| x.to_owned().clone())
|
};
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
options.args
|
|
||||||
};
|
|
||||||
|
|
||||||
let (key, peer_id) = generators::generate_node_identity(&name)?;
|
let (key, peer_id) = generators::generate_node_identity(&name)?;
|
||||||
|
|
||||||
let mut name_capitalized = name.clone();
|
let mut name_capitalized = name.clone();
|
||||||
let seed = format!(
|
let seed = format!("//{}{name_capitalized}", name_capitalized.remove(0).to_uppercase());
|
||||||
"//{}{name_capitalized}",
|
let accounts = generators::generate_node_keys(&seed)?;
|
||||||
name_capitalized.remove(0).to_uppercase()
|
let mut accounts = NodeAccounts { seed, accounts };
|
||||||
);
|
|
||||||
let accounts = generators::generate_node_keys(&seed)?;
|
|
||||||
let mut accounts = NodeAccounts { seed, accounts };
|
|
||||||
|
|
||||||
if evm_based {
|
if evm_based {
|
||||||
if let Some(session_key) = options.override_eth_key.as_ref() {
|
if let Some(session_key) = options.override_eth_key.as_ref() {
|
||||||
accounts
|
accounts.accounts.insert("eth".into(), NodeAccount::new(session_key, session_key));
|
||||||
.accounts
|
}
|
||||||
.insert("eth".into(), NodeAccount::new(session_key, session_key));
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (full_node_p2p_port, full_node_prometheus_port) = if full_node_present {
|
let (full_node_p2p_port, full_node_prometheus_port) = if full_node_present {
|
||||||
(
|
(
|
||||||
Some(generators::generate_node_port(None)?),
|
Some(generators::generate_node_port(None)?),
|
||||||
Some(generators::generate_node_port(None)?),
|
Some(generators::generate_node_port(None)?),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(None, None)
|
(None, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name,
|
name,
|
||||||
key,
|
key,
|
||||||
peer_id,
|
peer_id,
|
||||||
image,
|
image,
|
||||||
command,
|
command,
|
||||||
subcommand,
|
subcommand,
|
||||||
args,
|
args,
|
||||||
available_args_output: None,
|
available_args_output: None,
|
||||||
is_validator: options.is_validator,
|
is_validator: options.is_validator,
|
||||||
is_invulnerable: false,
|
is_invulnerable: false,
|
||||||
is_bootnode: false,
|
is_bootnode: false,
|
||||||
initial_balance: 0,
|
initial_balance: 0,
|
||||||
env: options.env,
|
env: options.env,
|
||||||
bootnodes_addresses: vec![],
|
bootnodes_addresses: vec![],
|
||||||
resources: None,
|
resources: None,
|
||||||
p2p_cert_hash: None,
|
p2p_cert_hash: None,
|
||||||
db_snapshot: None,
|
db_snapshot: None,
|
||||||
accounts,
|
accounts,
|
||||||
// should be deprecated now!
|
// should be deprecated now!
|
||||||
ws_port: generators::generate_node_port(None)?,
|
ws_port: generators::generate_node_port(None)?,
|
||||||
rpc_port: generators::generate_node_port(options.rpc_port)?,
|
rpc_port: generators::generate_node_port(options.rpc_port)?,
|
||||||
prometheus_port: generators::generate_node_port(options.prometheus_port)?,
|
prometheus_port: generators::generate_node_port(options.prometheus_port)?,
|
||||||
p2p_port: generators::generate_node_port(options.p2p_port)?,
|
p2p_port: generators::generate_node_port(options.p2p_port)?,
|
||||||
full_node_p2p_port,
|
full_node_p2p_port,
|
||||||
full_node_prometheus_port,
|
full_node_prometheus_port,
|
||||||
node_log_path: None,
|
node_log_path: None,
|
||||||
keystore_path: None,
|
keystore_path: None,
|
||||||
keystore_key_types: vec![],
|
keystore_key_types: vec![],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn supports_arg(&self, arg: impl AsRef<str>) -> bool {
|
pub(crate) fn supports_arg(&self, arg: impl AsRef<str>) -> bool {
|
||||||
self.available_args_output
|
self.available_args_output
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect(&format!(
|
.expect(&format!("available args should be present at this point {THIS_IS_A_BUG}"))
|
||||||
"available args should be present at this point {THIS_IS_A_BUG}"
|
.contains(arg.as_ref())
|
||||||
))
|
}
|
||||||
.contains(arg.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn command(&self) -> &str {
|
pub fn command(&self) -> &str {
|
||||||
self.command.as_str()
|
self.command.as_str()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+139
-139
@@ -1,181 +1,181 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use configuration::{
|
use configuration::{
|
||||||
shared::{
|
shared::{
|
||||||
helpers::generate_unique_node_name_from_names,
|
helpers::generate_unique_node_name_from_names,
|
||||||
resources::Resources,
|
resources::Resources,
|
||||||
types::{Arg, AssetLocation, Chain, Command, Image},
|
types::{Arg, AssetLocation, Chain, Command, Image},
|
||||||
},
|
},
|
||||||
types::JsonOverrides,
|
types::JsonOverrides,
|
||||||
NodeConfig, RelaychainConfig,
|
NodeConfig, RelaychainConfig,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use support::replacer::apply_replacements;
|
use support::replacer::apply_replacements;
|
||||||
|
|
||||||
use super::node::NodeSpec;
|
use super::node::NodeSpec;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::OrchestratorError,
|
errors::OrchestratorError,
|
||||||
generators::chain_spec::{ChainSpec, Context},
|
generators::chain_spec::{ChainSpec, Context},
|
||||||
shared::{constants::DEFAULT_CHAIN_SPEC_TPL_COMMAND, types::ChainDefaultContext},
|
shared::{constants::DEFAULT_CHAIN_SPEC_TPL_COMMAND, types::ChainDefaultContext},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A relaychain configuration spec
|
/// A relaychain configuration spec
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct RelaychainSpec {
|
pub struct RelaychainSpec {
|
||||||
/// Chain to use (e.g. rococo-local).
|
/// Chain to use (e.g. rococo-local).
|
||||||
pub(crate) chain: Chain,
|
pub(crate) chain: Chain,
|
||||||
|
|
||||||
/// Default command to run the node. Can be overridden on each node.
|
/// Default command to run the node. Can be overridden on each node.
|
||||||
pub(crate) default_command: Option<Command>,
|
pub(crate) default_command: Option<Command>,
|
||||||
|
|
||||||
/// Default image to use (only podman/k8s). Can be overridden on each node.
|
/// Default image to use (only podman/k8s). Can be overridden on each node.
|
||||||
pub(crate) default_image: Option<Image>,
|
pub(crate) default_image: Option<Image>,
|
||||||
|
|
||||||
/// Default resources. Can be overridden on each node.
|
/// Default resources. Can be overridden on each node.
|
||||||
pub(crate) default_resources: Option<Resources>,
|
pub(crate) default_resources: Option<Resources>,
|
||||||
|
|
||||||
/// Default database snapshot. Can be overridden on each node.
|
/// Default database snapshot. Can be overridden on each node.
|
||||||
pub(crate) default_db_snapshot: Option<AssetLocation>,
|
pub(crate) default_db_snapshot: Option<AssetLocation>,
|
||||||
|
|
||||||
/// Default arguments to use in nodes. Can be overridden on each node.
|
/// Default arguments to use in nodes. Can be overridden on each node.
|
||||||
pub(crate) default_args: Vec<Arg>,
|
pub(crate) default_args: Vec<Arg>,
|
||||||
|
|
||||||
// chain_spec_path: Option<AssetLocation>,
|
// chain_spec_path: Option<AssetLocation>,
|
||||||
pub(crate) chain_spec: ChainSpec,
|
pub(crate) chain_spec: ChainSpec,
|
||||||
|
|
||||||
/// Set the count of nominators to generator (used with PoS networks).
|
/// Set the count of nominators to generator (used with PoS networks).
|
||||||
pub(crate) random_nominators_count: u32,
|
pub(crate) random_nominators_count: u32,
|
||||||
|
|
||||||
/// Set the max nominators value (used with PoS networks).
|
/// Set the max nominators value (used with PoS networks).
|
||||||
pub(crate) max_nominations: u8,
|
pub(crate) max_nominations: u8,
|
||||||
|
|
||||||
/// Genesis overrides as JSON value.
|
/// Genesis overrides as JSON value.
|
||||||
pub(crate) runtime_genesis_patch: Option<serde_json::Value>,
|
pub(crate) runtime_genesis_patch: Option<serde_json::Value>,
|
||||||
|
|
||||||
/// Wasm override path/url to use.
|
/// Wasm override path/url to use.
|
||||||
pub(crate) wasm_override: Option<AssetLocation>,
|
pub(crate) wasm_override: Option<AssetLocation>,
|
||||||
|
|
||||||
/// Nodes to run.
|
/// Nodes to run.
|
||||||
pub(crate) nodes: Vec<NodeSpec>,
|
pub(crate) nodes: Vec<NodeSpec>,
|
||||||
|
|
||||||
/// Raw chain-spec override path, url or inline json to use.
|
/// Raw chain-spec override path, url or inline json to use.
|
||||||
pub(crate) raw_spec_override: Option<JsonOverrides>,
|
pub(crate) raw_spec_override: Option<JsonOverrides>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RelaychainSpec {
|
impl RelaychainSpec {
|
||||||
pub fn from_config(config: &RelaychainConfig) -> Result<RelaychainSpec, OrchestratorError> {
|
pub fn from_config(config: &RelaychainConfig) -> Result<RelaychainSpec, OrchestratorError> {
|
||||||
// Relaychain main command to use, in order:
|
// Relaychain main command to use, in order:
|
||||||
// set as `default_command` or
|
// set as `default_command` or
|
||||||
// use the command of the first node.
|
// use the command of the first node.
|
||||||
// If non of those is set, return an error.
|
// If non of those is set, return an error.
|
||||||
let main_cmd = config
|
let main_cmd = config
|
||||||
.default_command()
|
.default_command()
|
||||||
.or(config.nodes().first().and_then(|node| node.command()))
|
.or(config.nodes().first().and_then(|node| node.command()))
|
||||||
.ok_or(OrchestratorError::InvalidConfig(
|
.ok_or(OrchestratorError::InvalidConfig(
|
||||||
"Relaychain, either default_command or first node with a command needs to be set."
|
"Relaychain, either default_command or first node with a command needs to be set."
|
||||||
.to_string(),
|
.to_string(),
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
// TODO: internally we use image as String
|
// TODO: internally we use image as String
|
||||||
let main_image = config
|
let main_image = config
|
||||||
.default_image()
|
.default_image()
|
||||||
.or(config.nodes().first().and_then(|node| node.image()))
|
.or(config.nodes().first().and_then(|node| node.image()))
|
||||||
.map(|image| image.as_str().to_string());
|
.map(|image| image.as_str().to_string());
|
||||||
|
|
||||||
let replacements = HashMap::from([
|
let replacements = HashMap::from([
|
||||||
("disableBootnodes", "--disable-default-bootnode"),
|
("disableBootnodes", "--disable-default-bootnode"),
|
||||||
("mainCommand", main_cmd.as_str()),
|
("mainCommand", main_cmd.as_str()),
|
||||||
]);
|
]);
|
||||||
let tmpl = if let Some(tmpl) = config.chain_spec_command() {
|
let tmpl = if let Some(tmpl) = config.chain_spec_command() {
|
||||||
apply_replacements(tmpl, &replacements)
|
apply_replacements(tmpl, &replacements)
|
||||||
} else {
|
} else {
|
||||||
apply_replacements(DEFAULT_CHAIN_SPEC_TPL_COMMAND, &replacements)
|
apply_replacements(DEFAULT_CHAIN_SPEC_TPL_COMMAND, &replacements)
|
||||||
};
|
};
|
||||||
|
|
||||||
let chain_spec = ChainSpec::new(config.chain().as_str(), Context::Relay)
|
let chain_spec = ChainSpec::new(config.chain().as_str(), Context::Relay)
|
||||||
.set_chain_name(config.chain().as_str())
|
.set_chain_name(config.chain().as_str())
|
||||||
.command(
|
.command(
|
||||||
tmpl.as_str(),
|
tmpl.as_str(),
|
||||||
config.chain_spec_command_is_local(),
|
config.chain_spec_command_is_local(),
|
||||||
config.chain_spec_command_output_path(),
|
config.chain_spec_command_output_path(),
|
||||||
)
|
)
|
||||||
.image(main_image.clone());
|
.image(main_image.clone());
|
||||||
|
|
||||||
// Add asset location if present
|
// Add asset location if present
|
||||||
let chain_spec = if let Some(chain_spec_path) = config.chain_spec_path() {
|
let chain_spec = if let Some(chain_spec_path) = config.chain_spec_path() {
|
||||||
chain_spec.asset_location(chain_spec_path.clone())
|
chain_spec.asset_location(chain_spec_path.clone())
|
||||||
} else {
|
} else {
|
||||||
chain_spec
|
chain_spec
|
||||||
};
|
};
|
||||||
|
|
||||||
// add chain-spec runtime if present
|
// add chain-spec runtime if present
|
||||||
let chain_spec = if let Some(chain_spec_runtime) = config.chain_spec_runtime() {
|
let chain_spec = if let Some(chain_spec_runtime) = config.chain_spec_runtime() {
|
||||||
chain_spec.runtime(chain_spec_runtime.clone())
|
chain_spec.runtime(chain_spec_runtime.clone())
|
||||||
} else {
|
} else {
|
||||||
chain_spec
|
chain_spec
|
||||||
};
|
};
|
||||||
|
|
||||||
// build the `node_specs`
|
// build the `node_specs`
|
||||||
let chain_context = ChainDefaultContext {
|
let chain_context = ChainDefaultContext {
|
||||||
default_command: config.default_command(),
|
default_command: config.default_command(),
|
||||||
default_image: config.default_image(),
|
default_image: config.default_image(),
|
||||||
default_resources: config.default_resources(),
|
default_resources: config.default_resources(),
|
||||||
default_db_snapshot: config.default_db_snapshot(),
|
default_db_snapshot: config.default_db_snapshot(),
|
||||||
default_args: config.default_args(),
|
default_args: config.default_args(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut nodes: Vec<NodeConfig> = config.nodes().into_iter().cloned().collect();
|
let mut nodes: Vec<NodeConfig> = config.nodes().into_iter().cloned().collect();
|
||||||
nodes.extend(
|
nodes.extend(
|
||||||
config
|
config
|
||||||
.group_node_configs()
|
.group_node_configs()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|node_group| node_group.expand_group_configs()),
|
.flat_map(|node_group| node_group.expand_group_configs()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut names = HashSet::new();
|
let mut names = HashSet::new();
|
||||||
let (nodes, mut errs) = nodes
|
let (nodes, mut errs) = nodes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|node_config| NodeSpec::from_config(node_config, &chain_context, false, false))
|
.map(|node_config| NodeSpec::from_config(node_config, &chain_context, false, false))
|
||||||
.fold((vec![], vec![]), |(mut nodes, mut errs), result| {
|
.fold((vec![], vec![]), |(mut nodes, mut errs), result| {
|
||||||
match result {
|
match result {
|
||||||
Ok(mut node) => {
|
Ok(mut node) => {
|
||||||
let unique_name =
|
let unique_name =
|
||||||
generate_unique_node_name_from_names(node.name, &mut names);
|
generate_unique_node_name_from_names(node.name, &mut names);
|
||||||
node.name = unique_name;
|
node.name = unique_name;
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
},
|
},
|
||||||
Err(err) => errs.push(err),
|
Err(err) => errs.push(err),
|
||||||
}
|
}
|
||||||
(nodes, errs)
|
(nodes, errs)
|
||||||
});
|
});
|
||||||
|
|
||||||
if !errs.is_empty() {
|
if !errs.is_empty() {
|
||||||
// TODO: merge errs, maybe return something like Result<Sometype, Vec<OrchestratorError>>
|
// TODO: merge errs, maybe return something like Result<Sometype, Vec<OrchestratorError>>
|
||||||
return Err(errs.swap_remove(0));
|
return Err(errs.swap_remove(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(RelaychainSpec {
|
Ok(RelaychainSpec {
|
||||||
chain: config.chain().clone(),
|
chain: config.chain().clone(),
|
||||||
default_command: config.default_command().cloned(),
|
default_command: config.default_command().cloned(),
|
||||||
default_image: config.default_image().cloned(),
|
default_image: config.default_image().cloned(),
|
||||||
default_resources: config.default_resources().cloned(),
|
default_resources: config.default_resources().cloned(),
|
||||||
default_db_snapshot: config.default_db_snapshot().cloned(),
|
default_db_snapshot: config.default_db_snapshot().cloned(),
|
||||||
wasm_override: config.wasm_override().cloned(),
|
wasm_override: config.wasm_override().cloned(),
|
||||||
default_args: config.default_args().into_iter().cloned().collect(),
|
default_args: config.default_args().into_iter().cloned().collect(),
|
||||||
chain_spec,
|
chain_spec,
|
||||||
random_nominators_count: config.random_nominators_count().unwrap_or(0),
|
random_nominators_count: config.random_nominators_count().unwrap_or(0),
|
||||||
max_nominations: config.max_nominations().unwrap_or(24),
|
max_nominations: config.max_nominations().unwrap_or(24),
|
||||||
runtime_genesis_patch: config.runtime_genesis_patch().cloned(),
|
runtime_genesis_patch: config.runtime_genesis_patch().cloned(),
|
||||||
nodes,
|
nodes,
|
||||||
raw_spec_override: config.raw_spec_override().cloned(),
|
raw_spec_override: config.raw_spec_override().cloned(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chain_spec(&self) -> &ChainSpec {
|
pub fn chain_spec(&self) -> &ChainSpec {
|
||||||
&self.chain_spec
|
&self.chain_spec
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chain_spec_mut(&mut self) -> &mut ChainSpec {
|
pub fn chain_spec_mut(&mut self) -> &mut ChainSpec {
|
||||||
&mut self.chain_spec
|
&mut self.chain_spec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+294
-318
@@ -1,12 +1,12 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
use configuration::{
|
use configuration::{
|
||||||
shared::{helpers::generate_unique_node_name_from_names, resources::Resources},
|
shared::{helpers::generate_unique_node_name_from_names, resources::Resources},
|
||||||
types::{Arg, AssetLocation, Chain, Command, Image, JsonOverrides},
|
types::{Arg, AssetLocation, Chain, Command, Image, JsonOverrides},
|
||||||
NodeConfig, ParachainConfig, RegistrationStrategy,
|
NodeConfig, ParachainConfig, RegistrationStrategy,
|
||||||
};
|
};
|
||||||
use provider::DynNamespace;
|
use provider::DynNamespace;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -15,372 +15,348 @@ use tracing::debug;
|
|||||||
|
|
||||||
use super::node::NodeSpec;
|
use super::node::NodeSpec;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::OrchestratorError,
|
errors::OrchestratorError,
|
||||||
generators::{
|
generators::{
|
||||||
chain_spec::{ChainSpec, Context, ParaGenesisConfig},
|
chain_spec::{ChainSpec, Context, ParaGenesisConfig},
|
||||||
para_artifact::*,
|
para_artifact::*,
|
||||||
},
|
},
|
||||||
shared::{constants::DEFAULT_CHAIN_SPEC_TPL_COMMAND, types::ChainDefaultContext},
|
shared::{constants::DEFAULT_CHAIN_SPEC_TPL_COMMAND, types::ChainDefaultContext},
|
||||||
ScopedFilesystem,
|
ScopedFilesystem,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct TeyrchainSpec {
|
pub struct TeyrchainSpec {
|
||||||
// `name` of the parachain (used in some corner cases)
|
// `name` of the parachain (used in some corner cases)
|
||||||
// name: Option<Chain>,
|
// name: Option<Chain>,
|
||||||
/// Parachain id
|
/// Parachain id
|
||||||
pub(crate) id: u32,
|
pub(crate) id: u32,
|
||||||
|
|
||||||
/// Unique id of the parachain, in the patter of <para_id>-<n>
|
/// Unique id of the parachain, in the patter of <para_id>-<n>
|
||||||
/// where the suffix is only present if more than one parachain is set with the same id
|
/// where the suffix is only present if more than one parachain is set with the same id
|
||||||
pub(crate) unique_id: String,
|
pub(crate) unique_id: String,
|
||||||
|
|
||||||
/// Default command to run the node. Can be overridden on each node.
|
/// Default command to run the node. Can be overridden on each node.
|
||||||
pub(crate) default_command: Option<Command>,
|
pub(crate) default_command: Option<Command>,
|
||||||
|
|
||||||
/// Default image to use (only podman/k8s). Can be overridden on each node.
|
/// Default image to use (only podman/k8s). Can be overridden on each node.
|
||||||
pub(crate) default_image: Option<Image>,
|
pub(crate) default_image: Option<Image>,
|
||||||
|
|
||||||
/// Default resources. Can be overridden on each node.
|
/// Default resources. Can be overridden on each node.
|
||||||
pub(crate) default_resources: Option<Resources>,
|
pub(crate) default_resources: Option<Resources>,
|
||||||
|
|
||||||
/// Default database snapshot. Can be overridden on each node.
|
/// Default database snapshot. Can be overridden on each node.
|
||||||
pub(crate) default_db_snapshot: Option<AssetLocation>,
|
pub(crate) default_db_snapshot: Option<AssetLocation>,
|
||||||
|
|
||||||
/// Default arguments to use in nodes. Can be overridden on each node.
|
/// Default arguments to use in nodes. Can be overridden on each node.
|
||||||
pub(crate) default_args: Vec<Arg>,
|
pub(crate) default_args: Vec<Arg>,
|
||||||
|
|
||||||
/// Chain-spec, only needed by cumulus based paras
|
/// Chain-spec, only needed by cumulus based paras
|
||||||
pub(crate) chain_spec: Option<ChainSpec>,
|
pub(crate) chain_spec: Option<ChainSpec>,
|
||||||
|
|
||||||
/// Do not automatically assign a bootnode role if no nodes are marked as bootnodes.
|
/// Do not automatically assign a bootnode role if no nodes are marked as bootnodes.
|
||||||
pub(crate) no_default_bootnodes: bool,
|
pub(crate) no_default_bootnodes: bool,
|
||||||
|
|
||||||
/// Registration strategy to use
|
/// Registration strategy to use
|
||||||
pub(crate) registration_strategy: RegistrationStrategy,
|
pub(crate) registration_strategy: RegistrationStrategy,
|
||||||
|
|
||||||
/// Onboard as parachain or parathread
|
/// Onboard as parachain or parathread
|
||||||
pub(crate) onboard_as_parachain: bool,
|
pub(crate) onboard_as_parachain: bool,
|
||||||
|
|
||||||
/// Is the parachain cumulus-based
|
/// Is the parachain cumulus-based
|
||||||
pub(crate) is_cumulus_based: bool,
|
pub(crate) is_cumulus_based: bool,
|
||||||
|
|
||||||
/// Is the parachain evm-based
|
/// Is the parachain evm-based
|
||||||
pub(crate) is_evm_based: bool,
|
pub(crate) is_evm_based: bool,
|
||||||
|
|
||||||
/// Initial balance
|
/// Initial balance
|
||||||
pub(crate) initial_balance: u128,
|
pub(crate) initial_balance: u128,
|
||||||
|
|
||||||
/// Genesis state (head) to register the parachain
|
/// Genesis state (head) to register the parachain
|
||||||
pub(crate) genesis_state: ParaArtifact,
|
pub(crate) genesis_state: ParaArtifact,
|
||||||
|
|
||||||
/// Genesis WASM to register the parachain
|
/// Genesis WASM to register the parachain
|
||||||
pub(crate) genesis_wasm: ParaArtifact,
|
pub(crate) genesis_wasm: ParaArtifact,
|
||||||
|
|
||||||
/// Genesis overrides as JSON value.
|
/// Genesis overrides as JSON value.
|
||||||
pub(crate) genesis_overrides: Option<serde_json::Value>,
|
pub(crate) genesis_overrides: Option<serde_json::Value>,
|
||||||
|
|
||||||
/// Wasm override path/url to use.
|
/// Wasm override path/url to use.
|
||||||
pub(crate) wasm_override: Option<AssetLocation>,
|
pub(crate) wasm_override: Option<AssetLocation>,
|
||||||
|
|
||||||
/// Collators to spawn
|
/// Collators to spawn
|
||||||
pub(crate) collators: Vec<NodeSpec>,
|
pub(crate) collators: Vec<NodeSpec>,
|
||||||
|
|
||||||
/// Raw chain-spec override path, url or inline json to use.
|
/// Raw chain-spec override path, url or inline json to use.
|
||||||
pub(crate) raw_spec_override: Option<JsonOverrides>,
|
pub(crate) raw_spec_override: Option<JsonOverrides>,
|
||||||
|
|
||||||
/// Bootnodes addresses to use for the parachain nodes
|
/// Bootnodes addresses to use for the parachain nodes
|
||||||
pub(crate) bootnodes_addresses: Vec<multiaddr::Multiaddr>,
|
pub(crate) bootnodes_addresses: Vec<multiaddr::Multiaddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TeyrchainSpec {
|
impl TeyrchainSpec {
|
||||||
pub fn from_config(
|
pub fn from_config(
|
||||||
config: &ParachainConfig,
|
config: &ParachainConfig,
|
||||||
relay_chain: Chain,
|
relay_chain: Chain,
|
||||||
) -> Result<TeyrchainSpec, OrchestratorError> {
|
) -> Result<TeyrchainSpec, OrchestratorError> {
|
||||||
let main_cmd = if let Some(cmd) = config.default_command() {
|
let main_cmd = if let Some(cmd) = config.default_command() {
|
||||||
cmd
|
cmd
|
||||||
} else if let Some(first_node) = config.collators().first() {
|
} else if let Some(first_node) = config.collators().first() {
|
||||||
let Some(cmd) = first_node.command() else {
|
let Some(cmd) = first_node.command() else {
|
||||||
return Err(OrchestratorError::InvalidConfig(format!("Parachain {}, either default_command or command in the first node needs to be set.", config.id())));
|
return Err(OrchestratorError::InvalidConfig(format!("Parachain {}, either default_command or command in the first node needs to be set.", config.id())));
|
||||||
};
|
};
|
||||||
|
|
||||||
cmd
|
cmd
|
||||||
} else {
|
} else {
|
||||||
return Err(OrchestratorError::InvalidConfig(format!(
|
return Err(OrchestratorError::InvalidConfig(format!(
|
||||||
"Parachain {}, without nodes and default_command isn't set.",
|
"Parachain {}, without nodes and default_command isn't set.",
|
||||||
config.id()
|
config.id()
|
||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: internally we use image as String
|
// TODO: internally we use image as String
|
||||||
let main_image = config
|
let main_image = config
|
||||||
.default_image()
|
.default_image()
|
||||||
.or(config.collators().first().and_then(|node| node.image()))
|
.or(config.collators().first().and_then(|node| node.image()))
|
||||||
.map(|image| image.as_str().to_string());
|
.map(|image| image.as_str().to_string());
|
||||||
|
|
||||||
let chain_spec = if config.is_cumulus_based() {
|
let chain_spec = if config.is_cumulus_based() {
|
||||||
// we need a chain-spec
|
// we need a chain-spec
|
||||||
let chain_name = if let Some(chain_name) = config.chain() {
|
let chain_name =
|
||||||
chain_name.as_str()
|
if let Some(chain_name) = config.chain() { chain_name.as_str() } else { "" };
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
|
|
||||||
let chain_spec_builder = if chain_name.is_empty() {
|
let chain_spec_builder = if chain_name.is_empty() {
|
||||||
// if the chain don't have name use the unique_id for the name of the file
|
// if the chain don't have name use the unique_id for the name of the file
|
||||||
ChainSpec::new(
|
ChainSpec::new(
|
||||||
config.unique_id().to_string(),
|
config.unique_id().to_string(),
|
||||||
Context::Para {
|
Context::Para { relay_chain, para_id: config.id() },
|
||||||
relay_chain,
|
)
|
||||||
para_id: config.id(),
|
} else {
|
||||||
},
|
let chain_spec_file_name = if config.unique_id().contains('-') {
|
||||||
)
|
&format!("{}-{}", chain_name, config.unique_id())
|
||||||
} else {
|
} else {
|
||||||
let chain_spec_file_name = if config.unique_id().contains('-') {
|
chain_name
|
||||||
&format!("{}-{}", chain_name, config.unique_id())
|
};
|
||||||
} else {
|
ChainSpec::new(
|
||||||
chain_name
|
chain_spec_file_name,
|
||||||
};
|
Context::Para { relay_chain, para_id: config.id() },
|
||||||
ChainSpec::new(
|
)
|
||||||
chain_spec_file_name,
|
};
|
||||||
Context::Para {
|
let chain_spec_builder = chain_spec_builder.set_chain_name(chain_name);
|
||||||
relay_chain,
|
|
||||||
para_id: config.id(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let chain_spec_builder = chain_spec_builder.set_chain_name(chain_name);
|
|
||||||
|
|
||||||
let replacements = HashMap::from([
|
let replacements = HashMap::from([
|
||||||
("disableBootnodes", "--disable-default-bootnode"),
|
("disableBootnodes", "--disable-default-bootnode"),
|
||||||
("mainCommand", main_cmd.as_str()),
|
("mainCommand", main_cmd.as_str()),
|
||||||
]);
|
]);
|
||||||
let tmpl = if let Some(tmpl) = config.chain_spec_command() {
|
let tmpl = if let Some(tmpl) = config.chain_spec_command() {
|
||||||
apply_replacements(tmpl, &replacements)
|
apply_replacements(tmpl, &replacements)
|
||||||
} else {
|
} else {
|
||||||
apply_replacements(DEFAULT_CHAIN_SPEC_TPL_COMMAND, &replacements)
|
apply_replacements(DEFAULT_CHAIN_SPEC_TPL_COMMAND, &replacements)
|
||||||
};
|
};
|
||||||
|
|
||||||
let chain_spec = chain_spec_builder
|
let chain_spec = chain_spec_builder
|
||||||
.command(
|
.command(
|
||||||
tmpl.as_str(),
|
tmpl.as_str(),
|
||||||
config.chain_spec_command_is_local(),
|
config.chain_spec_command_is_local(),
|
||||||
config.chain_spec_command_output_path(),
|
config.chain_spec_command_output_path(),
|
||||||
)
|
)
|
||||||
.image(main_image.clone());
|
.image(main_image.clone());
|
||||||
|
|
||||||
let chain_spec = if let Some(chain_spec_path) = config.chain_spec_path() {
|
let chain_spec = if let Some(chain_spec_path) = config.chain_spec_path() {
|
||||||
chain_spec.asset_location(chain_spec_path.clone())
|
chain_spec.asset_location(chain_spec_path.clone())
|
||||||
} else {
|
} else {
|
||||||
chain_spec
|
chain_spec
|
||||||
};
|
};
|
||||||
|
|
||||||
// add chain-spec runtime if present
|
// add chain-spec runtime if present
|
||||||
let chain_spec = if let Some(chain_spec_runtime) = config.chain_spec_runtime() {
|
let chain_spec = if let Some(chain_spec_runtime) = config.chain_spec_runtime() {
|
||||||
chain_spec.runtime(chain_spec_runtime.clone())
|
chain_spec.runtime(chain_spec_runtime.clone())
|
||||||
} else {
|
} else {
|
||||||
chain_spec
|
chain_spec
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(chain_spec)
|
Some(chain_spec)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// build the `node_specs`
|
// build the `node_specs`
|
||||||
let chain_context = ChainDefaultContext {
|
let chain_context = ChainDefaultContext {
|
||||||
default_command: config.default_command(),
|
default_command: config.default_command(),
|
||||||
default_image: config.default_image(),
|
default_image: config.default_image(),
|
||||||
default_resources: config.default_resources(),
|
default_resources: config.default_resources(),
|
||||||
default_db_snapshot: config.default_db_snapshot(),
|
default_db_snapshot: config.default_db_snapshot(),
|
||||||
default_args: config.default_args(),
|
default_args: config.default_args(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// We want to track the errors for all the nodes and report them ones
|
// We want to track the errors for all the nodes and report them ones
|
||||||
let mut errs: Vec<OrchestratorError> = Default::default();
|
let mut errs: Vec<OrchestratorError> = Default::default();
|
||||||
let mut collators: Vec<NodeSpec> = Default::default();
|
let mut collators: Vec<NodeSpec> = Default::default();
|
||||||
|
|
||||||
let mut nodes: Vec<NodeConfig> = config.collators().into_iter().cloned().collect();
|
let mut nodes: Vec<NodeConfig> = config.collators().into_iter().cloned().collect();
|
||||||
nodes.extend(
|
nodes.extend(
|
||||||
config
|
config
|
||||||
.group_collators_configs()
|
.group_collators_configs()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|node_group| node_group.expand_group_configs()),
|
.flat_map(|node_group| node_group.expand_group_configs()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut names = HashSet::new();
|
let mut names = HashSet::new();
|
||||||
for node_config in nodes {
|
for node_config in nodes {
|
||||||
match NodeSpec::from_config(&node_config, &chain_context, true, config.is_evm_based()) {
|
match NodeSpec::from_config(&node_config, &chain_context, true, config.is_evm_based()) {
|
||||||
Ok(mut node) => {
|
Ok(mut node) => {
|
||||||
let unique_name = generate_unique_node_name_from_names(node.name, &mut names);
|
let unique_name = generate_unique_node_name_from_names(node.name, &mut names);
|
||||||
node.name = unique_name;
|
node.name = unique_name;
|
||||||
collators.push(node)
|
collators.push(node)
|
||||||
},
|
},
|
||||||
Err(err) => errs.push(err),
|
Err(err) => errs.push(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let genesis_state = if let Some(path) = config.genesis_state_path() {
|
let genesis_state = if let Some(path) = config.genesis_state_path() {
|
||||||
ParaArtifact::new(
|
ParaArtifact::new(
|
||||||
ParaArtifactType::State,
|
ParaArtifactType::State,
|
||||||
ParaArtifactBuildOption::Path(path.to_string()),
|
ParaArtifactBuildOption::Path(path.to_string()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let cmd = if let Some(cmd) = config.genesis_state_generator() {
|
let cmd =
|
||||||
cmd.cmd()
|
if let Some(cmd) = config.genesis_state_generator() { cmd.cmd() } else { main_cmd };
|
||||||
} else {
|
ParaArtifact::new(
|
||||||
main_cmd
|
ParaArtifactType::State,
|
||||||
};
|
ParaArtifactBuildOption::Command(cmd.as_str().into()),
|
||||||
ParaArtifact::new(
|
)
|
||||||
ParaArtifactType::State,
|
.image(main_image.clone())
|
||||||
ParaArtifactBuildOption::Command(cmd.as_str().into()),
|
};
|
||||||
)
|
|
||||||
.image(main_image.clone())
|
|
||||||
};
|
|
||||||
|
|
||||||
let genesis_wasm = if let Some(path) = config.genesis_wasm_path() {
|
let genesis_wasm = if let Some(path) = config.genesis_wasm_path() {
|
||||||
ParaArtifact::new(
|
ParaArtifact::new(
|
||||||
ParaArtifactType::Wasm,
|
ParaArtifactType::Wasm,
|
||||||
ParaArtifactBuildOption::Path(path.to_string()),
|
ParaArtifactBuildOption::Path(path.to_string()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let cmd = if let Some(cmd) = config.genesis_wasm_generator() {
|
let cmd = if let Some(cmd) = config.genesis_wasm_generator() {
|
||||||
cmd.as_str()
|
cmd.as_str()
|
||||||
} else {
|
} else {
|
||||||
main_cmd.as_str()
|
main_cmd.as_str()
|
||||||
};
|
};
|
||||||
ParaArtifact::new(
|
ParaArtifact::new(ParaArtifactType::Wasm, ParaArtifactBuildOption::Command(cmd.into()))
|
||||||
ParaArtifactType::Wasm,
|
.image(main_image.clone())
|
||||||
ParaArtifactBuildOption::Command(cmd.into()),
|
};
|
||||||
)
|
|
||||||
.image(main_image.clone())
|
|
||||||
};
|
|
||||||
|
|
||||||
let para_spec = TeyrchainSpec {
|
let para_spec = TeyrchainSpec {
|
||||||
id: config.id(),
|
id: config.id(),
|
||||||
// ensure unique id is set at this point, if not just set to the para_id
|
// ensure unique id is set at this point, if not just set to the para_id
|
||||||
unique_id: if config.unique_id().is_empty() {
|
unique_id: if config.unique_id().is_empty() {
|
||||||
config.id().to_string()
|
config.id().to_string()
|
||||||
} else {
|
} else {
|
||||||
config.unique_id().to_string()
|
config.unique_id().to_string()
|
||||||
},
|
},
|
||||||
default_command: config.default_command().cloned(),
|
default_command: config.default_command().cloned(),
|
||||||
default_image: config.default_image().cloned(),
|
default_image: config.default_image().cloned(),
|
||||||
default_resources: config.default_resources().cloned(),
|
default_resources: config.default_resources().cloned(),
|
||||||
default_db_snapshot: config.default_db_snapshot().cloned(),
|
default_db_snapshot: config.default_db_snapshot().cloned(),
|
||||||
wasm_override: config.wasm_override().cloned(),
|
wasm_override: config.wasm_override().cloned(),
|
||||||
default_args: config.default_args().into_iter().cloned().collect(),
|
default_args: config.default_args().into_iter().cloned().collect(),
|
||||||
chain_spec,
|
chain_spec,
|
||||||
no_default_bootnodes: config.no_default_bootnodes(),
|
no_default_bootnodes: config.no_default_bootnodes(),
|
||||||
registration_strategy: config
|
registration_strategy: config
|
||||||
.registration_strategy()
|
.registration_strategy()
|
||||||
.unwrap_or(&RegistrationStrategy::InGenesis)
|
.unwrap_or(&RegistrationStrategy::InGenesis)
|
||||||
.clone(),
|
.clone(),
|
||||||
onboard_as_parachain: config.onboard_as_parachain(),
|
onboard_as_parachain: config.onboard_as_parachain(),
|
||||||
is_cumulus_based: config.is_cumulus_based(),
|
is_cumulus_based: config.is_cumulus_based(),
|
||||||
is_evm_based: config.is_evm_based(),
|
is_evm_based: config.is_evm_based(),
|
||||||
initial_balance: config.initial_balance(),
|
initial_balance: config.initial_balance(),
|
||||||
genesis_state,
|
genesis_state,
|
||||||
genesis_wasm,
|
genesis_wasm,
|
||||||
genesis_overrides: config.genesis_overrides().cloned(),
|
genesis_overrides: config.genesis_overrides().cloned(),
|
||||||
collators,
|
collators,
|
||||||
raw_spec_override: config.raw_spec_override().cloned(),
|
raw_spec_override: config.raw_spec_override().cloned(),
|
||||||
bootnodes_addresses: config.bootnodes_addresses().into_iter().cloned().collect(),
|
bootnodes_addresses: config.bootnodes_addresses().into_iter().cloned().collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(para_spec)
|
Ok(para_spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn registration_strategy(&self) -> &RegistrationStrategy {
|
pub fn registration_strategy(&self) -> &RegistrationStrategy {
|
||||||
&self.registration_strategy
|
&self.registration_strategy
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_genesis_config(&self) -> Result<ParaGenesisConfig<&PathBuf>, OrchestratorError> {
|
pub fn get_genesis_config(&self) -> Result<ParaGenesisConfig<&PathBuf>, OrchestratorError> {
|
||||||
let genesis_config = ParaGenesisConfig {
|
let genesis_config = ParaGenesisConfig {
|
||||||
state_path: self.genesis_state.artifact_path().ok_or(
|
state_path: self.genesis_state.artifact_path().ok_or(
|
||||||
OrchestratorError::InvariantError(
|
OrchestratorError::InvariantError(
|
||||||
"artifact path for state must be set at this point",
|
"artifact path for state must be set at this point",
|
||||||
),
|
),
|
||||||
)?,
|
)?,
|
||||||
wasm_path: self.genesis_wasm.artifact_path().ok_or(
|
wasm_path: self.genesis_wasm.artifact_path().ok_or(
|
||||||
OrchestratorError::InvariantError(
|
OrchestratorError::InvariantError(
|
||||||
"artifact path for wasm must be set at this point",
|
"artifact path for wasm must be set at this point",
|
||||||
),
|
),
|
||||||
)?,
|
)?,
|
||||||
id: self.id,
|
id: self.id,
|
||||||
as_parachain: self.onboard_as_parachain,
|
as_parachain: self.onboard_as_parachain,
|
||||||
};
|
};
|
||||||
Ok(genesis_config)
|
Ok(genesis_config)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> u32 {
|
pub fn id(&self) -> u32 {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chain_spec(&self) -> Option<&ChainSpec> {
|
pub fn chain_spec(&self) -> Option<&ChainSpec> {
|
||||||
self.chain_spec.as_ref()
|
self.chain_spec.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chain_spec_mut(&mut self) -> Option<&mut ChainSpec> {
|
pub fn chain_spec_mut(&mut self) -> Option<&mut ChainSpec> {
|
||||||
self.chain_spec.as_mut()
|
self.chain_spec.as_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build parachain chain-spec
|
/// Build parachain chain-spec
|
||||||
///
|
///
|
||||||
/// This function customize the chain-spec (if is possible) and build the raw version
|
/// This function customize the chain-spec (if is possible) and build the raw version
|
||||||
/// of the chain-spec.
|
/// of the chain-spec.
|
||||||
pub(crate) async fn build_chain_spec<'a, T>(
|
pub(crate) async fn build_chain_spec<'a, T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
relay_chain_id: &str,
|
relay_chain_id: &str,
|
||||||
ns: &DynNamespace,
|
ns: &DynNamespace,
|
||||||
scoped_fs: &ScopedFilesystem<'a, T>,
|
scoped_fs: &ScopedFilesystem<'a, T>,
|
||||||
) -> Result<Option<PathBuf>, anyhow::Error>
|
) -> Result<Option<PathBuf>, anyhow::Error>
|
||||||
where
|
where
|
||||||
T: FileSystem,
|
T: FileSystem,
|
||||||
{
|
{
|
||||||
let cloned = self.clone();
|
let cloned = self.clone();
|
||||||
let chain_spec_raw_path = if let Some(chain_spec) = self.chain_spec.as_mut() {
|
let chain_spec_raw_path = if let Some(chain_spec) = self.chain_spec.as_mut() {
|
||||||
debug!("parachain chain-spec building!");
|
debug!("parachain chain-spec building!");
|
||||||
chain_spec.build(ns, scoped_fs).await?;
|
chain_spec.build(ns, scoped_fs).await?;
|
||||||
debug!("parachain chain-spec built!");
|
debug!("parachain chain-spec built!");
|
||||||
|
|
||||||
chain_spec
|
chain_spec.customize_para(&cloned, relay_chain_id, scoped_fs).await?;
|
||||||
.customize_para(&cloned, relay_chain_id, scoped_fs)
|
debug!("parachain chain-spec customized!");
|
||||||
.await?;
|
chain_spec.build_raw(ns, scoped_fs, Some(relay_chain_id.try_into()?)).await?;
|
||||||
debug!("parachain chain-spec customized!");
|
debug!("parachain chain-spec raw built!");
|
||||||
chain_spec
|
|
||||||
.build_raw(ns, scoped_fs, Some(relay_chain_id.try_into()?))
|
|
||||||
.await?;
|
|
||||||
debug!("parachain chain-spec raw built!");
|
|
||||||
|
|
||||||
// override wasm if needed
|
// override wasm if needed
|
||||||
if let Some(ref wasm_override) = self.wasm_override {
|
if let Some(ref wasm_override) = self.wasm_override {
|
||||||
chain_spec.override_code(scoped_fs, wasm_override).await?;
|
chain_spec.override_code(scoped_fs, wasm_override).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// override raw spec if needed
|
// override raw spec if needed
|
||||||
if let Some(ref raw_spec_override) = self.raw_spec_override {
|
if let Some(ref raw_spec_override) = self.raw_spec_override {
|
||||||
chain_spec
|
chain_spec.override_raw_spec(scoped_fs, raw_spec_override).await?;
|
||||||
.override_raw_spec(scoped_fs, raw_spec_override)
|
}
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let chain_spec_raw_path =
|
let chain_spec_raw_path = chain_spec.raw_path().ok_or(
|
||||||
chain_spec
|
OrchestratorError::InvariantError("chain-spec raw path should be set now"),
|
||||||
.raw_path()
|
)?;
|
||||||
.ok_or(OrchestratorError::InvariantError(
|
|
||||||
"chain-spec raw path should be set now",
|
|
||||||
))?;
|
|
||||||
|
|
||||||
Some(chain_spec_raw_path.to_path_buf())
|
Some(chain_spec_raw_path.to_path_buf())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
Ok(chain_spec_raw_path)
|
Ok(chain_spec_raw_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the bootnodes addresses for the parachain spec
|
/// Get the bootnodes addresses for the parachain spec
|
||||||
pub(crate) fn bootnodes_addresses(&self) -> Vec<&multiaddr::Multiaddr> {
|
pub(crate) fn bootnodes_addresses(&self) -> Vec<&multiaddr::Multiaddr> {
|
||||||
self.bootnodes_addresses.iter().collect()
|
self.bootnodes_addresses.iter().collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ pub const RPC_HTTP_PORT: u16 = 9933;
|
|||||||
pub const P2P_PORT: u16 = 30333;
|
pub const P2P_PORT: u16 = 30333;
|
||||||
// default command template to build chain-spec
|
// default command template to build chain-spec
|
||||||
pub const DEFAULT_CHAIN_SPEC_TPL_COMMAND: &str =
|
pub const DEFAULT_CHAIN_SPEC_TPL_COMMAND: &str =
|
||||||
"{{mainCommand}} build-spec --chain {{chainName}} {{disableBootnodes}}";
|
"{{mainCommand}} build-spec --chain {{chainName}} {{disableBootnodes}}";
|
||||||
// interval to determine how often to run node liveness checks
|
// interval to determine how often to run node liveness checks
|
||||||
pub const NODE_MONITORING_INTERVAL_SECONDS: u64 = 15;
|
pub const NODE_MONITORING_INTERVAL_SECONDS: u64 = 15;
|
||||||
// how long to wait before a node is considered unresponsive
|
// how long to wait before a node is considered unresponsive
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
net::TcpListener,
|
net::TcpListener,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{Arc, RwLock},
|
sync::{Arc, RwLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
use configuration::shared::{
|
use configuration::shared::{
|
||||||
resources::Resources,
|
resources::Resources,
|
||||||
types::{Arg, AssetLocation, Command, Image, Port},
|
types::{Arg, AssetLocation, Command, Image, Port},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -15,85 +15,75 @@ pub type Accounts = HashMap<String, NodeAccount>;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct NodeAccount {
|
pub struct NodeAccount {
|
||||||
pub address: String,
|
pub address: String,
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeAccount {
|
impl NodeAccount {
|
||||||
pub fn new(addr: impl Into<String>, pk: impl Into<String>) -> Self {
|
pub fn new(addr: impl Into<String>, pk: impl Into<String>) -> Self {
|
||||||
Self {
|
Self { address: addr.into(), public_key: pk.into() }
|
||||||
address: addr.into(),
|
}
|
||||||
public_key: pk.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct NodeAccounts {
|
pub struct NodeAccounts {
|
||||||
pub seed: String,
|
pub seed: String,
|
||||||
pub accounts: Accounts,
|
pub accounts: Accounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
|
||||||
pub struct ParkedPort(
|
pub struct ParkedPort(pub(crate) Port, #[serde(skip)] pub(crate) Arc<RwLock<Option<TcpListener>>>);
|
||||||
pub(crate) Port,
|
|
||||||
#[serde(skip)] pub(crate) Arc<RwLock<Option<TcpListener>>>,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl ParkedPort {
|
impl ParkedPort {
|
||||||
pub(crate) fn new(port: u16, listener: TcpListener) -> ParkedPort {
|
pub(crate) fn new(port: u16, listener: TcpListener) -> ParkedPort {
|
||||||
let listener = Arc::new(RwLock::new(Some(listener)));
|
let listener = Arc::new(RwLock::new(Some(listener)));
|
||||||
ParkedPort(port, listener)
|
ParkedPort(port, listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn drop_listener(&self) {
|
pub(crate) fn drop_listener(&self) {
|
||||||
// drop the listener will allow the running node to start listenen connections
|
// drop the listener will allow the running node to start listenen connections
|
||||||
let mut l = self.1.write().unwrap();
|
let mut l = self.1.write().unwrap();
|
||||||
*l = None;
|
*l = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ChainDefaultContext<'a> {
|
pub struct ChainDefaultContext<'a> {
|
||||||
pub default_command: Option<&'a Command>,
|
pub default_command: Option<&'a Command>,
|
||||||
pub default_image: Option<&'a Image>,
|
pub default_image: Option<&'a Image>,
|
||||||
pub default_resources: Option<&'a Resources>,
|
pub default_resources: Option<&'a Resources>,
|
||||||
pub default_db_snapshot: Option<&'a AssetLocation>,
|
pub default_db_snapshot: Option<&'a AssetLocation>,
|
||||||
pub default_args: Vec<&'a Arg>,
|
pub default_args: Vec<&'a Arg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RegisterParachainOptions {
|
pub struct RegisterParachainOptions {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub wasm_path: PathBuf,
|
pub wasm_path: PathBuf,
|
||||||
pub state_path: PathBuf,
|
pub state_path: PathBuf,
|
||||||
pub node_ws_url: String,
|
pub node_ws_url: String,
|
||||||
pub onboard_as_para: bool,
|
pub onboard_as_para: bool,
|
||||||
pub seed: Option<[u8; 32]>,
|
pub seed: Option<[u8; 32]>,
|
||||||
pub finalization: bool,
|
pub finalization: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RuntimeUpgradeOptions {
|
pub struct RuntimeUpgradeOptions {
|
||||||
/// Location of the wasm file (could be either a local file or an url)
|
/// Location of the wasm file (could be either a local file or an url)
|
||||||
pub wasm: AssetLocation,
|
pub wasm: AssetLocation,
|
||||||
/// Name of the node to use as rpc endpoint
|
/// Name of the node to use as rpc endpoint
|
||||||
pub node_name: Option<String>,
|
pub node_name: Option<String>,
|
||||||
/// Seed to use to sign and submit (default to //Alice)
|
/// Seed to use to sign and submit (default to //Alice)
|
||||||
pub seed: Option<[u8; 32]>,
|
pub seed: Option<[u8; 32]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RuntimeUpgradeOptions {
|
impl RuntimeUpgradeOptions {
|
||||||
pub fn new(wasm: AssetLocation) -> Self {
|
pub fn new(wasm: AssetLocation) -> Self {
|
||||||
Self {
|
Self { wasm, node_name: None, seed: None }
|
||||||
wasm,
|
}
|
||||||
node_name: None,
|
|
||||||
seed: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ParachainGenesisArgs {
|
pub struct ParachainGenesisArgs {
|
||||||
pub genesis_head: String,
|
pub genesis_head: String,
|
||||||
pub validation_code: String,
|
pub validation_code: String,
|
||||||
pub parachain: bool,
|
pub parachain: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
+222
-252
@@ -3,303 +3,273 @@ use std::{collections::HashMap, path::PathBuf};
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use configuration::GlobalSettings;
|
use configuration::GlobalSettings;
|
||||||
use provider::{
|
use provider::{
|
||||||
constants::{LOCALHOST, NODE_CONFIG_DIR, NODE_DATA_DIR, NODE_RELAY_DATA_DIR, P2P_PORT},
|
constants::{LOCALHOST, NODE_CONFIG_DIR, NODE_DATA_DIR, NODE_RELAY_DATA_DIR, P2P_PORT},
|
||||||
shared::helpers::running_in_ci,
|
shared::helpers::running_in_ci,
|
||||||
types::{SpawnNodeOptions, TransferedFile},
|
types::{SpawnNodeOptions, TransferedFile},
|
||||||
DynNamespace,
|
DynNamespace,
|
||||||
};
|
};
|
||||||
use support::{
|
use support::{
|
||||||
constants::THIS_IS_A_BUG, fs::FileSystem, replacer::apply_running_network_replacements,
|
constants::THIS_IS_A_BUG, fs::FileSystem, replacer::apply_running_network_replacements,
|
||||||
};
|
};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
generators,
|
generators,
|
||||||
network::node::NetworkNode,
|
network::node::NetworkNode,
|
||||||
network_spec::{node::NodeSpec, teyrchain::TeyrchainSpec},
|
network_spec::{node::NodeSpec, teyrchain::TeyrchainSpec},
|
||||||
shared::constants::{FULL_NODE_PROMETHEUS_PORT, PROMETHEUS_PORT, RPC_PORT},
|
shared::constants::{FULL_NODE_PROMETHEUS_PORT, PROMETHEUS_PORT, RPC_PORT},
|
||||||
ScopedFilesystem, ZombieRole,
|
ScopedFilesystem, ZombieRole,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SpawnNodeCtx<'a, T: FileSystem> {
|
pub struct SpawnNodeCtx<'a, T: FileSystem> {
|
||||||
/// Relaychain id, from the chain-spec (e.g rococo_local_testnet)
|
/// Relaychain id, from the chain-spec (e.g rococo_local_testnet)
|
||||||
pub(crate) chain_id: &'a str,
|
pub(crate) chain_id: &'a str,
|
||||||
// Parachain id, from the chain-spec (e.g local_testnet)
|
// Parachain id, from the chain-spec (e.g local_testnet)
|
||||||
pub(crate) parachain_id: Option<&'a str>,
|
pub(crate) parachain_id: Option<&'a str>,
|
||||||
/// Relaychain chain name (e.g rococo-local)
|
/// Relaychain chain name (e.g rococo-local)
|
||||||
pub(crate) chain: &'a str,
|
pub(crate) chain: &'a str,
|
||||||
/// Role of the node in the network
|
/// Role of the node in the network
|
||||||
pub(crate) role: ZombieRole,
|
pub(crate) role: ZombieRole,
|
||||||
/// Ref to the namespace
|
/// Ref to the namespace
|
||||||
pub(crate) ns: &'a DynNamespace,
|
pub(crate) ns: &'a DynNamespace,
|
||||||
/// Ref to an scoped filesystem (encapsulate fs actions inside the ns directory)
|
/// Ref to an scoped filesystem (encapsulate fs actions inside the ns directory)
|
||||||
pub(crate) scoped_fs: &'a ScopedFilesystem<'a, T>,
|
pub(crate) scoped_fs: &'a ScopedFilesystem<'a, T>,
|
||||||
/// Ref to a parachain (used to spawn collators)
|
/// Ref to a parachain (used to spawn collators)
|
||||||
pub(crate) parachain: Option<&'a TeyrchainSpec>,
|
pub(crate) parachain: Option<&'a TeyrchainSpec>,
|
||||||
/// The string representation of the bootnode address to pass to nodes
|
/// The string representation of the bootnode address to pass to nodes
|
||||||
pub(crate) bootnodes_addr: &'a Vec<String>,
|
pub(crate) bootnodes_addr: &'a Vec<String>,
|
||||||
/// Flag to wait node is ready or not
|
/// Flag to wait node is ready or not
|
||||||
/// Ready state means we can query Prometheus internal server
|
/// Ready state means we can query Prometheus internal server
|
||||||
pub(crate) wait_ready: bool,
|
pub(crate) wait_ready: bool,
|
||||||
/// A json representation of the running nodes with their names as 'key'
|
/// A json representation of the running nodes with their names as 'key'
|
||||||
pub(crate) nodes_by_name: serde_json::Value,
|
pub(crate) nodes_by_name: serde_json::Value,
|
||||||
/// A ref to the global settings
|
/// A ref to the global settings
|
||||||
pub(crate) global_settings: &'a GlobalSettings,
|
pub(crate) global_settings: &'a GlobalSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn spawn_node<'a, T>(
|
pub async fn spawn_node<'a, T>(
|
||||||
node: &NodeSpec,
|
node: &NodeSpec,
|
||||||
mut files_to_inject: Vec<TransferedFile>,
|
mut files_to_inject: Vec<TransferedFile>,
|
||||||
ctx: &SpawnNodeCtx<'a, T>,
|
ctx: &SpawnNodeCtx<'a, T>,
|
||||||
) -> Result<NetworkNode, anyhow::Error>
|
) -> Result<NetworkNode, anyhow::Error>
|
||||||
where
|
where
|
||||||
T: FileSystem,
|
T: FileSystem,
|
||||||
{
|
{
|
||||||
let mut created_paths = vec![];
|
let mut created_paths = vec![];
|
||||||
// Create and inject the keystore IFF
|
// Create and inject the keystore IFF
|
||||||
// - The node is validator in the relaychain
|
// - The node is validator in the relaychain
|
||||||
// - The node is collator (encoded as validator) and the parachain is cumulus_based
|
// - The node is collator (encoded as validator) and the parachain is cumulus_based
|
||||||
// (parachain_id) should be set then.
|
// (parachain_id) should be set then.
|
||||||
if node.is_validator && (ctx.parachain.is_none() || ctx.parachain_id.is_some()) {
|
if node.is_validator && (ctx.parachain.is_none() || ctx.parachain_id.is_some()) {
|
||||||
// Generate keystore for node
|
// Generate keystore for node
|
||||||
let node_files_path = if let Some(para) = ctx.parachain {
|
let node_files_path =
|
||||||
para.id.to_string()
|
if let Some(para) = ctx.parachain { para.id.to_string() } else { node.name.clone() };
|
||||||
} else {
|
let asset_hub_polkadot =
|
||||||
node.name.clone()
|
ctx.parachain_id.map(|id| id.starts_with("asset-hub-polkadot")).unwrap_or_default();
|
||||||
};
|
let keystore_key_types = node.keystore_key_types.iter().map(String::as_str).collect();
|
||||||
let asset_hub_polkadot = ctx
|
let key_filenames = generators::generate_node_keystore(
|
||||||
.parachain_id
|
&node.accounts,
|
||||||
.map(|id| id.starts_with("asset-hub-polkadot"))
|
&node_files_path,
|
||||||
.unwrap_or_default();
|
ctx.scoped_fs,
|
||||||
let keystore_key_types = node.keystore_key_types.iter().map(String::as_str).collect();
|
asset_hub_polkadot,
|
||||||
let key_filenames = generators::generate_node_keystore(
|
keystore_key_types,
|
||||||
&node.accounts,
|
)
|
||||||
&node_files_path,
|
.await
|
||||||
ctx.scoped_fs,
|
.unwrap();
|
||||||
asset_hub_polkadot,
|
|
||||||
keystore_key_types,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Paths returned are relative to the base dir, we need to convert into
|
// Paths returned are relative to the base dir, we need to convert into
|
||||||
// fullpaths to inject them in the nodes.
|
// fullpaths to inject them in the nodes.
|
||||||
let remote_keystore_chain_id = if let Some(id) = ctx.parachain_id {
|
let remote_keystore_chain_id =
|
||||||
id
|
if let Some(id) = ctx.parachain_id { id } else { ctx.chain_id };
|
||||||
} else {
|
|
||||||
ctx.chain_id
|
|
||||||
};
|
|
||||||
|
|
||||||
let keystore_path = node.keystore_path.clone().unwrap_or(PathBuf::from(format!(
|
let keystore_path = node
|
||||||
"/data/chains/{remote_keystore_chain_id}/keystore",
|
.keystore_path
|
||||||
)));
|
.clone()
|
||||||
|
.unwrap_or(PathBuf::from(format!("/data/chains/{remote_keystore_chain_id}/keystore",)));
|
||||||
|
|
||||||
for key_filename in key_filenames {
|
for key_filename in key_filenames {
|
||||||
let f = TransferedFile::new(
|
let f = TransferedFile::new(
|
||||||
PathBuf::from(format!(
|
PathBuf::from(format!(
|
||||||
"{}/{}/{}",
|
"{}/{}/{}",
|
||||||
ctx.ns.base_dir().to_string_lossy(),
|
ctx.ns.base_dir().to_string_lossy(),
|
||||||
node_files_path,
|
node_files_path,
|
||||||
key_filename.to_string_lossy()
|
key_filename.to_string_lossy()
|
||||||
)),
|
)),
|
||||||
keystore_path.join(key_filename),
|
keystore_path.join(key_filename),
|
||||||
);
|
);
|
||||||
files_to_inject.push(f);
|
files_to_inject.push(f);
|
||||||
}
|
}
|
||||||
created_paths.push(keystore_path);
|
created_paths.push(keystore_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
let base_dir = format!("{}/{}", ctx.ns.base_dir().to_string_lossy(), &node.name);
|
let base_dir = format!("{}/{}", ctx.ns.base_dir().to_string_lossy(), &node.name);
|
||||||
|
|
||||||
let (cfg_path, data_path, relay_data_path) = if !ctx.ns.capabilities().prefix_with_full_path {
|
let (cfg_path, data_path, relay_data_path) = if !ctx.ns.capabilities().prefix_with_full_path {
|
||||||
(
|
(NODE_CONFIG_DIR.into(), NODE_DATA_DIR.into(), NODE_RELAY_DATA_DIR.into())
|
||||||
NODE_CONFIG_DIR.into(),
|
} else {
|
||||||
NODE_DATA_DIR.into(),
|
let cfg_path = format!("{}{NODE_CONFIG_DIR}", &base_dir);
|
||||||
NODE_RELAY_DATA_DIR.into(),
|
let data_path = format!("{}{NODE_DATA_DIR}", &base_dir);
|
||||||
)
|
let relay_data_path = format!("{}{NODE_RELAY_DATA_DIR}", &base_dir);
|
||||||
} else {
|
(cfg_path, data_path, relay_data_path)
|
||||||
let cfg_path = format!("{}{NODE_CONFIG_DIR}", &base_dir);
|
};
|
||||||
let data_path = format!("{}{NODE_DATA_DIR}", &base_dir);
|
|
||||||
let relay_data_path = format!("{}{NODE_RELAY_DATA_DIR}", &base_dir);
|
|
||||||
(cfg_path, data_path, relay_data_path)
|
|
||||||
};
|
|
||||||
|
|
||||||
let gen_opts = generators::GenCmdOptions {
|
let gen_opts = generators::GenCmdOptions {
|
||||||
relay_chain_name: ctx.chain,
|
relay_chain_name: ctx.chain,
|
||||||
cfg_path: &cfg_path, // TODO: get from provider/ns
|
cfg_path: &cfg_path, // TODO: get from provider/ns
|
||||||
data_path: &data_path, // TODO: get from provider
|
data_path: &data_path, // TODO: get from provider
|
||||||
relay_data_path: &relay_data_path, // TODO: get from provider
|
relay_data_path: &relay_data_path, // TODO: get from provider
|
||||||
use_wrapper: false, // TODO: get from provider
|
use_wrapper: false, // TODO: get from provider
|
||||||
bootnode_addr: ctx.bootnodes_addr.clone(),
|
bootnode_addr: ctx.bootnodes_addr.clone(),
|
||||||
use_default_ports_in_cmd: ctx.ns.capabilities().use_default_ports_in_cmd,
|
use_default_ports_in_cmd: ctx.ns.capabilities().use_default_ports_in_cmd,
|
||||||
// IFF the provider require an image (e.g k8s) we know this is not native
|
// IFF the provider require an image (e.g k8s) we know this is not native
|
||||||
is_native: !ctx.ns.capabilities().requires_image,
|
is_native: !ctx.ns.capabilities().requires_image,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut collator_full_node_prom_port: Option<u16> = None;
|
let mut collator_full_node_prom_port: Option<u16> = None;
|
||||||
let mut collator_full_node_prom_port_external: Option<u16> = None;
|
let mut collator_full_node_prom_port_external: Option<u16> = None;
|
||||||
|
|
||||||
let (program, args) = match ctx.role {
|
let (program, args) = match ctx.role {
|
||||||
// Collator should be `non-cumulus` one (e.g adder/undying)
|
// Collator should be `non-cumulus` one (e.g adder/undying)
|
||||||
ZombieRole::Node | ZombieRole::Collator => {
|
ZombieRole::Node | ZombieRole::Collator => {
|
||||||
let maybe_para_id = ctx.parachain.map(|para| para.id);
|
let maybe_para_id = ctx.parachain.map(|para| para.id);
|
||||||
|
|
||||||
generators::generate_node_command(node, gen_opts, maybe_para_id)
|
generators::generate_node_command(node, gen_opts, maybe_para_id)
|
||||||
},
|
},
|
||||||
ZombieRole::CumulusCollator => {
|
ZombieRole::CumulusCollator => {
|
||||||
let para = ctx.parachain.expect(&format!(
|
let para = ctx
|
||||||
"parachain must be part of the context {THIS_IS_A_BUG}"
|
.parachain
|
||||||
));
|
.expect(&format!("parachain must be part of the context {THIS_IS_A_BUG}"));
|
||||||
collator_full_node_prom_port = node.full_node_prometheus_port.as_ref().map(|p| p.0);
|
collator_full_node_prom_port = node.full_node_prometheus_port.as_ref().map(|p| p.0);
|
||||||
|
|
||||||
generators::generate_node_command_cumulus(node, gen_opts, para.id)
|
generators::generate_node_command_cumulus(node, gen_opts, para.id)
|
||||||
},
|
},
|
||||||
_ => unreachable!(), /* TODO: do we need those?
|
_ => unreachable!(), /* TODO: do we need those?
|
||||||
* ZombieRole::Bootnode => todo!(),
|
* ZombieRole::Bootnode => todo!(),
|
||||||
* ZombieRole::Companion => todo!(), */
|
* ZombieRole::Companion => todo!(), */
|
||||||
};
|
};
|
||||||
|
|
||||||
// apply running networ replacements
|
// apply running networ replacements
|
||||||
let args: Vec<String> = args
|
let args: Vec<String> = args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| apply_running_network_replacements(arg, &ctx.nodes_by_name))
|
.map(|arg| apply_running_network_replacements(arg, &ctx.nodes_by_name))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
info!(
|
info!("🚀 {}, spawning.... with command: {} {}", node.name, program, args.join(" "));
|
||||||
"🚀 {}, spawning.... with command: {} {}",
|
|
||||||
node.name,
|
|
||||||
program,
|
|
||||||
args.join(" ")
|
|
||||||
);
|
|
||||||
|
|
||||||
let ports = if ctx.ns.capabilities().use_default_ports_in_cmd {
|
let ports = if ctx.ns.capabilities().use_default_ports_in_cmd {
|
||||||
// should use default ports to as internal
|
// should use default ports to as internal
|
||||||
[
|
[
|
||||||
(P2P_PORT, node.p2p_port.0),
|
(P2P_PORT, node.p2p_port.0),
|
||||||
(RPC_PORT, node.rpc_port.0),
|
(RPC_PORT, node.rpc_port.0),
|
||||||
(PROMETHEUS_PORT, node.prometheus_port.0),
|
(PROMETHEUS_PORT, node.prometheus_port.0),
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
[
|
[(P2P_PORT, P2P_PORT), (RPC_PORT, RPC_PORT), (PROMETHEUS_PORT, PROMETHEUS_PORT)]
|
||||||
(P2P_PORT, P2P_PORT),
|
};
|
||||||
(RPC_PORT, RPC_PORT),
|
|
||||||
(PROMETHEUS_PORT, PROMETHEUS_PORT),
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
let spawn_ops = SpawnNodeOptions::new(node.name.clone(), program)
|
let spawn_ops = SpawnNodeOptions::new(node.name.clone(), program)
|
||||||
.args(args)
|
.args(args)
|
||||||
.env(
|
.env(node.env.iter().map(|var| (var.name.clone(), var.value.clone())))
|
||||||
node.env
|
.injected_files(files_to_inject)
|
||||||
.iter()
|
.created_paths(created_paths)
|
||||||
.map(|var| (var.name.clone(), var.value.clone())),
|
.db_snapshot(node.db_snapshot.clone())
|
||||||
)
|
.port_mapping(HashMap::from(ports))
|
||||||
.injected_files(files_to_inject)
|
.node_log_path(node.node_log_path.clone());
|
||||||
.created_paths(created_paths)
|
|
||||||
.db_snapshot(node.db_snapshot.clone())
|
|
||||||
.port_mapping(HashMap::from(ports))
|
|
||||||
.node_log_path(node.node_log_path.clone());
|
|
||||||
|
|
||||||
let spawn_ops = if let Some(image) = node.image.as_ref() {
|
let spawn_ops = if let Some(image) = node.image.as_ref() {
|
||||||
spawn_ops.image(image.as_str())
|
spawn_ops.image(image.as_str())
|
||||||
} else {
|
} else {
|
||||||
spawn_ops
|
spawn_ops
|
||||||
};
|
};
|
||||||
|
|
||||||
// Drops the port parking listeners before spawn
|
// Drops the port parking listeners before spawn
|
||||||
node.ws_port.drop_listener();
|
node.ws_port.drop_listener();
|
||||||
node.p2p_port.drop_listener();
|
node.p2p_port.drop_listener();
|
||||||
node.rpc_port.drop_listener();
|
node.rpc_port.drop_listener();
|
||||||
node.prometheus_port.drop_listener();
|
node.prometheus_port.drop_listener();
|
||||||
if let Some(port) = &node.full_node_p2p_port {
|
if let Some(port) = &node.full_node_p2p_port {
|
||||||
port.drop_listener();
|
port.drop_listener();
|
||||||
}
|
}
|
||||||
if let Some(port) = &node.full_node_prometheus_port {
|
if let Some(port) = &node.full_node_prometheus_port {
|
||||||
port.drop_listener();
|
port.drop_listener();
|
||||||
}
|
}
|
||||||
|
|
||||||
let running_node = ctx.ns.spawn_node(&spawn_ops).await.with_context(|| {
|
let running_node = ctx.ns.spawn_node(&spawn_ops).await.with_context(|| {
|
||||||
format!(
|
format!("Failed to spawn node: {} with opts: {:#?}", node.name, spawn_ops)
|
||||||
"Failed to spawn node: {} with opts: {:#?}",
|
})?;
|
||||||
node.name, spawn_ops
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut ip_to_use = if let Some(local_ip) = ctx.global_settings.local_ip() {
|
let mut ip_to_use =
|
||||||
*local_ip
|
if let Some(local_ip) = ctx.global_settings.local_ip() { *local_ip } else { LOCALHOST };
|
||||||
} else {
|
|
||||||
LOCALHOST
|
|
||||||
};
|
|
||||||
|
|
||||||
let (rpc_port_external, prometheus_port_external, p2p_external);
|
let (rpc_port_external, prometheus_port_external, p2p_external);
|
||||||
|
|
||||||
if running_in_ci() && ctx.ns.provider_name() == "k8s" {
|
if running_in_ci() && ctx.ns.provider_name() == "k8s" {
|
||||||
// running kubernets in ci require to use ip and default port
|
// running kubernets in ci require to use ip and default port
|
||||||
(rpc_port_external, prometheus_port_external, p2p_external) =
|
(rpc_port_external, prometheus_port_external, p2p_external) =
|
||||||
(RPC_PORT, PROMETHEUS_PORT, P2P_PORT);
|
(RPC_PORT, PROMETHEUS_PORT, P2P_PORT);
|
||||||
collator_full_node_prom_port_external = Some(FULL_NODE_PROMETHEUS_PORT);
|
collator_full_node_prom_port_external = Some(FULL_NODE_PROMETHEUS_PORT);
|
||||||
ip_to_use = running_node.ip().await?;
|
ip_to_use = running_node.ip().await?;
|
||||||
} else {
|
} else {
|
||||||
// Create port-forward iff we are not in CI or provider doesn't use the default ports (native)
|
// Create port-forward iff we are not in CI or provider doesn't use the default ports (native)
|
||||||
let ports = futures::future::try_join_all(vec![
|
let ports = futures::future::try_join_all(vec![
|
||||||
running_node.create_port_forward(node.rpc_port.0, RPC_PORT),
|
running_node.create_port_forward(node.rpc_port.0, RPC_PORT),
|
||||||
running_node.create_port_forward(node.prometheus_port.0, PROMETHEUS_PORT),
|
running_node.create_port_forward(node.prometheus_port.0, PROMETHEUS_PORT),
|
||||||
])
|
])
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
(rpc_port_external, prometheus_port_external, p2p_external) = (
|
(rpc_port_external, prometheus_port_external, p2p_external) = (
|
||||||
ports[0].unwrap_or(node.rpc_port.0),
|
ports[0].unwrap_or(node.rpc_port.0),
|
||||||
ports[1].unwrap_or(node.prometheus_port.0),
|
ports[1].unwrap_or(node.prometheus_port.0),
|
||||||
// p2p don't need port-fwd
|
// p2p don't need port-fwd
|
||||||
node.p2p_port.0,
|
node.p2p_port.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(full_node_prom_port) = collator_full_node_prom_port {
|
if let Some(full_node_prom_port) = collator_full_node_prom_port {
|
||||||
let port_fwd = running_node
|
let port_fwd = running_node
|
||||||
.create_port_forward(full_node_prom_port, FULL_NODE_PROMETHEUS_PORT)
|
.create_port_forward(full_node_prom_port, FULL_NODE_PROMETHEUS_PORT)
|
||||||
.await?;
|
.await?;
|
||||||
collator_full_node_prom_port_external = Some(port_fwd.unwrap_or(full_node_prom_port));
|
collator_full_node_prom_port_external = Some(port_fwd.unwrap_or(full_node_prom_port));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let multiaddr = generators::generate_node_bootnode_addr(
|
let multiaddr = generators::generate_node_bootnode_addr(
|
||||||
&node.peer_id,
|
&node.peer_id,
|
||||||
&running_node.ip().await?,
|
&running_node.ip().await?,
|
||||||
p2p_external,
|
p2p_external,
|
||||||
running_node.args().as_ref(),
|
running_node.args().as_ref(),
|
||||||
&node.p2p_cert_hash,
|
&node.p2p_cert_hash,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let ws_uri = format!("ws://{ip_to_use}:{rpc_port_external}");
|
let ws_uri = format!("ws://{ip_to_use}:{rpc_port_external}");
|
||||||
let prometheus_uri = format!("http://{ip_to_use}:{prometheus_port_external}/metrics");
|
let prometheus_uri = format!("http://{ip_to_use}:{prometheus_port_external}/metrics");
|
||||||
info!("🚀 {}, should be running now", node.name);
|
info!("🚀 {}, should be running now", node.name);
|
||||||
info!(
|
info!(
|
||||||
"💻 {}: direct link (pjs) https://polkadot.js.org/apps/?rpc={ws_uri}#/explorer",
|
"💻 {}: direct link (pjs) https://polkadot.js.org/apps/?rpc={ws_uri}#/explorer",
|
||||||
node.name
|
node.name
|
||||||
);
|
);
|
||||||
info!(
|
info!(
|
||||||
"💻 {}: direct link (papi) https://dev.papi.how/explorer#networkId=custom&endpoint={ws_uri}",
|
"💻 {}: direct link (papi) https://dev.papi.how/explorer#networkId=custom&endpoint={ws_uri}",
|
||||||
node.name
|
node.name
|
||||||
);
|
);
|
||||||
|
|
||||||
info!("📊 {}: metrics link {prometheus_uri}", node.name);
|
info!("📊 {}: metrics link {prometheus_uri}", node.name);
|
||||||
|
|
||||||
if let Some(full_node_prom_port) = collator_full_node_prom_port_external {
|
if let Some(full_node_prom_port) = collator_full_node_prom_port_external {
|
||||||
info!(
|
info!(
|
||||||
"📊 {}: collator full-node metrics link http://{}:{}/metrics",
|
"📊 {}: collator full-node metrics link http://{}:{}/metrics",
|
||||||
node.name, ip_to_use, full_node_prom_port
|
node.name, ip_to_use, full_node_prom_port
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("📓 logs cmd: {}", running_node.log_cmd());
|
info!("📓 logs cmd: {}", running_node.log_cmd());
|
||||||
|
|
||||||
Ok(NetworkNode::new(
|
Ok(NetworkNode::new(
|
||||||
node.name.clone(),
|
node.name.clone(),
|
||||||
ws_uri,
|
ws_uri,
|
||||||
prometheus_uri,
|
prometheus_uri,
|
||||||
multiaddr,
|
multiaddr,
|
||||||
node.clone(),
|
node.clone(),
|
||||||
running_node,
|
running_node,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,42 +2,38 @@ use pezkuwi_subxt::{backend::rpc::RpcClient, OnlineClient};
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait ClientFromUrl: Sized {
|
pub trait ClientFromUrl: Sized {
|
||||||
async fn from_secure_url(url: &str) -> Result<Self, pezkuwi_subxt::Error>;
|
async fn from_secure_url(url: &str) -> Result<Self, pezkuwi_subxt::Error>;
|
||||||
async fn from_insecure_url(url: &str) -> Result<Self, pezkuwi_subxt::Error>;
|
async fn from_insecure_url(url: &str) -> Result<Self, pezkuwi_subxt::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<Config: pezkuwi_subxt::Config + Send + Sync> ClientFromUrl for OnlineClient<Config> {
|
impl<Config: pezkuwi_subxt::Config + Send + Sync> ClientFromUrl for OnlineClient<Config> {
|
||||||
async fn from_secure_url(url: &str) -> Result<Self, pezkuwi_subxt::Error> {
|
async fn from_secure_url(url: &str) -> Result<Self, pezkuwi_subxt::Error> {
|
||||||
Self::from_url(url).await.map_err(Into::into)
|
Self::from_url(url).await.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn from_insecure_url(url: &str) -> Result<Self, pezkuwi_subxt::Error> {
|
async fn from_insecure_url(url: &str) -> Result<Self, pezkuwi_subxt::Error> {
|
||||||
Self::from_insecure_url(url).await.map_err(Into::into)
|
Self::from_insecure_url(url).await.map_err(Into::into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl ClientFromUrl for RpcClient {
|
impl ClientFromUrl for RpcClient {
|
||||||
async fn from_secure_url(url: &str) -> Result<Self, pezkuwi_subxt::Error> {
|
async fn from_secure_url(url: &str) -> Result<Self, pezkuwi_subxt::Error> {
|
||||||
Self::from_url(url)
|
Self::from_url(url).await.map_err(pezkuwi_subxt::Error::from)
|
||||||
.await
|
}
|
||||||
.map_err(pezkuwi_subxt::Error::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn from_insecure_url(url: &str) -> Result<Self, pezkuwi_subxt::Error> {
|
async fn from_insecure_url(url: &str) -> Result<Self, pezkuwi_subxt::Error> {
|
||||||
Self::from_insecure_url(url)
|
Self::from_insecure_url(url).await.map_err(pezkuwi_subxt::Error::from)
|
||||||
.await
|
}
|
||||||
.map_err(pezkuwi_subxt::Error::from)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_client_from_url<T: ClientFromUrl + Send>(
|
pub async fn get_client_from_url<T: ClientFromUrl + Send>(
|
||||||
url: &str,
|
url: &str,
|
||||||
) -> Result<T, pezkuwi_subxt::Error> {
|
) -> Result<T, pezkuwi_subxt::Error> {
|
||||||
if pezkuwi_subxt::utils::url_is_secure(url)? {
|
if pezkuwi_subxt::utils::url_is_secure(url)? {
|
||||||
T::from_secure_url(url).await
|
T::from_secure_url(url).await
|
||||||
} else {
|
} else {
|
||||||
T::from_insecure_url(url).await
|
T::from_insecure_url(url).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-55
@@ -5,65 +5,52 @@ use tracing::{debug, info};
|
|||||||
use crate::network::node::NetworkNode;
|
use crate::network::node::NetworkNode;
|
||||||
|
|
||||||
pub async fn upgrade(
|
pub async fn upgrade(
|
||||||
node: &NetworkNode,
|
node: &NetworkNode,
|
||||||
wasm_data: &[u8],
|
wasm_data: &[u8],
|
||||||
sudo: &Keypair,
|
sudo: &Keypair,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
debug!(
|
debug!("Upgrading runtime, using node: {} with endpoting {}", node.name, node.ws_uri);
|
||||||
"Upgrading runtime, using node: {} with endpoting {}",
|
let api: OnlineClient<BizinikiwConfig> = node.wait_client().await?;
|
||||||
node.name, node.ws_uri
|
|
||||||
);
|
|
||||||
let api: OnlineClient<BizinikiwConfig> = node.wait_client().await?;
|
|
||||||
|
|
||||||
let upgrade = pezkuwi_subxt::dynamic::tx(
|
let upgrade = pezkuwi_subxt::dynamic::tx(
|
||||||
"System",
|
"System",
|
||||||
"set_code_without_checks",
|
"set_code_without_checks",
|
||||||
vec![Value::from_bytes(wasm_data)],
|
vec![Value::from_bytes(wasm_data)],
|
||||||
);
|
);
|
||||||
|
|
||||||
let sudo_call = pezkuwi_subxt::dynamic::tx(
|
let sudo_call = pezkuwi_subxt::dynamic::tx(
|
||||||
"Sudo",
|
"Sudo",
|
||||||
"sudo_unchecked_weight",
|
"sudo_unchecked_weight",
|
||||||
vec![
|
vec![
|
||||||
upgrade.into_value(),
|
upgrade.into_value(),
|
||||||
Value::named_composite([
|
Value::named_composite([
|
||||||
("ref_time", Value::primitive(1.into())),
|
("ref_time", Value::primitive(1.into())),
|
||||||
("proof_size", Value::primitive(1.into())),
|
("proof_size", Value::primitive(1.into())),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut tx = api
|
let mut tx = api.tx().sign_and_submit_then_watch_default(&sudo_call, sudo).await?;
|
||||||
.tx()
|
|
||||||
.sign_and_submit_then_watch_default(&sudo_call, sudo)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Below we use the low level API to replicate the `wait_for_in_block` behaviour
|
// Below we use the low level API to replicate the `wait_for_in_block` behaviour
|
||||||
// which was removed in subxt 0.33.0. See https://github.com/paritytech/subxt/pull/1237.
|
// which was removed in subxt 0.33.0. See https://github.com/paritytech/subxt/pull/1237.
|
||||||
while let Some(status) = tx.next().await {
|
while let Some(status) = tx.next().await {
|
||||||
let status = status?;
|
let status = status?;
|
||||||
match &status {
|
match &status {
|
||||||
TxStatus::InBestBlock(tx_in_block) | TxStatus::InFinalizedBlock(tx_in_block) => {
|
TxStatus::InBestBlock(tx_in_block) | TxStatus::InFinalizedBlock(tx_in_block) => {
|
||||||
let _result = tx_in_block.wait_for_success().await?;
|
let _result = tx_in_block.wait_for_success().await?;
|
||||||
let block_status = if status.as_finalized().is_some() {
|
let block_status =
|
||||||
"Finalized"
|
if status.as_finalized().is_some() { "Finalized" } else { "Best" };
|
||||||
} else {
|
info!("[{}] In block: {:#?}", block_status, tx_in_block.block_hash());
|
||||||
"Best"
|
},
|
||||||
};
|
TxStatus::Error { message }
|
||||||
info!(
|
| TxStatus::Invalid { message }
|
||||||
"[{}] In block: {:#?}",
|
| TxStatus::Dropped { message } => {
|
||||||
block_status,
|
return Err(anyhow::format_err!("Error submitting tx: {message}"));
|
||||||
tx_in_block.block_hash()
|
},
|
||||||
);
|
_ => continue,
|
||||||
},
|
}
|
||||||
TxStatus::Error { message }
|
}
|
||||||
| TxStatus::Invalid { message }
|
|
||||||
| TxStatus::Dropped { message } => {
|
|
||||||
return Err(anyhow::format_err!("Error submitting tx: {message}"));
|
|
||||||
},
|
|
||||||
_ => continue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use serde::Deserializer;
|
|||||||
|
|
||||||
pub fn default_as_empty_vec<'de, D, T>(_deserializer: D) -> Result<Vec<T>, D::Error>
|
pub fn default_as_empty_vec<'de, D, T>(_deserializer: D) -> Result<Vec<T>, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
|
|||||||
+129
-136
@@ -6,12 +6,12 @@ use pest_derive::Parser;
|
|||||||
/// An error at parsing level.
|
/// An error at parsing level.
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum ParserError {
|
pub enum ParserError {
|
||||||
#[error("error parsing input")]
|
#[error("error parsing input")]
|
||||||
ParseError(Box<pest::error::Error<Rule>>),
|
ParseError(Box<pest::error::Error<Rule>>),
|
||||||
#[error("root node should be valid: {0}")]
|
#[error("root node should be valid: {0}")]
|
||||||
ParseRootNodeError(String),
|
ParseRootNodeError(String),
|
||||||
#[error("can't cast metric value as f64: {0}")]
|
#[error("can't cast metric value as f64: {0}")]
|
||||||
CastValueError(#[from] ParseFloatError),
|
CastValueError(#[from] ParseFloatError),
|
||||||
}
|
}
|
||||||
|
|
||||||
// This include forces recompiling this source file if the grammar file changes.
|
// This include forces recompiling this source file if the grammar file changes.
|
||||||
@@ -25,154 +25,147 @@ pub struct MetricsParser;
|
|||||||
pub type MetricMap = HashMap<String, f64>;
|
pub type MetricMap = HashMap<String, f64>;
|
||||||
|
|
||||||
pub fn parse(input: &str) -> Result<MetricMap, ParserError> {
|
pub fn parse(input: &str) -> Result<MetricMap, ParserError> {
|
||||||
let mut metric_map: MetricMap = Default::default();
|
let mut metric_map: MetricMap = Default::default();
|
||||||
let mut pairs = MetricsParser::parse(Rule::statement, input)
|
let mut pairs = MetricsParser::parse(Rule::statement, input)
|
||||||
.map_err(|e| ParserError::ParseError(Box::new(e)))?;
|
.map_err(|e| ParserError::ParseError(Box::new(e)))?;
|
||||||
|
|
||||||
let root = pairs
|
let root = pairs.next().ok_or(ParserError::ParseRootNodeError(pairs.as_str().to_string()))?;
|
||||||
.next()
|
for token in root.into_inner() {
|
||||||
.ok_or(ParserError::ParseRootNodeError(pairs.as_str().to_string()))?;
|
if token.as_rule() == Rule::block {
|
||||||
for token in root.into_inner() {
|
let inner = token.into_inner();
|
||||||
if token.as_rule() == Rule::block {
|
for value in inner {
|
||||||
let inner = token.into_inner();
|
match value.as_rule() {
|
||||||
for value in inner {
|
Rule::genericomment | Rule::typexpr | Rule::helpexpr => {
|
||||||
match value.as_rule() {
|
// don't need to collect comments/types/helpers blocks.
|
||||||
Rule::genericomment | Rule::typexpr | Rule::helpexpr => {
|
continue;
|
||||||
// don't need to collect comments/types/helpers blocks.
|
},
|
||||||
continue;
|
Rule::promstmt => {
|
||||||
},
|
let mut key: &str = "";
|
||||||
Rule::promstmt => {
|
let mut labels: Vec<(&str, &str)> = Vec::new();
|
||||||
let mut key: &str = "";
|
let mut val: f64 = 0_f64;
|
||||||
let mut labels: Vec<(&str, &str)> = Vec::new();
|
for v in value.clone().into_inner() {
|
||||||
let mut val: f64 = 0_f64;
|
match &v.as_rule() {
|
||||||
for v in value.clone().into_inner() {
|
Rule::key => {
|
||||||
match &v.as_rule() {
|
key = v.as_span().as_str();
|
||||||
Rule::key => {
|
},
|
||||||
key = v.as_span().as_str();
|
Rule::NaN | Rule::posInf | Rule::negInf => {
|
||||||
},
|
// noop (not used in substrate metrics)
|
||||||
Rule::NaN | Rule::posInf | Rule::negInf => {
|
},
|
||||||
// noop (not used in substrate metrics)
|
Rule::number => {
|
||||||
},
|
val = v.as_span().as_str().parse::<f64>()?;
|
||||||
Rule::number => {
|
},
|
||||||
val = v.as_span().as_str().parse::<f64>()?;
|
Rule::labels => {
|
||||||
},
|
// SAFETY: use unwrap should be safe since we are just
|
||||||
Rule::labels => {
|
// walking the parser struct and if are matching a label
|
||||||
// SAFETY: use unwrap should be safe since we are just
|
// should have a key/vals
|
||||||
// walking the parser struct and if are matching a label
|
for p in v.into_inner() {
|
||||||
// should have a key/vals
|
let mut inner = p.into_inner();
|
||||||
for p in v.into_inner() {
|
let key = inner.next().unwrap().as_span().as_str();
|
||||||
let mut inner = p.into_inner();
|
let value = inner
|
||||||
let key = inner.next().unwrap().as_span().as_str();
|
.next()
|
||||||
let value = inner
|
.unwrap()
|
||||||
.next()
|
.into_inner()
|
||||||
.unwrap()
|
.next()
|
||||||
.into_inner()
|
.unwrap()
|
||||||
.next()
|
.as_span()
|
||||||
.unwrap()
|
.as_str();
|
||||||
.as_span()
|
|
||||||
.as_str();
|
|
||||||
|
|
||||||
labels.push((key, value));
|
labels.push((key, value));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
todo!("not implemented");
|
todo!("not implemented");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we should store to make it compatible with zombienet v1:
|
// we should store to make it compatible with zombienet v1:
|
||||||
// key_without_prefix
|
// key_without_prefix
|
||||||
// key_without_prefix_and_without_chain
|
// key_without_prefix_and_without_chain
|
||||||
// key_with_prefix_with_chain
|
// key_with_prefix_with_chain
|
||||||
// key_with_prefix_and_without_chain
|
// key_with_prefix_and_without_chain
|
||||||
let key_with_out_prefix =
|
let key_with_out_prefix =
|
||||||
key.split('_').collect::<Vec<&str>>()[1..].join("_");
|
key.split('_').collect::<Vec<&str>>()[1..].join("_");
|
||||||
let (labels_without_chain, labels_with_chain) =
|
let (labels_without_chain, labels_with_chain) =
|
||||||
labels.iter().fold((vec![], vec![]), |mut acc, item| {
|
labels.iter().fold((vec![], vec![]), |mut acc, item| {
|
||||||
if item.0.eq("chain") {
|
if item.0.eq("chain") {
|
||||||
acc.1.push(format!("{}=\"{}\"", item.0, item.1));
|
acc.1.push(format!("{}=\"{}\"", item.0, item.1));
|
||||||
} else {
|
} else {
|
||||||
acc.0.push(format!("{}=\"{}\"", item.0, item.1));
|
acc.0.push(format!("{}=\"{}\"", item.0, item.1));
|
||||||
acc.1.push(format!("{}=\"{}\"", item.0, item.1));
|
acc.1.push(format!("{}=\"{}\"", item.0, item.1));
|
||||||
}
|
}
|
||||||
acc
|
acc
|
||||||
});
|
});
|
||||||
|
|
||||||
let labels_with_chain_str = if labels_with_chain.is_empty() {
|
let labels_with_chain_str = if labels_with_chain.is_empty() {
|
||||||
String::from("")
|
String::from("")
|
||||||
} else {
|
} else {
|
||||||
format!("{{{}}}", labels_with_chain.join(","))
|
format!("{{{}}}", labels_with_chain.join(","))
|
||||||
};
|
};
|
||||||
|
|
||||||
let labels_without_chain_str = if labels_without_chain.is_empty() {
|
let labels_without_chain_str = if labels_without_chain.is_empty() {
|
||||||
String::from("")
|
String::from("")
|
||||||
} else {
|
} else {
|
||||||
format!("{{{}}}", labels_without_chain.join(","))
|
format!("{{{}}}", labels_without_chain.join(","))
|
||||||
};
|
};
|
||||||
|
|
||||||
metric_map.insert(format!("{key}{labels_without_chain_str}"), val);
|
metric_map.insert(format!("{key}{labels_without_chain_str}"), val);
|
||||||
metric_map.insert(
|
metric_map.insert(
|
||||||
format!("{key_with_out_prefix}{labels_without_chain_str}"),
|
format!("{key_with_out_prefix}{labels_without_chain_str}"),
|
||||||
val,
|
val,
|
||||||
);
|
);
|
||||||
metric_map.insert(format!("{key}{labels_with_chain_str}"), val);
|
metric_map.insert(format!("{key}{labels_with_chain_str}"), val);
|
||||||
metric_map
|
metric_map
|
||||||
.insert(format!("{key_with_out_prefix}{labels_with_chain_str}"), val);
|
.insert(format!("{key_with_out_prefix}{labels_with_chain_str}"), val);
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(metric_map)
|
Ok(metric_map)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_metrics_works() {
|
fn parse_metrics_works() {
|
||||||
let metrics_raw = fs::read_to_string("./testing/metrics.txt").unwrap();
|
let metrics_raw = fs::read_to_string("./testing/metrics.txt").unwrap();
|
||||||
let metrics = parse(&metrics_raw).unwrap();
|
let metrics = parse(&metrics_raw).unwrap();
|
||||||
|
|
||||||
// full key
|
// full key
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metrics
|
metrics
|
||||||
.get("polkadot_node_is_active_validator{chain=\"rococo_local_testnet\"}")
|
.get("polkadot_node_is_active_validator{chain=\"rococo_local_testnet\"}")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
&1_f64
|
&1_f64
|
||||||
);
|
);
|
||||||
// with prefix and no chain
|
// with prefix and no chain
|
||||||
assert_eq!(
|
assert_eq!(metrics.get("polkadot_node_is_active_validator").unwrap(), &1_f64);
|
||||||
metrics.get("polkadot_node_is_active_validator").unwrap(),
|
// no prefix with chain
|
||||||
&1_f64
|
assert_eq!(
|
||||||
);
|
metrics.get("node_is_active_validator{chain=\"rococo_local_testnet\"}").unwrap(),
|
||||||
// no prefix with chain
|
&1_f64
|
||||||
assert_eq!(
|
);
|
||||||
metrics
|
// no prefix without chain
|
||||||
.get("node_is_active_validator{chain=\"rococo_local_testnet\"}")
|
assert_eq!(metrics.get("node_is_active_validator").unwrap(), &1_f64);
|
||||||
.unwrap(),
|
}
|
||||||
&1_f64
|
|
||||||
);
|
|
||||||
// no prefix without chain
|
|
||||||
assert_eq!(metrics.get("node_is_active_validator").unwrap(), &1_f64);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_invalid_metrics_str_should_fail() {
|
fn parse_invalid_metrics_str_should_fail() {
|
||||||
let metrics_raw = r"
|
let metrics_raw = r"
|
||||||
# HELP polkadot_node_is_active_validator Tracks if the validator is in the active set. Updates at session boundary.
|
# HELP polkadot_node_is_active_validator Tracks if the validator is in the active set. Updates at session boundary.
|
||||||
# TYPE polkadot_node_is_active_validator gauge
|
# TYPE polkadot_node_is_active_validator gauge
|
||||||
polkadot_node_is_active_validator{chain=} 1
|
polkadot_node_is_active_validator{chain=} 1
|
||||||
";
|
";
|
||||||
|
|
||||||
let metrics = parse(metrics_raw);
|
let metrics = parse(metrics_raw);
|
||||||
assert!(metrics.is_err());
|
assert!(metrics.is_err());
|
||||||
assert!(matches!(metrics, Err(ParserError::ParseError(_))));
|
assert!(matches!(metrics, Err(ParserError::ParseError(_))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+417
-458
@@ -16,581 +16,540 @@ pub type Result<T> = core::result::Result<T, Error>;
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DockerClient {
|
pub struct DockerClient {
|
||||||
using_podman: bool,
|
using_podman: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ContainerRunOptions {
|
pub struct ContainerRunOptions {
|
||||||
image: String,
|
image: String,
|
||||||
command: Vec<String>,
|
command: Vec<String>,
|
||||||
env: Option<Vec<(String, String)>>,
|
env: Option<Vec<(String, String)>>,
|
||||||
volume_mounts: Option<HashMap<String, String>>,
|
volume_mounts: Option<HashMap<String, String>>,
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
entrypoint: Option<String>,
|
entrypoint: Option<String>,
|
||||||
port_mapping: HashMap<Port, Port>,
|
port_mapping: HashMap<Port, Port>,
|
||||||
rm: bool,
|
rm: bool,
|
||||||
detach: bool,
|
detach: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Container {
|
enum Container {
|
||||||
Docker(DockerContainer),
|
Docker(DockerContainer),
|
||||||
Podman(PodmanContainer),
|
Podman(PodmanContainer),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we may don't need this
|
// TODO: we may don't need this
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct DockerContainer {
|
struct DockerContainer {
|
||||||
#[serde(alias = "Names", deserialize_with = "deserialize_list")]
|
#[serde(alias = "Names", deserialize_with = "deserialize_list")]
|
||||||
names: Vec<String>,
|
names: Vec<String>,
|
||||||
#[serde(alias = "Ports", deserialize_with = "deserialize_list")]
|
#[serde(alias = "Ports", deserialize_with = "deserialize_list")]
|
||||||
ports: Vec<String>,
|
ports: Vec<String>,
|
||||||
#[serde(alias = "State")]
|
#[serde(alias = "State")]
|
||||||
state: String,
|
state: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we may don't need this
|
// TODO: we may don't need this
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct PodmanPort {
|
struct PodmanPort {
|
||||||
host_ip: String,
|
host_ip: String,
|
||||||
container_port: u16,
|
container_port: u16,
|
||||||
host_port: u16,
|
host_port: u16,
|
||||||
range: u16,
|
range: u16,
|
||||||
protocol: String,
|
protocol: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we may don't need this
|
// TODO: we may don't need this
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct PodmanContainer {
|
struct PodmanContainer {
|
||||||
#[serde(alias = "Id")]
|
#[serde(alias = "Id")]
|
||||||
id: String,
|
id: String,
|
||||||
#[serde(alias = "Image")]
|
#[serde(alias = "Image")]
|
||||||
image: String,
|
image: String,
|
||||||
#[serde(alias = "Mounts")]
|
#[serde(alias = "Mounts")]
|
||||||
mounts: Vec<String>,
|
mounts: Vec<String>,
|
||||||
#[serde(alias = "Names")]
|
#[serde(alias = "Names")]
|
||||||
names: Vec<String>,
|
names: Vec<String>,
|
||||||
#[serde(alias = "Ports", deserialize_with = "deserialize_null_as_default")]
|
#[serde(alias = "Ports", deserialize_with = "deserialize_null_as_default")]
|
||||||
ports: Vec<PodmanPort>,
|
ports: Vec<PodmanPort>,
|
||||||
#[serde(alias = "State")]
|
#[serde(alias = "State")]
|
||||||
state: String,
|
state: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_list<'de, D>(deserializer: D) -> std::result::Result<Vec<String>, D::Error>
|
fn deserialize_list<'de, D>(deserializer: D) -> std::result::Result<Vec<String>, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let str_sequence = String::deserialize(deserializer)?;
|
let str_sequence = String::deserialize(deserializer)?;
|
||||||
Ok(str_sequence
|
Ok(str_sequence
|
||||||
.split(',')
|
.split(',')
|
||||||
.filter(|item| !item.is_empty())
|
.filter(|item| !item.is_empty())
|
||||||
.map(|item| item.to_owned())
|
.map(|item| item.to_owned())
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_null_as_default<'de, D, T>(deserializer: D) -> std::result::Result<T, D::Error>
|
fn deserialize_null_as_default<'de, D, T>(deserializer: D) -> std::result::Result<T, D::Error>
|
||||||
where
|
where
|
||||||
T: Default + Deserialize<'de>,
|
T: Default + Deserialize<'de>,
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let opt = Option::deserialize(deserializer)?;
|
let opt = Option::deserialize(deserializer)?;
|
||||||
Ok(opt.unwrap_or_default())
|
Ok(opt.unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerRunOptions {
|
impl ContainerRunOptions {
|
||||||
pub fn new<S>(image: &str, command: Vec<S>) -> Self
|
pub fn new<S>(image: &str, command: Vec<S>) -> Self
|
||||||
where
|
where
|
||||||
S: Into<String> + std::fmt::Debug + Send + Clone,
|
S: Into<String> + std::fmt::Debug + Send + Clone,
|
||||||
{
|
{
|
||||||
ContainerRunOptions {
|
ContainerRunOptions {
|
||||||
image: image.to_string(),
|
image: image.to_string(),
|
||||||
command: command
|
command: command.clone().into_iter().map(|s| s.into()).collect::<Vec<_>>(),
|
||||||
.clone()
|
env: None,
|
||||||
.into_iter()
|
volume_mounts: None,
|
||||||
.map(|s| s.into())
|
name: None,
|
||||||
.collect::<Vec<_>>(),
|
entrypoint: None,
|
||||||
env: None,
|
port_mapping: HashMap::default(),
|
||||||
volume_mounts: None,
|
rm: false,
|
||||||
name: None,
|
detach: true, // add -d flag by default
|
||||||
entrypoint: None,
|
}
|
||||||
port_mapping: HashMap::default(),
|
}
|
||||||
rm: false,
|
|
||||||
detach: true, // add -d flag by default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn env<S>(mut self, env: Vec<(S, S)>) -> Self
|
pub fn env<S>(mut self, env: Vec<(S, S)>) -> Self
|
||||||
where
|
where
|
||||||
S: Into<String> + std::fmt::Debug + Send + Clone,
|
S: Into<String> + std::fmt::Debug + Send + Clone,
|
||||||
{
|
{
|
||||||
self.env = Some(
|
self.env = Some(env.into_iter().map(|(name, value)| (name.into(), value.into())).collect());
|
||||||
env.into_iter()
|
self
|
||||||
.map(|(name, value)| (name.into(), value.into()))
|
}
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn volume_mounts<S>(mut self, volume_mounts: HashMap<S, S>) -> Self
|
pub fn volume_mounts<S>(mut self, volume_mounts: HashMap<S, S>) -> Self
|
||||||
where
|
where
|
||||||
S: Into<String> + std::fmt::Debug + Send + Clone,
|
S: Into<String> + std::fmt::Debug + Send + Clone,
|
||||||
{
|
{
|
||||||
self.volume_mounts = Some(
|
self.volume_mounts = Some(
|
||||||
volume_mounts
|
volume_mounts
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(source, target)| (source.into(), target.into()))
|
.map(|(source, target)| (source.into(), target.into()))
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name<S>(mut self, name: S) -> Self
|
pub fn name<S>(mut self, name: S) -> Self
|
||||||
where
|
where
|
||||||
S: Into<String> + std::fmt::Debug + Send + Clone,
|
S: Into<String> + std::fmt::Debug + Send + Clone,
|
||||||
{
|
{
|
||||||
self.name = Some(name.into());
|
self.name = Some(name.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entrypoint<S>(mut self, entrypoint: S) -> Self
|
pub fn entrypoint<S>(mut self, entrypoint: S) -> Self
|
||||||
where
|
where
|
||||||
S: Into<String> + std::fmt::Debug + Send + Clone,
|
S: Into<String> + std::fmt::Debug + Send + Clone,
|
||||||
{
|
{
|
||||||
self.entrypoint = Some(entrypoint.into());
|
self.entrypoint = Some(entrypoint.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn port_mapping(mut self, port_mapping: &HashMap<Port, Port>) -> Self {
|
pub fn port_mapping(mut self, port_mapping: &HashMap<Port, Port>) -> Self {
|
||||||
self.port_mapping.clone_from(port_mapping);
|
self.port_mapping.clone_from(port_mapping);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rm(mut self) -> Self {
|
pub fn rm(mut self) -> Self {
|
||||||
self.rm = true;
|
self.rm = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn detach(mut self, choice: bool) -> Self {
|
pub fn detach(mut self, choice: bool) -> Self {
|
||||||
self.detach = choice;
|
self.detach = choice;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DockerClient {
|
impl DockerClient {
|
||||||
pub async fn new() -> Result<Self> {
|
pub async fn new() -> Result<Self> {
|
||||||
let using_podman = Self::is_using_podman().await?;
|
let using_podman = Self::is_using_podman().await?;
|
||||||
|
|
||||||
Ok(DockerClient { using_podman })
|
Ok(DockerClient { using_podman })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn client_binary(&self) -> String {
|
pub fn client_binary(&self) -> String {
|
||||||
String::from(if self.using_podman {
|
String::from(if self.using_podman { "podman" } else { "docker" })
|
||||||
"podman"
|
}
|
||||||
} else {
|
|
||||||
"docker"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn is_using_podman() -> Result<bool> {
|
async fn is_using_podman() -> Result<bool> {
|
||||||
if let Ok(output) = tokio::process::Command::new("docker")
|
if let Ok(output) = tokio::process::Command::new("docker").arg("version").output().await {
|
||||||
.arg("version")
|
// detect whether we're actually running podman with docker emulation
|
||||||
.output()
|
return Ok(String::from_utf8_lossy(&output.stdout).to_lowercase().contains("podman"));
|
||||||
.await
|
}
|
||||||
{
|
|
||||||
// detect whether we're actually running podman with docker emulation
|
|
||||||
return Ok(String::from_utf8_lossy(&output.stdout)
|
|
||||||
.to_lowercase()
|
|
||||||
.contains("podman"));
|
|
||||||
}
|
|
||||||
|
|
||||||
tokio::process::Command::new("podman")
|
tokio::process::Command::new("podman")
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| anyhow!("Failed to detect container engine: {err}"))?;
|
.map_err(|err| anyhow!("Failed to detect container engine: {err}"))?;
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DockerClient {
|
impl DockerClient {
|
||||||
fn client_command(&self) -> tokio::process::Command {
|
fn client_command(&self) -> tokio::process::Command {
|
||||||
tokio::process::Command::new(self.client_binary())
|
tokio::process::Command::new(self.client_binary())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_volume(&self, name: &str) -> Result<()> {
|
pub async fn create_volume(&self, name: &str) -> Result<()> {
|
||||||
let result = self
|
let result = self
|
||||||
.client_command()
|
.client_command()
|
||||||
.args(["volume", "create", name])
|
.args(["volume", "create", name])
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| anyhow!("Failed to create volume '{name}': {err}"))?;
|
.map_err(|err| anyhow!("Failed to create volume '{name}': {err}"))?;
|
||||||
|
|
||||||
if !result.status.success() {
|
if !result.status.success() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"Failed to create volume '{name}': {}",
|
"Failed to create volume '{name}': {}",
|
||||||
String::from_utf8_lossy(&result.stderr)
|
String::from_utf8_lossy(&result.stderr)
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn container_run(&self, options: ContainerRunOptions) -> Result<String> {
|
pub async fn container_run(&self, options: ContainerRunOptions) -> Result<String> {
|
||||||
let mut cmd = self.client_command();
|
let mut cmd = self.client_command();
|
||||||
cmd.args(["run", "--platform", "linux/amd64"]);
|
cmd.args(["run", "--platform", "linux/amd64"]);
|
||||||
|
|
||||||
if options.detach {
|
if options.detach {
|
||||||
cmd.arg("-d");
|
cmd.arg("-d");
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::apply_cmd_options(&mut cmd, &options);
|
Self::apply_cmd_options(&mut cmd, &options);
|
||||||
|
|
||||||
trace!("cmd: {:?}", cmd);
|
trace!("cmd: {:?}", cmd);
|
||||||
|
|
||||||
let result = cmd.output().await.map_err(|err| {
|
let result = cmd.output().await.map_err(|err| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"Failed to run container with image '{image}' and command '{command}': {err}",
|
"Failed to run container with image '{image}' and command '{command}': {err}",
|
||||||
image = options.image,
|
image = options.image,
|
||||||
command = options.command.join(" "),
|
command = options.command.join(" "),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !result.status.success() {
|
if !result.status.success() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"Failed to run container with image '{image}' and command '{command}': {err}",
|
"Failed to run container with image '{image}' and command '{command}': {err}",
|
||||||
image = options.image,
|
image = options.image,
|
||||||
command = options.command.join(" "),
|
command = options.command.join(" "),
|
||||||
err = String::from_utf8_lossy(&result.stderr)
|
err = String::from_utf8_lossy(&result.stderr)
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(String::from_utf8_lossy(&result.stdout).to_string())
|
Ok(String::from_utf8_lossy(&result.stdout).to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn container_create(&self, options: ContainerRunOptions) -> Result<String> {
|
pub async fn container_create(&self, options: ContainerRunOptions) -> Result<String> {
|
||||||
let mut cmd = self.client_command();
|
let mut cmd = self.client_command();
|
||||||
cmd.args(["container", "create"]);
|
cmd.args(["container", "create"]);
|
||||||
|
|
||||||
Self::apply_cmd_options(&mut cmd, &options);
|
Self::apply_cmd_options(&mut cmd, &options);
|
||||||
|
|
||||||
trace!("cmd: {:?}", cmd);
|
trace!("cmd: {:?}", cmd);
|
||||||
|
|
||||||
let result = cmd.output().await.map_err(|err| {
|
let result = cmd.output().await.map_err(|err| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"Failed to run container with image '{image}' and command '{command}': {err}",
|
"Failed to run container with image '{image}' and command '{command}': {err}",
|
||||||
image = options.image,
|
image = options.image,
|
||||||
command = options.command.join(" "),
|
command = options.command.join(" "),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !result.status.success() {
|
if !result.status.success() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"Failed to run container with image '{image}' and command '{command}': {err}",
|
"Failed to run container with image '{image}' and command '{command}': {err}",
|
||||||
image = options.image,
|
image = options.image,
|
||||||
command = options.command.join(" "),
|
command = options.command.join(" "),
|
||||||
err = String::from_utf8_lossy(&result.stderr)
|
err = String::from_utf8_lossy(&result.stderr)
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(String::from_utf8_lossy(&result.stdout).to_string())
|
Ok(String::from_utf8_lossy(&result.stdout).to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn container_exec<S>(
|
pub async fn container_exec<S>(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
command: Vec<S>,
|
command: Vec<S>,
|
||||||
env: Option<Vec<(S, S)>>,
|
env: Option<Vec<(S, S)>>,
|
||||||
as_user: Option<S>,
|
as_user: Option<S>,
|
||||||
) -> Result<ExecutionResult>
|
) -> Result<ExecutionResult>
|
||||||
where
|
where
|
||||||
S: Into<String> + std::fmt::Debug + Send + Clone,
|
S: Into<String> + std::fmt::Debug + Send + Clone,
|
||||||
{
|
{
|
||||||
let mut cmd = self.client_command();
|
let mut cmd = self.client_command();
|
||||||
cmd.arg("exec");
|
cmd.arg("exec");
|
||||||
|
|
||||||
if let Some(env) = env {
|
if let Some(env) = env {
|
||||||
for env_var in env {
|
for env_var in env {
|
||||||
cmd.args(["-e", &format!("{}={}", env_var.0.into(), env_var.1.into())]);
|
cmd.args(["-e", &format!("{}={}", env_var.0.into(), env_var.1.into())]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(user) = as_user {
|
if let Some(user) = as_user {
|
||||||
cmd.args(["-u", user.into().as_ref()]);
|
cmd.args(["-u", user.into().as_ref()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.arg(name);
|
cmd.arg(name);
|
||||||
|
|
||||||
cmd.args(
|
cmd.args(command.clone().into_iter().map(|s| <S as Into<String>>::into(s)));
|
||||||
command
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.map(|s| <S as Into<String>>::into(s)),
|
|
||||||
);
|
|
||||||
|
|
||||||
trace!("cmd is : {:?}", cmd);
|
trace!("cmd is : {:?}", cmd);
|
||||||
|
|
||||||
let result = cmd.output().await.map_err(|err| {
|
let result = cmd.output().await.map_err(|err| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"Failed to exec '{}' on '{}': {err}",
|
"Failed to exec '{}' on '{}': {err}",
|
||||||
command
|
command
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| <S as Into<String>>::into(s))
|
.map(|s| <S as Into<String>>::into(s))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" "),
|
.join(" "),
|
||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !result.status.success() {
|
if !result.status.success() {
|
||||||
return Ok(Err((
|
return Ok(Err((result.status, String::from_utf8_lossy(&result.stderr).to_string())));
|
||||||
result.status,
|
}
|
||||||
String::from_utf8_lossy(&result.stderr).to_string(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Ok(String::from_utf8_lossy(&result.stdout).to_string()))
|
Ok(Ok(String::from_utf8_lossy(&result.stdout).to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn container_cp(
|
pub async fn container_cp(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
local_path: &Path,
|
local_path: &Path,
|
||||||
remote_path: &Path,
|
remote_path: &Path,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let result = self
|
let result = self
|
||||||
.client_command()
|
.client_command()
|
||||||
.args([
|
.args([
|
||||||
"cp",
|
"cp",
|
||||||
local_path.to_string_lossy().as_ref(),
|
local_path.to_string_lossy().as_ref(),
|
||||||
&format!("{name}:{}", remote_path.to_string_lossy().as_ref()),
|
&format!("{name}:{}", remote_path.to_string_lossy().as_ref()),
|
||||||
])
|
])
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"Failed copy file '{file}' to container '{name}': {err}",
|
"Failed copy file '{file}' to container '{name}': {err}",
|
||||||
file = local_path.to_string_lossy(),
|
file = local_path.to_string_lossy(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !result.status.success() {
|
if !result.status.success() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"Failed to copy file '{file}' to container '{name}': {err}",
|
"Failed to copy file '{file}' to container '{name}': {err}",
|
||||||
file = local_path.to_string_lossy(),
|
file = local_path.to_string_lossy(),
|
||||||
err = String::from_utf8_lossy(&result.stderr)
|
err = String::from_utf8_lossy(&result.stderr)
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn container_rm(&self, name: &str) -> Result<()> {
|
pub async fn container_rm(&self, name: &str) -> Result<()> {
|
||||||
let result = self
|
let result = self
|
||||||
.client_command()
|
.client_command()
|
||||||
.args(["rm", "--force", "--volumes", name])
|
.args(["rm", "--force", "--volumes", name])
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| anyhow!("Failed do remove container '{name}: {err}"))?;
|
.map_err(|err| anyhow!("Failed do remove container '{name}: {err}"))?;
|
||||||
|
|
||||||
if !result.status.success() {
|
if !result.status.success() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"Failed to remove container '{name}': {err}",
|
"Failed to remove container '{name}': {err}",
|
||||||
err = String::from_utf8_lossy(&result.stderr)
|
err = String::from_utf8_lossy(&result.stderr)
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn namespaced_containers_rm(&self, namespace: &str) -> Result<()> {
|
pub async fn namespaced_containers_rm(&self, namespace: &str) -> Result<()> {
|
||||||
let container_names: Vec<String> = self
|
let container_names: Vec<String> = self
|
||||||
.get_containers()
|
.get_containers()
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|container| match container {
|
.filter_map(|container| match container {
|
||||||
Container::Docker(container) => {
|
Container::Docker(container) => {
|
||||||
if let Some(name) = container.names.first() {
|
if let Some(name) = container.names.first() {
|
||||||
if name.starts_with(namespace) {
|
if name.starts_with(namespace) {
|
||||||
return Some(name.to_string());
|
return Some(name.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
Container::Podman(container) => {
|
Container::Podman(container) => {
|
||||||
if let Some(name) = container.names.first() {
|
if let Some(name) = container.names.first() {
|
||||||
if name.starts_with(namespace) {
|
if name.starts_with(namespace) {
|
||||||
return Some(name.to_string());
|
return Some(name.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
info!("{:?}", container_names);
|
info!("{:?}", container_names);
|
||||||
let futures = container_names
|
let futures =
|
||||||
.iter()
|
container_names.iter().map(|name| self.container_rm(name)).collect::<Vec<_>>();
|
||||||
.map(|name| self.container_rm(name))
|
try_join_all(futures).await?;
|
||||||
.collect::<Vec<_>>();
|
|
||||||
try_join_all(futures).await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn container_ip(&self, container_name: &str) -> Result<String> {
|
pub async fn container_ip(&self, container_name: &str) -> Result<String> {
|
||||||
let ip = if self.using_podman {
|
let ip = if self.using_podman {
|
||||||
"127.0.0.1".into()
|
"127.0.0.1".into()
|
||||||
} else {
|
} else {
|
||||||
let mut cmd = tokio::process::Command::new("docker");
|
let mut cmd = tokio::process::Command::new("docker");
|
||||||
cmd.args(vec![
|
cmd.args(vec!["inspect", "-f", "{{ .NetworkSettings.IPAddress }}", container_name]);
|
||||||
"inspect",
|
|
||||||
"-f",
|
|
||||||
"{{ .NetworkSettings.IPAddress }}",
|
|
||||||
container_name,
|
|
||||||
]);
|
|
||||||
|
|
||||||
trace!("CMD: {cmd:?}");
|
trace!("CMD: {cmd:?}");
|
||||||
|
|
||||||
let res = cmd
|
let res = cmd
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| anyhow!("Failed to get docker container ip, output: {err}"))?;
|
.map_err(|err| anyhow!("Failed to get docker container ip, output: {err}"))?;
|
||||||
|
|
||||||
String::from_utf8(res.stdout)
|
String::from_utf8(res.stdout)
|
||||||
.map_err(|err| anyhow!("Failed to get docker container ip, output: {err}"))?
|
.map_err(|err| anyhow!("Failed to get docker container ip, output: {err}"))?
|
||||||
.trim()
|
.trim()
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("IP: {ip}");
|
trace!("IP: {ip}");
|
||||||
Ok(ip)
|
Ok(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_containers(&self) -> Result<Vec<Container>> {
|
async fn get_containers(&self) -> Result<Vec<Container>> {
|
||||||
let containers = if self.using_podman {
|
let containers = if self.using_podman {
|
||||||
self.get_podman_containers()
|
self.get_podman_containers().await?.into_iter().map(Container::Podman).collect()
|
||||||
.await?
|
} else {
|
||||||
.into_iter()
|
self.get_docker_containers().await?.into_iter().map(Container::Docker).collect()
|
||||||
.map(Container::Podman)
|
};
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
self.get_docker_containers()
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.map(Container::Docker)
|
|
||||||
.collect()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(containers)
|
Ok(containers)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_podman_containers(&self) -> Result<Vec<PodmanContainer>> {
|
async fn get_podman_containers(&self) -> Result<Vec<PodmanContainer>> {
|
||||||
let res = tokio::process::Command::new("podman")
|
let res = tokio::process::Command::new("podman")
|
||||||
.args(vec!["ps", "--all", "--no-trunc", "--format", "json"])
|
.args(vec!["ps", "--all", "--no-trunc", "--format", "json"])
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| anyhow!("Failed to get podman containers output: {err}"))?;
|
.map_err(|err| anyhow!("Failed to get podman containers output: {err}"))?;
|
||||||
|
|
||||||
let stdout = String::from_utf8_lossy(&res.stdout);
|
let stdout = String::from_utf8_lossy(&res.stdout);
|
||||||
|
|
||||||
let containers = serde_json::from_str(&stdout)
|
let containers = serde_json::from_str(&stdout)
|
||||||
.map_err(|err| anyhow!("Failed to parse podman containers output: {err}"))?;
|
.map_err(|err| anyhow!("Failed to parse podman containers output: {err}"))?;
|
||||||
|
|
||||||
Ok(containers)
|
Ok(containers)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_docker_containers(&self) -> Result<Vec<DockerContainer>> {
|
async fn get_docker_containers(&self) -> Result<Vec<DockerContainer>> {
|
||||||
let res = tokio::process::Command::new("docker")
|
let res = tokio::process::Command::new("docker")
|
||||||
.args(vec!["ps", "--all", "--no-trunc", "--format", "json"])
|
.args(vec!["ps", "--all", "--no-trunc", "--format", "json"])
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let stdout = String::from_utf8_lossy(&res.stdout);
|
let stdout = String::from_utf8_lossy(&res.stdout);
|
||||||
|
|
||||||
let mut containers = vec![];
|
let mut containers = vec![];
|
||||||
for line in stdout.lines() {
|
for line in stdout.lines() {
|
||||||
containers.push(
|
containers.push(
|
||||||
serde_json::from_str::<DockerContainer>(line)
|
serde_json::from_str::<DockerContainer>(line)
|
||||||
.map_err(|err| anyhow!("Failed to parse docker container output: {err}"))?,
|
.map_err(|err| anyhow!("Failed to parse docker container output: {err}"))?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(containers)
|
Ok(containers)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn container_logs(&self, container_name: &str) -> Result<String> {
|
pub(crate) async fn container_logs(&self, container_name: &str) -> Result<String> {
|
||||||
let output = Command::new("sh")
|
let output = Command::new("sh")
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(format!("docker logs -t '{container_name}' 2>&1"))
|
.arg(format!("docker logs -t '{container_name}' 2>&1"))
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"Failed to spawn docker logs command for container '{container_name}': {err}"
|
"Failed to spawn docker logs command for container '{container_name}': {err}"
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let logs = String::from_utf8_lossy(&output.stdout).to_string();
|
let logs = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
// stderr was redirected to stdout, so logs should contain the error message if any
|
// stderr was redirected to stdout, so logs should contain the error message if any
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"Failed to get logs for container '{name}': {logs}",
|
"Failed to get logs for container '{name}': {logs}",
|
||||||
name = container_name,
|
name = container_name,
|
||||||
logs = &logs
|
logs = &logs
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(logs)
|
Ok(logs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_cmd_options(cmd: &mut Command, options: &ContainerRunOptions) {
|
fn apply_cmd_options(cmd: &mut Command, options: &ContainerRunOptions) {
|
||||||
if options.rm {
|
if options.rm {
|
||||||
cmd.arg("--rm");
|
cmd.arg("--rm");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(entrypoint) = options.entrypoint.as_ref() {
|
if let Some(entrypoint) = options.entrypoint.as_ref() {
|
||||||
cmd.args(["--entrypoint", entrypoint]);
|
cmd.args(["--entrypoint", entrypoint]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(volume_mounts) = options.volume_mounts.as_ref() {
|
if let Some(volume_mounts) = options.volume_mounts.as_ref() {
|
||||||
for (source, target) in volume_mounts {
|
for (source, target) in volume_mounts {
|
||||||
cmd.args(["-v", &format!("{source}:{target}")]);
|
cmd.args(["-v", &format!("{source}:{target}")]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(env) = options.env.as_ref() {
|
if let Some(env) = options.env.as_ref() {
|
||||||
for env_var in env {
|
for env_var in env {
|
||||||
cmd.args(["-e", &format!("{}={}", env_var.0, env_var.1)]);
|
cmd.args(["-e", &format!("{}={}", env_var.0, env_var.1)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add published ports
|
// add published ports
|
||||||
for (container_port, host_port) in options.port_mapping.iter() {
|
for (container_port, host_port) in options.port_mapping.iter() {
|
||||||
cmd.args(["-p", &format!("{host_port}:{container_port}")]);
|
cmd.args(["-p", &format!("{host_port}:{container_port}")]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(name) = options.name.as_ref() {
|
if let Some(name) = options.name.as_ref() {
|
||||||
cmd.args(["--name", name]);
|
cmd.args(["--name", name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.arg(&options.image);
|
cmd.arg(&options.image);
|
||||||
|
|
||||||
for arg in &options.command {
|
for arg in &options.command {
|
||||||
cmd.arg(arg);
|
cmd.arg(arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+363
-387
@@ -1,8 +1,8 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@@ -12,212 +12,207 @@ use tracing::{debug, trace, warn};
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
client::{ContainerRunOptions, DockerClient},
|
client::{ContainerRunOptions, DockerClient},
|
||||||
node::DockerNode,
|
node::DockerNode,
|
||||||
DockerProvider,
|
DockerProvider,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::NAMESPACE_PREFIX,
|
constants::NAMESPACE_PREFIX,
|
||||||
docker::{
|
docker::{
|
||||||
node::{DeserializableDockerNodeOptions, DockerNodeOptions},
|
node::{DeserializableDockerNodeOptions, DockerNodeOptions},
|
||||||
provider,
|
provider,
|
||||||
},
|
},
|
||||||
shared::helpers::extract_execution_result,
|
shared::helpers::extract_execution_result,
|
||||||
types::{
|
types::{
|
||||||
GenerateFileCommand, GenerateFilesOptions, ProviderCapabilities, RunCommandOptions,
|
GenerateFileCommand, GenerateFilesOptions, ProviderCapabilities, RunCommandOptions,
|
||||||
SpawnNodeOptions,
|
SpawnNodeOptions,
|
||||||
},
|
},
|
||||||
DynNode, ProviderError, ProviderNamespace, ProviderNode,
|
DynNode, ProviderError, ProviderNamespace, ProviderNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct DockerNamespace<FS>
|
pub struct DockerNamespace<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone,
|
FS: FileSystem + Send + Sync + Clone,
|
||||||
{
|
{
|
||||||
weak: Weak<DockerNamespace<FS>>,
|
weak: Weak<DockerNamespace<FS>>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
provider: Weak<DockerProvider<FS>>,
|
provider: Weak<DockerProvider<FS>>,
|
||||||
name: String,
|
name: String,
|
||||||
base_dir: PathBuf,
|
base_dir: PathBuf,
|
||||||
capabilities: ProviderCapabilities,
|
capabilities: ProviderCapabilities,
|
||||||
docker_client: DockerClient,
|
docker_client: DockerClient,
|
||||||
filesystem: FS,
|
filesystem: FS,
|
||||||
delete_on_drop: Arc<Mutex<bool>>,
|
delete_on_drop: Arc<Mutex<bool>>,
|
||||||
pub(super) nodes: RwLock<HashMap<String, Arc<DockerNode<FS>>>>,
|
pub(super) nodes: RwLock<HashMap<String, Arc<DockerNode<FS>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FS> DockerNamespace<FS>
|
impl<FS> DockerNamespace<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||||
{
|
{
|
||||||
pub(super) async fn new(
|
pub(super) async fn new(
|
||||||
provider: &Weak<DockerProvider<FS>>,
|
provider: &Weak<DockerProvider<FS>>,
|
||||||
tmp_dir: &PathBuf,
|
tmp_dir: &PathBuf,
|
||||||
capabilities: &ProviderCapabilities,
|
capabilities: &ProviderCapabilities,
|
||||||
docker_client: &DockerClient,
|
docker_client: &DockerClient,
|
||||||
filesystem: &FS,
|
filesystem: &FS,
|
||||||
custom_base_dir: Option<&Path>,
|
custom_base_dir: Option<&Path>,
|
||||||
) -> Result<Arc<Self>, ProviderError> {
|
) -> Result<Arc<Self>, ProviderError> {
|
||||||
let name = format!("{}{}", NAMESPACE_PREFIX, Uuid::new_v4());
|
let name = format!("{}{}", NAMESPACE_PREFIX, Uuid::new_v4());
|
||||||
let base_dir = if let Some(custom_base_dir) = custom_base_dir {
|
let base_dir = if let Some(custom_base_dir) = custom_base_dir {
|
||||||
if !filesystem.exists(custom_base_dir).await {
|
if !filesystem.exists(custom_base_dir).await {
|
||||||
filesystem.create_dir(custom_base_dir).await?;
|
filesystem.create_dir(custom_base_dir).await?;
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
"⚠️ Using and existing directory {} as base dir",
|
"⚠️ Using and existing directory {} as base dir",
|
||||||
custom_base_dir.to_string_lossy()
|
custom_base_dir.to_string_lossy()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
PathBuf::from(custom_base_dir)
|
PathBuf::from(custom_base_dir)
|
||||||
} else {
|
} else {
|
||||||
let base_dir = PathBuf::from_iter([tmp_dir, &PathBuf::from(&name)]);
|
let base_dir = PathBuf::from_iter([tmp_dir, &PathBuf::from(&name)]);
|
||||||
filesystem.create_dir(&base_dir).await?;
|
filesystem.create_dir(&base_dir).await?;
|
||||||
base_dir
|
base_dir
|
||||||
};
|
};
|
||||||
|
|
||||||
let namespace = Arc::new_cyclic(|weak| DockerNamespace {
|
let namespace = Arc::new_cyclic(|weak| DockerNamespace {
|
||||||
weak: weak.clone(),
|
weak: weak.clone(),
|
||||||
provider: provider.clone(),
|
provider: provider.clone(),
|
||||||
name,
|
name,
|
||||||
base_dir,
|
base_dir,
|
||||||
capabilities: capabilities.clone(),
|
capabilities: capabilities.clone(),
|
||||||
filesystem: filesystem.clone(),
|
filesystem: filesystem.clone(),
|
||||||
docker_client: docker_client.clone(),
|
docker_client: docker_client.clone(),
|
||||||
nodes: RwLock::new(HashMap::new()),
|
nodes: RwLock::new(HashMap::new()),
|
||||||
delete_on_drop: Arc::new(Mutex::new(true)),
|
delete_on_drop: Arc::new(Mutex::new(true)),
|
||||||
});
|
});
|
||||||
|
|
||||||
namespace.initialize().await?;
|
namespace.initialize().await?;
|
||||||
|
|
||||||
Ok(namespace)
|
Ok(namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn attach_to_live(
|
pub(super) async fn attach_to_live(
|
||||||
provider: &Weak<DockerProvider<FS>>,
|
provider: &Weak<DockerProvider<FS>>,
|
||||||
capabilities: &ProviderCapabilities,
|
capabilities: &ProviderCapabilities,
|
||||||
docker_client: &DockerClient,
|
docker_client: &DockerClient,
|
||||||
filesystem: &FS,
|
filesystem: &FS,
|
||||||
custom_base_dir: &Path,
|
custom_base_dir: &Path,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> Result<Arc<Self>, ProviderError> {
|
) -> Result<Arc<Self>, ProviderError> {
|
||||||
let base_dir = custom_base_dir.to_path_buf();
|
let base_dir = custom_base_dir.to_path_buf();
|
||||||
|
|
||||||
let namespace = Arc::new_cyclic(|weak| DockerNamespace {
|
let namespace = Arc::new_cyclic(|weak| DockerNamespace {
|
||||||
weak: weak.clone(),
|
weak: weak.clone(),
|
||||||
provider: provider.clone(),
|
provider: provider.clone(),
|
||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
base_dir,
|
base_dir,
|
||||||
capabilities: capabilities.clone(),
|
capabilities: capabilities.clone(),
|
||||||
filesystem: filesystem.clone(),
|
filesystem: filesystem.clone(),
|
||||||
docker_client: docker_client.clone(),
|
docker_client: docker_client.clone(),
|
||||||
nodes: RwLock::new(HashMap::new()),
|
nodes: RwLock::new(HashMap::new()),
|
||||||
delete_on_drop: Arc::new(Mutex::new(false)),
|
delete_on_drop: Arc::new(Mutex::new(false)),
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(namespace)
|
Ok(namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn initialize(&self) -> Result<(), ProviderError> {
|
async fn initialize(&self) -> Result<(), ProviderError> {
|
||||||
// let ns_scripts_shared = PathBuf::from_iter([&self.base_dir, &PathBuf::from("shared-scripts")]);
|
// let ns_scripts_shared = PathBuf::from_iter([&self.base_dir, &PathBuf::from("shared-scripts")]);
|
||||||
// self.filesystem.create_dir(&ns_scripts_shared).await?;
|
// self.filesystem.create_dir(&ns_scripts_shared).await?;
|
||||||
self.initialize_zombie_scripts_volume().await?;
|
self.initialize_zombie_scripts_volume().await?;
|
||||||
self.initialize_helper_binaries_volume().await?;
|
self.initialize_helper_binaries_volume().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn initialize_zombie_scripts_volume(&self) -> Result<(), ProviderError> {
|
async fn initialize_zombie_scripts_volume(&self) -> Result<(), ProviderError> {
|
||||||
let local_zombie_wrapper_path =
|
let local_zombie_wrapper_path =
|
||||||
PathBuf::from_iter([&self.base_dir, &PathBuf::from("zombie-wrapper.sh")]);
|
PathBuf::from_iter([&self.base_dir, &PathBuf::from("zombie-wrapper.sh")]);
|
||||||
|
|
||||||
self.filesystem
|
self.filesystem
|
||||||
.write(
|
.write(&local_zombie_wrapper_path, include_str!("../shared/scripts/zombie-wrapper.sh"))
|
||||||
&local_zombie_wrapper_path,
|
.await?;
|
||||||
include_str!("../shared/scripts/zombie-wrapper.sh"),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let local_helper_binaries_downloader_path = PathBuf::from_iter([
|
let local_helper_binaries_downloader_path =
|
||||||
&self.base_dir,
|
PathBuf::from_iter([&self.base_dir, &PathBuf::from("helper-binaries-downloader.sh")]);
|
||||||
&PathBuf::from("helper-binaries-downloader.sh"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
self.filesystem
|
self.filesystem
|
||||||
.write(
|
.write(
|
||||||
&local_helper_binaries_downloader_path,
|
&local_helper_binaries_downloader_path,
|
||||||
include_str!("../shared/scripts/helper-binaries-downloader.sh"),
|
include_str!("../shared/scripts/helper-binaries-downloader.sh"),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let zombie_wrapper_volume_name = format!("{}-zombie-wrapper", self.name);
|
let zombie_wrapper_volume_name = format!("{}-zombie-wrapper", self.name);
|
||||||
let zombie_wrapper_container_name = format!("{}-scripts", self.name);
|
let zombie_wrapper_container_name = format!("{}-scripts", self.name);
|
||||||
|
|
||||||
self.docker_client
|
self.docker_client
|
||||||
.create_volume(&zombie_wrapper_volume_name)
|
.create_volume(&zombie_wrapper_volume_name)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
||||||
|
|
||||||
self.docker_client
|
self.docker_client
|
||||||
.container_create(
|
.container_create(
|
||||||
ContainerRunOptions::new("alpine:latest", vec!["tail", "-f", "/dev/null"])
|
ContainerRunOptions::new("alpine:latest", vec!["tail", "-f", "/dev/null"])
|
||||||
.volume_mounts(HashMap::from([(
|
.volume_mounts(HashMap::from([(
|
||||||
zombie_wrapper_volume_name.as_str(),
|
zombie_wrapper_volume_name.as_str(),
|
||||||
"/scripts",
|
"/scripts",
|
||||||
)]))
|
)]))
|
||||||
.name(&zombie_wrapper_container_name)
|
.name(&zombie_wrapper_container_name)
|
||||||
.rm(),
|
.rm(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
||||||
|
|
||||||
// copy the scripts
|
// copy the scripts
|
||||||
self.docker_client
|
self.docker_client
|
||||||
.container_cp(
|
.container_cp(
|
||||||
&zombie_wrapper_container_name,
|
&zombie_wrapper_container_name,
|
||||||
&local_zombie_wrapper_path,
|
&local_zombie_wrapper_path,
|
||||||
&PathBuf::from("/scripts/zombie-wrapper.sh"),
|
&PathBuf::from("/scripts/zombie-wrapper.sh"),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
||||||
|
|
||||||
self.docker_client
|
self.docker_client
|
||||||
.container_cp(
|
.container_cp(
|
||||||
&zombie_wrapper_container_name,
|
&zombie_wrapper_container_name,
|
||||||
&local_helper_binaries_downloader_path,
|
&local_helper_binaries_downloader_path,
|
||||||
&PathBuf::from("/scripts/helper-binaries-downloader.sh"),
|
&PathBuf::from("/scripts/helper-binaries-downloader.sh"),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
||||||
|
|
||||||
// set permissions for rwx on whole volume recursively
|
// set permissions for rwx on whole volume recursively
|
||||||
self.docker_client
|
self.docker_client
|
||||||
.container_run(
|
.container_run(
|
||||||
ContainerRunOptions::new("alpine:latest", vec!["chmod", "-R", "777", "/scripts"])
|
ContainerRunOptions::new("alpine:latest", vec!["chmod", "-R", "777", "/scripts"])
|
||||||
.volume_mounts(HashMap::from([(
|
.volume_mounts(HashMap::from([(
|
||||||
zombie_wrapper_volume_name.as_ref(),
|
zombie_wrapper_volume_name.as_ref(),
|
||||||
"/scripts",
|
"/scripts",
|
||||||
)]))
|
)]))
|
||||||
.rm(),
|
.rm(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn initialize_helper_binaries_volume(&self) -> Result<(), ProviderError> {
|
async fn initialize_helper_binaries_volume(&self) -> Result<(), ProviderError> {
|
||||||
let helper_binaries_volume_name = format!("{}-helper-binaries", self.name);
|
let helper_binaries_volume_name = format!("{}-helper-binaries", self.name);
|
||||||
let zombie_wrapper_volume_name = format!("{}-zombie-wrapper", self.name);
|
let zombie_wrapper_volume_name = format!("{}-zombie-wrapper", self.name);
|
||||||
|
|
||||||
self.docker_client
|
self.docker_client
|
||||||
.create_volume(&helper_binaries_volume_name)
|
.create_volume(&helper_binaries_volume_name)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
||||||
|
|
||||||
// download binaries to volume
|
// download binaries to volume
|
||||||
self.docker_client
|
self.docker_client
|
||||||
.container_run(
|
.container_run(
|
||||||
ContainerRunOptions::new(
|
ContainerRunOptions::new(
|
||||||
"alpine:latest",
|
"alpine:latest",
|
||||||
vec!["ash", "/scripts/helper-binaries-downloader.sh"],
|
vec!["ash", "/scripts/helper-binaries-downloader.sh"],
|
||||||
)
|
)
|
||||||
@@ -234,261 +229,242 @@ where
|
|||||||
// wait until complete
|
// wait until complete
|
||||||
.detach(false)
|
.detach(false)
|
||||||
.rm(),
|
.rm(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
||||||
|
|
||||||
// set permissions for rwx on whole volume recursively
|
// set permissions for rwx on whole volume recursively
|
||||||
self.docker_client
|
self.docker_client
|
||||||
.container_run(
|
.container_run(
|
||||||
ContainerRunOptions::new("alpine:latest", vec!["chmod", "-R", "777", "/helpers"])
|
ContainerRunOptions::new("alpine:latest", vec!["chmod", "-R", "777", "/helpers"])
|
||||||
.volume_mounts(HashMap::from([(
|
.volume_mounts(HashMap::from([(
|
||||||
helper_binaries_volume_name.as_ref(),
|
helper_binaries_volume_name.as_ref(),
|
||||||
"/helpers",
|
"/helpers",
|
||||||
)]))
|
)]))
|
||||||
.rm(),
|
.rm(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
.map_err(|err| ProviderError::CreateNamespaceFailed(self.name.clone(), err.into()))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_delete_on_drop(&self, delete_on_drop: bool) {
|
pub async fn set_delete_on_drop(&self, delete_on_drop: bool) {
|
||||||
*self.delete_on_drop.lock().await = delete_on_drop;
|
*self.delete_on_drop.lock().await = delete_on_drop;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_on_drop(&self) -> bool {
|
pub async fn delete_on_drop(&self) -> bool {
|
||||||
if let Ok(delete_on_drop) = self.delete_on_drop.try_lock() {
|
if let Ok(delete_on_drop) = self.delete_on_drop.try_lock() {
|
||||||
*delete_on_drop
|
*delete_on_drop
|
||||||
} else {
|
} else {
|
||||||
// if we can't lock just remove the ns
|
// if we can't lock just remove the ns
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<FS> ProviderNamespace for DockerNamespace<FS>
|
impl<FS> ProviderNamespace for DockerNamespace<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base_dir(&self) -> &PathBuf {
|
fn base_dir(&self) -> &PathBuf {
|
||||||
&self.base_dir
|
&self.base_dir
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capabilities(&self) -> &ProviderCapabilities {
|
fn capabilities(&self) -> &ProviderCapabilities {
|
||||||
&self.capabilities
|
&self.capabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provider_name(&self) -> &str {
|
fn provider_name(&self) -> &str {
|
||||||
provider::PROVIDER_NAME
|
provider::PROVIDER_NAME
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn detach(&self) {
|
async fn detach(&self) {
|
||||||
self.set_delete_on_drop(false).await;
|
self.set_delete_on_drop(false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn is_detached(&self) -> bool {
|
async fn is_detached(&self) -> bool {
|
||||||
self.delete_on_drop().await
|
self.delete_on_drop().await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn nodes(&self) -> HashMap<String, DynNode> {
|
async fn nodes(&self) -> HashMap<String, DynNode> {
|
||||||
self.nodes
|
self.nodes
|
||||||
.read()
|
.read()
|
||||||
.await
|
.await
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, node)| (name.clone(), node.clone() as DynNode))
|
.map(|(name, node)| (name.clone(), node.clone() as DynNode))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_node_available_args(
|
async fn get_node_available_args(
|
||||||
&self,
|
&self,
|
||||||
(command, image): (String, Option<String>),
|
(command, image): (String, Option<String>),
|
||||||
) -> Result<String, ProviderError> {
|
) -> Result<String, ProviderError> {
|
||||||
let node_image = image.expect(&format!("image should be present when getting node available args with docker provider {THIS_IS_A_BUG}"));
|
let node_image = image.expect(&format!("image should be present when getting node available args with docker provider {THIS_IS_A_BUG}"));
|
||||||
|
|
||||||
let temp_node = self
|
let temp_node = self
|
||||||
.spawn_node(
|
.spawn_node(
|
||||||
&SpawnNodeOptions::new(format!("temp-{}", Uuid::new_v4()), "cat".to_string())
|
&SpawnNodeOptions::new(format!("temp-{}", Uuid::new_v4()), "cat".to_string())
|
||||||
.image(node_image.clone()),
|
.image(node_image.clone()),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let available_args_output = temp_node
|
let available_args_output = temp_node
|
||||||
.run_command(RunCommandOptions::new(command.clone()).args(vec!["--help"]))
|
.run_command(RunCommandOptions::new(command.clone()).args(vec!["--help"]))
|
||||||
.await?
|
.await?
|
||||||
.map_err(|(_exit, status)| {
|
.map_err(|(_exit, status)| {
|
||||||
ProviderError::NodeAvailableArgsError(node_image, command, status)
|
ProviderError::NodeAvailableArgsError(node_image, command, status)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
temp_node.destroy().await?;
|
temp_node.destroy().await?;
|
||||||
|
|
||||||
Ok(available_args_output)
|
Ok(available_args_output)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_node(&self, options: &SpawnNodeOptions) -> Result<DynNode, ProviderError> {
|
async fn spawn_node(&self, options: &SpawnNodeOptions) -> Result<DynNode, ProviderError> {
|
||||||
debug!("spawn option {:?}", options);
|
debug!("spawn option {:?}", options);
|
||||||
|
|
||||||
let node = DockerNode::new(DockerNodeOptions {
|
let node = DockerNode::new(DockerNodeOptions {
|
||||||
namespace: &self.weak,
|
namespace: &self.weak,
|
||||||
namespace_base_dir: &self.base_dir,
|
namespace_base_dir: &self.base_dir,
|
||||||
name: &options.name,
|
name: &options.name,
|
||||||
image: options.image.as_ref(),
|
image: options.image.as_ref(),
|
||||||
program: &options.program,
|
program: &options.program,
|
||||||
args: &options.args,
|
args: &options.args,
|
||||||
env: &options.env,
|
env: &options.env,
|
||||||
startup_files: &options.injected_files,
|
startup_files: &options.injected_files,
|
||||||
db_snapshot: options.db_snapshot.as_ref(),
|
db_snapshot: options.db_snapshot.as_ref(),
|
||||||
docker_client: &self.docker_client,
|
docker_client: &self.docker_client,
|
||||||
container_name: format!("{}-{}", self.name, options.name),
|
container_name: format!("{}-{}", self.name, options.name),
|
||||||
filesystem: &self.filesystem,
|
filesystem: &self.filesystem,
|
||||||
port_mapping: options.port_mapping.as_ref().unwrap_or(&HashMap::default()),
|
port_mapping: options.port_mapping.as_ref().unwrap_or(&HashMap::default()),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.nodes
|
self.nodes.write().await.insert(node.name().to_string(), node.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(node.name().to_string(), node.clone());
|
|
||||||
|
|
||||||
Ok(node)
|
Ok(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_node_from_json(
|
async fn spawn_node_from_json(
|
||||||
&self,
|
&self,
|
||||||
json_value: &serde_json::Value,
|
json_value: &serde_json::Value,
|
||||||
) -> Result<DynNode, ProviderError> {
|
) -> Result<DynNode, ProviderError> {
|
||||||
let deserializable: DeserializableDockerNodeOptions =
|
let deserializable: DeserializableDockerNodeOptions =
|
||||||
serde_json::from_value(json_value.clone())?;
|
serde_json::from_value(json_value.clone())?;
|
||||||
let options = DockerNodeOptions::from_deserializable(
|
let options = DockerNodeOptions::from_deserializable(
|
||||||
&deserializable,
|
&deserializable,
|
||||||
&self.weak,
|
&self.weak,
|
||||||
&self.base_dir,
|
&self.base_dir,
|
||||||
&self.docker_client,
|
&self.docker_client,
|
||||||
&self.filesystem,
|
&self.filesystem,
|
||||||
);
|
);
|
||||||
|
|
||||||
let node = DockerNode::attach_to_live(options).await?;
|
let node = DockerNode::attach_to_live(options).await?;
|
||||||
|
|
||||||
self.nodes
|
self.nodes.write().await.insert(node.name().to_string(), node.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(node.name().to_string(), node.clone());
|
|
||||||
|
|
||||||
Ok(node)
|
Ok(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn generate_files(&self, options: GenerateFilesOptions) -> Result<(), ProviderError> {
|
async fn generate_files(&self, options: GenerateFilesOptions) -> Result<(), ProviderError> {
|
||||||
debug!("generate files options {options:#?}");
|
debug!("generate files options {options:#?}");
|
||||||
|
|
||||||
let node_name = options
|
let node_name = options.temp_name.unwrap_or_else(|| format!("temp-{}", Uuid::new_v4()));
|
||||||
.temp_name
|
let node_image = options.image.expect(&format!(
|
||||||
.unwrap_or_else(|| format!("temp-{}", Uuid::new_v4()));
|
"image should be present when generating files with docker provider {THIS_IS_A_BUG}"
|
||||||
let node_image = options.image.expect(&format!(
|
));
|
||||||
"image should be present when generating files with docker provider {THIS_IS_A_BUG}"
|
|
||||||
));
|
|
||||||
|
|
||||||
// run dummy command in a new container
|
// run dummy command in a new container
|
||||||
let temp_node = self
|
let temp_node = self
|
||||||
.spawn_node(
|
.spawn_node(
|
||||||
&SpawnNodeOptions::new(node_name, "cat".to_string())
|
&SpawnNodeOptions::new(node_name, "cat".to_string())
|
||||||
.injected_files(options.injected_files)
|
.injected_files(options.injected_files)
|
||||||
.image(node_image),
|
.image(node_image),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for GenerateFileCommand {
|
for GenerateFileCommand { program, args, env, local_output_path } in options.commands {
|
||||||
program,
|
let local_output_full_path = format!(
|
||||||
args,
|
"{}{}{}",
|
||||||
env,
|
self.base_dir.to_string_lossy(),
|
||||||
local_output_path,
|
if local_output_path.starts_with("/") { "" } else { "/" },
|
||||||
} in options.commands
|
local_output_path.to_string_lossy()
|
||||||
{
|
);
|
||||||
let local_output_full_path = format!(
|
|
||||||
"{}{}{}",
|
|
||||||
self.base_dir.to_string_lossy(),
|
|
||||||
if local_output_path.starts_with("/") {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
"/"
|
|
||||||
},
|
|
||||||
local_output_path.to_string_lossy()
|
|
||||||
);
|
|
||||||
|
|
||||||
let contents = extract_execution_result(
|
let contents = extract_execution_result(
|
||||||
&temp_node,
|
&temp_node,
|
||||||
RunCommandOptions { program, args, env },
|
RunCommandOptions { program, args, env },
|
||||||
options.expected_path.as_ref(),
|
options.expected_path.as_ref(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
self.filesystem
|
self.filesystem
|
||||||
.write(local_output_full_path, contents)
|
.write(local_output_full_path, contents)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ProviderError::FileGenerationFailed(err.into()))?;
|
.map_err(|err| ProviderError::FileGenerationFailed(err.into()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
temp_node.destroy().await
|
temp_node.destroy().await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn static_setup(&self) -> Result<(), ProviderError> {
|
async fn static_setup(&self) -> Result<(), ProviderError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn destroy(&self) -> Result<(), ProviderError> {
|
async fn destroy(&self) -> Result<(), ProviderError> {
|
||||||
let _ = self
|
let _ =
|
||||||
.docker_client
|
self.docker_client.namespaced_containers_rm(&self.name).await.map_err(|err| {
|
||||||
.namespaced_containers_rm(&self.name)
|
ProviderError::DeleteNamespaceFailed(self.name.clone(), err.into())
|
||||||
.await
|
})?;
|
||||||
.map_err(|err| ProviderError::DeleteNamespaceFailed(self.name.clone(), err.into()))?;
|
|
||||||
|
|
||||||
if let Some(provider) = self.provider.upgrade() {
|
if let Some(provider) = self.provider.upgrade() {
|
||||||
provider.namespaces.write().await.remove(&self.name);
|
provider.namespaces.write().await.remove(&self.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FS> Drop for DockerNamespace<FS>
|
impl<FS> Drop for DockerNamespace<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone,
|
FS: FileSystem + Send + Sync + Clone,
|
||||||
{
|
{
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let ns_name = self.name.clone();
|
let ns_name = self.name.clone();
|
||||||
if let Ok(delete_on_drop) = self.delete_on_drop.try_lock() {
|
if let Ok(delete_on_drop) = self.delete_on_drop.try_lock() {
|
||||||
if *delete_on_drop {
|
if *delete_on_drop {
|
||||||
let client = self.docker_client.clone();
|
let client = self.docker_client.clone();
|
||||||
let provider = self.provider.upgrade();
|
let provider = self.provider.upgrade();
|
||||||
|
|
||||||
let handler = thread::spawn(move || {
|
let handler = thread::spawn(move || {
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
rt.block_on(async move {
|
rt.block_on(async move {
|
||||||
trace!("🧟 deleting ns {ns_name} from cluster");
|
trace!("🧟 deleting ns {ns_name} from cluster");
|
||||||
let _ = client.namespaced_containers_rm(&ns_name).await;
|
let _ = client.namespaced_containers_rm(&ns_name).await;
|
||||||
trace!("✅ deleted");
|
trace!("✅ deleted");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if handler.join().is_ok() {
|
if handler.join().is_ok() {
|
||||||
if let Some(provider) = provider {
|
if let Some(provider) = provider {
|
||||||
if let Ok(mut p) = provider.namespaces.try_write() {
|
if let Ok(mut p) = provider.namespaces.try_write() {
|
||||||
p.remove(&self.name);
|
p.remove(&self.name);
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
"⚠️ Can not acquire write lock to the provider, ns {} not removed",
|
"⚠️ Can not acquire write lock to the provider, ns {} not removed",
|
||||||
self.name
|
self.name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
trace!("⚠️ leaking ns {ns_name} in cluster");
|
trace!("⚠️ leaking ns {ns_name} in cluster");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+522
-545
File diff suppressed because it is too large
Load Diff
+107
-116
@@ -1,7 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@@ -10,152 +10,143 @@ use tokio::sync::RwLock;
|
|||||||
|
|
||||||
use super::{client::DockerClient, namespace::DockerNamespace};
|
use super::{client::DockerClient, namespace::DockerNamespace};
|
||||||
use crate::{
|
use crate::{
|
||||||
shared::helpers::extract_namespace_info, types::ProviderCapabilities, DynNamespace, Provider,
|
shared::helpers::extract_namespace_info, types::ProviderCapabilities, DynNamespace, Provider,
|
||||||
ProviderError, ProviderNamespace,
|
ProviderError, ProviderNamespace,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PROVIDER_NAME: &str = "docker";
|
pub const PROVIDER_NAME: &str = "docker";
|
||||||
|
|
||||||
pub struct DockerProvider<FS>
|
pub struct DockerProvider<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone,
|
FS: FileSystem + Send + Sync + Clone,
|
||||||
{
|
{
|
||||||
weak: Weak<DockerProvider<FS>>,
|
weak: Weak<DockerProvider<FS>>,
|
||||||
capabilities: ProviderCapabilities,
|
capabilities: ProviderCapabilities,
|
||||||
tmp_dir: PathBuf,
|
tmp_dir: PathBuf,
|
||||||
docker_client: DockerClient,
|
docker_client: DockerClient,
|
||||||
filesystem: FS,
|
filesystem: FS,
|
||||||
pub(super) namespaces: RwLock<HashMap<String, Arc<DockerNamespace<FS>>>>,
|
pub(super) namespaces: RwLock<HashMap<String, Arc<DockerNamespace<FS>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FS> DockerProvider<FS>
|
impl<FS> DockerProvider<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||||
{
|
{
|
||||||
pub async fn new(filesystem: FS) -> Arc<Self> {
|
pub async fn new(filesystem: FS) -> Arc<Self> {
|
||||||
let docker_client = DockerClient::new().await.unwrap();
|
let docker_client = DockerClient::new().await.unwrap();
|
||||||
|
|
||||||
let provider = Arc::new_cyclic(|weak| DockerProvider {
|
let provider = Arc::new_cyclic(|weak| DockerProvider {
|
||||||
weak: weak.clone(),
|
weak: weak.clone(),
|
||||||
capabilities: ProviderCapabilities {
|
capabilities: ProviderCapabilities {
|
||||||
requires_image: true,
|
requires_image: true,
|
||||||
has_resources: false,
|
has_resources: false,
|
||||||
prefix_with_full_path: false,
|
prefix_with_full_path: false,
|
||||||
use_default_ports_in_cmd: true,
|
use_default_ports_in_cmd: true,
|
||||||
},
|
},
|
||||||
tmp_dir: std::env::temp_dir(),
|
tmp_dir: std::env::temp_dir(),
|
||||||
docker_client,
|
docker_client,
|
||||||
filesystem,
|
filesystem,
|
||||||
namespaces: RwLock::new(HashMap::new()),
|
namespaces: RwLock::new(HashMap::new()),
|
||||||
});
|
});
|
||||||
|
|
||||||
let cloned_provider = provider.clone();
|
let cloned_provider = provider.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
tokio::signal::ctrl_c().await.unwrap();
|
tokio::signal::ctrl_c().await.unwrap();
|
||||||
for (_, ns) in cloned_provider.namespaces().await {
|
for (_, ns) in cloned_provider.namespaces().await {
|
||||||
if ns.is_detached().await {
|
if ns.is_detached().await {
|
||||||
// best effort
|
// best effort
|
||||||
let _ = ns.destroy().await;
|
let _ = ns.destroy().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// exit the process (130, SIGINT)
|
// exit the process (130, SIGINT)
|
||||||
std::process::exit(130)
|
std::process::exit(130)
|
||||||
});
|
});
|
||||||
|
|
||||||
provider
|
provider
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tmp_dir(mut self, tmp_dir: impl Into<PathBuf>) -> Self {
|
pub fn tmp_dir(mut self, tmp_dir: impl Into<PathBuf>) -> Self {
|
||||||
self.tmp_dir = tmp_dir.into();
|
self.tmp_dir = tmp_dir.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<FS> Provider for DockerProvider<FS>
|
impl<FS> Provider for DockerProvider<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
PROVIDER_NAME
|
PROVIDER_NAME
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capabilities(&self) -> &ProviderCapabilities {
|
fn capabilities(&self) -> &ProviderCapabilities {
|
||||||
&self.capabilities
|
&self.capabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn namespaces(&self) -> HashMap<String, DynNamespace> {
|
async fn namespaces(&self) -> HashMap<String, DynNamespace> {
|
||||||
self.namespaces
|
self.namespaces
|
||||||
.read()
|
.read()
|
||||||
.await
|
.await
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, namespace)| (name.clone(), namespace.clone() as DynNamespace))
|
.map(|(name, namespace)| (name.clone(), namespace.clone() as DynNamespace))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_namespace(&self) -> Result<DynNamespace, ProviderError> {
|
async fn create_namespace(&self) -> Result<DynNamespace, ProviderError> {
|
||||||
let namespace = DockerNamespace::new(
|
let namespace = DockerNamespace::new(
|
||||||
&self.weak,
|
&self.weak,
|
||||||
&self.tmp_dir,
|
&self.tmp_dir,
|
||||||
&self.capabilities,
|
&self.capabilities,
|
||||||
&self.docker_client,
|
&self.docker_client,
|
||||||
&self.filesystem,
|
&self.filesystem,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.namespaces
|
self.namespaces.write().await.insert(namespace.name().to_string(), namespace.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(namespace.name().to_string(), namespace.clone());
|
|
||||||
|
|
||||||
Ok(namespace)
|
Ok(namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_namespace_with_base_dir(
|
async fn create_namespace_with_base_dir(
|
||||||
&self,
|
&self,
|
||||||
base_dir: &Path,
|
base_dir: &Path,
|
||||||
) -> Result<DynNamespace, ProviderError> {
|
) -> Result<DynNamespace, ProviderError> {
|
||||||
let namespace = DockerNamespace::new(
|
let namespace = DockerNamespace::new(
|
||||||
&self.weak,
|
&self.weak,
|
||||||
&self.tmp_dir,
|
&self.tmp_dir,
|
||||||
&self.capabilities,
|
&self.capabilities,
|
||||||
&self.docker_client,
|
&self.docker_client,
|
||||||
&self.filesystem,
|
&self.filesystem,
|
||||||
Some(base_dir),
|
Some(base_dir),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.namespaces
|
self.namespaces.write().await.insert(namespace.name().to_string(), namespace.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(namespace.name().to_string(), namespace.clone());
|
|
||||||
|
|
||||||
Ok(namespace)
|
Ok(namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_namespace_from_json(
|
async fn create_namespace_from_json(
|
||||||
&self,
|
&self,
|
||||||
json_value: &serde_json::Value,
|
json_value: &serde_json::Value,
|
||||||
) -> Result<DynNamespace, ProviderError> {
|
) -> Result<DynNamespace, ProviderError> {
|
||||||
let (base_dir, name) = extract_namespace_info(json_value)?;
|
let (base_dir, name) = extract_namespace_info(json_value)?;
|
||||||
|
|
||||||
let namespace = DockerNamespace::attach_to_live(
|
let namespace = DockerNamespace::attach_to_live(
|
||||||
&self.weak,
|
&self.weak,
|
||||||
&self.capabilities,
|
&self.capabilities,
|
||||||
&self.docker_client,
|
&self.docker_client,
|
||||||
&self.filesystem,
|
&self.filesystem,
|
||||||
&base_dir,
|
&base_dir,
|
||||||
&name,
|
&name,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.namespaces
|
self.namespaces.write().await.insert(namespace.name().to_string(), namespace.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(namespace.name().to_string(), namespace.clone());
|
|
||||||
|
|
||||||
Ok(namespace)
|
Ok(namespace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+433
-473
File diff suppressed because it is too large
Load Diff
+432
-482
File diff suppressed because it is too large
Load Diff
+695
-730
File diff suppressed because it is too large
Load Diff
+142
-154
@@ -2,76 +2,73 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
use configuration::shared::resources::{ResourceQuantity, Resources};
|
use configuration::shared::resources::{ResourceQuantity, Resources};
|
||||||
use k8s_openapi::{
|
use k8s_openapi::{
|
||||||
api::core::v1::{
|
api::core::v1::{
|
||||||
ConfigMapVolumeSource, Container, EnvVar, PodSpec, ResourceRequirements, Volume,
|
ConfigMapVolumeSource, Container, EnvVar, PodSpec, ResourceRequirements, Volume,
|
||||||
VolumeMount,
|
VolumeMount,
|
||||||
},
|
},
|
||||||
apimachinery::pkg::api::resource::Quantity,
|
apimachinery::pkg::api::resource::Quantity,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) struct PodSpecBuilder;
|
pub(super) struct PodSpecBuilder;
|
||||||
|
|
||||||
impl PodSpecBuilder {
|
impl PodSpecBuilder {
|
||||||
pub(super) fn build(
|
pub(super) fn build(
|
||||||
name: &str,
|
name: &str,
|
||||||
image: &str,
|
image: &str,
|
||||||
resources: Option<&Resources>,
|
resources: Option<&Resources>,
|
||||||
program: &str,
|
program: &str,
|
||||||
args: &[String],
|
args: &[String],
|
||||||
env: &[(String, String)],
|
env: &[(String, String)],
|
||||||
) -> PodSpec {
|
) -> PodSpec {
|
||||||
PodSpec {
|
PodSpec {
|
||||||
hostname: Some(name.to_string()),
|
hostname: Some(name.to_string()),
|
||||||
init_containers: Some(vec![Self::build_helper_binaries_setup_container()]),
|
init_containers: Some(vec![Self::build_helper_binaries_setup_container()]),
|
||||||
containers: vec![Self::build_main_container(
|
containers: vec![Self::build_main_container(
|
||||||
name, image, resources, program, args, env,
|
name, image, resources, program, args, env,
|
||||||
)],
|
)],
|
||||||
volumes: Some(Self::build_volumes()),
|
volumes: Some(Self::build_volumes()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_main_container(
|
fn build_main_container(
|
||||||
name: &str,
|
name: &str,
|
||||||
image: &str,
|
image: &str,
|
||||||
resources: Option<&Resources>,
|
resources: Option<&Resources>,
|
||||||
program: &str,
|
program: &str,
|
||||||
args: &[String],
|
args: &[String],
|
||||||
env: &[(String, String)],
|
env: &[(String, String)],
|
||||||
) -> Container {
|
) -> Container {
|
||||||
Container {
|
Container {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
image: Some(image.to_string()),
|
image: Some(image.to_string()),
|
||||||
image_pull_policy: Some("Always".to_string()),
|
image_pull_policy: Some("Always".to_string()),
|
||||||
command: Some(
|
command: Some(
|
||||||
[
|
[vec!["/zombie-wrapper.sh".to_string(), program.to_string()], args.to_vec()]
|
||||||
vec!["/zombie-wrapper.sh".to_string(), program.to_string()],
|
.concat(),
|
||||||
args.to_vec(),
|
),
|
||||||
]
|
env: Some(
|
||||||
.concat(),
|
env.iter()
|
||||||
),
|
.map(|(name, value)| EnvVar {
|
||||||
env: Some(
|
name: name.clone(),
|
||||||
env.iter()
|
value: Some(value.clone()),
|
||||||
.map(|(name, value)| EnvVar {
|
value_from: None,
|
||||||
name: name.clone(),
|
})
|
||||||
value: Some(value.clone()),
|
.collect(),
|
||||||
value_from: None,
|
),
|
||||||
})
|
volume_mounts: Some(Self::build_volume_mounts(vec![VolumeMount {
|
||||||
.collect(),
|
name: "zombie-wrapper-volume".to_string(),
|
||||||
),
|
mount_path: "/zombie-wrapper.sh".to_string(),
|
||||||
volume_mounts: Some(Self::build_volume_mounts(vec![VolumeMount {
|
sub_path: Some("zombie-wrapper.sh".to_string()),
|
||||||
name: "zombie-wrapper-volume".to_string(),
|
..Default::default()
|
||||||
mount_path: "/zombie-wrapper.sh".to_string(),
|
}])),
|
||||||
sub_path: Some("zombie-wrapper.sh".to_string()),
|
resources: Self::build_resources_requirements(resources),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}])),
|
}
|
||||||
resources: Self::build_resources_requirements(resources),
|
}
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_helper_binaries_setup_container() -> Container {
|
fn build_helper_binaries_setup_container() -> Container {
|
||||||
Container {
|
Container {
|
||||||
name: "helper-binaries-setup".to_string(),
|
name: "helper-binaries-setup".to_string(),
|
||||||
image: Some("europe-west3-docker.pkg.dev/parity-zombienet/zombienet-public-images/alpine:latest".to_string()),
|
image: Some("europe-west3-docker.pkg.dev/parity-zombienet/zombienet-public-images/alpine:latest".to_string()),
|
||||||
image_pull_policy: Some("IfNotPresent".to_string()),
|
image_pull_policy: Some("IfNotPresent".to_string()),
|
||||||
@@ -87,102 +84,93 @@ impl PodSpecBuilder {
|
|||||||
]),
|
]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_volumes() -> Vec<Volume> {
|
fn build_volumes() -> Vec<Volume> {
|
||||||
vec![
|
vec![
|
||||||
Volume {
|
Volume { name: "cfg".to_string(), ..Default::default() },
|
||||||
name: "cfg".to_string(),
|
Volume { name: "data".to_string(), ..Default::default() },
|
||||||
..Default::default()
|
Volume { name: "relay-data".to_string(), ..Default::default() },
|
||||||
},
|
Volume {
|
||||||
Volume {
|
name: "zombie-wrapper-volume".to_string(),
|
||||||
name: "data".to_string(),
|
config_map: Some(ConfigMapVolumeSource {
|
||||||
..Default::default()
|
name: Some("zombie-wrapper".to_string()),
|
||||||
},
|
default_mode: Some(0o755),
|
||||||
Volume {
|
..Default::default()
|
||||||
name: "relay-data".to_string(),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Volume {
|
Volume {
|
||||||
name: "zombie-wrapper-volume".to_string(),
|
name: "helper-binaries-downloader-volume".to_string(),
|
||||||
config_map: Some(ConfigMapVolumeSource {
|
config_map: Some(ConfigMapVolumeSource {
|
||||||
name: Some("zombie-wrapper".to_string()),
|
name: Some("helper-binaries-downloader".to_string()),
|
||||||
default_mode: Some(0o755),
|
default_mode: Some(0o755),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Volume {
|
]
|
||||||
name: "helper-binaries-downloader-volume".to_string(),
|
}
|
||||||
config_map: Some(ConfigMapVolumeSource {
|
|
||||||
name: Some("helper-binaries-downloader".to_string()),
|
|
||||||
default_mode: Some(0o755),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_volume_mounts(non_default_mounts: Vec<VolumeMount>) -> Vec<VolumeMount> {
|
fn build_volume_mounts(non_default_mounts: Vec<VolumeMount>) -> Vec<VolumeMount> {
|
||||||
[
|
[
|
||||||
vec![
|
vec![
|
||||||
VolumeMount {
|
VolumeMount {
|
||||||
name: "cfg".to_string(),
|
name: "cfg".to_string(),
|
||||||
mount_path: "/cfg".to_string(),
|
mount_path: "/cfg".to_string(),
|
||||||
read_only: Some(false),
|
read_only: Some(false),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
VolumeMount {
|
VolumeMount {
|
||||||
name: "data".to_string(),
|
name: "data".to_string(),
|
||||||
mount_path: "/data".to_string(),
|
mount_path: "/data".to_string(),
|
||||||
read_only: Some(false),
|
read_only: Some(false),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
VolumeMount {
|
VolumeMount {
|
||||||
name: "relay-data".to_string(),
|
name: "relay-data".to_string(),
|
||||||
mount_path: "/relay-data".to_string(),
|
mount_path: "/relay-data".to_string(),
|
||||||
read_only: Some(false),
|
read_only: Some(false),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
non_default_mounts,
|
non_default_mounts,
|
||||||
]
|
]
|
||||||
.concat()
|
.concat()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_resources_requirements(resources: Option<&Resources>) -> Option<ResourceRequirements> {
|
fn build_resources_requirements(resources: Option<&Resources>) -> Option<ResourceRequirements> {
|
||||||
resources.map(|resources| ResourceRequirements {
|
resources.map(|resources| ResourceRequirements {
|
||||||
limits: Self::build_resources_requirements_quantities(
|
limits: Self::build_resources_requirements_quantities(
|
||||||
resources.limit_cpu(),
|
resources.limit_cpu(),
|
||||||
resources.limit_memory(),
|
resources.limit_memory(),
|
||||||
),
|
),
|
||||||
requests: Self::build_resources_requirements_quantities(
|
requests: Self::build_resources_requirements_quantities(
|
||||||
resources.request_cpu(),
|
resources.request_cpu(),
|
||||||
resources.request_memory(),
|
resources.request_memory(),
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_resources_requirements_quantities(
|
fn build_resources_requirements_quantities(
|
||||||
cpu: Option<&ResourceQuantity>,
|
cpu: Option<&ResourceQuantity>,
|
||||||
memory: Option<&ResourceQuantity>,
|
memory: Option<&ResourceQuantity>,
|
||||||
) -> Option<BTreeMap<String, Quantity>> {
|
) -> Option<BTreeMap<String, Quantity>> {
|
||||||
let mut quantities = BTreeMap::new();
|
let mut quantities = BTreeMap::new();
|
||||||
|
|
||||||
if let Some(cpu) = cpu {
|
if let Some(cpu) = cpu {
|
||||||
quantities.insert("cpu".to_string(), Quantity(cpu.as_str().to_string()));
|
quantities.insert("cpu".to_string(), Quantity(cpu.as_str().to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(memory) = memory {
|
if let Some(memory) = memory {
|
||||||
quantities.insert("memory".to_string(), Quantity(memory.as_str().to_string()));
|
quantities.insert("memory".to_string(), Quantity(memory.as_str().to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !quantities.is_empty() {
|
if !quantities.is_empty() {
|
||||||
Some(quantities)
|
Some(quantities)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@@ -10,136 +10,127 @@ use tokio::sync::RwLock;
|
|||||||
|
|
||||||
use super::{client::KubernetesClient, namespace::KubernetesNamespace};
|
use super::{client::KubernetesClient, namespace::KubernetesNamespace};
|
||||||
use crate::{
|
use crate::{
|
||||||
shared::helpers::extract_namespace_info, types::ProviderCapabilities, DynNamespace, Provider,
|
shared::helpers::extract_namespace_info, types::ProviderCapabilities, DynNamespace, Provider,
|
||||||
ProviderError, ProviderNamespace,
|
ProviderError, ProviderNamespace,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PROVIDER_NAME: &str = "k8s";
|
pub const PROVIDER_NAME: &str = "k8s";
|
||||||
|
|
||||||
pub struct KubernetesProvider<FS>
|
pub struct KubernetesProvider<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone,
|
FS: FileSystem + Send + Sync + Clone,
|
||||||
{
|
{
|
||||||
weak: Weak<KubernetesProvider<FS>>,
|
weak: Weak<KubernetesProvider<FS>>,
|
||||||
capabilities: ProviderCapabilities,
|
capabilities: ProviderCapabilities,
|
||||||
tmp_dir: PathBuf,
|
tmp_dir: PathBuf,
|
||||||
k8s_client: KubernetesClient,
|
k8s_client: KubernetesClient,
|
||||||
filesystem: FS,
|
filesystem: FS,
|
||||||
pub(super) namespaces: RwLock<HashMap<String, Arc<KubernetesNamespace<FS>>>>,
|
pub(super) namespaces: RwLock<HashMap<String, Arc<KubernetesNamespace<FS>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FS> KubernetesProvider<FS>
|
impl<FS> KubernetesProvider<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone,
|
FS: FileSystem + Send + Sync + Clone,
|
||||||
{
|
{
|
||||||
pub async fn new(filesystem: FS) -> Arc<Self> {
|
pub async fn new(filesystem: FS) -> Arc<Self> {
|
||||||
let k8s_client = KubernetesClient::new().await.unwrap();
|
let k8s_client = KubernetesClient::new().await.unwrap();
|
||||||
|
|
||||||
Arc::new_cyclic(|weak| KubernetesProvider {
|
Arc::new_cyclic(|weak| KubernetesProvider {
|
||||||
weak: weak.clone(),
|
weak: weak.clone(),
|
||||||
capabilities: ProviderCapabilities {
|
capabilities: ProviderCapabilities {
|
||||||
requires_image: true,
|
requires_image: true,
|
||||||
has_resources: true,
|
has_resources: true,
|
||||||
prefix_with_full_path: false,
|
prefix_with_full_path: false,
|
||||||
use_default_ports_in_cmd: true,
|
use_default_ports_in_cmd: true,
|
||||||
},
|
},
|
||||||
tmp_dir: std::env::temp_dir(),
|
tmp_dir: std::env::temp_dir(),
|
||||||
k8s_client,
|
k8s_client,
|
||||||
filesystem,
|
filesystem,
|
||||||
namespaces: RwLock::new(HashMap::new()),
|
namespaces: RwLock::new(HashMap::new()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tmp_dir(mut self, tmp_dir: impl Into<PathBuf>) -> Self {
|
pub fn tmp_dir(mut self, tmp_dir: impl Into<PathBuf>) -> Self {
|
||||||
self.tmp_dir = tmp_dir.into();
|
self.tmp_dir = tmp_dir.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<FS> Provider for KubernetesProvider<FS>
|
impl<FS> Provider for KubernetesProvider<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
PROVIDER_NAME
|
PROVIDER_NAME
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capabilities(&self) -> &ProviderCapabilities {
|
fn capabilities(&self) -> &ProviderCapabilities {
|
||||||
&self.capabilities
|
&self.capabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn namespaces(&self) -> HashMap<String, DynNamespace> {
|
async fn namespaces(&self) -> HashMap<String, DynNamespace> {
|
||||||
self.namespaces
|
self.namespaces
|
||||||
.read()
|
.read()
|
||||||
.await
|
.await
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, namespace)| (name.clone(), namespace.clone() as DynNamespace))
|
.map(|(name, namespace)| (name.clone(), namespace.clone() as DynNamespace))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_namespace(&self) -> Result<DynNamespace, ProviderError> {
|
async fn create_namespace(&self) -> Result<DynNamespace, ProviderError> {
|
||||||
let namespace = KubernetesNamespace::new(
|
let namespace = KubernetesNamespace::new(
|
||||||
&self.weak,
|
&self.weak,
|
||||||
&self.tmp_dir,
|
&self.tmp_dir,
|
||||||
&self.capabilities,
|
&self.capabilities,
|
||||||
&self.k8s_client,
|
&self.k8s_client,
|
||||||
&self.filesystem,
|
&self.filesystem,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.namespaces
|
self.namespaces.write().await.insert(namespace.name().to_string(), namespace.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(namespace.name().to_string(), namespace.clone());
|
|
||||||
|
|
||||||
Ok(namespace)
|
Ok(namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_namespace_with_base_dir(
|
async fn create_namespace_with_base_dir(
|
||||||
&self,
|
&self,
|
||||||
base_dir: &Path,
|
base_dir: &Path,
|
||||||
) -> Result<DynNamespace, ProviderError> {
|
) -> Result<DynNamespace, ProviderError> {
|
||||||
let namespace = KubernetesNamespace::new(
|
let namespace = KubernetesNamespace::new(
|
||||||
&self.weak,
|
&self.weak,
|
||||||
&self.tmp_dir,
|
&self.tmp_dir,
|
||||||
&self.capabilities,
|
&self.capabilities,
|
||||||
&self.k8s_client,
|
&self.k8s_client,
|
||||||
&self.filesystem,
|
&self.filesystem,
|
||||||
Some(base_dir),
|
Some(base_dir),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.namespaces
|
self.namespaces.write().await.insert(namespace.name().to_string(), namespace.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(namespace.name().to_string(), namespace.clone());
|
|
||||||
|
|
||||||
Ok(namespace)
|
Ok(namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_namespace_from_json(
|
async fn create_namespace_from_json(
|
||||||
&self,
|
&self,
|
||||||
json_value: &serde_json::Value,
|
json_value: &serde_json::Value,
|
||||||
) -> Result<DynNamespace, ProviderError> {
|
) -> Result<DynNamespace, ProviderError> {
|
||||||
let (base_dir, name) = extract_namespace_info(json_value)?;
|
let (base_dir, name) = extract_namespace_info(json_value)?;
|
||||||
|
|
||||||
let namespace = KubernetesNamespace::attach_to_live(
|
let namespace = KubernetesNamespace::attach_to_live(
|
||||||
&self.weak,
|
&self.weak,
|
||||||
&self.capabilities,
|
&self.capabilities,
|
||||||
&self.k8s_client,
|
&self.k8s_client,
|
||||||
&self.filesystem,
|
&self.filesystem,
|
||||||
&base_dir,
|
&base_dir,
|
||||||
&name,
|
&name,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.namespaces
|
self.namespaces.write().await.insert(namespace.name().to_string(), namespace.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(namespace.name().to_string(), namespace.clone());
|
|
||||||
|
|
||||||
Ok(namespace)
|
Ok(namespace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+154
-154
@@ -5,253 +5,253 @@ mod native;
|
|||||||
pub mod shared;
|
pub mod shared;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
net::IpAddr,
|
net::IpAddr,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use shared::{
|
use shared::{
|
||||||
constants::LOCALHOST,
|
constants::LOCALHOST,
|
||||||
types::{
|
types::{
|
||||||
ExecutionResult, GenerateFilesOptions, ProviderCapabilities, RunCommandOptions,
|
ExecutionResult, GenerateFilesOptions, ProviderCapabilities, RunCommandOptions,
|
||||||
RunScriptOptions, SpawnNodeOptions,
|
RunScriptOptions, SpawnNodeOptions,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use support::fs::FileSystemError;
|
use support::fs::FileSystemError;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub enum ProviderError {
|
pub enum ProviderError {
|
||||||
#[error("Failed to create client '{0}': {1}")]
|
#[error("Failed to create client '{0}': {1}")]
|
||||||
CreateClientFailed(String, anyhow::Error),
|
CreateClientFailed(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Failed to create namespace '{0}': {1}")]
|
#[error("Failed to create namespace '{0}': {1}")]
|
||||||
CreateNamespaceFailed(String, anyhow::Error),
|
CreateNamespaceFailed(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Failed to spawn node '{0}': {1}")]
|
#[error("Failed to spawn node '{0}': {1}")]
|
||||||
NodeSpawningFailed(String, anyhow::Error),
|
NodeSpawningFailed(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Error running command '{0}' {1}: {2}")]
|
#[error("Error running command '{0}' {1}: {2}")]
|
||||||
RunCommandError(String, String, anyhow::Error),
|
RunCommandError(String, String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Error running script'{0}': {1}")]
|
#[error("Error running script'{0}': {1}")]
|
||||||
RunScriptError(String, anyhow::Error),
|
RunScriptError(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Invalid network configuration field {0}")]
|
#[error("Invalid network configuration field {0}")]
|
||||||
InvalidConfig(String),
|
InvalidConfig(String),
|
||||||
|
|
||||||
#[error("Failed to retrieve node available args using image {0} and command {1}: {2}")]
|
#[error("Failed to retrieve node available args using image {0} and command {1}: {2}")]
|
||||||
NodeAvailableArgsError(String, String, String),
|
NodeAvailableArgsError(String, String, String),
|
||||||
|
|
||||||
#[error("Can not recover node: {0}")]
|
#[error("Can not recover node: {0}")]
|
||||||
MissingNode(String),
|
MissingNode(String),
|
||||||
|
|
||||||
#[error("Can not recover node: {0} info, field: {1}")]
|
#[error("Can not recover node: {0} info, field: {1}")]
|
||||||
MissingNodeInfo(String, String),
|
MissingNodeInfo(String, String),
|
||||||
|
|
||||||
#[error("File generation failed: {0}")]
|
#[error("File generation failed: {0}")]
|
||||||
FileGenerationFailed(anyhow::Error),
|
FileGenerationFailed(anyhow::Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
FileSystemError(#[from] FileSystemError),
|
FileSystemError(#[from] FileSystemError),
|
||||||
|
|
||||||
#[error("Invalid script path for {0}")]
|
#[error("Invalid script path for {0}")]
|
||||||
InvalidScriptPath(anyhow::Error),
|
InvalidScriptPath(anyhow::Error),
|
||||||
|
|
||||||
#[error("Script with path {0} not found")]
|
#[error("Script with path {0} not found")]
|
||||||
ScriptNotFound(PathBuf),
|
ScriptNotFound(PathBuf),
|
||||||
|
|
||||||
#[error("Failed to retrieve process ID for node '{0}'")]
|
#[error("Failed to retrieve process ID for node '{0}'")]
|
||||||
ProcessIdRetrievalFailed(String),
|
ProcessIdRetrievalFailed(String),
|
||||||
|
|
||||||
#[error("Failed to pause node '{0}': {1}")]
|
#[error("Failed to pause node '{0}': {1}")]
|
||||||
PauseNodeFailed(String, anyhow::Error),
|
PauseNodeFailed(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Failed to resume node '{0}': {1}")]
|
#[error("Failed to resume node '{0}': {1}")]
|
||||||
ResumeNodeFailed(String, anyhow::Error),
|
ResumeNodeFailed(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Failed to kill node '{0}': {1}")]
|
#[error("Failed to kill node '{0}': {1}")]
|
||||||
KillNodeFailed(String, anyhow::Error),
|
KillNodeFailed(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Failed to restart node '{0}': {1}")]
|
#[error("Failed to restart node '{0}': {1}")]
|
||||||
RestartNodeFailed(String, anyhow::Error),
|
RestartNodeFailed(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Failed to destroy node '{0}': {1}")]
|
#[error("Failed to destroy node '{0}': {1}")]
|
||||||
DestroyNodeFailed(String, anyhow::Error),
|
DestroyNodeFailed(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Failed to get logs for node '{0}': {1}")]
|
#[error("Failed to get logs for node '{0}': {1}")]
|
||||||
GetLogsFailed(String, anyhow::Error),
|
GetLogsFailed(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Failed to dump logs for node '{0}': {1}")]
|
#[error("Failed to dump logs for node '{0}': {1}")]
|
||||||
DumpLogsFailed(String, anyhow::Error),
|
DumpLogsFailed(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Failed to copy file from node '{0}': {1}")]
|
#[error("Failed to copy file from node '{0}': {1}")]
|
||||||
CopyFileFromNodeError(String, anyhow::Error),
|
CopyFileFromNodeError(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Failed to setup fileserver: {0}")]
|
#[error("Failed to setup fileserver: {0}")]
|
||||||
FileServerSetupError(anyhow::Error),
|
FileServerSetupError(anyhow::Error),
|
||||||
|
|
||||||
#[error("Error uploading file: '{0}': {1}")]
|
#[error("Error uploading file: '{0}': {1}")]
|
||||||
UploadFile(String, anyhow::Error),
|
UploadFile(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Error downloading file: '{0}': {1}")]
|
#[error("Error downloading file: '{0}': {1}")]
|
||||||
DownloadFile(String, anyhow::Error),
|
DownloadFile(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Error sending file '{0}' to {1}: {2}")]
|
#[error("Error sending file '{0}' to {1}: {2}")]
|
||||||
SendFile(String, String, anyhow::Error),
|
SendFile(String, String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Error creating port-forward '{0}:{1}': {2}")]
|
#[error("Error creating port-forward '{0}:{1}': {2}")]
|
||||||
PortForwardError(u16, u16, anyhow::Error),
|
PortForwardError(u16, u16, anyhow::Error),
|
||||||
|
|
||||||
#[error("Failed to delete namespace '{0}': {1}")]
|
#[error("Failed to delete namespace '{0}': {1}")]
|
||||||
DeleteNamespaceFailed(String, anyhow::Error),
|
DeleteNamespaceFailed(String, anyhow::Error),
|
||||||
|
|
||||||
#[error("Serialization error")]
|
#[error("Serialization error")]
|
||||||
SerializationError(#[from] serde_json::Error),
|
SerializationError(#[from] serde_json::Error),
|
||||||
|
|
||||||
#[error("Failed to acquire lock: {0}")]
|
#[error("Failed to acquire lock: {0}")]
|
||||||
FailedToAcquireLock(String),
|
FailedToAcquireLock(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Provider {
|
pub trait Provider {
|
||||||
fn name(&self) -> &str;
|
fn name(&self) -> &str;
|
||||||
|
|
||||||
fn capabilities(&self) -> &ProviderCapabilities;
|
fn capabilities(&self) -> &ProviderCapabilities;
|
||||||
|
|
||||||
async fn namespaces(&self) -> HashMap<String, DynNamespace>;
|
async fn namespaces(&self) -> HashMap<String, DynNamespace>;
|
||||||
|
|
||||||
async fn create_namespace(&self) -> Result<DynNamespace, ProviderError>;
|
async fn create_namespace(&self) -> Result<DynNamespace, ProviderError>;
|
||||||
|
|
||||||
async fn create_namespace_with_base_dir(
|
async fn create_namespace_with_base_dir(
|
||||||
&self,
|
&self,
|
||||||
base_dir: &Path,
|
base_dir: &Path,
|
||||||
) -> Result<DynNamespace, ProviderError>;
|
) -> Result<DynNamespace, ProviderError>;
|
||||||
|
|
||||||
async fn create_namespace_from_json(
|
async fn create_namespace_from_json(
|
||||||
&self,
|
&self,
|
||||||
json_value: &serde_json::Value,
|
json_value: &serde_json::Value,
|
||||||
) -> Result<DynNamespace, ProviderError>;
|
) -> Result<DynNamespace, ProviderError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DynProvider = Arc<dyn Provider + Send + Sync>;
|
pub type DynProvider = Arc<dyn Provider + Send + Sync>;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ProviderNamespace {
|
pub trait ProviderNamespace {
|
||||||
fn name(&self) -> &str;
|
fn name(&self) -> &str;
|
||||||
|
|
||||||
fn base_dir(&self) -> &PathBuf;
|
fn base_dir(&self) -> &PathBuf;
|
||||||
|
|
||||||
fn capabilities(&self) -> &ProviderCapabilities;
|
fn capabilities(&self) -> &ProviderCapabilities;
|
||||||
|
|
||||||
fn provider_name(&self) -> &str;
|
fn provider_name(&self) -> &str;
|
||||||
|
|
||||||
async fn detach(&self) {
|
async fn detach(&self) {
|
||||||
// noop by default
|
// noop by default
|
||||||
warn!("Detach is not implemented for {}", self.name());
|
warn!("Detach is not implemented for {}", self.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn is_detached(&self) -> bool {
|
async fn is_detached(&self) -> bool {
|
||||||
// false by default
|
// false by default
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn nodes(&self) -> HashMap<String, DynNode>;
|
async fn nodes(&self) -> HashMap<String, DynNode>;
|
||||||
|
|
||||||
async fn get_node_available_args(
|
async fn get_node_available_args(
|
||||||
&self,
|
&self,
|
||||||
options: (String, Option<String>),
|
options: (String, Option<String>),
|
||||||
) -> Result<String, ProviderError>;
|
) -> Result<String, ProviderError>;
|
||||||
|
|
||||||
async fn spawn_node(&self, options: &SpawnNodeOptions) -> Result<DynNode, ProviderError>;
|
async fn spawn_node(&self, options: &SpawnNodeOptions) -> Result<DynNode, ProviderError>;
|
||||||
|
|
||||||
async fn spawn_node_from_json(
|
async fn spawn_node_from_json(
|
||||||
&self,
|
&self,
|
||||||
json_value: &serde_json::Value,
|
json_value: &serde_json::Value,
|
||||||
) -> Result<DynNode, ProviderError>;
|
) -> Result<DynNode, ProviderError>;
|
||||||
|
|
||||||
async fn generate_files(&self, options: GenerateFilesOptions) -> Result<(), ProviderError>;
|
async fn generate_files(&self, options: GenerateFilesOptions) -> Result<(), ProviderError>;
|
||||||
|
|
||||||
async fn destroy(&self) -> Result<(), ProviderError>;
|
async fn destroy(&self) -> Result<(), ProviderError>;
|
||||||
|
|
||||||
async fn static_setup(&self) -> Result<(), ProviderError>;
|
async fn static_setup(&self) -> Result<(), ProviderError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DynNamespace = Arc<dyn ProviderNamespace + Send + Sync>;
|
pub type DynNamespace = Arc<dyn ProviderNamespace + Send + Sync>;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ProviderNode: erased_serde::Serialize {
|
pub trait ProviderNode: erased_serde::Serialize {
|
||||||
fn name(&self) -> &str;
|
fn name(&self) -> &str;
|
||||||
|
|
||||||
fn args(&self) -> Vec<&str>;
|
fn args(&self) -> Vec<&str>;
|
||||||
|
|
||||||
fn base_dir(&self) -> &PathBuf;
|
fn base_dir(&self) -> &PathBuf;
|
||||||
|
|
||||||
fn config_dir(&self) -> &PathBuf;
|
fn config_dir(&self) -> &PathBuf;
|
||||||
|
|
||||||
fn data_dir(&self) -> &PathBuf;
|
fn data_dir(&self) -> &PathBuf;
|
||||||
|
|
||||||
fn relay_data_dir(&self) -> &PathBuf;
|
fn relay_data_dir(&self) -> &PathBuf;
|
||||||
|
|
||||||
fn scripts_dir(&self) -> &PathBuf;
|
fn scripts_dir(&self) -> &PathBuf;
|
||||||
|
|
||||||
fn log_path(&self) -> &PathBuf;
|
fn log_path(&self) -> &PathBuf;
|
||||||
|
|
||||||
fn log_cmd(&self) -> String;
|
fn log_cmd(&self) -> String;
|
||||||
|
|
||||||
// Return the absolute path to the file in the `node` perspective
|
// Return the absolute path to the file in the `node` perspective
|
||||||
// TODO: purpose?
|
// TODO: purpose?
|
||||||
fn path_in_node(&self, file: &Path) -> PathBuf;
|
fn path_in_node(&self, file: &Path) -> PathBuf;
|
||||||
|
|
||||||
async fn logs(&self) -> Result<String, ProviderError>;
|
async fn logs(&self) -> Result<String, ProviderError>;
|
||||||
|
|
||||||
async fn dump_logs(&self, local_dest: PathBuf) -> Result<(), ProviderError>;
|
async fn dump_logs(&self, local_dest: PathBuf) -> Result<(), ProviderError>;
|
||||||
|
|
||||||
// By default return localhost, should be overrided for k8s
|
// By default return localhost, should be overrided for k8s
|
||||||
async fn ip(&self) -> Result<IpAddr, ProviderError> {
|
async fn ip(&self) -> Result<IpAddr, ProviderError> {
|
||||||
Ok(LOCALHOST)
|
Ok(LOCALHOST)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Noop by default (native/docker provider)
|
// Noop by default (native/docker provider)
|
||||||
async fn create_port_forward(
|
async fn create_port_forward(
|
||||||
&self,
|
&self,
|
||||||
_local_port: u16,
|
_local_port: u16,
|
||||||
_remote_port: u16,
|
_remote_port: u16,
|
||||||
) -> Result<Option<u16>, ProviderError> {
|
) -> Result<Option<u16>, ProviderError> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_command(
|
async fn run_command(
|
||||||
&self,
|
&self,
|
||||||
options: RunCommandOptions,
|
options: RunCommandOptions,
|
||||||
) -> Result<ExecutionResult, ProviderError>;
|
) -> Result<ExecutionResult, ProviderError>;
|
||||||
|
|
||||||
async fn run_script(&self, options: RunScriptOptions)
|
async fn run_script(&self, options: RunScriptOptions)
|
||||||
-> Result<ExecutionResult, ProviderError>;
|
-> Result<ExecutionResult, ProviderError>;
|
||||||
|
|
||||||
async fn send_file(
|
async fn send_file(
|
||||||
&self,
|
&self,
|
||||||
local_file_path: &Path,
|
local_file_path: &Path,
|
||||||
remote_file_path: &Path,
|
remote_file_path: &Path,
|
||||||
mode: &str,
|
mode: &str,
|
||||||
) -> Result<(), ProviderError>;
|
) -> Result<(), ProviderError>;
|
||||||
|
|
||||||
async fn receive_file(
|
async fn receive_file(
|
||||||
&self,
|
&self,
|
||||||
remote_file_path: &Path,
|
remote_file_path: &Path,
|
||||||
local_file_path: &Path,
|
local_file_path: &Path,
|
||||||
) -> Result<(), ProviderError>;
|
) -> Result<(), ProviderError>;
|
||||||
|
|
||||||
async fn pause(&self) -> Result<(), ProviderError>;
|
async fn pause(&self) -> Result<(), ProviderError>;
|
||||||
|
|
||||||
async fn resume(&self) -> Result<(), ProviderError>;
|
async fn resume(&self) -> Result<(), ProviderError>;
|
||||||
|
|
||||||
async fn restart(&self, after: Option<Duration>) -> Result<(), ProviderError>;
|
async fn restart(&self, after: Option<Duration>) -> Result<(), ProviderError>;
|
||||||
|
|
||||||
async fn destroy(&self) -> Result<(), ProviderError>;
|
async fn destroy(&self) -> Result<(), ProviderError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DynNode = Arc<dyn ProviderNode + Send + Sync>;
|
pub type DynNode = Arc<dyn ProviderNode + Send + Sync>;
|
||||||
|
|||||||
+273
-299
@@ -1,7 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@@ -12,363 +12,337 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use super::node::{NativeNode, NativeNodeOptions};
|
use super::node::{NativeNode, NativeNodeOptions};
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::NAMESPACE_PREFIX,
|
constants::NAMESPACE_PREFIX,
|
||||||
native::{node::DeserializableNativeNodeOptions, provider},
|
native::{node::DeserializableNativeNodeOptions, provider},
|
||||||
shared::helpers::extract_execution_result,
|
shared::helpers::extract_execution_result,
|
||||||
types::{
|
types::{
|
||||||
GenerateFileCommand, GenerateFilesOptions, ProviderCapabilities, RunCommandOptions,
|
GenerateFileCommand, GenerateFilesOptions, ProviderCapabilities, RunCommandOptions,
|
||||||
SpawnNodeOptions,
|
SpawnNodeOptions,
|
||||||
},
|
},
|
||||||
DynNode, NativeProvider, ProviderError, ProviderNamespace, ProviderNode,
|
DynNode, NativeProvider, ProviderError, ProviderNamespace, ProviderNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) struct NativeNamespace<FS>
|
pub(super) struct NativeNamespace<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone,
|
FS: FileSystem + Send + Sync + Clone,
|
||||||
{
|
{
|
||||||
weak: Weak<NativeNamespace<FS>>,
|
weak: Weak<NativeNamespace<FS>>,
|
||||||
name: String,
|
name: String,
|
||||||
provider: Weak<NativeProvider<FS>>,
|
provider: Weak<NativeProvider<FS>>,
|
||||||
base_dir: PathBuf,
|
base_dir: PathBuf,
|
||||||
capabilities: ProviderCapabilities,
|
capabilities: ProviderCapabilities,
|
||||||
filesystem: FS,
|
filesystem: FS,
|
||||||
pub(super) nodes: RwLock<HashMap<String, Arc<NativeNode<FS>>>>,
|
pub(super) nodes: RwLock<HashMap<String, Arc<NativeNode<FS>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FS> NativeNamespace<FS>
|
impl<FS> NativeNamespace<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||||
{
|
{
|
||||||
pub(super) async fn new(
|
pub(super) async fn new(
|
||||||
provider: &Weak<NativeProvider<FS>>,
|
provider: &Weak<NativeProvider<FS>>,
|
||||||
tmp_dir: &PathBuf,
|
tmp_dir: &PathBuf,
|
||||||
capabilities: &ProviderCapabilities,
|
capabilities: &ProviderCapabilities,
|
||||||
filesystem: &FS,
|
filesystem: &FS,
|
||||||
custom_base_dir: Option<&Path>,
|
custom_base_dir: Option<&Path>,
|
||||||
) -> Result<Arc<Self>, ProviderError> {
|
) -> Result<Arc<Self>, ProviderError> {
|
||||||
let name = format!("{}{}", NAMESPACE_PREFIX, Uuid::new_v4());
|
let name = format!("{}{}", NAMESPACE_PREFIX, Uuid::new_v4());
|
||||||
let base_dir = if let Some(custom_base_dir) = custom_base_dir {
|
let base_dir = if let Some(custom_base_dir) = custom_base_dir {
|
||||||
if !filesystem.exists(custom_base_dir).await {
|
if !filesystem.exists(custom_base_dir).await {
|
||||||
filesystem.create_dir_all(custom_base_dir).await?;
|
filesystem.create_dir_all(custom_base_dir).await?;
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
"⚠️ Using and existing directory {} as base dir",
|
"⚠️ Using and existing directory {} as base dir",
|
||||||
custom_base_dir.to_string_lossy()
|
custom_base_dir.to_string_lossy()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
PathBuf::from(custom_base_dir)
|
PathBuf::from(custom_base_dir)
|
||||||
} else {
|
} else {
|
||||||
let base_dir = PathBuf::from_iter([tmp_dir, &PathBuf::from(&name)]);
|
let base_dir = PathBuf::from_iter([tmp_dir, &PathBuf::from(&name)]);
|
||||||
filesystem.create_dir(&base_dir).await?;
|
filesystem.create_dir(&base_dir).await?;
|
||||||
base_dir
|
base_dir
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Arc::new_cyclic(|weak| NativeNamespace {
|
Ok(Arc::new_cyclic(|weak| NativeNamespace {
|
||||||
weak: weak.clone(),
|
weak: weak.clone(),
|
||||||
provider: provider.clone(),
|
provider: provider.clone(),
|
||||||
name,
|
name,
|
||||||
base_dir,
|
base_dir,
|
||||||
capabilities: capabilities.clone(),
|
capabilities: capabilities.clone(),
|
||||||
filesystem: filesystem.clone(),
|
filesystem: filesystem.clone(),
|
||||||
nodes: RwLock::new(HashMap::new()),
|
nodes: RwLock::new(HashMap::new()),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn attach_to_live(
|
pub(super) async fn attach_to_live(
|
||||||
provider: &Weak<NativeProvider<FS>>,
|
provider: &Weak<NativeProvider<FS>>,
|
||||||
capabilities: &ProviderCapabilities,
|
capabilities: &ProviderCapabilities,
|
||||||
filesystem: &FS,
|
filesystem: &FS,
|
||||||
custom_base_dir: &Path,
|
custom_base_dir: &Path,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> Result<Arc<Self>, ProviderError> {
|
) -> Result<Arc<Self>, ProviderError> {
|
||||||
let base_dir = custom_base_dir.to_path_buf();
|
let base_dir = custom_base_dir.to_path_buf();
|
||||||
|
|
||||||
Ok(Arc::new_cyclic(|weak| NativeNamespace {
|
Ok(Arc::new_cyclic(|weak| NativeNamespace {
|
||||||
weak: weak.clone(),
|
weak: weak.clone(),
|
||||||
provider: provider.clone(),
|
provider: provider.clone(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
base_dir,
|
base_dir,
|
||||||
capabilities: capabilities.clone(),
|
capabilities: capabilities.clone(),
|
||||||
filesystem: filesystem.clone(),
|
filesystem: filesystem.clone(),
|
||||||
nodes: RwLock::new(HashMap::new()),
|
nodes: RwLock::new(HashMap::new()),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<FS> ProviderNamespace for NativeNamespace<FS>
|
impl<FS> ProviderNamespace for NativeNamespace<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base_dir(&self) -> &PathBuf {
|
fn base_dir(&self) -> &PathBuf {
|
||||||
&self.base_dir
|
&self.base_dir
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capabilities(&self) -> &ProviderCapabilities {
|
fn capabilities(&self) -> &ProviderCapabilities {
|
||||||
&self.capabilities
|
&self.capabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provider_name(&self) -> &str {
|
fn provider_name(&self) -> &str {
|
||||||
provider::PROVIDER_NAME
|
provider::PROVIDER_NAME
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn nodes(&self) -> HashMap<String, DynNode> {
|
async fn nodes(&self) -> HashMap<String, DynNode> {
|
||||||
self.nodes
|
self.nodes
|
||||||
.read()
|
.read()
|
||||||
.await
|
.await
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, node)| (name.clone(), node.clone() as DynNode))
|
.map(|(name, node)| (name.clone(), node.clone() as DynNode))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_node_available_args(
|
async fn get_node_available_args(
|
||||||
&self,
|
&self,
|
||||||
(command, _image): (String, Option<String>),
|
(command, _image): (String, Option<String>),
|
||||||
) -> Result<String, ProviderError> {
|
) -> Result<String, ProviderError> {
|
||||||
let temp_node = self
|
let temp_node = self
|
||||||
.spawn_node(
|
.spawn_node(
|
||||||
&SpawnNodeOptions::new(format!("temp-{}", Uuid::new_v4()), "bash".to_string())
|
&SpawnNodeOptions::new(format!("temp-{}", Uuid::new_v4()), "bash".to_string())
|
||||||
.args(vec!["-c", "while :; do sleep 1; done"]),
|
.args(vec!["-c", "while :; do sleep 1; done"]),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let available_args_output = temp_node
|
let available_args_output = temp_node
|
||||||
.run_command(RunCommandOptions::new(command.clone()).args(vec!["--help"]))
|
.run_command(RunCommandOptions::new(command.clone()).args(vec!["--help"]))
|
||||||
.await?
|
.await?
|
||||||
.map_err(|(_exit, status)| {
|
.map_err(|(_exit, status)| {
|
||||||
ProviderError::NodeAvailableArgsError("".to_string(), command, status)
|
ProviderError::NodeAvailableArgsError("".to_string(), command, status)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
temp_node.destroy().await?;
|
temp_node.destroy().await?;
|
||||||
|
|
||||||
Ok(available_args_output)
|
Ok(available_args_output)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_node(&self, options: &SpawnNodeOptions) -> Result<DynNode, ProviderError> {
|
async fn spawn_node(&self, options: &SpawnNodeOptions) -> Result<DynNode, ProviderError> {
|
||||||
trace!("spawn node options {options:?}");
|
trace!("spawn node options {options:?}");
|
||||||
|
|
||||||
let node = NativeNode::new(NativeNodeOptions {
|
let node = NativeNode::new(NativeNodeOptions {
|
||||||
namespace: &self.weak,
|
namespace: &self.weak,
|
||||||
namespace_base_dir: &self.base_dir,
|
namespace_base_dir: &self.base_dir,
|
||||||
name: &options.name,
|
name: &options.name,
|
||||||
program: &options.program,
|
program: &options.program,
|
||||||
args: &options.args,
|
args: &options.args,
|
||||||
env: &options.env,
|
env: &options.env,
|
||||||
startup_files: &options.injected_files,
|
startup_files: &options.injected_files,
|
||||||
created_paths: &options.created_paths,
|
created_paths: &options.created_paths,
|
||||||
db_snapshot: options.db_snapshot.as_ref(),
|
db_snapshot: options.db_snapshot.as_ref(),
|
||||||
filesystem: &self.filesystem,
|
filesystem: &self.filesystem,
|
||||||
node_log_path: options.node_log_path.as_ref(),
|
node_log_path: options.node_log_path.as_ref(),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.nodes
|
self.nodes.write().await.insert(options.name.clone(), node.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(options.name.clone(), node.clone());
|
|
||||||
|
|
||||||
Ok(node)
|
Ok(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_node_from_json(
|
async fn spawn_node_from_json(
|
||||||
&self,
|
&self,
|
||||||
json_value: &serde_json::Value,
|
json_value: &serde_json::Value,
|
||||||
) -> Result<DynNode, ProviderError> {
|
) -> Result<DynNode, ProviderError> {
|
||||||
let deserializable: DeserializableNativeNodeOptions =
|
let deserializable: DeserializableNativeNodeOptions =
|
||||||
serde_json::from_value(json_value.clone())?;
|
serde_json::from_value(json_value.clone())?;
|
||||||
let options = NativeNodeOptions::from_deserializable(
|
let options = NativeNodeOptions::from_deserializable(
|
||||||
&deserializable,
|
&deserializable,
|
||||||
&self.weak,
|
&self.weak,
|
||||||
&self.base_dir,
|
&self.base_dir,
|
||||||
&self.filesystem,
|
&self.filesystem,
|
||||||
);
|
);
|
||||||
|
|
||||||
let pid = json_value
|
let pid = json_value
|
||||||
.get("process_handle")
|
.get("process_handle")
|
||||||
.and_then(|v| v.as_i64())
|
.and_then(|v| v.as_i64())
|
||||||
.ok_or_else(|| ProviderError::InvalidConfig("Missing pid field".to_string()))?
|
.ok_or_else(|| ProviderError::InvalidConfig("Missing pid field".to_string()))?
|
||||||
as i32;
|
as i32;
|
||||||
let node = NativeNode::attach_to_live(options, pid).await?;
|
let node = NativeNode::attach_to_live(options, pid).await?;
|
||||||
|
|
||||||
self.nodes
|
self.nodes.write().await.insert(node.name().to_string(), node.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(node.name().to_string(), node.clone());
|
|
||||||
|
|
||||||
Ok(node)
|
Ok(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn generate_files(&self, options: GenerateFilesOptions) -> Result<(), ProviderError> {
|
async fn generate_files(&self, options: GenerateFilesOptions) -> Result<(), ProviderError> {
|
||||||
let node_name = if let Some(name) = options.temp_name {
|
let node_name = if let Some(name) = options.temp_name {
|
||||||
name
|
name
|
||||||
} else {
|
} else {
|
||||||
format!("temp-{}", Uuid::new_v4())
|
format!("temp-{}", Uuid::new_v4())
|
||||||
};
|
};
|
||||||
|
|
||||||
// we spawn a node doing nothing but looping so we can execute our commands
|
// we spawn a node doing nothing but looping so we can execute our commands
|
||||||
let temp_node = self
|
let temp_node = self
|
||||||
.spawn_node(
|
.spawn_node(
|
||||||
&SpawnNodeOptions::new(node_name, "bash".to_string())
|
&SpawnNodeOptions::new(node_name, "bash".to_string())
|
||||||
.args(vec!["-c", "while :; do sleep 1; done"])
|
.args(vec!["-c", "while :; do sleep 1; done"])
|
||||||
.injected_files(options.injected_files),
|
.injected_files(options.injected_files),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for GenerateFileCommand {
|
for GenerateFileCommand { program, args, env, local_output_path } in options.commands {
|
||||||
program,
|
trace!(
|
||||||
args,
|
"🏗 building file {:?} in path {} with command {} {}",
|
||||||
env,
|
local_output_path.as_os_str(),
|
||||||
local_output_path,
|
self.base_dir.to_string_lossy(),
|
||||||
} in options.commands
|
program,
|
||||||
{
|
args.join(" ")
|
||||||
trace!(
|
);
|
||||||
"🏗 building file {:?} in path {} with command {} {}",
|
let local_output_full_path = format!(
|
||||||
local_output_path.as_os_str(),
|
"{}{}{}",
|
||||||
self.base_dir.to_string_lossy(),
|
self.base_dir.to_string_lossy(),
|
||||||
program,
|
if local_output_path.starts_with("/") { "" } else { "/" },
|
||||||
args.join(" ")
|
local_output_path.to_string_lossy()
|
||||||
);
|
);
|
||||||
let local_output_full_path = format!(
|
|
||||||
"{}{}{}",
|
|
||||||
self.base_dir.to_string_lossy(),
|
|
||||||
if local_output_path.starts_with("/") {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
"/"
|
|
||||||
},
|
|
||||||
local_output_path.to_string_lossy()
|
|
||||||
);
|
|
||||||
|
|
||||||
let contents = extract_execution_result(
|
let contents = extract_execution_result(
|
||||||
&temp_node,
|
&temp_node,
|
||||||
RunCommandOptions { program, args, env },
|
RunCommandOptions { program, args, env },
|
||||||
options.expected_path.as_ref(),
|
options.expected_path.as_ref(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
self.filesystem
|
self.filesystem
|
||||||
.write(local_output_full_path, contents)
|
.write(local_output_full_path, contents)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ProviderError::FileGenerationFailed(err.into()))?;
|
.map_err(|err| ProviderError::FileGenerationFailed(err.into()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
temp_node.destroy().await
|
temp_node.destroy().await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn static_setup(&self) -> Result<(), ProviderError> {
|
async fn static_setup(&self) -> Result<(), ProviderError> {
|
||||||
// no static setup exists for native provider
|
// no static setup exists for native provider
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn destroy(&self) -> Result<(), ProviderError> {
|
async fn destroy(&self) -> Result<(), ProviderError> {
|
||||||
let mut names = vec![];
|
let mut names = vec![];
|
||||||
|
|
||||||
for node in self.nodes.read().await.values() {
|
for node in self.nodes.read().await.values() {
|
||||||
node.abort()
|
node.abort()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ProviderError::DestroyNodeFailed(node.name().to_string(), err))?;
|
.map_err(|err| ProviderError::DestroyNodeFailed(node.name().to_string(), err))?;
|
||||||
names.push(node.name().to_string());
|
names.push(node.name().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut nodes = self.nodes.write().await;
|
let mut nodes = self.nodes.write().await;
|
||||||
for name in names {
|
for name in names {
|
||||||
nodes.remove(&name);
|
nodes.remove(&name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(provider) = self.provider.upgrade() {
|
if let Some(provider) = self.provider.upgrade() {
|
||||||
provider.namespaces.write().await.remove(&self.name);
|
provider.namespaces.write().await.remove(&self.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use support::fs::local::LocalFileSystem;
|
use support::fs::local::LocalFileSystem;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{GenerateFileCommand, GenerateFilesOptions},
|
types::{GenerateFileCommand, GenerateFilesOptions},
|
||||||
NativeProvider, Provider,
|
NativeProvider, Provider,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn unique_temp_dir() -> PathBuf {
|
fn unique_temp_dir() -> PathBuf {
|
||||||
let mut base = std::env::temp_dir();
|
let mut base = std::env::temp_dir();
|
||||||
base.push(format!("znet_native_ns_test_{}", uuid::Uuid::new_v4()));
|
base.push(format!("znet_native_ns_test_{}", uuid::Uuid::new_v4()));
|
||||||
base
|
base
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn generate_files_uses_expected_path_when_provided() {
|
async fn generate_files_uses_expected_path_when_provided() {
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
let provider = NativeProvider::new(fs.clone());
|
let provider = NativeProvider::new(fs.clone());
|
||||||
let base_dir = unique_temp_dir();
|
let base_dir = unique_temp_dir();
|
||||||
// Namespace builder will create directory if needed
|
// Namespace builder will create directory if needed
|
||||||
let ns = provider
|
let ns = provider
|
||||||
.create_namespace_with_base_dir(&base_dir)
|
.create_namespace_with_base_dir(&base_dir)
|
||||||
.await
|
.await
|
||||||
.expect("namespace should be created");
|
.expect("namespace should be created");
|
||||||
|
|
||||||
// Create a unique on-host path that the native node will write to
|
// Create a unique on-host path that the native node will write to
|
||||||
let expected_path =
|
let expected_path =
|
||||||
std::env::temp_dir().join(format!("znet_expected_{}.json", uuid::Uuid::new_v4()));
|
std::env::temp_dir().join(format!("znet_expected_{}.json", uuid::Uuid::new_v4()));
|
||||||
|
|
||||||
// Command will write JSON into expected_path; stdout will be something else to ensure we don't read it
|
// Command will write JSON into expected_path; stdout will be something else to ensure we don't read it
|
||||||
let program = "bash".to_string();
|
let program = "bash".to_string();
|
||||||
let script = format!(
|
let script = format!(
|
||||||
"echo -n '{{\"hello\":\"world\"}}' > {} && echo should_not_be_used",
|
"echo -n '{{\"hello\":\"world\"}}' > {} && echo should_not_be_used",
|
||||||
expected_path.to_string_lossy()
|
expected_path.to_string_lossy()
|
||||||
);
|
);
|
||||||
let args: Vec<String> = vec!["-lc".into(), script];
|
let args: Vec<String> = vec!["-lc".into(), script];
|
||||||
|
|
||||||
let out_name = PathBuf::from("result_expected.json");
|
let out_name = PathBuf::from("result_expected.json");
|
||||||
let cmd = GenerateFileCommand::new(program, out_name.clone()).args(args);
|
let cmd = GenerateFileCommand::new(program, out_name.clone()).args(args);
|
||||||
let options = GenerateFilesOptions::new(vec![cmd], None, Some(expected_path.clone()));
|
let options = GenerateFilesOptions::new(vec![cmd], None, Some(expected_path.clone()));
|
||||||
|
|
||||||
ns.generate_files(options)
|
ns.generate_files(options).await.expect("generation should succeed");
|
||||||
.await
|
|
||||||
.expect("generation should succeed");
|
|
||||||
|
|
||||||
// Read produced file from namespace base_dir
|
// Read produced file from namespace base_dir
|
||||||
let produced_path = base_dir.join(out_name);
|
let produced_path = base_dir.join(out_name);
|
||||||
let produced = fs
|
let produced = fs.read_to_string(&produced_path).await.expect("should read produced file");
|
||||||
.read_to_string(&produced_path)
|
assert_eq!(produced, "{\"hello\":\"world\"}");
|
||||||
.await
|
}
|
||||||
.expect("should read produced file");
|
|
||||||
assert_eq!(produced, "{\"hello\":\"world\"}");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn generate_files_uses_stdout_when_expected_path_absent() {
|
async fn generate_files_uses_stdout_when_expected_path_absent() {
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
let provider = NativeProvider::new(fs.clone());
|
let provider = NativeProvider::new(fs.clone());
|
||||||
let base_dir = unique_temp_dir();
|
let base_dir = unique_temp_dir();
|
||||||
let ns = provider
|
let ns = provider
|
||||||
.create_namespace_with_base_dir(&base_dir)
|
.create_namespace_with_base_dir(&base_dir)
|
||||||
.await
|
.await
|
||||||
.expect("namespace should be created");
|
.expect("namespace should be created");
|
||||||
|
|
||||||
// Command prints to stdout only
|
// Command prints to stdout only
|
||||||
let program = "bash".to_string();
|
let program = "bash".to_string();
|
||||||
let args: Vec<String> = vec!["-lc".into(), "echo -n 42".into()];
|
let args: Vec<String> = vec!["-lc".into(), "echo -n 42".into()];
|
||||||
|
|
||||||
let out_name = PathBuf::from("result_stdout.txt");
|
let out_name = PathBuf::from("result_stdout.txt");
|
||||||
let cmd = GenerateFileCommand::new(program, out_name.clone()).args(args);
|
let cmd = GenerateFileCommand::new(program, out_name.clone()).args(args);
|
||||||
let options = GenerateFilesOptions::new(vec![cmd], None, None);
|
let options = GenerateFilesOptions::new(vec![cmd], None, None);
|
||||||
|
|
||||||
ns.generate_files(options)
|
ns.generate_files(options).await.expect("generation should succeed");
|
||||||
.await
|
|
||||||
.expect("generation should succeed");
|
|
||||||
|
|
||||||
let produced_path = base_dir.join(out_name);
|
let produced_path = base_dir.join(out_name);
|
||||||
let produced = fs
|
let produced = fs.read_to_string(&produced_path).await.expect("should read produced file");
|
||||||
.read_to_string(&produced_path)
|
assert_eq!(produced, "42");
|
||||||
.await
|
}
|
||||||
.expect("should read produced file");
|
|
||||||
assert_eq!(produced, "42");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+550
-578
File diff suppressed because it is too large
Load Diff
+92
-101
@@ -1,7 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@@ -10,133 +10,124 @@ use tokio::sync::RwLock;
|
|||||||
|
|
||||||
use super::namespace::NativeNamespace;
|
use super::namespace::NativeNamespace;
|
||||||
use crate::{
|
use crate::{
|
||||||
shared::helpers::extract_namespace_info, types::ProviderCapabilities, DynNamespace, Provider,
|
shared::helpers::extract_namespace_info, types::ProviderCapabilities, DynNamespace, Provider,
|
||||||
ProviderError, ProviderNamespace,
|
ProviderError, ProviderNamespace,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PROVIDER_NAME: &str = "native";
|
pub const PROVIDER_NAME: &str = "native";
|
||||||
|
|
||||||
pub struct NativeProvider<FS>
|
pub struct NativeProvider<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone,
|
FS: FileSystem + Send + Sync + Clone,
|
||||||
{
|
{
|
||||||
weak: Weak<NativeProvider<FS>>,
|
weak: Weak<NativeProvider<FS>>,
|
||||||
capabilities: ProviderCapabilities,
|
capabilities: ProviderCapabilities,
|
||||||
tmp_dir: PathBuf,
|
tmp_dir: PathBuf,
|
||||||
filesystem: FS,
|
filesystem: FS,
|
||||||
pub(super) namespaces: RwLock<HashMap<String, Arc<NativeNamespace<FS>>>>,
|
pub(super) namespaces: RwLock<HashMap<String, Arc<NativeNamespace<FS>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FS> NativeProvider<FS>
|
impl<FS> NativeProvider<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone,
|
FS: FileSystem + Send + Sync + Clone,
|
||||||
{
|
{
|
||||||
pub fn new(filesystem: FS) -> Arc<Self> {
|
pub fn new(filesystem: FS) -> Arc<Self> {
|
||||||
Arc::new_cyclic(|weak| NativeProvider {
|
Arc::new_cyclic(|weak| NativeProvider {
|
||||||
weak: weak.clone(),
|
weak: weak.clone(),
|
||||||
capabilities: ProviderCapabilities {
|
capabilities: ProviderCapabilities {
|
||||||
has_resources: false,
|
has_resources: false,
|
||||||
requires_image: false,
|
requires_image: false,
|
||||||
prefix_with_full_path: true,
|
prefix_with_full_path: true,
|
||||||
use_default_ports_in_cmd: false,
|
use_default_ports_in_cmd: false,
|
||||||
},
|
},
|
||||||
// NOTE: temp_dir in linux return `/tmp` but on mac something like
|
// NOTE: temp_dir in linux return `/tmp` but on mac something like
|
||||||
// `/var/folders/rz/1cyx7hfj31qgb98d8_cg7jwh0000gn/T/`, having
|
// `/var/folders/rz/1cyx7hfj31qgb98d8_cg7jwh0000gn/T/`, having
|
||||||
// one `trailing slash` and the other no can cause issues if
|
// one `trailing slash` and the other no can cause issues if
|
||||||
// you try to build a fullpath by concatenate. Use Pathbuf to prevent the issue.
|
// you try to build a fullpath by concatenate. Use Pathbuf to prevent the issue.
|
||||||
tmp_dir: std::env::temp_dir(),
|
tmp_dir: std::env::temp_dir(),
|
||||||
filesystem,
|
filesystem,
|
||||||
namespaces: RwLock::new(HashMap::new()),
|
namespaces: RwLock::new(HashMap::new()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tmp_dir(mut self, tmp_dir: impl Into<PathBuf>) -> Self {
|
pub fn tmp_dir(mut self, tmp_dir: impl Into<PathBuf>) -> Self {
|
||||||
self.tmp_dir = tmp_dir.into();
|
self.tmp_dir = tmp_dir.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<FS> Provider for NativeProvider<FS>
|
impl<FS> Provider for NativeProvider<FS>
|
||||||
where
|
where
|
||||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||||
{
|
{
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
PROVIDER_NAME
|
PROVIDER_NAME
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capabilities(&self) -> &ProviderCapabilities {
|
fn capabilities(&self) -> &ProviderCapabilities {
|
||||||
&self.capabilities
|
&self.capabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn namespaces(&self) -> HashMap<String, DynNamespace> {
|
async fn namespaces(&self) -> HashMap<String, DynNamespace> {
|
||||||
self.namespaces
|
self.namespaces
|
||||||
.read()
|
.read()
|
||||||
.await
|
.await
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, namespace)| (name.clone(), namespace.clone() as DynNamespace))
|
.map(|(name, namespace)| (name.clone(), namespace.clone() as DynNamespace))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_namespace(&self) -> Result<DynNamespace, ProviderError> {
|
async fn create_namespace(&self) -> Result<DynNamespace, ProviderError> {
|
||||||
let namespace = NativeNamespace::new(
|
let namespace = NativeNamespace::new(
|
||||||
&self.weak,
|
&self.weak,
|
||||||
&self.tmp_dir,
|
&self.tmp_dir,
|
||||||
&self.capabilities,
|
&self.capabilities,
|
||||||
&self.filesystem,
|
&self.filesystem,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.namespaces
|
self.namespaces.write().await.insert(namespace.name().to_string(), namespace.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(namespace.name().to_string(), namespace.clone());
|
|
||||||
|
|
||||||
Ok(namespace)
|
Ok(namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_namespace_with_base_dir(
|
async fn create_namespace_with_base_dir(
|
||||||
&self,
|
&self,
|
||||||
base_dir: &Path,
|
base_dir: &Path,
|
||||||
) -> Result<DynNamespace, ProviderError> {
|
) -> Result<DynNamespace, ProviderError> {
|
||||||
let namespace = NativeNamespace::new(
|
let namespace = NativeNamespace::new(
|
||||||
&self.weak,
|
&self.weak,
|
||||||
&self.tmp_dir,
|
&self.tmp_dir,
|
||||||
&self.capabilities,
|
&self.capabilities,
|
||||||
&self.filesystem,
|
&self.filesystem,
|
||||||
Some(base_dir),
|
Some(base_dir),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.namespaces
|
self.namespaces.write().await.insert(namespace.name().to_string(), namespace.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(namespace.name().to_string(), namespace.clone());
|
|
||||||
|
|
||||||
Ok(namespace)
|
Ok(namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_namespace_from_json(
|
async fn create_namespace_from_json(
|
||||||
&self,
|
&self,
|
||||||
json_value: &serde_json::Value,
|
json_value: &serde_json::Value,
|
||||||
) -> Result<DynNamespace, ProviderError> {
|
) -> Result<DynNamespace, ProviderError> {
|
||||||
let (base_dir, name) = extract_namespace_info(json_value)?;
|
let (base_dir, name) = extract_namespace_info(json_value)?;
|
||||||
|
|
||||||
let namespace = NativeNamespace::attach_to_live(
|
let namespace = NativeNamespace::attach_to_live(
|
||||||
&self.weak,
|
&self.weak,
|
||||||
&self.capabilities,
|
&self.capabilities,
|
||||||
&self.filesystem,
|
&self.filesystem,
|
||||||
&base_dir,
|
&base_dir,
|
||||||
&name,
|
&name,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.namespaces
|
self.namespaces.write().await.insert(namespace.name().to_string(), namespace.clone());
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(namespace.name().to_string(), namespace.clone());
|
|
||||||
|
|
||||||
Ok(namespace)
|
Ok(namespace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,74 +6,69 @@ use crate::{types::RunCommandOptions, DynNode, ProviderError};
|
|||||||
|
|
||||||
/// Check if we are running in `CI` by checking the 'RUN_IN_CI' env var
|
/// Check if we are running in `CI` by checking the 'RUN_IN_CI' env var
|
||||||
pub fn running_in_ci() -> bool {
|
pub fn running_in_ci() -> bool {
|
||||||
env::var("RUN_IN_CI").unwrap_or_default() == "1"
|
env::var("RUN_IN_CI").unwrap_or_default() == "1"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executes a command on a temporary node and extracts the execution result either from the
|
/// Executes a command on a temporary node and extracts the execution result either from the
|
||||||
/// standard output or a file.
|
/// standard output or a file.
|
||||||
pub async fn extract_execution_result(
|
pub async fn extract_execution_result(
|
||||||
temp_node: &DynNode,
|
temp_node: &DynNode,
|
||||||
options: RunCommandOptions,
|
options: RunCommandOptions,
|
||||||
expected_path: Option<&PathBuf>,
|
expected_path: Option<&PathBuf>,
|
||||||
) -> Result<String, ProviderError> {
|
) -> Result<String, ProviderError> {
|
||||||
let output_contents = temp_node
|
let output_contents = temp_node
|
||||||
.run_command(options)
|
.run_command(options)
|
||||||
.await?
|
.await?
|
||||||
.map_err(|(_, msg)| ProviderError::FileGenerationFailed(anyhow!("{msg}")))?;
|
.map_err(|(_, msg)| ProviderError::FileGenerationFailed(anyhow!("{msg}")))?;
|
||||||
|
|
||||||
// If an expected_path is provided, read the file contents from inside the container
|
// If an expected_path is provided, read the file contents from inside the container
|
||||||
if let Some(expected_path) = expected_path.as_ref() {
|
if let Some(expected_path) = expected_path.as_ref() {
|
||||||
Ok(temp_node
|
Ok(temp_node
|
||||||
.run_command(
|
.run_command(
|
||||||
RunCommandOptions::new("cat")
|
RunCommandOptions::new("cat")
|
||||||
.args(vec![expected_path.to_string_lossy().to_string()]),
|
.args(vec![expected_path.to_string_lossy().to_string()]),
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.map_err(|(_, msg)| {
|
.map_err(|(_, msg)| {
|
||||||
ProviderError::FileGenerationFailed(anyhow!(format!(
|
ProviderError::FileGenerationFailed(anyhow!(format!(
|
||||||
"failed reading expected_path {}: {}",
|
"failed reading expected_path {}: {}",
|
||||||
expected_path.display(),
|
expected_path.display(),
|
||||||
msg
|
msg
|
||||||
)))
|
)))
|
||||||
})?)
|
})?)
|
||||||
} else {
|
} else {
|
||||||
Ok(output_contents)
|
Ok(output_contents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_namespace_info(
|
pub fn extract_namespace_info(
|
||||||
json_value: &serde_json::Value,
|
json_value: &serde_json::Value,
|
||||||
) -> Result<(PathBuf, String), ProviderError> {
|
) -> Result<(PathBuf, String), ProviderError> {
|
||||||
let base_dir = json_value
|
let base_dir =
|
||||||
.get("local_base_dir")
|
json_value.get("local_base_dir").and_then(|v| v.as_str()).map(PathBuf::from).ok_or(
|
||||||
.and_then(|v| v.as_str())
|
ProviderError::InvalidConfig(
|
||||||
.map(PathBuf::from)
|
"`field local_base_dir` is missing from zombie.json".to_string(),
|
||||||
.ok_or(ProviderError::InvalidConfig(
|
),
|
||||||
"`field local_base_dir` is missing from zombie.json".to_string(),
|
)?;
|
||||||
))?;
|
|
||||||
|
|
||||||
let name =
|
let name = json_value.get("ns").and_then(|v| v.as_str()).ok_or(
|
||||||
json_value
|
ProviderError::InvalidConfig("field `ns` is missing from zombie.json".to_string()),
|
||||||
.get("ns")
|
)?;
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.ok_or(ProviderError::InvalidConfig(
|
|
||||||
"field `ns` is missing from zombie.json".to_string(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
Ok((base_dir, name.to_string()))
|
Ok((base_dir, name.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_runing_in_ci_env_var() {
|
fn check_runing_in_ci_env_var() {
|
||||||
assert!(!running_in_ci());
|
assert!(!running_in_ci());
|
||||||
// now set the env var
|
// now set the env var
|
||||||
env::set_var("RUN_IN_CI", "1");
|
env::set_var("RUN_IN_CI", "1");
|
||||||
assert!(running_in_ci());
|
assert!(running_in_ci());
|
||||||
// reset
|
// reset
|
||||||
env::set_var("RUN_IN_CI", "");
|
env::set_var("RUN_IN_CI", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+286
-297
@@ -1,7 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::ExitStatus,
|
process::ExitStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
use configuration::{shared::resources::Resources, types::AssetLocation};
|
use configuration::{shared::resources::Resources, types::AssetLocation};
|
||||||
@@ -13,363 +13,352 @@ pub type ExecutionResult = Result<String, (ExitStatus, String)>;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ProviderCapabilities {
|
pub struct ProviderCapabilities {
|
||||||
// default ports internal
|
// default ports internal
|
||||||
/// Ensure that we have an image for each node (k8s/podman/docker)
|
/// Ensure that we have an image for each node (k8s/podman/docker)
|
||||||
pub requires_image: bool,
|
pub requires_image: bool,
|
||||||
/// Allow to customize the resources through manifest (k8s).
|
/// Allow to customize the resources through manifest (k8s).
|
||||||
pub has_resources: bool,
|
pub has_resources: bool,
|
||||||
/// Used in native to prefix filepath with fullpath
|
/// Used in native to prefix filepath with fullpath
|
||||||
pub prefix_with_full_path: bool,
|
pub prefix_with_full_path: bool,
|
||||||
/// Use default ports in node cmd/args.
|
/// Use default ports in node cmd/args.
|
||||||
/// NOTE: generally used in k8s/dockers since the images expose those ports.
|
/// NOTE: generally used in k8s/dockers since the images expose those ports.
|
||||||
pub use_default_ports_in_cmd: bool,
|
pub use_default_ports_in_cmd: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SpawnNodeOptions {
|
pub struct SpawnNodeOptions {
|
||||||
/// Name of the node
|
/// Name of the node
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// Image of the node (IFF is supported by the provider)
|
/// Image of the node (IFF is supported by the provider)
|
||||||
pub image: Option<String>,
|
pub image: Option<String>,
|
||||||
/// Resources to apply to the node (IFF is supported by the provider)
|
/// Resources to apply to the node (IFF is supported by the provider)
|
||||||
pub resources: Option<Resources>,
|
pub resources: Option<Resources>,
|
||||||
/// Main command to execute
|
/// Main command to execute
|
||||||
pub program: String,
|
pub program: String,
|
||||||
/// Arguments to pass to the main command
|
/// Arguments to pass to the main command
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
/// Environment to set when running the `program`
|
/// Environment to set when running the `program`
|
||||||
pub env: Vec<(String, String)>,
|
pub env: Vec<(String, String)>,
|
||||||
// TODO: rename startup_files
|
// TODO: rename startup_files
|
||||||
/// Files to inject at startup
|
/// Files to inject at startup
|
||||||
pub injected_files: Vec<TransferedFile>,
|
pub injected_files: Vec<TransferedFile>,
|
||||||
/// Paths to create before start the node (e.g keystore)
|
/// Paths to create before start the node (e.g keystore)
|
||||||
/// should be created with `create_dir_all` in order
|
/// should be created with `create_dir_all` in order
|
||||||
/// to create the full path even when we have missing parts
|
/// to create the full path even when we have missing parts
|
||||||
pub created_paths: Vec<PathBuf>,
|
pub created_paths: Vec<PathBuf>,
|
||||||
/// Database snapshot to be injected (should be a tgz file)
|
/// Database snapshot to be injected (should be a tgz file)
|
||||||
/// Could be a local or remote asset
|
/// Could be a local or remote asset
|
||||||
pub db_snapshot: Option<AssetLocation>,
|
pub db_snapshot: Option<AssetLocation>,
|
||||||
pub port_mapping: Option<HashMap<Port, Port>>,
|
pub port_mapping: Option<HashMap<Port, Port>>,
|
||||||
/// Optionally specify a log path for the node
|
/// Optionally specify a log path for the node
|
||||||
pub node_log_path: Option<PathBuf>,
|
pub node_log_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpawnNodeOptions {
|
impl SpawnNodeOptions {
|
||||||
pub fn new<S>(name: S, program: S) -> Self
|
pub fn new<S>(name: S, program: S) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
name: name.as_ref().to_string(),
|
name: name.as_ref().to_string(),
|
||||||
image: None,
|
image: None,
|
||||||
resources: None,
|
resources: None,
|
||||||
program: program.as_ref().to_string(),
|
program: program.as_ref().to_string(),
|
||||||
args: vec![],
|
args: vec![],
|
||||||
env: vec![],
|
env: vec![],
|
||||||
injected_files: vec![],
|
injected_files: vec![],
|
||||||
created_paths: vec![],
|
created_paths: vec![],
|
||||||
db_snapshot: None,
|
db_snapshot: None,
|
||||||
port_mapping: None,
|
port_mapping: None,
|
||||||
node_log_path: None,
|
node_log_path: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image<S>(mut self, image: S) -> Self
|
pub fn image<S>(mut self, image: S) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
self.image = Some(image.as_ref().to_string());
|
self.image = Some(image.as_ref().to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resources(mut self, resources: Resources) -> Self {
|
pub fn resources(mut self, resources: Resources) -> Self {
|
||||||
self.resources = Some(resources);
|
self.resources = Some(resources);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn db_snapshot(mut self, db_snap: Option<AssetLocation>) -> Self {
|
pub fn db_snapshot(mut self, db_snap: Option<AssetLocation>) -> Self {
|
||||||
self.db_snapshot = db_snap;
|
self.db_snapshot = db_snap;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn args<S, I>(mut self, args: I) -> Self
|
pub fn args<S, I>(mut self, args: I) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
I: IntoIterator<Item = S>,
|
I: IntoIterator<Item = S>,
|
||||||
{
|
{
|
||||||
self.args = args.into_iter().map(|s| s.as_ref().to_string()).collect();
|
self.args = args.into_iter().map(|s| s.as_ref().to_string()).collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn env<S, I>(mut self, env: I) -> Self
|
pub fn env<S, I>(mut self, env: I) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
I: IntoIterator<Item = (S, S)>,
|
I: IntoIterator<Item = (S, S)>,
|
||||||
{
|
{
|
||||||
self.env = env
|
self.env = env
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, value)| (name.as_ref().to_string(), value.as_ref().to_string()))
|
.map(|(name, value)| (name.as_ref().to_string(), value.as_ref().to_string()))
|
||||||
.collect();
|
.collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn injected_files<I>(mut self, injected_files: I) -> Self
|
pub fn injected_files<I>(mut self, injected_files: I) -> Self
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = TransferedFile>,
|
I: IntoIterator<Item = TransferedFile>,
|
||||||
{
|
{
|
||||||
self.injected_files = injected_files.into_iter().collect();
|
self.injected_files = injected_files.into_iter().collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn created_paths<P, I>(mut self, created_paths: I) -> Self
|
pub fn created_paths<P, I>(mut self, created_paths: I) -> Self
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
I: IntoIterator<Item = P>,
|
I: IntoIterator<Item = P>,
|
||||||
{
|
{
|
||||||
self.created_paths = created_paths
|
self.created_paths = created_paths.into_iter().map(|path| path.as_ref().into()).collect();
|
||||||
.into_iter()
|
self
|
||||||
.map(|path| path.as_ref().into())
|
}
|
||||||
.collect();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn port_mapping(mut self, ports: HashMap<Port, Port>) -> Self {
|
pub fn port_mapping(mut self, ports: HashMap<Port, Port>) -> Self {
|
||||||
self.port_mapping = Some(ports);
|
self.port_mapping = Some(ports);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn node_log_path(mut self, path: Option<PathBuf>) -> Self {
|
pub fn node_log_path(mut self, path: Option<PathBuf>) -> Self {
|
||||||
self.node_log_path = path;
|
self.node_log_path = path;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GenerateFileCommand {
|
pub struct GenerateFileCommand {
|
||||||
pub program: String,
|
pub program: String,
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
pub env: Vec<(String, String)>,
|
pub env: Vec<(String, String)>,
|
||||||
pub local_output_path: PathBuf,
|
pub local_output_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenerateFileCommand {
|
impl GenerateFileCommand {
|
||||||
pub fn new<S, P>(program: S, local_output_path: P) -> Self
|
pub fn new<S, P>(program: S, local_output_path: P) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
program: program.as_ref().to_string(),
|
program: program.as_ref().to_string(),
|
||||||
args: vec![],
|
args: vec![],
|
||||||
env: vec![],
|
env: vec![],
|
||||||
local_output_path: local_output_path.as_ref().into(),
|
local_output_path: local_output_path.as_ref().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn args<S, I>(mut self, args: I) -> Self
|
pub fn args<S, I>(mut self, args: I) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
I: IntoIterator<Item = S>,
|
I: IntoIterator<Item = S>,
|
||||||
{
|
{
|
||||||
self.args = args.into_iter().map(|s| s.as_ref().to_string()).collect();
|
self.args = args.into_iter().map(|s| s.as_ref().to_string()).collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn env<S, I>(mut self, env: I) -> Self
|
pub fn env<S, I>(mut self, env: I) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
I: IntoIterator<Item = (S, S)>,
|
I: IntoIterator<Item = (S, S)>,
|
||||||
{
|
{
|
||||||
self.env = env
|
self.env = env
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, value)| (name.as_ref().to_string(), value.as_ref().to_string()))
|
.map(|(name, value)| (name.as_ref().to_string(), value.as_ref().to_string()))
|
||||||
.collect();
|
.collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GenerateFilesOptions {
|
pub struct GenerateFilesOptions {
|
||||||
pub commands: Vec<GenerateFileCommand>,
|
pub commands: Vec<GenerateFileCommand>,
|
||||||
pub image: Option<String>,
|
pub image: Option<String>,
|
||||||
pub injected_files: Vec<TransferedFile>,
|
pub injected_files: Vec<TransferedFile>,
|
||||||
// Allow to control the name of the node used to create the files.
|
// Allow to control the name of the node used to create the files.
|
||||||
pub temp_name: Option<String>,
|
pub temp_name: Option<String>,
|
||||||
pub expected_path: Option<PathBuf>,
|
pub expected_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenerateFilesOptions {
|
impl GenerateFilesOptions {
|
||||||
pub fn new<I>(commands: I, image: Option<String>, expected_path: Option<PathBuf>) -> Self
|
pub fn new<I>(commands: I, image: Option<String>, expected_path: Option<PathBuf>) -> Self
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = GenerateFileCommand>,
|
I: IntoIterator<Item = GenerateFileCommand>,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
commands: commands.into_iter().collect(),
|
commands: commands.into_iter().collect(),
|
||||||
injected_files: vec![],
|
injected_files: vec![],
|
||||||
image,
|
image,
|
||||||
temp_name: None,
|
temp_name: None,
|
||||||
expected_path,
|
expected_path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_files<I>(
|
pub fn with_files<I>(
|
||||||
commands: I,
|
commands: I,
|
||||||
image: Option<String>,
|
image: Option<String>,
|
||||||
injected_files: &[TransferedFile],
|
injected_files: &[TransferedFile],
|
||||||
expected_path: Option<PathBuf>,
|
expected_path: Option<PathBuf>,
|
||||||
) -> Self
|
) -> Self
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = GenerateFileCommand>,
|
I: IntoIterator<Item = GenerateFileCommand>,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
commands: commands.into_iter().collect(),
|
commands: commands.into_iter().collect(),
|
||||||
injected_files: injected_files.into(),
|
injected_files: injected_files.into(),
|
||||||
image,
|
image,
|
||||||
temp_name: None,
|
temp_name: None,
|
||||||
expected_path,
|
expected_path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image<S>(mut self, image: S) -> Self
|
pub fn image<S>(mut self, image: S) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
self.image = Some(image.as_ref().to_string());
|
self.image = Some(image.as_ref().to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn injected_files<I>(mut self, injected_files: I) -> Self
|
pub fn injected_files<I>(mut self, injected_files: I) -> Self
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = TransferedFile>,
|
I: IntoIterator<Item = TransferedFile>,
|
||||||
{
|
{
|
||||||
self.injected_files = injected_files.into_iter().collect();
|
self.injected_files = injected_files.into_iter().collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn temp_name(mut self, name: impl Into<String>) -> Self {
|
pub fn temp_name(mut self, name: impl Into<String>) -> Self {
|
||||||
self.temp_name = Some(name.into());
|
self.temp_name = Some(name.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RunCommandOptions {
|
pub struct RunCommandOptions {
|
||||||
pub program: String,
|
pub program: String,
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
pub env: Vec<(String, String)>,
|
pub env: Vec<(String, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunCommandOptions {
|
impl RunCommandOptions {
|
||||||
pub fn new<S>(program: S) -> Self
|
pub fn new<S>(program: S) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
Self {
|
Self { program: program.as_ref().to_string(), args: vec![], env: vec![] }
|
||||||
program: program.as_ref().to_string(),
|
}
|
||||||
args: vec![],
|
|
||||||
env: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn args<S, I>(mut self, args: I) -> Self
|
pub fn args<S, I>(mut self, args: I) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
I: IntoIterator<Item = S>,
|
I: IntoIterator<Item = S>,
|
||||||
{
|
{
|
||||||
self.args = args.into_iter().map(|s| s.as_ref().to_string()).collect();
|
self.args = args.into_iter().map(|s| s.as_ref().to_string()).collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn env<S, I>(mut self, env: I) -> Self
|
pub fn env<S, I>(mut self, env: I) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
I: IntoIterator<Item = (S, S)>,
|
I: IntoIterator<Item = (S, S)>,
|
||||||
{
|
{
|
||||||
self.env = env
|
self.env = env
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, value)| (name.as_ref().to_string(), value.as_ref().to_string()))
|
.map(|(name, value)| (name.as_ref().to_string(), value.as_ref().to_string()))
|
||||||
.collect();
|
.collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RunScriptOptions {
|
pub struct RunScriptOptions {
|
||||||
pub local_script_path: PathBuf,
|
pub local_script_path: PathBuf,
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
pub env: Vec<(String, String)>,
|
pub env: Vec<(String, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunScriptOptions {
|
impl RunScriptOptions {
|
||||||
pub fn new<P>(local_script_path: P) -> Self
|
pub fn new<P>(local_script_path: P) -> Self
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
Self {
|
Self { local_script_path: local_script_path.as_ref().into(), args: vec![], env: vec![] }
|
||||||
local_script_path: local_script_path.as_ref().into(),
|
}
|
||||||
args: vec![],
|
|
||||||
env: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn args<S, I>(mut self, args: I) -> Self
|
pub fn args<S, I>(mut self, args: I) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
I: IntoIterator<Item = S>,
|
I: IntoIterator<Item = S>,
|
||||||
{
|
{
|
||||||
self.args = args.into_iter().map(|s| s.as_ref().to_string()).collect();
|
self.args = args.into_iter().map(|s| s.as_ref().to_string()).collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn env<S, I>(mut self, env: I) -> Self
|
pub fn env<S, I>(mut self, env: I) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
I: IntoIterator<Item = (S, S)>,
|
I: IntoIterator<Item = (S, S)>,
|
||||||
{
|
{
|
||||||
self.env = env
|
self.env = env
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, value)| (name.as_ref().to_string(), value.as_ref().to_string()))
|
.map(|(name, value)| (name.as_ref().to_string(), value.as_ref().to_string()))
|
||||||
.collect();
|
.collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(team): I think we can rename it to FileMap?
|
// TODO(team): I think we can rename it to FileMap?
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct TransferedFile {
|
pub struct TransferedFile {
|
||||||
pub local_path: PathBuf,
|
pub local_path: PathBuf,
|
||||||
pub remote_path: PathBuf,
|
pub remote_path: PathBuf,
|
||||||
// TODO: Can be narrowed to have strict typing on this?
|
// TODO: Can be narrowed to have strict typing on this?
|
||||||
pub mode: String,
|
pub mode: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransferedFile {
|
impl TransferedFile {
|
||||||
pub fn new<P>(local_path: P, remote_path: P) -> Self
|
pub fn new<P>(local_path: P, remote_path: P) -> Self
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
local_path: local_path.as_ref().into(),
|
local_path: local_path.as_ref().into(),
|
||||||
remote_path: remote_path.as_ref().into(),
|
remote_path: remote_path.as_ref().into(),
|
||||||
mode: "0644".to_string(), // default to rw-r--r--
|
mode: "0644".to_string(), // default to rw-r--r--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mode<S>(mut self, mode: S) -> Self
|
pub fn mode<S>(mut self, mode: S) -> Self
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
self.mode = mode.as_ref().to_string();
|
self.mode = mode.as_ref().to_string();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for TransferedFile {
|
impl std::fmt::Display for TransferedFile {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"File to transfer (local: {}, remote: {})",
|
"File to transfer (local: {}, remote: {})",
|
||||||
self.local_path.display(),
|
self.local_path.display(),
|
||||||
self.remote_path.display()
|
self.remote_path.display()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-47
@@ -2,8 +2,8 @@
|
|||||||
use std::{env, future::Future, path::PathBuf, pin::Pin};
|
use std::{env, future::Future, path::PathBuf, pin::Pin};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AttachToLive, AttachToLiveNetwork, LocalFileSystem, Network, NetworkConfig, NetworkConfigExt,
|
AttachToLive, AttachToLiveNetwork, LocalFileSystem, Network, NetworkConfig, NetworkConfigExt,
|
||||||
OrchestratorError,
|
OrchestratorError,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_POLKADOT_IMAGE: &str = "docker.io/parity/polkadot:latest";
|
const DEFAULT_POLKADOT_IMAGE: &str = "docker.io/parity/polkadot:latest";
|
||||||
@@ -11,80 +11,80 @@ const DEFAULT_CUMULUS_IMAGE: &str = "docker.io/parity/polkadot-parachain:latest"
|
|||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Images {
|
pub struct Images {
|
||||||
pub polkadot: String,
|
pub polkadot: String,
|
||||||
pub cumulus: String,
|
pub cumulus: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Images {
|
impl Images {
|
||||||
/// Alias for polkadot field - returns reference to pezkuwi/polkadot image
|
/// Alias for polkadot field - returns reference to pezkuwi/polkadot image
|
||||||
pub fn pezkuwi(&self) -> &str {
|
pub fn pezkuwi(&self) -> &str {
|
||||||
&self.polkadot
|
&self.polkadot
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alias for cumulus field - returns reference to pezcumulus/cumulus image
|
/// Alias for cumulus field - returns reference to pezcumulus/cumulus image
|
||||||
pub fn pezcumulus(&self) -> &str {
|
pub fn pezcumulus(&self) -> &str {
|
||||||
&self.cumulus
|
&self.cumulus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Provider {
|
pub enum Provider {
|
||||||
Native,
|
Native,
|
||||||
K8s,
|
K8s,
|
||||||
Docker,
|
Docker,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Provider {
|
impl Provider {
|
||||||
pub fn get_spawn_fn(
|
pub fn get_spawn_fn(
|
||||||
&self,
|
&self,
|
||||||
) -> fn(NetworkConfig) -> Pin<Box<dyn Future<Output = SpawnResult> + Send>> {
|
) -> fn(NetworkConfig) -> Pin<Box<dyn Future<Output = SpawnResult> + Send>> {
|
||||||
match self {
|
match self {
|
||||||
Provider::Native => NetworkConfigExt::spawn_native,
|
Provider::Native => NetworkConfigExt::spawn_native,
|
||||||
Provider::K8s => NetworkConfigExt::spawn_k8s,
|
Provider::K8s => NetworkConfigExt::spawn_k8s,
|
||||||
Provider::Docker => NetworkConfigExt::spawn_docker,
|
Provider::Docker => NetworkConfigExt::spawn_docker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use `docker` as default provider
|
// Use `docker` as default provider
|
||||||
impl From<String> for Provider {
|
impl From<String> for Provider {
|
||||||
fn from(value: String) -> Self {
|
fn from(value: String) -> Self {
|
||||||
match value.to_ascii_lowercase().as_ref() {
|
match value.to_ascii_lowercase().as_ref() {
|
||||||
"native" => Provider::Native,
|
"native" => Provider::Native,
|
||||||
"k8s" => Provider::K8s,
|
"k8s" => Provider::K8s,
|
||||||
_ => Provider::Docker, // default provider
|
_ => Provider::Docker, // default provider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_images_from_env() -> Images {
|
pub fn get_images_from_env() -> Images {
|
||||||
let polkadot = env::var("POLKADOT_IMAGE").unwrap_or(DEFAULT_POLKADOT_IMAGE.into());
|
let polkadot = env::var("POLKADOT_IMAGE").unwrap_or(DEFAULT_POLKADOT_IMAGE.into());
|
||||||
let cumulus = env::var("CUMULUS_IMAGE").unwrap_or(DEFAULT_CUMULUS_IMAGE.into());
|
let cumulus = env::var("CUMULUS_IMAGE").unwrap_or(DEFAULT_CUMULUS_IMAGE.into());
|
||||||
Images { polkadot, cumulus }
|
Images { polkadot, cumulus }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_provider_from_env() -> Provider {
|
pub fn get_provider_from_env() -> Provider {
|
||||||
env::var("ZOMBIE_PROVIDER").unwrap_or_default().into()
|
env::var("ZOMBIE_PROVIDER").unwrap_or_default().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type SpawnResult = Result<Network<LocalFileSystem>, OrchestratorError>;
|
pub type SpawnResult = Result<Network<LocalFileSystem>, OrchestratorError>;
|
||||||
pub fn get_spawn_fn() -> fn(NetworkConfig) -> Pin<Box<dyn Future<Output = SpawnResult> + Send>> {
|
pub fn get_spawn_fn() -> fn(NetworkConfig) -> Pin<Box<dyn Future<Output = SpawnResult> + Send>> {
|
||||||
let provider = get_provider_from_env();
|
let provider = get_provider_from_env();
|
||||||
|
|
||||||
match provider {
|
match provider {
|
||||||
Provider::Native => NetworkConfigExt::spawn_native,
|
Provider::Native => NetworkConfigExt::spawn_native,
|
||||||
Provider::K8s => NetworkConfigExt::spawn_k8s,
|
Provider::K8s => NetworkConfigExt::spawn_k8s,
|
||||||
Provider::Docker => NetworkConfigExt::spawn_docker,
|
Provider::Docker => NetworkConfigExt::spawn_docker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type AttachResult = Result<Network<LocalFileSystem>, OrchestratorError>;
|
pub type AttachResult = Result<Network<LocalFileSystem>, OrchestratorError>;
|
||||||
|
|
||||||
pub fn get_attach_fn() -> fn(PathBuf) -> Pin<Box<dyn Future<Output = AttachResult> + Send>> {
|
pub fn get_attach_fn() -> fn(PathBuf) -> Pin<Box<dyn Future<Output = AttachResult> + Send>> {
|
||||||
let provider = get_provider_from_env();
|
let provider = get_provider_from_env();
|
||||||
|
|
||||||
match provider {
|
match provider {
|
||||||
Provider::Native => AttachToLiveNetwork::attach_native,
|
Provider::Native => AttachToLiveNetwork::attach_native,
|
||||||
Provider::K8s => AttachToLiveNetwork::attach_k8s,
|
Provider::K8s => AttachToLiveNetwork::attach_k8s,
|
||||||
Provider::Docker => AttachToLiveNetwork::attach_docker,
|
Provider::Docker => AttachToLiveNetwork::attach_docker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+86
-86
@@ -2,20 +2,20 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
pub use configuration::{
|
pub use configuration::{
|
||||||
GlobalSettings, GlobalSettingsBuilder, NetworkConfig, NetworkConfigBuilder,
|
GlobalSettings, GlobalSettingsBuilder, NetworkConfig, NetworkConfigBuilder,
|
||||||
RegistrationStrategy, WithRelaychain,
|
RegistrationStrategy, WithRelaychain,
|
||||||
};
|
};
|
||||||
pub use orchestrator::{
|
pub use orchestrator::{
|
||||||
errors::OrchestratorError,
|
errors::OrchestratorError,
|
||||||
network::{node::NetworkNode, Network},
|
network::{node::NetworkNode, Network},
|
||||||
pezsc_chain_spec, AddCollatorOptions, AddNodeOptions, Orchestrator,
|
pezsc_chain_spec, AddCollatorOptions, AddNodeOptions, Orchestrator,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helpers used for interact with the network
|
// Helpers used for interact with the network
|
||||||
pub mod tx_helper {
|
pub mod tx_helper {
|
||||||
pub use orchestrator::{
|
pub use orchestrator::{
|
||||||
network::chain_upgrade::ChainUpgrade, shared::types::RuntimeUpgradeOptions,
|
network::chain_upgrade::ChainUpgrade, shared::types::RuntimeUpgradeOptions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
use provider::{DockerProvider, KubernetesProvider, NativeProvider};
|
use provider::{DockerProvider, KubernetesProvider, NativeProvider};
|
||||||
@@ -32,100 +32,100 @@ pub use pezkuwi_subxt_signer as subxt_signer;
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait NetworkConfigExt {
|
pub trait NetworkConfigExt {
|
||||||
/// Spawns a network using the native or k8s provider.
|
/// Spawns a network using the native or k8s provider.
|
||||||
///
|
///
|
||||||
/// # Example:
|
/// # Example:
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use zombienet_sdk::{NetworkConfig, NetworkConfigExt};
|
/// # use zombienet_sdk::{NetworkConfig, NetworkConfigExt};
|
||||||
/// # async fn example() -> Result<(), zombienet_sdk::OrchestratorError> {
|
/// # async fn example() -> Result<(), zombienet_sdk::OrchestratorError> {
|
||||||
/// let network = NetworkConfig::load_from_toml("config.toml")?
|
/// let network = NetworkConfig::load_from_toml("config.toml")?
|
||||||
/// .spawn_native()
|
/// .spawn_native()
|
||||||
/// .await?;
|
/// .await?;
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
async fn spawn_native(self) -> Result<Network<LocalFileSystem>, OrchestratorError>;
|
async fn spawn_native(self) -> Result<Network<LocalFileSystem>, OrchestratorError>;
|
||||||
async fn spawn_k8s(self) -> Result<Network<LocalFileSystem>, OrchestratorError>;
|
async fn spawn_k8s(self) -> Result<Network<LocalFileSystem>, OrchestratorError>;
|
||||||
async fn spawn_docker(self) -> Result<Network<LocalFileSystem>, OrchestratorError>;
|
async fn spawn_docker(self) -> Result<Network<LocalFileSystem>, OrchestratorError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait AttachToLive {
|
pub trait AttachToLive {
|
||||||
/// Attaches to a running live network using the native, docker or k8s provider.
|
/// Attaches to a running live network using the native, docker or k8s provider.
|
||||||
///
|
///
|
||||||
/// # Example:
|
/// # Example:
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use zombienet_sdk::{AttachToLive, AttachToLiveNetwork};
|
/// # use zombienet_sdk::{AttachToLive, AttachToLiveNetwork};
|
||||||
/// # use std::path::PathBuf;
|
/// # use std::path::PathBuf;
|
||||||
/// # async fn example() -> Result<(), zombienet_sdk::OrchestratorError> {
|
/// # async fn example() -> Result<(), zombienet_sdk::OrchestratorError> {
|
||||||
/// let zombie_json_path = PathBuf::from("some/path/zombie.json");
|
/// let zombie_json_path = PathBuf::from("some/path/zombie.json");
|
||||||
/// let network = AttachToLiveNetwork::attach_native(zombie_json_path).await?;
|
/// let network = AttachToLiveNetwork::attach_native(zombie_json_path).await?;
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
async fn attach_native(
|
async fn attach_native(
|
||||||
zombie_json_path: PathBuf,
|
zombie_json_path: PathBuf,
|
||||||
) -> Result<Network<LocalFileSystem>, OrchestratorError>;
|
) -> Result<Network<LocalFileSystem>, OrchestratorError>;
|
||||||
async fn attach_k8s(
|
async fn attach_k8s(
|
||||||
zombie_json_path: PathBuf,
|
zombie_json_path: PathBuf,
|
||||||
) -> Result<Network<LocalFileSystem>, OrchestratorError>;
|
) -> Result<Network<LocalFileSystem>, OrchestratorError>;
|
||||||
async fn attach_docker(
|
async fn attach_docker(
|
||||||
zombie_json_path: PathBuf,
|
zombie_json_path: PathBuf,
|
||||||
) -> Result<Network<LocalFileSystem>, OrchestratorError>;
|
) -> Result<Network<LocalFileSystem>, OrchestratorError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl NetworkConfigExt for NetworkConfig {
|
impl NetworkConfigExt for NetworkConfig {
|
||||||
async fn spawn_native(self) -> Result<Network<LocalFileSystem>, OrchestratorError> {
|
async fn spawn_native(self) -> Result<Network<LocalFileSystem>, OrchestratorError> {
|
||||||
let filesystem = LocalFileSystem;
|
let filesystem = LocalFileSystem;
|
||||||
let provider = NativeProvider::new(filesystem.clone());
|
let provider = NativeProvider::new(filesystem.clone());
|
||||||
let orchestrator = Orchestrator::new(filesystem, provider);
|
let orchestrator = Orchestrator::new(filesystem, provider);
|
||||||
orchestrator.spawn(self).await
|
orchestrator.spawn(self).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_k8s(self) -> Result<Network<LocalFileSystem>, OrchestratorError> {
|
async fn spawn_k8s(self) -> Result<Network<LocalFileSystem>, OrchestratorError> {
|
||||||
let filesystem = LocalFileSystem;
|
let filesystem = LocalFileSystem;
|
||||||
let provider = KubernetesProvider::new(filesystem.clone()).await;
|
let provider = KubernetesProvider::new(filesystem.clone()).await;
|
||||||
let orchestrator = Orchestrator::new(filesystem, provider);
|
let orchestrator = Orchestrator::new(filesystem, provider);
|
||||||
orchestrator.spawn(self).await
|
orchestrator.spawn(self).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_docker(self) -> Result<Network<LocalFileSystem>, OrchestratorError> {
|
async fn spawn_docker(self) -> Result<Network<LocalFileSystem>, OrchestratorError> {
|
||||||
let filesystem = LocalFileSystem;
|
let filesystem = LocalFileSystem;
|
||||||
let provider = DockerProvider::new(filesystem.clone()).await;
|
let provider = DockerProvider::new(filesystem.clone()).await;
|
||||||
let orchestrator = Orchestrator::new(filesystem, provider);
|
let orchestrator = Orchestrator::new(filesystem, provider);
|
||||||
orchestrator.spawn(self).await
|
orchestrator.spawn(self).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AttachToLiveNetwork;
|
pub struct AttachToLiveNetwork;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl AttachToLive for AttachToLiveNetwork {
|
impl AttachToLive for AttachToLiveNetwork {
|
||||||
async fn attach_native(
|
async fn attach_native(
|
||||||
zombie_json_path: PathBuf,
|
zombie_json_path: PathBuf,
|
||||||
) -> Result<Network<LocalFileSystem>, OrchestratorError> {
|
) -> Result<Network<LocalFileSystem>, OrchestratorError> {
|
||||||
let filesystem = LocalFileSystem;
|
let filesystem = LocalFileSystem;
|
||||||
let provider = NativeProvider::new(filesystem.clone());
|
let provider = NativeProvider::new(filesystem.clone());
|
||||||
let orchestrator = Orchestrator::new(filesystem, provider);
|
let orchestrator = Orchestrator::new(filesystem, provider);
|
||||||
orchestrator.attach_to_live(zombie_json_path.as_ref()).await
|
orchestrator.attach_to_live(zombie_json_path.as_ref()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn attach_k8s(
|
async fn attach_k8s(
|
||||||
zombie_json_path: PathBuf,
|
zombie_json_path: PathBuf,
|
||||||
) -> Result<Network<LocalFileSystem>, OrchestratorError> {
|
) -> Result<Network<LocalFileSystem>, OrchestratorError> {
|
||||||
let filesystem = LocalFileSystem;
|
let filesystem = LocalFileSystem;
|
||||||
let provider = KubernetesProvider::new(filesystem.clone()).await;
|
let provider = KubernetesProvider::new(filesystem.clone()).await;
|
||||||
let orchestrator = Orchestrator::new(filesystem, provider);
|
let orchestrator = Orchestrator::new(filesystem, provider);
|
||||||
orchestrator.attach_to_live(zombie_json_path.as_ref()).await
|
orchestrator.attach_to_live(zombie_json_path.as_ref()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn attach_docker(
|
async fn attach_docker(
|
||||||
zombie_json_path: PathBuf,
|
zombie_json_path: PathBuf,
|
||||||
) -> Result<Network<LocalFileSystem>, OrchestratorError> {
|
) -> Result<Network<LocalFileSystem>, OrchestratorError> {
|
||||||
let filesystem = LocalFileSystem;
|
let filesystem = LocalFileSystem;
|
||||||
let provider = DockerProvider::new(filesystem.clone()).await;
|
let provider = DockerProvider::new(filesystem.clone()).await;
|
||||||
let orchestrator = Orchestrator::new(filesystem, provider);
|
let orchestrator = Orchestrator::new(filesystem, provider);
|
||||||
orchestrator.attach_to_live(zombie_json_path.as_ref()).await
|
orchestrator.attach_to_live(zombie_json_path.as_ref()).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-39
@@ -5,9 +5,9 @@ const BEST_BLOCK_METRIC: &str = "block_height{status=\"best\"}";
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn rococo_local_with_omni_node_and_wasm_runtime() {
|
async fn rococo_local_with_omni_node_and_wasm_runtime() {
|
||||||
let _ = tracing_subscriber::fmt::try_init();
|
let _ = tracing_subscriber::fmt::try_init();
|
||||||
|
|
||||||
let config = NetworkConfigBuilder::new()
|
let config = NetworkConfigBuilder::new()
|
||||||
.with_relaychain(|relaychain| {
|
.with_relaychain(|relaychain| {
|
||||||
relaychain
|
relaychain
|
||||||
.with_chain("rococo-local")
|
.with_chain("rococo-local")
|
||||||
@@ -29,47 +29,32 @@ async fn rococo_local_with_omni_node_and_wasm_runtime() {
|
|||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let spawn_fn = get_spawn_fn();
|
let spawn_fn = get_spawn_fn();
|
||||||
let network = spawn_fn(config).await.unwrap();
|
let network = spawn_fn(config).await.unwrap();
|
||||||
|
|
||||||
println!("🚀🚀🚀🚀 network deployed");
|
println!("🚀🚀🚀🚀 network deployed");
|
||||||
|
|
||||||
// wait 2 blocks
|
// wait 2 blocks
|
||||||
let alice = network.get_node("alice").unwrap();
|
let alice = network.get_node("alice").unwrap();
|
||||||
assert!(alice
|
assert!(alice.wait_metric(BEST_BLOCK_METRIC, |b| b > 2_f64).await.is_ok());
|
||||||
.wait_metric(BEST_BLOCK_METRIC, |b| b > 2_f64)
|
|
||||||
.await
|
|
||||||
.is_ok());
|
|
||||||
|
|
||||||
// omni-collator-1
|
// omni-collator-1
|
||||||
let collator = network.get_node("omni-collator-1").unwrap();
|
let collator = network.get_node("omni-collator-1").unwrap();
|
||||||
let client = collator
|
let client = collator.wait_client::<subxt::PolkadotConfig>().await.unwrap();
|
||||||
.wait_client::<subxt::PolkadotConfig>()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// wait 1 blocks
|
// wait 1 blocks
|
||||||
let mut blocks = client.blocks().subscribe_finalized().await.unwrap().take(1);
|
let mut blocks = client.blocks().subscribe_finalized().await.unwrap().take(1);
|
||||||
while let Some(block) = blocks.next().await {
|
while let Some(block) = blocks.next().await {
|
||||||
println!(
|
println!("Block (omni-collator-1) #{}", block.unwrap().header().number);
|
||||||
"Block (omni-collator-1) #{}",
|
}
|
||||||
block.unwrap().header().number
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// omni-collator-2
|
// omni-collator-2
|
||||||
let collator = network.get_node("omni-collator-2").unwrap();
|
let collator = network.get_node("omni-collator-2").unwrap();
|
||||||
let client = collator
|
let client = collator.wait_client::<subxt::PolkadotConfig>().await.unwrap();
|
||||||
.wait_client::<subxt::PolkadotConfig>()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// wait 1 blocks
|
// wait 1 blocks
|
||||||
let mut blocks = client.blocks().subscribe_finalized().await.unwrap().take(1);
|
let mut blocks = client.blocks().subscribe_finalized().await.unwrap().take(1);
|
||||||
while let Some(block) = blocks.next().await {
|
while let Some(block) = blocks.next().await {
|
||||||
println!(
|
println!("Block (omni-collator-2) #{}", block.unwrap().header().number);
|
||||||
"Block (omni-collator-2) #{}",
|
}
|
||||||
block.unwrap().header().number
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-39
@@ -5,9 +5,9 @@ const BEST_BLOCK_METRIC: &str = "block_height{status=\"best\"}";
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn polkadot_local_with_chain_spec_runtime() {
|
async fn polkadot_local_with_chain_spec_runtime() {
|
||||||
let _ = tracing_subscriber::fmt::try_init();
|
let _ = tracing_subscriber::fmt::try_init();
|
||||||
|
|
||||||
let config = NetworkConfigBuilder::new()
|
let config = NetworkConfigBuilder::new()
|
||||||
.with_relaychain(|relaychain| {
|
.with_relaychain(|relaychain| {
|
||||||
relaychain
|
relaychain
|
||||||
.with_chain("polkadot-local")
|
.with_chain("polkadot-local")
|
||||||
@@ -30,47 +30,32 @@ async fn polkadot_local_with_chain_spec_runtime() {
|
|||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let spawn_fn = get_spawn_fn();
|
let spawn_fn = get_spawn_fn();
|
||||||
let network = spawn_fn(config).await.unwrap();
|
let network = spawn_fn(config).await.unwrap();
|
||||||
|
|
||||||
println!("🚀🚀🚀🚀 network deployed");
|
println!("🚀🚀🚀🚀 network deployed");
|
||||||
|
|
||||||
// wait 2 blocks
|
// wait 2 blocks
|
||||||
let alice = network.get_node("alice").unwrap();
|
let alice = network.get_node("alice").unwrap();
|
||||||
assert!(alice
|
assert!(alice.wait_metric(BEST_BLOCK_METRIC, |b| b > 2_f64).await.is_ok());
|
||||||
.wait_metric(BEST_BLOCK_METRIC, |b| b > 2_f64)
|
|
||||||
.await
|
|
||||||
.is_ok());
|
|
||||||
|
|
||||||
// asset-hub-collator-1
|
// asset-hub-collator-1
|
||||||
let collator = network.get_node("asset-hub-collator-1").unwrap();
|
let collator = network.get_node("asset-hub-collator-1").unwrap();
|
||||||
let client = collator
|
let client = collator.wait_client::<subxt::PolkadotConfig>().await.unwrap();
|
||||||
.wait_client::<subxt::PolkadotConfig>()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// wait 1 blocks
|
// wait 1 blocks
|
||||||
let mut blocks = client.blocks().subscribe_finalized().await.unwrap().take(1);
|
let mut blocks = client.blocks().subscribe_finalized().await.unwrap().take(1);
|
||||||
while let Some(block) = blocks.next().await {
|
while let Some(block) = blocks.next().await {
|
||||||
println!(
|
println!("Block (asset-hub-collator-1) #{}", block.unwrap().header().number);
|
||||||
"Block (asset-hub-collator-1) #{}",
|
}
|
||||||
block.unwrap().header().number
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// asset-hub-collator-2
|
// asset-hub-collator-2
|
||||||
let collator = network.get_node("asset-hub-collator-2").unwrap();
|
let collator = network.get_node("asset-hub-collator-2").unwrap();
|
||||||
let client = collator
|
let client = collator.wait_client::<subxt::PolkadotConfig>().await.unwrap();
|
||||||
.wait_client::<subxt::PolkadotConfig>()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// wait 1 blocks
|
// wait 1 blocks
|
||||||
let mut blocks = client.blocks().subscribe_finalized().await.unwrap().take(1);
|
let mut blocks = client.blocks().subscribe_finalized().await.unwrap().take(1);
|
||||||
while let Some(block) = blocks.next().await {
|
while let Some(block) = blocks.next().await {
|
||||||
println!(
|
println!("Block (asset-hub-collator-2) #{}", block.unwrap().header().number);
|
||||||
"Block (asset-hub-collator-2) #{}",
|
}
|
||||||
block.unwrap().header().number
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-19
@@ -4,7 +4,7 @@ use configuration::{NetworkConfig, NetworkConfigBuilder};
|
|||||||
use zombienet_sdk::environment::get_spawn_fn;
|
use zombienet_sdk::environment::get_spawn_fn;
|
||||||
|
|
||||||
fn small_network() -> NetworkConfig {
|
fn small_network() -> NetworkConfig {
|
||||||
NetworkConfigBuilder::new()
|
NetworkConfigBuilder::new()
|
||||||
.with_relaychain(|r| {
|
.with_relaychain(|r| {
|
||||||
r.with_chain("rococo-local")
|
r.with_chain("rococo-local")
|
||||||
.with_default_command("polkadot")
|
.with_default_command("polkadot")
|
||||||
@@ -31,27 +31,24 @@ fn small_network() -> NetworkConfig {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn ci_native_smoke_should_works() {
|
async fn ci_native_smoke_should_works() {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
const BEST_BLOCK_METRIC: &str = "block_height{status=\"best\"}";
|
const BEST_BLOCK_METRIC: &str = "block_height{status=\"best\"}";
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let config = small_network();
|
let config = small_network();
|
||||||
let spawn_fn = get_spawn_fn();
|
let spawn_fn = get_spawn_fn();
|
||||||
|
|
||||||
let network = spawn_fn(config).await.unwrap();
|
let network = spawn_fn(config).await.unwrap();
|
||||||
|
|
||||||
let elapsed = now.elapsed();
|
let elapsed = now.elapsed();
|
||||||
println!("🚀🚀🚀🚀 network deployed in {elapsed:.2?}");
|
println!("🚀🚀🚀🚀 network deployed in {elapsed:.2?}");
|
||||||
|
|
||||||
network.wait_until_is_up(20).await.unwrap();
|
network.wait_until_is_up(20).await.unwrap();
|
||||||
|
|
||||||
let elapsed = now.elapsed();
|
let elapsed = now.elapsed();
|
||||||
println!("✅✅✅✅ network is up in {elapsed:.2?}");
|
println!("✅✅✅✅ network is up in {elapsed:.2?}");
|
||||||
|
|
||||||
// Get a ref to the node
|
// Get a ref to the node
|
||||||
let alice = network.get_node("alice").unwrap();
|
let alice = network.get_node("alice").unwrap();
|
||||||
// wait 10 blocks
|
// wait 10 blocks
|
||||||
alice
|
alice.wait_metric(BEST_BLOCK_METRIC, |x| x > 9_f64).await.unwrap();
|
||||||
.wait_metric(BEST_BLOCK_METRIC, |x| x > 9_f64)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|||||||
+109
-137
@@ -6,174 +6,146 @@ use orchestrator::{AddCollatorOptions, AddNodeOptions};
|
|||||||
use zombienet_sdk::environment::{get_attach_fn, get_spawn_fn};
|
use zombienet_sdk::environment::{get_attach_fn, get_spawn_fn};
|
||||||
|
|
||||||
fn small_network() -> NetworkConfig {
|
fn small_network() -> NetworkConfig {
|
||||||
NetworkConfigBuilder::new()
|
NetworkConfigBuilder::new()
|
||||||
.with_relaychain(|r| {
|
.with_relaychain(|r| {
|
||||||
r.with_chain("rococo-local")
|
r.with_chain("rococo-local")
|
||||||
.with_default_command("polkadot")
|
.with_default_command("polkadot")
|
||||||
.with_default_image("docker.io/parity/polkadot:v1.20.2")
|
.with_default_image("docker.io/parity/polkadot:v1.20.2")
|
||||||
.with_validator(|node| node.with_name("alice"))
|
.with_validator(|node| node.with_name("alice"))
|
||||||
.with_validator(|node| node.with_name("bob"))
|
.with_validator(|node| node.with_name("bob"))
|
||||||
})
|
})
|
||||||
.with_parachain(|p| {
|
.with_parachain(|p| {
|
||||||
p.with_id(2000).cumulus_based(true).with_collator(|n| {
|
p.with_id(2000).cumulus_based(true).with_collator(|n| {
|
||||||
n.with_name("collator")
|
n.with_name("collator")
|
||||||
.with_command("polkadot-parachain")
|
.with_command("polkadot-parachain")
|
||||||
.with_image("docker.io/parity/polkadot-parachain:1.7.0")
|
.with_image("docker.io/parity/polkadot-parachain:1.7.0")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.with_parachain(|p| {
|
.with_parachain(|p| {
|
||||||
p.with_id(3000).cumulus_based(true).with_collator(|n| {
|
p.with_id(3000).cumulus_based(true).with_collator(|n| {
|
||||||
n.with_name("collator-new")
|
n.with_name("collator-new")
|
||||||
.with_command("polkadot-parachain")
|
.with_command("polkadot-parachain")
|
||||||
.with_image("docker.io/parity/polkadot-parachain:v1.20.2")
|
.with_image("docker.io/parity/polkadot-parachain:v1.20.2")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.with_global_settings(|g| {
|
.with_global_settings(|g| {
|
||||||
g.with_base_dir(PathBuf::from("/tmp/zombie-1"))
|
g.with_base_dir(PathBuf::from("/tmp/zombie-1")).with_tear_down_on_failure(false)
|
||||||
.with_tear_down_on_failure(false)
|
})
|
||||||
})
|
.build()
|
||||||
.build()
|
.unwrap()
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn ci_k8s_basic_functionalities_should_works() {
|
async fn ci_k8s_basic_functionalities_should_works() {
|
||||||
let _ = tracing_subscriber::fmt::try_init();
|
let _ = tracing_subscriber::fmt::try_init();
|
||||||
|
|
||||||
const BEST_BLOCK_METRIC: &str = "block_height{status=\"best\"}";
|
const BEST_BLOCK_METRIC: &str = "block_height{status=\"best\"}";
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
let config = small_network();
|
let config = small_network();
|
||||||
let spawn_fn = get_spawn_fn();
|
let spawn_fn = get_spawn_fn();
|
||||||
|
|
||||||
let network = spawn_fn(config).await.unwrap();
|
let network = spawn_fn(config).await.unwrap();
|
||||||
|
|
||||||
let elapsed = now.elapsed();
|
let elapsed = now.elapsed();
|
||||||
println!("🚀🚀🚀🚀 network deployed in {elapsed:.2?}");
|
println!("🚀🚀🚀🚀 network deployed in {elapsed:.2?}");
|
||||||
|
|
||||||
// detach and attach to running
|
// detach and attach to running
|
||||||
network.detach().await;
|
network.detach().await;
|
||||||
drop(network);
|
drop(network);
|
||||||
let attach_fn = get_attach_fn();
|
let attach_fn = get_attach_fn();
|
||||||
let zombie_path = PathBuf::from("/tmp/zombie-1/zombie.json");
|
let zombie_path = PathBuf::from("/tmp/zombie-1/zombie.json");
|
||||||
let mut network = attach_fn(zombie_path).await.unwrap();
|
let mut network = attach_fn(zombie_path).await.unwrap();
|
||||||
|
|
||||||
// Get a ref to the node
|
// Get a ref to the node
|
||||||
let alice = network.get_node("alice").unwrap();
|
let alice = network.get_node("alice").unwrap();
|
||||||
|
|
||||||
let (_best_block_pass, client) = try_join!(
|
let (_best_block_pass, client) = try_join!(
|
||||||
alice.wait_metric(BEST_BLOCK_METRIC, |x| x > 5_f64),
|
alice.wait_metric(BEST_BLOCK_METRIC, |x| x > 5_f64),
|
||||||
alice.wait_client::<subxt::PolkadotConfig>()
|
alice.wait_client::<subxt::PolkadotConfig>()
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
alice
|
alice.wait_log_line_count("*rted #1*", true, 10).await.unwrap();
|
||||||
.wait_log_line_count("*rted #1*", true, 10)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// check best block through metrics with timeout
|
// check best block through metrics with timeout
|
||||||
assert!(alice
|
assert!(alice
|
||||||
.wait_metric_with_timeout(BEST_BLOCK_METRIC, |x| x > 10_f64, 45_u32)
|
.wait_metric_with_timeout(BEST_BLOCK_METRIC, |x| x > 10_f64, 45_u32)
|
||||||
.await
|
.await
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// ensure timeout error
|
// ensure timeout error
|
||||||
let best_block = alice.reports(BEST_BLOCK_METRIC).await.unwrap();
|
let best_block = alice.reports(BEST_BLOCK_METRIC).await.unwrap();
|
||||||
let res = alice
|
let res = alice
|
||||||
.wait_metric_with_timeout(BEST_BLOCK_METRIC, |x| x > (best_block * 2_f64), 10_u32)
|
.wait_metric_with_timeout(BEST_BLOCK_METRIC, |x| x > (best_block * 2_f64), 10_u32)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
|
|
||||||
// get single metric
|
// get single metric
|
||||||
let role = alice.reports("node_roles").await.unwrap();
|
let role = alice.reports("node_roles").await.unwrap();
|
||||||
println!("Role is {role}");
|
println!("Role is {role}");
|
||||||
assert_eq!(role, 4.0);
|
assert_eq!(role, 4.0);
|
||||||
|
|
||||||
// subxt
|
// subxt
|
||||||
// wait 3 blocks
|
// wait 3 blocks
|
||||||
let mut blocks = client.blocks().subscribe_finalized().await.unwrap().take(3);
|
let mut blocks = client.blocks().subscribe_finalized().await.unwrap().take(3);
|
||||||
while let Some(block) = blocks.next().await {
|
while let Some(block) = blocks.next().await {
|
||||||
println!("Block #{}", block.unwrap().header().number);
|
println!("Block #{}", block.unwrap().header().number);
|
||||||
}
|
}
|
||||||
|
|
||||||
// drop the client
|
// drop the client
|
||||||
drop(client);
|
drop(client);
|
||||||
|
|
||||||
// check best block through metrics
|
// check best block through metrics
|
||||||
let best_block = alice
|
let best_block = alice.reports("block_height{status=\"best\"}").await.unwrap();
|
||||||
.reports("block_height{status=\"best\"}")
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(best_block >= 2.0, "Current best {best_block}");
|
assert!(best_block >= 2.0, "Current best {best_block}");
|
||||||
|
|
||||||
// collator
|
// collator
|
||||||
let collator = network.get_node("collator").unwrap();
|
let collator = network.get_node("collator").unwrap();
|
||||||
let client = collator
|
let client = collator.wait_client::<subxt::PolkadotConfig>().await.unwrap();
|
||||||
.wait_client::<subxt::PolkadotConfig>()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// wait 3 blocks
|
// wait 3 blocks
|
||||||
let mut blocks = client.blocks().subscribe_finalized().await.unwrap().take(3);
|
let mut blocks = client.blocks().subscribe_finalized().await.unwrap().take(3);
|
||||||
while let Some(block) = blocks.next().await {
|
while let Some(block) = blocks.next().await {
|
||||||
println!("Block (para) #{}", block.unwrap().header().number);
|
println!("Block (para) #{}", block.unwrap().header().number);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add node
|
// add node
|
||||||
let opts = AddNodeOptions {
|
let opts = AddNodeOptions { rpc_port: Some(9444), is_validator: true, ..Default::default() };
|
||||||
rpc_port: Some(9444),
|
|
||||||
is_validator: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
network.add_node("new1", opts).await.unwrap();
|
network.add_node("new1", opts).await.unwrap();
|
||||||
|
|
||||||
// add collator
|
// add collator
|
||||||
let col_opts = AddCollatorOptions {
|
let col_opts = AddCollatorOptions {
|
||||||
command: Some("polkadot-parachain".try_into().unwrap()),
|
command: Some("polkadot-parachain".try_into().unwrap()),
|
||||||
image: Some(
|
image: Some("docker.io/parity/polkadot-parachain:1.7.0".try_into().unwrap()),
|
||||||
"docker.io/parity/polkadot-parachain:1.7.0"
|
..Default::default()
|
||||||
.try_into()
|
};
|
||||||
.unwrap(),
|
|
||||||
),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
network
|
network.add_collator("new-col-1", col_opts, 2000).await.unwrap();
|
||||||
.add_collator("new-col-1", col_opts, 2000)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// pause / resume
|
// pause / resume
|
||||||
let alice = network.get_node("alice").unwrap();
|
let alice = network.get_node("alice").unwrap();
|
||||||
alice.pause().await.unwrap();
|
alice.pause().await.unwrap();
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||||
|
|
||||||
let res_err = alice
|
let res_err = alice.wait_metric_with_timeout(BEST_BLOCK_METRIC, |x| x > 5_f64, 5_u32).await;
|
||||||
.wait_metric_with_timeout(BEST_BLOCK_METRIC, |x| x > 5_f64, 5_u32)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
assert!(res_err.is_err());
|
assert!(res_err.is_err());
|
||||||
|
|
||||||
alice.resume().await.unwrap();
|
alice.resume().await.unwrap();
|
||||||
alice
|
alice.wait_metric_with_timeout(BEST_BLOCK_METRIC, |x| x > 5_f64, 5_u32).await.unwrap();
|
||||||
.wait_metric_with_timeout(BEST_BLOCK_METRIC, |x| x > 5_f64, 5_u32)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// timeout connecting ws
|
// timeout connecting ws
|
||||||
let collator = network.get_node("collator").unwrap();
|
let collator = network.get_node("collator").unwrap();
|
||||||
collator.pause().await.unwrap();
|
collator.pause().await.unwrap();
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||||
|
|
||||||
let r = collator
|
let r = collator.wait_client_with_timeout::<subxt::PolkadotConfig>(1_u32).await;
|
||||||
.wait_client_with_timeout::<subxt::PolkadotConfig>(1_u32)
|
assert!(r.is_err());
|
||||||
.await;
|
|
||||||
assert!(r.is_err());
|
|
||||||
|
|
||||||
// tear down (optional if you don't detach the network)
|
// tear down (optional if you don't detach the network)
|
||||||
network.destroy().await.unwrap();
|
network.destroy().await.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,43 +2,43 @@ use zombienet_sdk::{environment::get_spawn_fn, NetworkConfigBuilder};
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn two_paras_same_id() {
|
async fn two_paras_same_id() {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
let spawn_fn = get_spawn_fn();
|
let spawn_fn = get_spawn_fn();
|
||||||
let config = NetworkConfigBuilder::new()
|
let config = NetworkConfigBuilder::new()
|
||||||
.with_relaychain(|r| {
|
.with_relaychain(|r| {
|
||||||
r.with_chain("rococo-local")
|
r.with_chain("rococo-local")
|
||||||
.with_default_command("polkadot")
|
.with_default_command("polkadot")
|
||||||
.with_default_image("docker.io/parity/polkadot:v1.7.0")
|
.with_default_image("docker.io/parity/polkadot:v1.7.0")
|
||||||
.with_validator(|node| node.with_name("alice"))
|
.with_validator(|node| node.with_name("alice"))
|
||||||
.with_validator(|node| node.with_name("bob"))
|
.with_validator(|node| node.with_name("bob"))
|
||||||
})
|
})
|
||||||
.with_parachain(|p| {
|
.with_parachain(|p| {
|
||||||
p.with_id(2000)
|
p.with_id(2000)
|
||||||
.with_default_command("polkadot-parachain")
|
.with_default_command("polkadot-parachain")
|
||||||
.with_default_image("docker.io/parity/polkadot-parachain:1.7.0")
|
.with_default_image("docker.io/parity/polkadot-parachain:1.7.0")
|
||||||
.with_collator(|n| n.with_name("collator"))
|
.with_collator(|n| n.with_name("collator"))
|
||||||
})
|
})
|
||||||
.with_parachain(|p| {
|
.with_parachain(|p| {
|
||||||
p.with_id(2000)
|
p.with_id(2000)
|
||||||
.with_default_command("polkadot-parachain")
|
.with_default_command("polkadot-parachain")
|
||||||
.with_default_image("docker.io/parity/polkadot-parachain:1.7.0")
|
.with_default_image("docker.io/parity/polkadot-parachain:1.7.0")
|
||||||
.with_registration_strategy(zombienet_sdk::RegistrationStrategy::Manual)
|
.with_registration_strategy(zombienet_sdk::RegistrationStrategy::Manual)
|
||||||
.with_collator(|n| n.with_name("collator1"))
|
.with_collator(|n| n.with_name("collator1"))
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let network = spawn_fn(config).await.unwrap();
|
let network = spawn_fn(config).await.unwrap();
|
||||||
|
|
||||||
assert!(network.get_node("collator").is_ok());
|
assert!(network.get_node("collator").is_ok());
|
||||||
assert!(network.get_node("collator1").is_ok());
|
assert!(network.get_node("collator1").is_ok());
|
||||||
|
|
||||||
// First parachain (out of two) is fetched
|
// First parachain (out of two) is fetched
|
||||||
assert_eq!(network.parachain(2000).unwrap().unique_id(), "2000");
|
assert_eq!(network.parachain(2000).unwrap().unique_id(), "2000");
|
||||||
|
|
||||||
// First and second parachain hav the same para_id
|
// First and second parachain hav the same para_id
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
network.parachain_by_unique_id("2000").unwrap().para_id(),
|
network.parachain_by_unique_id("2000").unwrap().para_id(),
|
||||||
network.parachain_by_unique_id("2000-1").unwrap().para_id(),
|
network.parachain_by_unique_id("2000-1").unwrap().para_id(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ pub const VALIDATION_CHECK: &str = "validation failed ";
|
|||||||
pub const PREFIX_CANT_BE_NONE: &str = "name prefix can't be None if a value exists ";
|
pub const PREFIX_CANT_BE_NONE: &str = "name prefix can't be None if a value exists ";
|
||||||
|
|
||||||
pub const GRAPH_CONTAINS_NAME: &str =
|
pub const GRAPH_CONTAINS_NAME: &str =
|
||||||
"graph contains node name; we initialize it with all node names";
|
"graph contains node name; we initialize it with all node names";
|
||||||
pub const GRAPH_CONTAINS_DEP: &str = "graph contains dep_name; we filter out deps not contained in by_name and populate the graph with all nodes";
|
pub const GRAPH_CONTAINS_DEP: &str = "graph contains dep_name; we filter out deps not contained in by_name and populate the graph with all nodes";
|
||||||
pub const INDEGREE_CONTAINS_NAME: &str =
|
pub const INDEGREE_CONTAINS_NAME: &str =
|
||||||
"indegree contains node name; we initialize it with all node names";
|
"indegree contains node name; we initialize it with all node names";
|
||||||
pub const QUEUE_NOT_EMPTY: &str = "queue is not empty; we're looping over its length";
|
pub const QUEUE_NOT_EMPTY: &str = "queue is not empty; we're looping over its length";
|
||||||
|
|
||||||
pub const THIS_IS_A_BUG: &str =
|
pub const THIS_IS_A_BUG: &str =
|
||||||
"- this is a bug please report it: https://github.com/paritytech/zombienet-sdk/issues";
|
"- this is a bug please report it: https://github.com/paritytech/zombienet-sdk/issues";
|
||||||
|
|
||||||
/// environment variable which can be used to override node spawn timeout
|
/// environment variable which can be used to override node spawn timeout
|
||||||
pub const ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS: &str = "ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS";
|
pub const ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS: &str = "ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS";
|
||||||
|
|||||||
+33
-33
@@ -10,51 +10,51 @@ pub mod local;
|
|||||||
pub struct FileSystemError(#[from] anyhow::Error);
|
pub struct FileSystemError(#[from] anyhow::Error);
|
||||||
|
|
||||||
impl From<std::io::Error> for FileSystemError {
|
impl From<std::io::Error> for FileSystemError {
|
||||||
fn from(error: std::io::Error) -> Self {
|
fn from(error: std::io::Error) -> Self {
|
||||||
Self(error.into())
|
Self(error.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type FileSystemResult<T> = Result<T, FileSystemError>;
|
pub type FileSystemResult<T> = Result<T, FileSystemError>;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait FileSystem {
|
pub trait FileSystem {
|
||||||
async fn create_dir<P>(&self, path: P) -> FileSystemResult<()>
|
async fn create_dir<P>(&self, path: P) -> FileSystemResult<()>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send;
|
P: AsRef<Path> + Send;
|
||||||
|
|
||||||
async fn create_dir_all<P>(&self, path: P) -> FileSystemResult<()>
|
async fn create_dir_all<P>(&self, path: P) -> FileSystemResult<()>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send;
|
P: AsRef<Path> + Send;
|
||||||
|
|
||||||
async fn read<P>(&self, path: P) -> FileSystemResult<Vec<u8>>
|
async fn read<P>(&self, path: P) -> FileSystemResult<Vec<u8>>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send;
|
P: AsRef<Path> + Send;
|
||||||
|
|
||||||
async fn read_to_string<P>(&self, path: P) -> FileSystemResult<String>
|
async fn read_to_string<P>(&self, path: P) -> FileSystemResult<String>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send;
|
P: AsRef<Path> + Send;
|
||||||
|
|
||||||
async fn write<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
|
async fn write<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send,
|
P: AsRef<Path> + Send,
|
||||||
C: AsRef<[u8]> + Send;
|
C: AsRef<[u8]> + Send;
|
||||||
|
|
||||||
async fn append<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
|
async fn append<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send,
|
P: AsRef<Path> + Send,
|
||||||
C: AsRef<[u8]> + Send;
|
C: AsRef<[u8]> + Send;
|
||||||
|
|
||||||
async fn copy<P1, P2>(&self, from: P1, to: P2) -> FileSystemResult<()>
|
async fn copy<P1, P2>(&self, from: P1, to: P2) -> FileSystemResult<()>
|
||||||
where
|
where
|
||||||
P1: AsRef<Path> + Send,
|
P1: AsRef<Path> + Send,
|
||||||
P2: AsRef<Path> + Send;
|
P2: AsRef<Path> + Send;
|
||||||
|
|
||||||
async fn set_mode<P>(&self, path: P, perm: u32) -> FileSystemResult<()>
|
async fn set_mode<P>(&self, path: P, perm: u32) -> FileSystemResult<()>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send;
|
P: AsRef<Path> + Send;
|
||||||
|
|
||||||
async fn exists<P>(&self, path: P) -> bool
|
async fn exists<P>(&self, path: P) -> bool
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send;
|
P: AsRef<Path> + Send;
|
||||||
}
|
}
|
||||||
|
|||||||
+748
-823
File diff suppressed because it is too large
Load Diff
+286
-299
@@ -10,381 +10,368 @@ pub struct LocalFileSystem;
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl FileSystem for LocalFileSystem {
|
impl FileSystem for LocalFileSystem {
|
||||||
async fn create_dir<P>(&self, path: P) -> FileSystemResult<()>
|
async fn create_dir<P>(&self, path: P) -> FileSystemResult<()>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send,
|
P: AsRef<Path> + Send,
|
||||||
{
|
{
|
||||||
tokio::fs::create_dir(path).await.map_err(Into::into)
|
tokio::fs::create_dir(path).await.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_dir_all<P>(&self, path: P) -> FileSystemResult<()>
|
async fn create_dir_all<P>(&self, path: P) -> FileSystemResult<()>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send,
|
P: AsRef<Path> + Send,
|
||||||
{
|
{
|
||||||
tokio::fs::create_dir_all(path).await.map_err(Into::into)
|
tokio::fs::create_dir_all(path).await.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read<P>(&self, path: P) -> FileSystemResult<Vec<u8>>
|
async fn read<P>(&self, path: P) -> FileSystemResult<Vec<u8>>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send,
|
P: AsRef<Path> + Send,
|
||||||
{
|
{
|
||||||
tokio::fs::read(path).await.map_err(Into::into)
|
tokio::fs::read(path).await.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_to_string<P>(&self, path: P) -> FileSystemResult<String>
|
async fn read_to_string<P>(&self, path: P) -> FileSystemResult<String>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send,
|
P: AsRef<Path> + Send,
|
||||||
{
|
{
|
||||||
tokio::fs::read_to_string(path).await.map_err(Into::into)
|
tokio::fs::read_to_string(path).await.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn write<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
|
async fn write<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send,
|
P: AsRef<Path> + Send,
|
||||||
C: AsRef<[u8]> + Send,
|
C: AsRef<[u8]> + Send,
|
||||||
{
|
{
|
||||||
tokio::fs::write(path, contents).await.map_err(Into::into)
|
tokio::fs::write(path, contents).await.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn append<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
|
async fn append<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send,
|
P: AsRef<Path> + Send,
|
||||||
C: AsRef<[u8]> + Send,
|
C: AsRef<[u8]> + Send,
|
||||||
{
|
{
|
||||||
let contents = contents.as_ref();
|
let contents = contents.as_ref();
|
||||||
let mut file = tokio::fs::OpenOptions::new()
|
let mut file = tokio::fs::OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.append(true)
|
.append(true)
|
||||||
.open(path)
|
.open(path)
|
||||||
.await
|
.await
|
||||||
.map_err(Into::<FileSystemError>::into)?;
|
.map_err(Into::<FileSystemError>::into)?;
|
||||||
|
|
||||||
file.write_all(contents)
|
file.write_all(contents).await.map_err(Into::<FileSystemError>::into)?;
|
||||||
.await
|
|
||||||
.map_err(Into::<FileSystemError>::into)?;
|
|
||||||
|
|
||||||
file.flush().await.and(Ok(())).map_err(Into::into)
|
file.flush().await.and(Ok(())).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn copy<P1, P2>(&self, from: P1, to: P2) -> FileSystemResult<()>
|
async fn copy<P1, P2>(&self, from: P1, to: P2) -> FileSystemResult<()>
|
||||||
where
|
where
|
||||||
P1: AsRef<Path> + Send,
|
P1: AsRef<Path> + Send,
|
||||||
P2: AsRef<Path> + Send,
|
P2: AsRef<Path> + Send,
|
||||||
{
|
{
|
||||||
tokio::fs::copy(from, to)
|
tokio::fs::copy(from, to).await.and(Ok(())).map_err(Into::into)
|
||||||
.await
|
}
|
||||||
.and(Ok(()))
|
|
||||||
.map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_mode<P>(&self, path: P, mode: u32) -> FileSystemResult<()>
|
async fn set_mode<P>(&self, path: P, mode: u32) -> FileSystemResult<()>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send,
|
P: AsRef<Path> + Send,
|
||||||
{
|
{
|
||||||
tokio::fs::set_permissions(path, Permissions::from_mode(mode))
|
tokio::fs::set_permissions(path, Permissions::from_mode(mode)).await.map_err(Into::into)
|
||||||
.await
|
}
|
||||||
.map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn exists<P>(&self, path: P) -> bool
|
async fn exists<P>(&self, path: P) -> bool
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + Send,
|
P: AsRef<Path> + Send,
|
||||||
{
|
{
|
||||||
path.as_ref().exists()
|
path.as_ref().exists()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const FILE_BITS: u32 = 0o100000;
|
const FILE_BITS: u32 = 0o100000;
|
||||||
const DIR_BITS: u32 = 0o40000;
|
const DIR_BITS: u32 = 0o40000;
|
||||||
|
|
||||||
fn setup() -> String {
|
fn setup() -> String {
|
||||||
let test_dir = format!("/tmp/unit_test_{}", Uuid::new_v4());
|
let test_dir = format!("/tmp/unit_test_{}", Uuid::new_v4());
|
||||||
std::fs::create_dir(&test_dir).unwrap();
|
std::fs::create_dir(&test_dir).unwrap();
|
||||||
test_dir
|
test_dir
|
||||||
}
|
}
|
||||||
|
|
||||||
fn teardown(test_dir: String) {
|
fn teardown(test_dir: String) {
|
||||||
std::fs::remove_dir_all(test_dir).unwrap();
|
std::fs::remove_dir_all(test_dir).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn create_dir_should_create_a_new_directory_at_path() {
|
async fn create_dir_should_create_a_new_directory_at_path() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let new_dir = format!("{test_dir}/mynewdir");
|
let new_dir = format!("{test_dir}/mynewdir");
|
||||||
fs.create_dir(&new_dir).await.unwrap();
|
fs.create_dir(&new_dir).await.unwrap();
|
||||||
|
|
||||||
let new_dir_path = Path::new(&new_dir);
|
let new_dir_path = Path::new(&new_dir);
|
||||||
assert!(new_dir_path.exists() && new_dir_path.is_dir());
|
assert!(new_dir_path.exists() && new_dir_path.is_dir());
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn create_dir_should_bubble_up_error_if_some_happens() {
|
async fn create_dir_should_bubble_up_error_if_some_happens() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let new_dir = format!("{test_dir}/mynewdir");
|
let new_dir = format!("{test_dir}/mynewdir");
|
||||||
// intentionally create new dir before calling function to force error
|
// intentionally create new dir before calling function to force error
|
||||||
std::fs::create_dir(&new_dir).unwrap();
|
std::fs::create_dir(&new_dir).unwrap();
|
||||||
let err = fs.create_dir(&new_dir).await.unwrap_err();
|
let err = fs.create_dir(&new_dir).await.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(err.to_string(), "File exists (os error 17)");
|
assert_eq!(err.to_string(), "File exists (os error 17)");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn create_dir_all_should_create_a_new_directory_and_all_of_it_ancestors_at_path() {
|
async fn create_dir_all_should_create_a_new_directory_and_all_of_it_ancestors_at_path() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let new_dir = format!("{test_dir}/the/path/to/mynewdir");
|
let new_dir = format!("{test_dir}/the/path/to/mynewdir");
|
||||||
fs.create_dir_all(&new_dir).await.unwrap();
|
fs.create_dir_all(&new_dir).await.unwrap();
|
||||||
|
|
||||||
let new_dir_path = Path::new(&new_dir);
|
let new_dir_path = Path::new(&new_dir);
|
||||||
assert!(new_dir_path.exists() && new_dir_path.is_dir());
|
assert!(new_dir_path.exists() && new_dir_path.is_dir());
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn create_dir_all_should_bubble_up_error_if_some_happens() {
|
async fn create_dir_all_should_bubble_up_error_if_some_happens() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let new_dir = format!("{test_dir}/the/path/to/mynewdir");
|
let new_dir = format!("{test_dir}/the/path/to/mynewdir");
|
||||||
// intentionally create new file as ancestor before calling function to force error
|
// intentionally create new file as ancestor before calling function to force error
|
||||||
std::fs::write(format!("{test_dir}/the"), b"test").unwrap();
|
std::fs::write(format!("{test_dir}/the"), b"test").unwrap();
|
||||||
let err = fs.create_dir_all(&new_dir).await.unwrap_err();
|
let err = fs.create_dir_all(&new_dir).await.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(err.to_string(), "Not a directory (os error 20)");
|
assert_eq!(err.to_string(), "Not a directory (os error 20)");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn read_should_return_the_contents_of_the_file_at_path() {
|
async fn read_should_return_the_contents_of_the_file_at_path() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let file_path = format!("{test_dir}/myfile");
|
let file_path = format!("{test_dir}/myfile");
|
||||||
std::fs::write(&file_path, b"Test").unwrap();
|
std::fs::write(&file_path, b"Test").unwrap();
|
||||||
let contents = fs.read(file_path).await.unwrap();
|
let contents = fs.read(file_path).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(contents, b"Test");
|
assert_eq!(contents, b"Test");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn read_should_bubble_up_error_if_some_happens() {
|
async fn read_should_bubble_up_error_if_some_happens() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let file_path = format!("{test_dir}/myfile");
|
let file_path = format!("{test_dir}/myfile");
|
||||||
// intentionally forget to create file to force error
|
// intentionally forget to create file to force error
|
||||||
let err = fs.read(file_path).await.unwrap_err();
|
let err = fs.read(file_path).await.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(err.to_string(), "No such file or directory (os error 2)");
|
assert_eq!(err.to_string(), "No such file or directory (os error 2)");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn read_to_string_should_return_the_contents_of_the_file_at_path_as_string() {
|
async fn read_to_string_should_return_the_contents_of_the_file_at_path_as_string() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let file_path = format!("{test_dir}/myfile");
|
let file_path = format!("{test_dir}/myfile");
|
||||||
std::fs::write(&file_path, b"Test").unwrap();
|
std::fs::write(&file_path, b"Test").unwrap();
|
||||||
let contents = fs.read_to_string(file_path).await.unwrap();
|
let contents = fs.read_to_string(file_path).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(contents, "Test");
|
assert_eq!(contents, "Test");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn read_to_string_should_bubble_up_error_if_some_happens() {
|
async fn read_to_string_should_bubble_up_error_if_some_happens() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let file_path = format!("{test_dir}/myfile");
|
let file_path = format!("{test_dir}/myfile");
|
||||||
// intentionally forget to create file to force error
|
// intentionally forget to create file to force error
|
||||||
let err = fs.read_to_string(file_path).await.unwrap_err();
|
let err = fs.read_to_string(file_path).await.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(err.to_string(), "No such file or directory (os error 2)");
|
assert_eq!(err.to_string(), "No such file or directory (os error 2)");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_should_create_a_new_file_at_path_with_contents() {
|
async fn write_should_create_a_new_file_at_path_with_contents() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let file_path = format!("{test_dir}/myfile");
|
let file_path = format!("{test_dir}/myfile");
|
||||||
fs.write(&file_path, "Test").await.unwrap();
|
fs.write(&file_path, "Test").await.unwrap();
|
||||||
|
|
||||||
assert_eq!(std::fs::read_to_string(file_path).unwrap(), "Test");
|
assert_eq!(std::fs::read_to_string(file_path).unwrap(), "Test");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_should_overwrite_an_existing_file_with_contents() {
|
async fn write_should_overwrite_an_existing_file_with_contents() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let file_path = format!("{test_dir}/myfile");
|
let file_path = format!("{test_dir}/myfile");
|
||||||
std::fs::write(&file_path, "Test").unwrap();
|
std::fs::write(&file_path, "Test").unwrap();
|
||||||
assert_eq!(std::fs::read_to_string(&file_path).unwrap(), "Test");
|
assert_eq!(std::fs::read_to_string(&file_path).unwrap(), "Test");
|
||||||
fs.write(&file_path, "Test updated").await.unwrap();
|
fs.write(&file_path, "Test updated").await.unwrap();
|
||||||
|
|
||||||
assert_eq!(std::fs::read_to_string(file_path).unwrap(), "Test updated");
|
assert_eq!(std::fs::read_to_string(file_path).unwrap(), "Test updated");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_should_bubble_up_error_if_some_happens() {
|
async fn write_should_bubble_up_error_if_some_happens() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let file_path = format!("{test_dir}/myfile");
|
let file_path = format!("{test_dir}/myfile");
|
||||||
// intentionally create directory instead of file to force error
|
// intentionally create directory instead of file to force error
|
||||||
std::fs::create_dir(&file_path).unwrap();
|
std::fs::create_dir(&file_path).unwrap();
|
||||||
let err = fs.write(&file_path, "Test").await.unwrap_err();
|
let err = fs.write(&file_path, "Test").await.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(err.to_string(), "Is a directory (os error 21)");
|
assert_eq!(err.to_string(), "Is a directory (os error 21)");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn append_should_create_a_new_file_at_path_with_contents() {
|
async fn append_should_create_a_new_file_at_path_with_contents() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let file_path = format!("{test_dir}/myfile");
|
let file_path = format!("{test_dir}/myfile");
|
||||||
fs.append(&file_path, "Test").await.unwrap();
|
fs.append(&file_path, "Test").await.unwrap();
|
||||||
|
|
||||||
assert_eq!(std::fs::read_to_string(file_path).unwrap(), "Test");
|
assert_eq!(std::fs::read_to_string(file_path).unwrap(), "Test");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn append_should_updates_an_existing_file_by_appending_contents() {
|
async fn append_should_updates_an_existing_file_by_appending_contents() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let file_path = format!("{test_dir}/myfile");
|
let file_path = format!("{test_dir}/myfile");
|
||||||
std::fs::write(&file_path, "Test").unwrap();
|
std::fs::write(&file_path, "Test").unwrap();
|
||||||
assert_eq!(std::fs::read_to_string(&file_path).unwrap(), "Test");
|
assert_eq!(std::fs::read_to_string(&file_path).unwrap(), "Test");
|
||||||
fs.append(&file_path, " updated").await.unwrap();
|
fs.append(&file_path, " updated").await.unwrap();
|
||||||
|
|
||||||
assert_eq!(std::fs::read_to_string(file_path).unwrap(), "Test updated");
|
assert_eq!(std::fs::read_to_string(file_path).unwrap(), "Test updated");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn append_should_bubble_up_error_if_some_happens() {
|
async fn append_should_bubble_up_error_if_some_happens() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let file_path = format!("{test_dir}/myfile");
|
let file_path = format!("{test_dir}/myfile");
|
||||||
// intentionally create directory instead of file to force error
|
// intentionally create directory instead of file to force error
|
||||||
std::fs::create_dir(&file_path).unwrap();
|
std::fs::create_dir(&file_path).unwrap();
|
||||||
let err = fs.append(&file_path, "Test").await.unwrap_err();
|
let err = fs.append(&file_path, "Test").await.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(err.to_string(), "Is a directory (os error 21)");
|
assert_eq!(err.to_string(), "Is a directory (os error 21)");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn copy_should_create_a_duplicate_of_source() {
|
async fn copy_should_create_a_duplicate_of_source() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let from_path = format!("{test_dir}/myfile");
|
let from_path = format!("{test_dir}/myfile");
|
||||||
std::fs::write(&from_path, "Test").unwrap();
|
std::fs::write(&from_path, "Test").unwrap();
|
||||||
let to_path = format!("{test_dir}/mycopy");
|
let to_path = format!("{test_dir}/mycopy");
|
||||||
fs.copy(&from_path, &to_path).await.unwrap();
|
fs.copy(&from_path, &to_path).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(std::fs::read_to_string(to_path).unwrap(), "Test");
|
assert_eq!(std::fs::read_to_string(to_path).unwrap(), "Test");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn copy_should_ovewrite_destination_if_alread_exists() {
|
async fn copy_should_ovewrite_destination_if_alread_exists() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let from_path = format!("{test_dir}/myfile");
|
let from_path = format!("{test_dir}/myfile");
|
||||||
std::fs::write(&from_path, "Test").unwrap();
|
std::fs::write(&from_path, "Test").unwrap();
|
||||||
let to_path = format!("{test_dir}/mycopy");
|
let to_path = format!("{test_dir}/mycopy");
|
||||||
std::fs::write(&from_path, "Some content").unwrap();
|
std::fs::write(&from_path, "Some content").unwrap();
|
||||||
fs.copy(&from_path, &to_path).await.unwrap();
|
fs.copy(&from_path, &to_path).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(std::fs::read_to_string(to_path).unwrap(), "Some content");
|
assert_eq!(std::fs::read_to_string(to_path).unwrap(), "Some content");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn copy_should_bubble_up_error_if_some_happens() {
|
async fn copy_should_bubble_up_error_if_some_happens() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
|
|
||||||
let from_path = format!("{test_dir}/nonexistentfile");
|
let from_path = format!("{test_dir}/nonexistentfile");
|
||||||
let to_path = format!("{test_dir}/mycopy");
|
let to_path = format!("{test_dir}/mycopy");
|
||||||
let err = fs.copy(&from_path, &to_path).await.unwrap_err();
|
let err = fs.copy(&from_path, &to_path).await.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(err.to_string(), "No such file or directory (os error 2)");
|
assert_eq!(err.to_string(), "No such file or directory (os error 2)");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn set_mode_should_update_the_file_mode_at_path() {
|
async fn set_mode_should_update_the_file_mode_at_path() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
let path = format!("{test_dir}/myfile");
|
let path = format!("{test_dir}/myfile");
|
||||||
std::fs::write(&path, "Test").unwrap();
|
std::fs::write(&path, "Test").unwrap();
|
||||||
assert!(std::fs::metadata(&path).unwrap().permissions().mode() != (FILE_BITS + 0o400));
|
assert!(std::fs::metadata(&path).unwrap().permissions().mode() != (FILE_BITS + 0o400));
|
||||||
|
|
||||||
fs.set_mode(&path, 0o400).await.unwrap();
|
fs.set_mode(&path, 0o400).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(std::fs::metadata(&path).unwrap().permissions().mode(), FILE_BITS + 0o400);
|
||||||
std::fs::metadata(&path).unwrap().permissions().mode(),
|
teardown(test_dir);
|
||||||
FILE_BITS + 0o400
|
}
|
||||||
);
|
|
||||||
teardown(test_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn set_mode_should_update_the_directory_mode_at_path() {
|
async fn set_mode_should_update_the_directory_mode_at_path() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
let path = format!("{test_dir}/mydir");
|
let path = format!("{test_dir}/mydir");
|
||||||
std::fs::create_dir(&path).unwrap();
|
std::fs::create_dir(&path).unwrap();
|
||||||
assert!(std::fs::metadata(&path).unwrap().permissions().mode() != (DIR_BITS + 0o700));
|
assert!(std::fs::metadata(&path).unwrap().permissions().mode() != (DIR_BITS + 0o700));
|
||||||
|
|
||||||
fs.set_mode(&path, 0o700).await.unwrap();
|
fs.set_mode(&path, 0o700).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(std::fs::metadata(&path).unwrap().permissions().mode(), DIR_BITS + 0o700);
|
||||||
std::fs::metadata(&path).unwrap().permissions().mode(),
|
teardown(test_dir);
|
||||||
DIR_BITS + 0o700
|
}
|
||||||
);
|
|
||||||
teardown(test_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn set_mode_should_bubble_up_error_if_some_happens() {
|
async fn set_mode_should_bubble_up_error_if_some_happens() {
|
||||||
let test_dir = setup();
|
let test_dir = setup();
|
||||||
let fs = LocalFileSystem;
|
let fs = LocalFileSystem;
|
||||||
let path = format!("{test_dir}/somemissingfile");
|
let path = format!("{test_dir}/somemissingfile");
|
||||||
// intentionnally don't create file
|
// intentionnally don't create file
|
||||||
|
|
||||||
let err = fs.set_mode(&path, 0o400).await.unwrap_err();
|
let err = fs.set_mode(&path, 0o400).await.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(err.to_string(), "No such file or directory (os error 2)");
|
assert_eq!(err.to_string(), "No such file or directory (os error 2)");
|
||||||
teardown(test_dir);
|
teardown(test_dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-38
@@ -8,53 +8,53 @@ use crate::constants::THIS_IS_A_BUG;
|
|||||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||||
|
|
||||||
pub async fn download_file(url: String, dest: String) -> Result<()> {
|
pub async fn download_file(url: String, dest: String) -> Result<()> {
|
||||||
let response = reqwest::get(url).await?;
|
let response = reqwest::get(url).await?;
|
||||||
let mut file = std::fs::File::create(dest)?;
|
let mut file = std::fs::File::create(dest)?;
|
||||||
let mut content = Cursor::new(response.bytes().await?);
|
let mut content = Cursor::new(response.bytes().await?);
|
||||||
std::io::copy(&mut content, &mut file)?;
|
std::io::copy(&mut content, &mut file)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait_ws_ready(url: &str) -> Result<()> {
|
pub async fn wait_ws_ready(url: &str) -> Result<()> {
|
||||||
let mut parsed = Url::from_str(url)?;
|
let mut parsed = Url::from_str(url)?;
|
||||||
parsed
|
parsed
|
||||||
.set_scheme("http")
|
.set_scheme("http")
|
||||||
.map_err(|_| anyhow::anyhow!("Can not set the scheme, {THIS_IS_A_BUG}"))?;
|
.map_err(|_| anyhow::anyhow!("Can not set the scheme, {THIS_IS_A_BUG}"))?;
|
||||||
|
|
||||||
let http_client = reqwest::Client::new();
|
let http_client = reqwest::Client::new();
|
||||||
loop {
|
loop {
|
||||||
let req = Request::new(Method::OPTIONS, parsed.clone());
|
let req = Request::new(Method::OPTIONS, parsed.clone());
|
||||||
let res = http_client.execute(req).await;
|
let res = http_client.execute(req).await;
|
||||||
match res {
|
match res {
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
if res.status() == StatusCode::OK {
|
if res.status() == StatusCode::OK {
|
||||||
// ready to go!
|
// ready to go!
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("http_client status: {}, continuing...", res.status());
|
trace!("http_client status: {}, continuing...", res.status());
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if !skip_err_while_waiting(&e) {
|
if !skip_err_while_waiting(&e) {
|
||||||
return Err(e.into());
|
return Err(e.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("http_client err: {}, continuing... ", e.to_string());
|
trace!("http_client err: {}, continuing... ", e.to_string());
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip_err_while_waiting(e: &reqwest::Error) -> bool {
|
pub fn skip_err_while_waiting(e: &reqwest::Error) -> bool {
|
||||||
// if the error is connecting/request could be the case that the node
|
// if the error is connecting/request could be the case that the node
|
||||||
// is not listening yet, so we keep waiting
|
// is not listening yet, so we keep waiting
|
||||||
// Skipped errs like:
|
// Skipped errs like:
|
||||||
// 'tcp connect error: Connection refused (os error 61)'
|
// 'tcp connect error: Connection refused (os error 61)'
|
||||||
// 'operation was canceled: connection closed before message completed'
|
// 'operation was canceled: connection closed before message completed'
|
||||||
// 'connection error: Connection reset by peer (os error 54)'
|
// 'connection error: Connection reset by peer (os error 54)'
|
||||||
e.is_connect() || e.is_request()
|
e.is_connect() || e.is_request()
|
||||||
}
|
}
|
||||||
|
|||||||
+138
-140
@@ -7,191 +7,189 @@ use tracing::{trace, warn};
|
|||||||
use crate::constants::{SHOULD_COMPILE, THIS_IS_A_BUG};
|
use crate::constants::{SHOULD_COMPILE, THIS_IS_A_BUG};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref RE: Regex = Regex::new(r#"\{\{([a-zA-Z0-9_]*)\}\}"#)
|
static ref RE: Regex = Regex::new(r#"\{\{([a-zA-Z0-9_]*)\}\}"#)
|
||||||
.unwrap_or_else(|_| panic!("{SHOULD_COMPILE}, {THIS_IS_A_BUG}"));
|
.unwrap_or_else(|_| panic!("{SHOULD_COMPILE}, {THIS_IS_A_BUG}"));
|
||||||
static ref TOKEN_PLACEHOLDER: Regex = Regex::new(r#"\{\{ZOMBIE:(.*?):(.*?)\}\}"#)
|
static ref TOKEN_PLACEHOLDER: Regex = Regex::new(r#"\{\{ZOMBIE:(.*?):(.*?)\}\}"#)
|
||||||
.unwrap_or_else(|_| panic!("{SHOULD_COMPILE}, {THIS_IS_A_BUG}"));
|
.unwrap_or_else(|_| panic!("{SHOULD_COMPILE}, {THIS_IS_A_BUG}"));
|
||||||
static ref PLACEHOLDER_COMPAT: HashMap<&'static str, &'static str> = {
|
static ref PLACEHOLDER_COMPAT: HashMap<&'static str, &'static str> = {
|
||||||
let mut m = HashMap::new();
|
let mut m = HashMap::new();
|
||||||
m.insert("multiAddress", "multiaddr");
|
m.insert("multiAddress", "multiaddr");
|
||||||
m.insert("wsUri", "ws_uri");
|
m.insert("wsUri", "ws_uri");
|
||||||
m.insert("prometheusUri", "prometheus_uri");
|
m.insert("prometheusUri", "prometheus_uri");
|
||||||
|
|
||||||
m
|
m
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if the text contains any TOKEN_PLACEHOLDER
|
/// Return true if the text contains any TOKEN_PLACEHOLDER
|
||||||
pub fn has_tokens(text: &str) -> bool {
|
pub fn has_tokens(text: &str) -> bool {
|
||||||
TOKEN_PLACEHOLDER.is_match(text)
|
TOKEN_PLACEHOLDER.is_match(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_replacements(text: &str, replacements: &HashMap<&str, &str>) -> String {
|
pub fn apply_replacements(text: &str, replacements: &HashMap<&str, &str>) -> String {
|
||||||
let augmented_text = RE.replace_all(text, |caps: &Captures| {
|
let augmented_text = RE.replace_all(text, |caps: &Captures| {
|
||||||
if let Some(replacements_value) = replacements.get(&caps[1]) {
|
if let Some(replacements_value) = replacements.get(&caps[1]) {
|
||||||
replacements_value.to_string()
|
replacements_value.to_string()
|
||||||
} else {
|
} else {
|
||||||
caps[0].to_string()
|
caps[0].to_string()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
augmented_text.to_string()
|
augmented_text.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_env_replacements(text: &str) -> String {
|
pub fn apply_env_replacements(text: &str) -> String {
|
||||||
let augmented_text = RE.replace_all(text, |caps: &Captures| {
|
let augmented_text = RE.replace_all(text, |caps: &Captures| {
|
||||||
if let Ok(replacements_value) = std::env::var(&caps[1]) {
|
if let Ok(replacements_value) = std::env::var(&caps[1]) {
|
||||||
replacements_value
|
replacements_value
|
||||||
} else {
|
} else {
|
||||||
caps[0].to_string()
|
caps[0].to_string()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
augmented_text.to_string()
|
augmented_text.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_running_network_replacements(text: &str, network: &serde_json::Value) -> String {
|
pub fn apply_running_network_replacements(text: &str, network: &serde_json::Value) -> String {
|
||||||
let augmented_text = TOKEN_PLACEHOLDER.replace_all(text, |caps: &Captures| {
|
let augmented_text = TOKEN_PLACEHOLDER.replace_all(text, |caps: &Captures| {
|
||||||
trace!("appling replacements for caps: {caps:#?}");
|
trace!("appling replacements for caps: {caps:#?}");
|
||||||
if let Some(node) = network.get(&caps[1]) {
|
if let Some(node) = network.get(&caps[1]) {
|
||||||
trace!("caps1 {} - node: {node}", &caps[1]);
|
trace!("caps1 {} - node: {node}", &caps[1]);
|
||||||
let field = *PLACEHOLDER_COMPAT.get(&caps[2]).unwrap_or(&&caps[2]);
|
let field = *PLACEHOLDER_COMPAT.get(&caps[2]).unwrap_or(&&caps[2]);
|
||||||
if let Some(val) = node.get(field) {
|
if let Some(val) = node.get(field) {
|
||||||
trace!("caps2 {} - node: {node}", field);
|
trace!("caps2 {} - node: {node}", field);
|
||||||
val.as_str().unwrap_or("Invalid string").to_string()
|
val.as_str().unwrap_or("Invalid string").to_string()
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
"⚠️ The node with name {} doesn't have the value {} in context",
|
"⚠️ The node with name {} doesn't have the value {} in context",
|
||||||
&caps[1], &caps[2]
|
&caps[1], &caps[2]
|
||||||
);
|
);
|
||||||
caps[0].to_string()
|
caps[0].to_string()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!("⚠️ No node with name {} in context", &caps[1]);
|
warn!("⚠️ No node with name {} in context", &caps[1]);
|
||||||
caps[0].to_string()
|
caps[0].to_string()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
augmented_text.to_string()
|
augmented_text.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tokens_to_replace(text: &str) -> HashSet<String> {
|
pub fn get_tokens_to_replace(text: &str) -> HashSet<String> {
|
||||||
let mut tokens = HashSet::new();
|
let mut tokens = HashSet::new();
|
||||||
|
|
||||||
TOKEN_PLACEHOLDER
|
TOKEN_PLACEHOLDER.captures_iter(text).for_each(|caps: Captures| {
|
||||||
.captures_iter(text)
|
tokens.insert(caps[1].to_string());
|
||||||
.for_each(|caps: Captures| {
|
});
|
||||||
tokens.insert(caps[1].to_string());
|
|
||||||
});
|
|
||||||
|
|
||||||
tokens
|
tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replace_should_works() {
|
fn replace_should_works() {
|
||||||
let text = "some {{namespace}}";
|
let text = "some {{namespace}}";
|
||||||
let mut replacements = HashMap::new();
|
let mut replacements = HashMap::new();
|
||||||
replacements.insert("namespace", "demo-123");
|
replacements.insert("namespace", "demo-123");
|
||||||
let res = apply_replacements(text, &replacements);
|
let res = apply_replacements(text, &replacements);
|
||||||
assert_eq!("some demo-123".to_string(), res);
|
assert_eq!("some demo-123".to_string(), res);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replace_env_should_works() {
|
fn replace_env_should_works() {
|
||||||
let text = "some {{namespace}}";
|
let text = "some {{namespace}}";
|
||||||
std::env::set_var("namespace", "demo-123");
|
std::env::set_var("namespace", "demo-123");
|
||||||
// let mut replacements = HashMap::new();
|
// let mut replacements = HashMap::new();
|
||||||
// replacements.insert("namespace", "demo-123");
|
// replacements.insert("namespace", "demo-123");
|
||||||
let res = apply_env_replacements(text);
|
let res = apply_env_replacements(text);
|
||||||
assert_eq!("some demo-123".to_string(), res);
|
assert_eq!("some demo-123".to_string(), res);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replace_multiple_should_works() {
|
fn replace_multiple_should_works() {
|
||||||
let text = r#"some {{namespace}}
|
let text = r#"some {{namespace}}
|
||||||
other is {{other}}"#;
|
other is {{other}}"#;
|
||||||
let augmented_text = r#"some demo-123
|
let augmented_text = r#"some demo-123
|
||||||
other is other-123"#;
|
other is other-123"#;
|
||||||
|
|
||||||
let mut replacements = HashMap::new();
|
let mut replacements = HashMap::new();
|
||||||
replacements.insert("namespace", "demo-123");
|
replacements.insert("namespace", "demo-123");
|
||||||
replacements.insert("other", "other-123");
|
replacements.insert("other", "other-123");
|
||||||
let res = apply_replacements(text, &replacements);
|
let res = apply_replacements(text, &replacements);
|
||||||
assert_eq!(augmented_text, res);
|
assert_eq!(augmented_text, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replace_multiple_with_missing_should_works() {
|
fn replace_multiple_with_missing_should_works() {
|
||||||
let text = r#"some {{namespace}}
|
let text = r#"some {{namespace}}
|
||||||
other is {{other}}"#;
|
other is {{other}}"#;
|
||||||
let augmented_text = r#"some demo-123
|
let augmented_text = r#"some demo-123
|
||||||
other is {{other}}"#;
|
other is {{other}}"#;
|
||||||
|
|
||||||
let mut replacements = HashMap::new();
|
let mut replacements = HashMap::new();
|
||||||
replacements.insert("namespace", "demo-123");
|
replacements.insert("namespace", "demo-123");
|
||||||
|
|
||||||
let res = apply_replacements(text, &replacements);
|
let res = apply_replacements(text, &replacements);
|
||||||
assert_eq!(augmented_text, res);
|
assert_eq!(augmented_text, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replace_without_replacement_should_leave_text_unchanged() {
|
fn replace_without_replacement_should_leave_text_unchanged() {
|
||||||
let text = "some {{namespace}}";
|
let text = "some {{namespace}}";
|
||||||
let mut replacements = HashMap::new();
|
let mut replacements = HashMap::new();
|
||||||
replacements.insert("other", "demo-123");
|
replacements.insert("other", "demo-123");
|
||||||
let res = apply_replacements(text, &replacements);
|
let res = apply_replacements(text, &replacements);
|
||||||
assert_eq!(text.to_string(), res);
|
assert_eq!(text.to_string(), res);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replace_running_network_should_work() {
|
fn replace_running_network_should_work() {
|
||||||
let network = json!({
|
let network = json!({
|
||||||
"alice" : {
|
"alice" : {
|
||||||
"multiaddr": "some/demo/127.0.0.1"
|
"multiaddr": "some/demo/127.0.0.1"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let res = apply_running_network_replacements("{{ZOMBIE:alice:multiaddr}}", &network);
|
let res = apply_running_network_replacements("{{ZOMBIE:alice:multiaddr}}", &network);
|
||||||
assert_eq!(res.as_str(), "some/demo/127.0.0.1");
|
assert_eq!(res.as_str(), "some/demo/127.0.0.1");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replace_running_network_with_compat_should_work() {
|
fn replace_running_network_with_compat_should_work() {
|
||||||
let network = json!({
|
let network = json!({
|
||||||
"alice" : {
|
"alice" : {
|
||||||
"multiaddr": "some/demo/127.0.0.1"
|
"multiaddr": "some/demo/127.0.0.1"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let res = apply_running_network_replacements("{{ZOMBIE:alice:multiAddress}}", &network);
|
let res = apply_running_network_replacements("{{ZOMBIE:alice:multiAddress}}", &network);
|
||||||
assert_eq!(res.as_str(), "some/demo/127.0.0.1");
|
assert_eq!(res.as_str(), "some/demo/127.0.0.1");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replace_running_network_with_missing_field_should_not_replace_nothing() {
|
fn replace_running_network_with_missing_field_should_not_replace_nothing() {
|
||||||
let network = json!({
|
let network = json!({
|
||||||
"alice" : {
|
"alice" : {
|
||||||
"multiaddr": "some/demo/127.0.0.1"
|
"multiaddr": "some/demo/127.0.0.1"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let res = apply_running_network_replacements("{{ZOMBIE:alice:someField}}", &network);
|
let res = apply_running_network_replacements("{{ZOMBIE:alice:someField}}", &network);
|
||||||
assert_eq!(res.as_str(), "{{ZOMBIE:alice:someField}}");
|
assert_eq!(res.as_str(), "{{ZOMBIE:alice:someField}}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_tokens_to_replace_should_work() {
|
fn get_tokens_to_replace_should_work() {
|
||||||
let res = get_tokens_to_replace("{{ZOMBIE:alice:multiaddr}} {{ZOMBIE:bob:multiaddr}}");
|
let res = get_tokens_to_replace("{{ZOMBIE:alice:multiaddr}} {{ZOMBIE:bob:multiaddr}}");
|
||||||
let mut expected = HashSet::new();
|
let mut expected = HashSet::new();
|
||||||
expected.insert("alice".to_string());
|
expected.insert("alice".to_string());
|
||||||
expected.insert("bob".to_string());
|
expected.insert("bob".to_string());
|
||||||
|
|
||||||
assert_eq!(res, expected);
|
assert_eq!(res, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-21
@@ -1,26 +1,19 @@
|
|||||||
# https://rust-lang.github.io/rustfmt/?version=v1.7.0
|
# Pezkuwi ZombieNet SDK - Stable Rustfmt Configuration
|
||||||
|
# Only stable features for compatibility with CI
|
||||||
|
|
||||||
# general
|
# Basic (stable)
|
||||||
indent_style = "Block"
|
hard_tabs = true
|
||||||
|
max_width = 100
|
||||||
|
use_small_heuristics = "Max"
|
||||||
|
|
||||||
# rewriting
|
# Imports (stable)
|
||||||
condense_wildcard_suffixes = true
|
reorder_imports = true
|
||||||
|
reorder_modules = true
|
||||||
|
|
||||||
|
# Consistency (stable)
|
||||||
|
newline_style = "Unix"
|
||||||
|
|
||||||
|
# Misc (stable)
|
||||||
match_block_trailing_comma = true
|
match_block_trailing_comma = true
|
||||||
use_field_init_shorthand = true
|
use_field_init_shorthand = true
|
||||||
use_try_shorthand = true
|
use_try_shorthand = true
|
||||||
|
|
||||||
# normalization
|
|
||||||
normalize_comments = true
|
|
||||||
normalize_doc_attributes = true
|
|
||||||
|
|
||||||
# reordering
|
|
||||||
reorder_impl_items = true
|
|
||||||
reorder_imports = true
|
|
||||||
reorder_modules = true
|
|
||||||
imports_granularity = "Crate"
|
|
||||||
group_imports = "StdExternalCrate"
|
|
||||||
|
|
||||||
# additional formating
|
|
||||||
format_code_in_doc_comments = true
|
|
||||||
format_macro_matchers = true
|
|
||||||
format_macro_bodies = true
|
|
||||||
|
|||||||
Reference in New Issue
Block a user