Refuse inconsistent State Anchor acquisition checkpoints
This commit is contained in:
parent
48c10e9a7a
commit
8886436841
2 changed files with 104 additions and 10 deletions
|
|
@ -159,18 +159,29 @@ impl<'a> StateAnchorCoordinator<'a> {
|
||||||
&expected_lease_held,
|
&expected_lease_held,
|
||||||
&expected_anchor_intent,
|
&expected_anchor_intent,
|
||||||
)? {
|
)? {
|
||||||
AcquisitionCheckpoint::Absent => self.domain_store.apply(&[
|
AcquisitionCheckpoint::Absent => {
|
||||||
DomainStoreMutation::PutParticipant(Participant::bare_intent(
|
if self.catalog_lease_checkpoint(&lease)? != CatalogLeaseCheckpoint::Absent {
|
||||||
generation_id,
|
return Err(StateAnchorCoordinatorError::InvalidState(
|
||||||
ParticipantRole::PreparedLower,
|
"catalog lease exists before Domain intent",
|
||||||
)),
|
));
|
||||||
DomainStoreMutation::PutParticipant(expected_prepared_intent.clone()),
|
}
|
||||||
])?,
|
self.domain_store.apply(&[
|
||||||
|
DomainStoreMutation::PutParticipant(Participant::bare_intent(
|
||||||
|
generation_id,
|
||||||
|
ParticipantRole::PreparedLower,
|
||||||
|
)),
|
||||||
|
DomainStoreMutation::PutParticipant(expected_prepared_intent.clone()),
|
||||||
|
])?;
|
||||||
|
}
|
||||||
AcquisitionCheckpoint::PreparedIntent => {}
|
AcquisitionCheckpoint::PreparedIntent => {}
|
||||||
AcquisitionCheckpoint::Complete => {
|
AcquisitionCheckpoint::Complete => {
|
||||||
self.catalog
|
return if self.catalog_lease_checkpoint(&lease)? == CatalogLeaseCheckpoint::Exact {
|
||||||
.acquire_or_resume_lease_checked(artifact, &lease)?;
|
Ok(lease)
|
||||||
return Ok(lease);
|
} else {
|
||||||
|
Err(StateAnchorCoordinatorError::InvalidState(
|
||||||
|
"completed Domain acquisition lacks its catalog lease",
|
||||||
|
))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.catalog
|
self.catalog
|
||||||
|
|
@ -482,6 +493,22 @@ impl<'a> StateAnchorCoordinator<'a> {
|
||||||
Ok(role(&snapshot.participants, generation_id, participant_role)?.clone())
|
Ok(role(&snapshot.participants, generation_id, participant_role)?.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn catalog_lease_checkpoint(
|
||||||
|
&self,
|
||||||
|
expected: &ArtifactLease,
|
||||||
|
) -> Result<CatalogLeaseCheckpoint, StateAnchorCoordinatorError> {
|
||||||
|
let snapshot = self.catalog.snapshot()?;
|
||||||
|
match snapshot.artifact_leases.iter().find(|lease| {
|
||||||
|
lease.domain_id == expected.domain_id && lease.generation_id == expected.generation_id
|
||||||
|
}) {
|
||||||
|
None => Ok(CatalogLeaseCheckpoint::Absent),
|
||||||
|
Some(observed) if observed == expected => Ok(CatalogLeaseCheckpoint::Exact),
|
||||||
|
Some(_) => Err(StateAnchorCoordinatorError::InvalidState(
|
||||||
|
"catalog generation lease ownership changed",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn release_catalog_lease_if_present(
|
fn release_catalog_lease_if_present(
|
||||||
&self,
|
&self,
|
||||||
authority: &StateAnchorReleaseAuthority,
|
authority: &StateAnchorReleaseAuthority,
|
||||||
|
|
@ -593,6 +620,12 @@ enum AcquisitionCheckpoint {
|
||||||
Complete,
|
Complete,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
enum CatalogLeaseCheckpoint {
|
||||||
|
Absent,
|
||||||
|
Exact,
|
||||||
|
}
|
||||||
|
|
||||||
fn acquisition_checkpoint(
|
fn acquisition_checkpoint(
|
||||||
participants: &[Participant],
|
participants: &[Participant],
|
||||||
generation_id: GenerationId,
|
generation_id: GenerationId,
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,67 @@ fn acquisition_resumes_after_catalog_lease_reopen() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completed_domain_checkpoint_refuses_absent_catalog_lease() {
|
||||||
|
let fixture = AcquisitionFixture::new(19);
|
||||||
|
let layout = StateAnchorLayout::create(&fixture.configuration).unwrap();
|
||||||
|
let store = fixture.open_store();
|
||||||
|
let coordinator = StateAnchorCoordinator::new(&store, &fixture.catalog);
|
||||||
|
let lease = coordinator
|
||||||
|
.begin_acquisition(fixture.generation_id, &fixture.artifact, &layout)
|
||||||
|
.unwrap();
|
||||||
|
fixture.catalog.release_lease(&lease).unwrap();
|
||||||
|
drop(store);
|
||||||
|
|
||||||
|
let store = fixture.open_store();
|
||||||
|
let coordinator = StateAnchorCoordinator::new(&store, &fixture.catalog);
|
||||||
|
assert!(
|
||||||
|
coordinator
|
||||||
|
.begin_acquisition(fixture.generation_id, &fixture.artifact, &layout)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert_fixture_states(&store, fixture.generation_id);
|
||||||
|
assert!(
|
||||||
|
fixture
|
||||||
|
.catalog
|
||||||
|
.snapshot()
|
||||||
|
.unwrap()
|
||||||
|
.artifact_leases
|
||||||
|
.is_empty()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn absent_domain_checkpoint_refuses_preexisting_catalog_lease() {
|
||||||
|
let fixture = AcquisitionFixture::new(20);
|
||||||
|
let layout = StateAnchorLayout::create(&fixture.configuration).unwrap();
|
||||||
|
let lease = fixture.lease();
|
||||||
|
fixture
|
||||||
|
.catalog
|
||||||
|
.acquire_lease_checked(&fixture.artifact, &lease)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let store = fixture.open_store();
|
||||||
|
let coordinator = StateAnchorCoordinator::new(&store, &fixture.catalog);
|
||||||
|
assert!(
|
||||||
|
coordinator
|
||||||
|
.begin_acquisition(fixture.generation_id, &fixture.artifact, &layout)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
let snapshot = store.snapshot().unwrap();
|
||||||
|
assert!(!snapshot.participants.iter().any(|participant| {
|
||||||
|
participant.generation_id() == fixture.generation_id
|
||||||
|
&& matches!(
|
||||||
|
participant.role(),
|
||||||
|
ParticipantRole::PreparedLower | ParticipantRole::StateAnchor
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
assert_eq!(
|
||||||
|
fixture.catalog.snapshot().unwrap().artifact_leases,
|
||||||
|
vec![lease]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn assert_fixture_states(store: &HostCoordinatorDomainStore, generation_id: GenerationId) {
|
fn assert_fixture_states(store: &HostCoordinatorDomainStore, generation_id: GenerationId) {
|
||||||
let snapshot = store.snapshot().unwrap();
|
let snapshot = store.snapshot().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue