Compose cold-start resources under Runtime Supervisor ownership
This commit is contained in:
parent
510dfccf5a
commit
33ac0ba4ee
2 changed files with 1827 additions and 139 deletions
|
|
@ -17,7 +17,8 @@ use lazaret_core::records::{
|
|||
SessionRegistrationResponse, TlsCredential,
|
||||
};
|
||||
use lazaret_host_coordinator::control::{
|
||||
CONTROL_ALPN, ControlError, PendingClientEnrollment, PublishedControlFace, TrustAnchor,
|
||||
CONTROL_ALPN, ControlError, PendingClientEnrollment, PublishedControlFace, SessionRegistry,
|
||||
StagedControlFace, TrustAnchor,
|
||||
};
|
||||
use lazaret_host_coordinator::domain_store::{DomainStoreMutation, HostCoordinatorDomainStore};
|
||||
use lazaret_linux::filesystem::try_exclusive_flock;
|
||||
|
|
@ -687,26 +688,15 @@ fn prepared_control_face_admits_only_after_generation_publication() {
|
|||
let directory = TestDirectory::new("publication-gate");
|
||||
let identity = generation_identity(7);
|
||||
let store = open_preparing_store(&directory, &identity);
|
||||
let face = Arc::new(
|
||||
PublishedControlFace::publish(
|
||||
&directory.0,
|
||||
getuid().as_raw(),
|
||||
TrustAnchor::create(identity.clone()).unwrap(),
|
||||
Arc::clone(&store),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let serving = server(&face);
|
||||
let config = PublishedControlFace::client_config(face.publication_identity(), None).unwrap();
|
||||
assert!(
|
||||
PublishedControlFace::connect(face.socket_path(), config, face.publication_identity())
|
||||
.is_err()
|
||||
);
|
||||
assert!(matches!(
|
||||
serving.join().unwrap().unwrap_err(),
|
||||
ControlError::StaleGeneration
|
||||
));
|
||||
let registry = Arc::new(SessionRegistry::new(Arc::clone(&store), identity.clone()));
|
||||
let staged = StagedControlFace::stage(
|
||||
&directory.0,
|
||||
getuid().as_raw(),
|
||||
TrustAnchor::create(identity.clone()).unwrap(),
|
||||
registry,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(std::os::unix::net::UnixStream::connect(directory.path("control.sock")).is_err());
|
||||
|
||||
store
|
||||
.apply(&[DomainStoreMutation::PutGeneration(generation(
|
||||
|
|
@ -714,6 +704,7 @@ fn prepared_control_face_admits_only_after_generation_publication() {
|
|||
DurableDecision::Published,
|
||||
))])
|
||||
.unwrap();
|
||||
let face = Arc::new(staged.expose().unwrap());
|
||||
let client = enroll(&face, ClientId::new(bytes(90)));
|
||||
assert_eq!(client.credential.generation_identity, identity);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,32 @@ impl LifecycleAdapter for RecordingAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
struct PublicationFailureAdapter;
|
||||
|
||||
impl LifecycleAdapter for PublicationFailureAdapter {
|
||||
type Error = &'static str;
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
_action: LifecycleAction,
|
||||
_generation: &Generation,
|
||||
) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn after_commit(
|
||||
&mut self,
|
||||
action: LifecycleAction,
|
||||
_generation: &Generation,
|
||||
) -> Result<(), Self::Error> {
|
||||
if action == LifecycleAction::PublishDomain {
|
||||
Err("exposure failed")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TestAdmission {
|
||||
session: Session,
|
||||
|
|
@ -272,6 +298,58 @@ fn exact_start_join_disconnect_and_teardown_actions_are_ordered() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cold_start_entry_reaches_publication_in_exact_modeled_order() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let store = open_store(&directory);
|
||||
let (domain_adapter, session_adapter) = adapters();
|
||||
let mut lifecycle = HostCoordinatorLifecycle::new(&store, domain_adapter, session_adapter);
|
||||
|
||||
lifecycle
|
||||
.bootstrap_new_domain(generation(1), session(10, 1))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(lifecycle.phase(), DomainPhase::Ready);
|
||||
assert!(lifecycle.is_published());
|
||||
assert_eq!(
|
||||
lifecycle.into_adapters().0.actions.lock().unwrap().clone(),
|
||||
vec![
|
||||
LifecycleAction::StartNewDomain,
|
||||
LifecycleAction::CreateTrustAnchor,
|
||||
LifecycleAction::StartRuntimeSupervisor,
|
||||
LifecycleAction::LeasePreparedLower,
|
||||
LifecycleAction::MountStateAnchor,
|
||||
LifecycleAction::StartSidecar,
|
||||
LifecycleAction::StartConnectivityHelper,
|
||||
LifecycleAction::MarkNetworkReady,
|
||||
LifecycleAction::ArmRuntimeCausalEdge,
|
||||
LifecycleAction::StartSandbox,
|
||||
LifecycleAction::PublishDomain,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publication_exposure_failure_retains_recoverable_durable_decision() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let store = open_store(&directory);
|
||||
let (_, session_adapter) = adapters();
|
||||
let mut lifecycle =
|
||||
HostCoordinatorLifecycle::new(&store, PublicationFailureAdapter, session_adapter);
|
||||
|
||||
assert!(
|
||||
lifecycle
|
||||
.bootstrap_new_domain(generation(1), session(10, 1))
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(lifecycle.phase(), DomainPhase::Failed);
|
||||
assert!(!lifecycle.is_published());
|
||||
assert_eq!(
|
||||
store.snapshot().unwrap().generations[0].durable_decision,
|
||||
DurableDecision::Published
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publication_and_invalid_transitions_fail_before_adapter_execution() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
|
|
|
|||
Loading…
Reference in a new issue