Skip to content

Commit

Permalink
Merge pull request #237 from subspace/full-reconfiguration
Browse files Browse the repository at this point in the history
Full reconfiguration
  • Loading branch information
nazar-pc authored Jul 23, 2024
2 parents a87cf9a + 3166f0f commit e97fe09
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 128 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ subspace-rpc-primitives = { git = "https://github.com/subspace/subspace", rev =
subspace-runtime-primitives = { git = "https://github.com/subspace/subspace", rev = "18dd43ab3ca9666aec256fb99c6cb7b8e4eeaea5" }
subspace-service = { git = "https://github.com/subspace/subspace", rev = "18dd43ab3ca9666aec256fb99c6cb7b8e4eeaea5" }
supports-color = "3.0.0"
tempfile = "3.10.1"
thiserror = "1.0.61"
thread-priority = "1.1.0"
tokio = { version = "1.38.0", features = ["fs", "time"] }
Expand Down
51 changes: 19 additions & 32 deletions src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,16 +185,14 @@ pub enum BackendNotification {
/// Progress in %: 0.0..=100.0
progress: f32,
},
IncompatibleChain {
ConfigurationFound {
raw_config: RawConfig,
},
IncompatibleChain {
compatible_chain: String,
},
NotConfigured,
// TODO: Indicate what is invalid so that UI can render it properly
ConfigurationIsInvalid {
// TODO: Remove suppression once used
#[allow(dead_code)]
config: RawConfig,
error: ConfigError,
},
ConfigSaveResult(anyhow::Result<()>),
Expand Down Expand Up @@ -239,10 +237,7 @@ struct LoadedBackend {

enum BackendLoadingResult {
Success(LoadedBackend),
IncompatibleChain {
raw_config: RawConfig,
compatible_chain: String,
},
IncompatibleChain { compatible_chain: String },
}

// NOTE: this is an async function, but it might do blocking operations and should be running on a
Expand Down Expand Up @@ -272,10 +267,7 @@ pub async fn create(
BackendAction::NewConfig { raw_config } => {
if let Err(error) = Config::try_from_raw_config(&raw_config).await {
notifications_sender
.send(BackendNotification::ConfigurationIsInvalid {
config: raw_config.clone(),
error,
})
.send(BackendNotification::ConfigurationIsInvalid { error })
.await?;
}

Expand Down Expand Up @@ -312,15 +304,9 @@ pub async fn create(
// Loaded successfully
loaded_backend
}
Ok(BackendLoadingResult::IncompatibleChain {
raw_config,
compatible_chain,
}) => {
Ok(BackendLoadingResult::IncompatibleChain { compatible_chain }) => {
if let Err(error) = notifications_sender
.send(BackendNotification::IncompatibleChain {
raw_config,
compatible_chain,
})
.send(BackendNotification::IncompatibleChain { compatible_chain })
.await
{
error!(%error, "Failed to send incompatible chain notification");
Expand Down Expand Up @@ -424,7 +410,6 @@ async fn load(
LoadedConsensusChainNode::Compatible(consensus_node) => consensus_node,
LoadedConsensusChainNode::Incompatible { compatible_chain } => {
return Ok(Some(BackendLoadingResult::IncompatibleChain {
raw_config,
compatible_chain,
}));
}
Expand Down Expand Up @@ -617,24 +602,23 @@ async fn load_configuration(
})
.await?;

// TODO: Make configuration errors recoverable
let maybe_config = RawConfig::read_from_path(&config_file_path).await?;
let maybe_raw_config = RawConfig::read_from_path(&config_file_path).await?;

notifications_sender
.send(BackendNotification::Loading {
step: LoadingStep::ConfigurationReadSuccessfully {
configuration_exists: maybe_config.is_some(),
configuration_exists: maybe_raw_config.is_some(),
},
progress: 0.0,
})
.await?;

Ok((config_file_path, maybe_config))
Ok((config_file_path, maybe_raw_config))
}

/// Returns `Ok(None)` if configuration failed validation
async fn check_configuration(
config: &RawConfig,
raw_config: &RawConfig,
notifications_sender: &mut mpsc::Sender<BackendNotification>,
) -> anyhow::Result<Option<Config>> {
notifications_sender
Expand All @@ -644,7 +628,13 @@ async fn check_configuration(
})
.await?;

match Config::try_from_raw_config(config).await {
notifications_sender
.send(BackendNotification::ConfigurationFound {
raw_config: raw_config.clone(),
})
.await?;

match Config::try_from_raw_config(raw_config).await {
Ok(config) => {
notifications_sender
.send(BackendNotification::Loading {
Expand All @@ -656,10 +646,7 @@ async fn check_configuration(
}
Err(error) => {
notifications_sender
.send(BackendNotification::ConfigurationIsInvalid {
config: config.clone(),
error,
})
.send(BackendNotification::ConfigurationIsInvalid { error })
.await?;

Ok(None)
Expand Down
74 changes: 50 additions & 24 deletions src/backend/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ use std::path::{Path, PathBuf};
use std::str::FromStr;
use subspace_core_primitives::PublicKey;
use subspace_farmer::utils::ss58::{parse_ss58_reward_address, Ss58ParsingError};
use tokio::fs;
use tokio::fs::OpenOptions;
use tokio::io::AsyncWriteExt;
use tokio::task;

const DEFAULT_SUBSTRATE_PORT: u16 = 30333;
const DEFAULT_SUBSPACE_PORT: u16 = 30433;
Expand Down Expand Up @@ -90,7 +89,7 @@ impl RawConfig {
};

let app_config_dir = config_local_dir.join(env!("CARGO_PKG_NAME"));
let config_file_path = match fs::create_dir(&app_config_dir).await {
let config_file_path = match tokio::fs::create_dir(&app_config_dir).await {
Ok(()) => app_config_dir.join("config.json"),
Err(error) => {
if error.kind() == io::ErrorKind::AlreadyExists {
Expand All @@ -105,7 +104,7 @@ impl RawConfig {
}

pub async fn read_from_path(config_file_path: &Path) -> Result<Option<Self>, RawConfigError> {
match fs::read_to_string(config_file_path).await {
match tokio::fs::read_to_string(config_file_path).await {
Ok(config) => serde_json::from_str::<Self>(&config)
.map(Some)
.map_err(RawConfigError::FailedToDeserialize),
Expand All @@ -120,7 +119,7 @@ impl RawConfig {
}

pub async fn write_to_path(&self, config_file_path: &Path) -> io::Result<()> {
let mut options = OpenOptions::new();
let mut options = tokio::fs::OpenOptions::new();
options.write(true).truncate(true).create(true);
#[cfg(unix)]
options.mode(0o600);
Expand Down Expand Up @@ -198,14 +197,14 @@ impl Config {
})?;

let node_path = raw_config.node_path().clone();
check_path(&node_path).await?;
check_path(node_path.clone()).await?;

let mut farms = Vec::with_capacity(raw_config.farms().len());

for farm in raw_config.farms() {
let path = PathBuf::from(&farm.path);

check_path(&path).await?;
check_path(path.clone()).await?;

let size = ByteSize::from_str(&farm.size)
.map_err(|error| ConfigError::InvalidSizeFormat {
Expand All @@ -229,35 +228,62 @@ impl Config {
}
}

async fn check_path(path: &Path) -> Result<(), ConfigError> {
let exists = fs::try_exists(&path)
.await
.map_err(|error| ConfigError::PathError {
async fn check_path(path: PathBuf) -> Result<(), ConfigError> {
let path_string = path.display().to_string();
task::spawn_blocking(move || {
let exists = path.try_exists().map_err(|error| ConfigError::PathError {
path: path.display().to_string(),
error,
})?;

if !exists {
let Some(parent) = path.parent() else {
return Err(ConfigError::InvalidPath {
if exists {
// Try to create a temporary file to check if path is writable
tempfile::tempfile_in(&path).map_err(|error| ConfigError::PathError {
path: path.display().to_string(),
});
};
error: io::Error::new(
io::ErrorKind::PermissionDenied,
format!("Path not writable: {error}"),
),
})?;
} else {
let Some(parent) = path.parent() else {
return Err(ConfigError::InvalidPath {
path: path.display().to_string(),
});
};

let parent_exists =
fs::try_exists(parent)
.await
let parent_exists = parent
.try_exists()
.map_err(|error| ConfigError::PathError {
path: path.display().to_string(),
error,
})?;

if !parent_exists {
return Err(ConfigError::InvalidPath {
if !parent_exists {
return Err(ConfigError::InvalidPath {
path: path.display().to_string(),
});
}

// Try to create a temporary file in parent directory to check if path is writable, and
// it would be possible to create a parent directory later
tempfile::tempfile_in(parent).map_err(|error| ConfigError::PathError {
path: path.display().to_string(),
});
error: io::Error::new(
io::ErrorKind::PermissionDenied,
format!("Path doesn't exist and can't be created: {error}"),
),
})?;
}
}

Ok(())
Ok(())
})
.await
.map_err(|error| ConfigError::PathError {
path: path_string,
error: io::Error::new(
io::ErrorKind::Other,
format!("Failed to spawn tokio task: {error}"),
),
})?
}
Loading

0 comments on commit e97fe09

Please sign in to comment.