Exclude image preparation from client startup deadline
This commit is contained in:
parent
8334fd379f
commit
55eed08db7
4 changed files with 106 additions and 22 deletions
|
|
@ -4,8 +4,8 @@ use std::path::PathBuf;
|
|||
use clap::{Args, Parser, Subcommand};
|
||||
use lazaret_core::records::StateResetScopeV1;
|
||||
|
||||
use crate::ClientCommand;
|
||||
use crate::state::{StateCommand, StateOperation};
|
||||
use crate::{ClientCommand, DEFAULT_STARTUP_TIMEOUT};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
|
|
@ -36,6 +36,14 @@ enum PublicCommand {
|
|||
struct RunArguments {
|
||||
#[command(flatten)]
|
||||
domain: DomainArguments,
|
||||
/// Maximum time to wait for Domain publication, in seconds.
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "SECONDS",
|
||||
default_value_t = DEFAULT_STARTUP_TIMEOUT.as_secs(),
|
||||
value_parser = clap::value_parser!(u64).range(1..)
|
||||
)]
|
||||
startup_timeout_seconds: u64,
|
||||
/// Command and arguments to run instead of the default `/bin/bash`.
|
||||
#[arg(last = true, num_args = 1.., value_name = "COMMAND")]
|
||||
command: Vec<OsString>,
|
||||
|
|
@ -121,6 +129,7 @@ pub fn parse(
|
|||
profile: arguments.domain.profile,
|
||||
workspace: arguments.domain.workspace.unwrap_or(current_workspace),
|
||||
configuration: arguments.domain.config,
|
||||
startup_timeout: std::time::Duration::from_secs(arguments.startup_timeout_seconds),
|
||||
argv: command_argv(arguments.command),
|
||||
}),
|
||||
PublicCommand::State { operation } => {
|
||||
|
|
@ -195,6 +204,7 @@ fn state_command(operation: StateArguments, current_workspace: PathBuf) -> State
|
|||
profile: domain.profile,
|
||||
workspace: domain.workspace.unwrap_or(current_workspace),
|
||||
configuration: domain.config,
|
||||
startup_timeout: DEFAULT_STARTUP_TIMEOUT,
|
||||
argv: Vec::new(),
|
||||
},
|
||||
machine,
|
||||
|
|
|
|||
|
|
@ -157,8 +157,22 @@ impl CoordinatorClientBackend {
|
|||
let mut child = child_broker()
|
||||
.spawn_bootstrap(&target, File::from(reader))
|
||||
.map_err(failure)?;
|
||||
match child.acquisition_status().map_err(failure)? {
|
||||
match child.bootstrap_status().map_err(failure)? {
|
||||
b'L' => {
|
||||
match child.bootstrap_status().map_err(failure)? {
|
||||
b'R' => {}
|
||||
b'E' => {
|
||||
let result = child.reap().map_err(failure)?;
|
||||
return Err(failure(format!(
|
||||
"Host Coordinator refused image preparation with {result:?}"
|
||||
)));
|
||||
}
|
||||
_ => {
|
||||
return Err(failure(
|
||||
"Host Coordinator sent an invalid image-preparation status",
|
||||
));
|
||||
}
|
||||
}
|
||||
self.coordinator = Some(child);
|
||||
self.bootstrap_owner = Some(writer);
|
||||
Ok(BootstrapAcquisition::Leader)
|
||||
|
|
@ -205,7 +219,16 @@ impl CoordinatorClientBackend {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
let mut observer = DomainObservation::new(initial_path, SystemObservationClock::start());
|
||||
let startup_timeout = self
|
||||
.command
|
||||
.as_ref()
|
||||
.ok_or_else(|| ClientError::Backend(failure("client command was not validated")))?
|
||||
.startup_timeout;
|
||||
let mut observer = DomainObservation::new(
|
||||
initial_path,
|
||||
SystemObservationClock::start(),
|
||||
startup_timeout,
|
||||
);
|
||||
let mut waiter_transfer = None;
|
||||
loop {
|
||||
let (state, endpoint) = match self.discover().map_err(ClientError::Backend)? {
|
||||
|
|
|
|||
|
|
@ -40,20 +40,16 @@ pub(crate) enum ObservationFailure {
|
|||
#[derive(Clone, Copy)]
|
||||
struct ObservationPolicy {
|
||||
retry: Duration,
|
||||
deadline: Duration,
|
||||
}
|
||||
|
||||
const BOOTSTRAP_POLICY: ObservationPolicy = ObservationPolicy {
|
||||
retry: Duration::from_millis(10),
|
||||
deadline: Duration::from_secs(30),
|
||||
};
|
||||
const WAITING_POLICY: ObservationPolicy = ObservationPolicy {
|
||||
retry: Duration::from_millis(10),
|
||||
deadline: Duration::from_secs(30),
|
||||
};
|
||||
const TRANSFER_POLICY: ObservationPolicy = ObservationPolicy {
|
||||
retry: Duration::from_millis(1),
|
||||
deadline: Duration::from_secs(30),
|
||||
};
|
||||
|
||||
pub(crate) trait ObservationClock {
|
||||
|
|
@ -91,11 +87,16 @@ impl ObservationClock for SystemObservationClock {
|
|||
pub(crate) struct DomainObservation<C> {
|
||||
path: ObservationPath,
|
||||
clock: C,
|
||||
deadline: Duration,
|
||||
}
|
||||
|
||||
impl<C: ObservationClock> DomainObservation<C> {
|
||||
pub(crate) fn new(path: ObservationPath, clock: C) -> Self {
|
||||
Self { path, clock }
|
||||
pub(crate) fn new(path: ObservationPath, clock: C, deadline: Duration) -> Self {
|
||||
Self {
|
||||
path,
|
||||
clock,
|
||||
deadline,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn path(&self) -> ObservationPath {
|
||||
|
|
@ -149,7 +150,7 @@ impl<C: ObservationClock> DomainObservation<C> {
|
|||
}
|
||||
|
||||
pub(crate) fn remaining(&self) -> Result<Duration, ObservationFailure> {
|
||||
let remaining = self.policy().deadline.saturating_sub(self.clock.elapsed());
|
||||
let remaining = self.deadline.saturating_sub(self.clock.elapsed());
|
||||
if remaining.is_zero() {
|
||||
Err(ObservationFailure::DeadlineExceeded)
|
||||
} else {
|
||||
|
|
@ -177,6 +178,8 @@ mod tests {
|
|||
use super::*;
|
||||
use lazaret_core::protocol::BootstrapWaitOutcome;
|
||||
|
||||
const TEST_DEADLINE: Duration = Duration::from_secs(47);
|
||||
|
||||
#[derive(Default)]
|
||||
struct FakeClock {
|
||||
elapsed: Duration,
|
||||
|
|
@ -227,7 +230,7 @@ mod tests {
|
|||
),
|
||||
];
|
||||
for (path, expected) in cases {
|
||||
let observer = DomainObservation::new(path, FakeClock::default());
|
||||
let observer = DomainObservation::new(path, FakeClock::default(), TEST_DEADLINE);
|
||||
assert_eq!(observer.discovery(DiscoveryState::Ready), expected[0]);
|
||||
assert_eq!(observer.discovery(DiscoveryState::Stale), expected[1]);
|
||||
assert_eq!(observer.discovery(DiscoveryState::Absent), expected[2]);
|
||||
|
|
@ -236,7 +239,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn waiter_lock_and_coordinator_outcomes_are_closed() {
|
||||
let observer = DomainObservation::new(ObservationPath::WaitingClient, FakeClock::default());
|
||||
let observer = DomainObservation::new(
|
||||
ObservationPath::WaitingClient,
|
||||
FakeClock::default(),
|
||||
TEST_DEADLINE,
|
||||
);
|
||||
assert_eq!(
|
||||
observer.waiter(BootstrapWaitOutcome::Waiting, false),
|
||||
WaiterAction::AwaitTerminal
|
||||
|
|
@ -263,7 +270,11 @@ mod tests {
|
|||
);
|
||||
assert_eq!(observer.lock_available(false), None);
|
||||
|
||||
let leader = DomainObservation::new(ObservationPath::BootstrapLeader, FakeClock::default());
|
||||
let leader = DomainObservation::new(
|
||||
ObservationPath::BootstrapLeader,
|
||||
FakeClock::default(),
|
||||
TEST_DEADLINE,
|
||||
);
|
||||
assert!(leader.coordinator_exited(true));
|
||||
assert!(!leader.coordinator_exited(false));
|
||||
assert!(!observer.coordinator_exited(true));
|
||||
|
|
@ -271,11 +282,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn retry_deadlines_and_transfer_restart_are_deterministic() {
|
||||
let mut observer =
|
||||
DomainObservation::new(ObservationPath::WaitingClient, FakeClock::default());
|
||||
let mut observer = DomainObservation::new(
|
||||
ObservationPath::WaitingClient,
|
||||
FakeClock::default(),
|
||||
TEST_DEADLINE,
|
||||
);
|
||||
observer.retry().unwrap();
|
||||
assert_eq!(observer.clock.waits, [Duration::from_millis(10)]);
|
||||
observer.clock.elapsed = WAITING_POLICY.deadline;
|
||||
observer.clock.elapsed = TEST_DEADLINE;
|
||||
assert_eq!(observer.retry(), Err(ObservationFailure::DeadlineExceeded));
|
||||
observer.transfer_publication();
|
||||
assert_eq!(observer.path(), ObservationPath::PublicationTransfer);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use std::fs::File;
|
|||
use std::io::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
use ed25519_dalek::SigningKey;
|
||||
use lazaret_control_client::{ControlClient, ControlClientError, PendingClientEnrollment};
|
||||
|
|
@ -25,11 +26,14 @@ pub mod private_roles;
|
|||
pub mod state;
|
||||
pub mod terminal;
|
||||
|
||||
pub const DEFAULT_STARTUP_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ClientCommand {
|
||||
pub profile: String,
|
||||
pub workspace: PathBuf,
|
||||
pub configuration: Option<PathBuf>,
|
||||
pub startup_timeout: Duration,
|
||||
pub argv: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue