|
@@ -44,6 +44,7 @@ pub struct ServeOpts {
|
|
|
/// the whole selected cluster; `Some("N")` = scale to N nodes;
|
|
/// the whole selected cluster; `Some("N")` = scale to N nodes;
|
|
|
/// `None` = unset. See `docker::serve`.
|
|
/// `None` = unset. See `docker::serve`.
|
|
|
pub fit: Option<String>,
|
|
pub fit: Option<String>,
|
|
|
|
|
+ pub clear: bool,
|
|
|
}
|
|
}
|
|
|
/// Shell-quote a string for safe interpolation into a remote `sh -c`
|
|
/// Shell-quote a string for safe interpolation into a remote `sh -c`
|
|
|
/// command (single-quote wrapping, embedded quotes escaped). Re-exported
|
|
/// command (single-quote wrapping, embedded quotes escaped). Re-exported
|
|
@@ -96,11 +97,10 @@ pub async fn serve(
|
|
|
use crate::ends::{SshNodePinger, SshProber, ha_controller_from_config};
|
|
use crate::ends::{SshNodePinger, SshProber, ha_controller_from_config};
|
|
|
use crate::orchestrate::{BootTiming, PrepareRequest, prepare_cluster};
|
|
use crate::orchestrate::{BootTiming, PrepareRequest, prepare_cluster};
|
|
|
|
|
|
|
|
- let power_ctrl = if opts.auto_power_on {
|
|
|
|
|
- ha_controller_from_config(config)
|
|
|
|
|
- } else {
|
|
|
|
|
- None
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ let power_ctrl = opts
|
|
|
|
|
+ .auto_power_on
|
|
|
|
|
+ .then(|| ha_controller_from_config(config))
|
|
|
|
|
+ .flatten();
|
|
|
// Effective solo: CLI flag overrides recipe field. When solo and no
|
|
// Effective solo: CLI flag overrides recipe field. When solo and no
|
|
|
// explicit --node, launch on exactly one node (the first free node in
|
|
// explicit --node, launch on exactly one node (the first free node in
|
|
|
// the smallest cluster that has one) with no distributed flags. When
|
|
// the smallest cluster that has one) with no distributed flags. When
|
|
@@ -177,13 +177,10 @@ pub async fn serve(
|
|
|
)
|
|
)
|
|
|
.await?;
|
|
.await?;
|
|
|
|
|
|
|
|
- use self::sync::sync_image_to_nodes;
|
|
|
|
|
-
|
|
|
|
|
- // Build image on specified node if needed.
|
|
|
|
|
- // `built_on` records the node that actually (re)built the image. After a
|
|
|
|
|
- // fresh build that image is authoritative — the sync step below must pull
|
|
|
|
|
- // FROM it, never let a peer's older image (by creation timestamp) overwrite
|
|
|
|
|
- // the new build.
|
|
|
|
|
|
|
+ // `built_on` records the node that (re)built the image this serve: after
|
|
|
|
|
+ // a fresh build that image is authoritative — the sync step below must
|
|
|
|
|
+ // pull FROM it, never let a peer's older image (by creation timestamp)
|
|
|
|
|
+ // overwrite the new build.
|
|
|
let mut built_on: Option<String> = None;
|
|
let mut built_on: Option<String> = None;
|
|
|
if let Some(ref build_node_name) = opts.build_node {
|
|
if let Some(ref build_node_name) = opts.build_node {
|
|
|
if !opts.dry_run && !cluster.nodes.iter().any(|n| n.name == *build_node_name) {
|
|
if !opts.dry_run && !cluster.nodes.iter().any(|n| n.name == *build_node_name) {
|
|
@@ -233,8 +230,8 @@ pub async fn serve(
|
|
|
// the new build. The "most recently created" heuristic only applies
|
|
// the new build. The "most recently created" heuristic only applies
|
|
|
// when nothing was built here and no explicit --sync-image-source was
|
|
// when nothing was built here and no explicit --sync-image-source was
|
|
|
// given — i.e. the nodes merely disagree and we pick the newest.
|
|
// given — i.e. the nodes merely disagree and we pick the newest.
|
|
|
- let sync_source = built_on.as_deref().or(opts.sync_image_source.as_deref());
|
|
|
|
|
- sync_image_to_nodes(config, &cluster, &recipe.docker_image, sync_source).await?;
|
|
|
|
|
|
|
+ let source = built_on.as_deref().or(opts.sync_image_source.as_deref());
|
|
|
|
|
+ self::sync::sync_image_to_nodes(config, &cluster, &recipe.docker_image, source).await?;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Drop page cache on all nodes right before starting containers.
|
|
// Drop page cache on all nodes right before starting containers.
|
|
@@ -272,6 +269,7 @@ pub async fn serve(
|
|
|
node_name: node_name.map(String::from),
|
|
node_name: node_name.map(String::from),
|
|
|
mods_remote_path,
|
|
mods_remote_path,
|
|
|
dry_run: opts.dry_run,
|
|
dry_run: opts.dry_run,
|
|
|
|
|
+ clear: opts.clear,
|
|
|
},
|
|
},
|
|
|
)
|
|
)
|
|
|
.await?;
|
|
.await?;
|
|
@@ -363,6 +361,7 @@ struct StartClusterOpts {
|
|
|
node_name: Option<String>,
|
|
node_name: Option<String>,
|
|
|
mods_remote_path: String,
|
|
mods_remote_path: String,
|
|
|
dry_run: bool,
|
|
dry_run: bool,
|
|
|
|
|
+ clear: bool,
|
|
|
}
|
|
}
|
|
|
async fn start_cluster(
|
|
async fn start_cluster(
|
|
|
config: &Config,
|
|
config: &Config,
|
|
@@ -422,6 +421,7 @@ async fn start_cluster(
|
|
|
// (`repo`+`subfolder`) are kept as specs and resolved per node inside the
|
|
// (`repo`+`subfolder`) are kept as specs and resolved per node inside the
|
|
|
// spawn loop, since the HF snapshot hash can differ across nodes.
|
|
// spawn loop, since the HF snapshot hash can differ across nodes.
|
|
|
let resolved = command::resolve_extra_mounts(config, recipe)?;
|
|
let resolved = command::resolve_extra_mounts(config, recipe)?;
|
|
|
|
|
+ command::clear_extra_mounts(config, &nodes, &resolved, opts.clear, opts.dry_run).await?;
|
|
|
let mut handles = Vec::new();
|
|
let mut handles = Vec::new();
|
|
|
for node in &nodes {
|
|
for node in &nodes {
|
|
|
let node = (*node).clone();
|
|
let node = (*node).clone();
|