Implement verified OCI prepared-lower lifecycle
This commit is contained in:
parent
54f3a08b91
commit
5458460717
1 changed files with 4973 additions and 273 deletions
|
|
@ -10,8 +10,8 @@ use std::time::{Duration, Instant};
|
|||
use lazaret_core::codec::{self, CodecError};
|
||||
use lazaret_core::identity::{PrepareOperationId, PreparedArtifactId};
|
||||
use lazaret_core::records::{
|
||||
ArtifactLease, ArtifactState, PrepareOperation, PreparedArtifact, StorageCatalog,
|
||||
StorageCatalogMetadata,
|
||||
ArtifactLease, ArtifactState, PrepareOperation, PrepareStageState, PreparedArtifact,
|
||||
StorageCatalog, StorageCatalogMetadata,
|
||||
};
|
||||
use lazaret_linux::filesystem::{ExclusiveFileLock, try_exclusive_flock};
|
||||
use redb::{
|
||||
|
|
@ -106,6 +106,11 @@ impl Catalog {
|
|||
///
|
||||
/// Returns an error if the operation identity already exists or the transaction fails.
|
||||
pub fn record_prepare(&self, operation: &PrepareOperation) -> Result<(), CatalogError> {
|
||||
if operation.stage_state != PrepareStageState::Intent
|
||||
|| !operation.has_valid_evidence_shape()
|
||||
{
|
||||
return Err(CatalogError::InvalidPrepareState);
|
||||
}
|
||||
self.write(|transaction| {
|
||||
let key = operation_key(operation.operation_id);
|
||||
let mut table = transaction
|
||||
|
|
@ -120,6 +125,44 @@ impl Catalog {
|
|||
})
|
||||
}
|
||||
|
||||
/// Binds exact physical stage ownership to an already durable preparation intent.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error for nonmatching identity, invalid phase/evidence, or transaction failure.
|
||||
pub fn bind_prepare_stage(
|
||||
&self,
|
||||
expected_intent: &PrepareOperation,
|
||||
owned_stage: &PrepareOperation,
|
||||
) -> Result<(), CatalogError> {
|
||||
if expected_intent.stage_state != PrepareStageState::Intent
|
||||
|| owned_stage.stage_state != PrepareStageState::Owned
|
||||
|| !expected_intent.has_valid_evidence_shape()
|
||||
|| !owned_stage.has_valid_evidence_shape()
|
||||
|| expected_intent.operation_id != owned_stage.operation_id
|
||||
|| expected_intent.expected_artifact_id != owned_stage.expected_artifact_id
|
||||
|| expected_intent.source_identity != owned_stage.source_identity
|
||||
|| expected_intent.selected_manifest_digest != owned_stage.selected_manifest_digest
|
||||
|| expected_intent.policy_id != owned_stage.policy_id
|
||||
|| expected_intent.id_map_digest != owned_stage.id_map_digest
|
||||
|| expected_intent.stage_path != owned_stage.stage_path
|
||||
{
|
||||
return Err(CatalogError::InvalidPrepareState);
|
||||
}
|
||||
self.write(|transaction| {
|
||||
let key = operation_key(expected_intent.operation_id);
|
||||
let mut table = transaction
|
||||
.open_table(PREPARES)
|
||||
.map_err(CatalogError::redb)?;
|
||||
require_exact(&table, &key, expected_intent, CatalogRow::PrepareOperation)?;
|
||||
let encoded = encode(owned_stage)?;
|
||||
table
|
||||
.insert(key.as_slice(), encoded.as_slice())
|
||||
.map_err(CatalogError::redb)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Removes only an exactly matching preparation ownership row.
|
||||
///
|
||||
/// # Errors
|
||||
|
|
@ -148,11 +191,19 @@ impl Catalog {
|
|||
expected_operation: &PrepareOperation,
|
||||
artifact: &PreparedArtifact,
|
||||
) -> Result<(), CatalogError> {
|
||||
if artifact.state != ArtifactState::Available {
|
||||
if artifact.state != ArtifactState::Available
|
||||
|| expected_operation.stage_state != PrepareStageState::Owned
|
||||
|| !expected_operation.has_valid_evidence_shape()
|
||||
{
|
||||
return Err(CatalogError::InvalidArtifactState);
|
||||
}
|
||||
if artifact.prepared_artifact_id != expected_operation.expected_artifact_id
|
||||
|| artifact.source_identity != expected_operation.source_identity
|
||||
|| artifact.selected_manifest_digest != expected_operation.selected_manifest_digest
|
||||
|| artifact.policy_id != expected_operation.policy_id
|
||||
|| artifact.id_map_digest != expected_operation.id_map_digest
|
||||
|| expected_operation.device != Some(artifact.device)
|
||||
|| expected_operation.inode != Some(artifact.inode)
|
||||
{
|
||||
return Err(CatalogError::OwnershipMismatch {
|
||||
row: CatalogRow::PrepareOperation,
|
||||
|
|
@ -196,6 +247,82 @@ impl Catalog {
|
|||
})
|
||||
}
|
||||
|
||||
/// Atomically replaces an exact reclaiming generation and consumes its prepare ownership.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error for mismatched old/new ownership, invalid state, leases, or transaction
|
||||
/// failure.
|
||||
pub fn publish_replacement(
|
||||
&self,
|
||||
expected_operation: &PrepareOperation,
|
||||
expected_reclaiming: &PreparedArtifact,
|
||||
replacement: &PreparedArtifact,
|
||||
) -> Result<(), CatalogError> {
|
||||
if expected_operation.stage_state != PrepareStageState::Owned
|
||||
|| !expected_operation.has_valid_evidence_shape()
|
||||
|| expected_reclaiming.state != ArtifactState::Reclaiming
|
||||
|| replacement.state != ArtifactState::Available
|
||||
|| replacement.prepared_artifact_id != expected_reclaiming.prepared_artifact_id
|
||||
|| replacement.prepared_artifact_id != expected_operation.expected_artifact_id
|
||||
|| replacement.source_identity != expected_operation.source_identity
|
||||
|| replacement.selected_manifest_digest != expected_operation.selected_manifest_digest
|
||||
|| replacement.policy_id != expected_operation.policy_id
|
||||
|| replacement.id_map_digest != expected_operation.id_map_digest
|
||||
|| expected_operation.device != Some(replacement.device)
|
||||
|| expected_operation.inode != Some(replacement.inode)
|
||||
|| replacement.source_identity != expected_reclaiming.source_identity
|
||||
|| replacement.selected_manifest_digest != expected_reclaiming.selected_manifest_digest
|
||||
|| replacement.policy_id != expected_reclaiming.policy_id
|
||||
|| replacement.id_map_digest != expected_reclaiming.id_map_digest
|
||||
|| replacement.path != expected_reclaiming.path
|
||||
{
|
||||
return Err(CatalogError::InvalidPrepareState);
|
||||
}
|
||||
self.write(|transaction| {
|
||||
let prepare_key = operation_key(expected_operation.operation_id);
|
||||
let artifact_key = artifact_key(replacement.prepared_artifact_id);
|
||||
{
|
||||
let prepares = transaction
|
||||
.open_table(PREPARES)
|
||||
.map_err(CatalogError::redb)?;
|
||||
require_exact(
|
||||
&prepares,
|
||||
&prepare_key,
|
||||
expected_operation,
|
||||
CatalogRow::PrepareOperation,
|
||||
)?;
|
||||
let artifacts = transaction
|
||||
.open_table(ARTIFACTS)
|
||||
.map_err(CatalogError::redb)?;
|
||||
require_exact(
|
||||
&artifacts,
|
||||
&artifact_key,
|
||||
expected_reclaiming,
|
||||
CatalogRow::PreparedArtifact,
|
||||
)?;
|
||||
}
|
||||
if artifact_has_lease(transaction, replacement.prepared_artifact_id)? {
|
||||
return Err(CatalogError::LeasesPresent);
|
||||
}
|
||||
let encoded = encode(replacement)?;
|
||||
let mut artifacts = transaction
|
||||
.open_table(ARTIFACTS)
|
||||
.map_err(CatalogError::redb)?;
|
||||
artifacts
|
||||
.insert(artifact_key.as_slice(), encoded.as_slice())
|
||||
.map_err(CatalogError::redb)?;
|
||||
drop(artifacts);
|
||||
let mut prepares = transaction
|
||||
.open_table(PREPARES)
|
||||
.map_err(CatalogError::redb)?;
|
||||
prepares
|
||||
.remove(prepare_key.as_slice())
|
||||
.map_err(CatalogError::redb)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Inserts one exact Domain-generation lease for an available artifact.
|
||||
///
|
||||
/// # Errors
|
||||
|
|
@ -570,6 +697,7 @@ pub enum CatalogError {
|
|||
AlreadyExists { row: CatalogRow },
|
||||
OwnershipMismatch { row: CatalogRow },
|
||||
InvalidArtifactState,
|
||||
InvalidPrepareState,
|
||||
ArtifactUnavailable,
|
||||
LeasesPresent,
|
||||
}
|
||||
|
|
@ -598,6 +726,9 @@ impl fmt::Display for CatalogError {
|
|||
Self::InvalidArtifactState => {
|
||||
formatter.write_str("artifact state is invalid for operation")
|
||||
}
|
||||
Self::InvalidPrepareState => {
|
||||
formatter.write_str("prepare phase or physical evidence is invalid")
|
||||
}
|
||||
Self::ArtifactUnavailable => {
|
||||
formatter.write_str("artifact is not available for leasing")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue