use crate::context::MutationContext;
use crate::error::CoreError;
use crate::metadata::{InodeRecord, MetadataState};
use crate::namespace::control::{read_head_object, ControlObjectLoadError};
use bytes::Bytes;
use loonfs_api::wire::control::{
encode_control_object, ControlObjectKind, HeadState, HeadStateEnvelope, NamespaceState,
WriterBlock,
};
use loonfs_api::{
ChangeSeq, ContentStoreId, ErrorCode, InodeKind, NamespaceId, NamespaceSummary, ROOT_INODE_ID,
};
use loonfs_objectstore::keys::wal_head;
use loonfs_objectstore::{ObjectStore, ObjectStoreError};
use thiserror::Error;
#[derive(Debug, Clone, Error)]
pub enum BootstrapNamespaceError {
#[error("holder id must not be empty")]
EmptyHolderId,
#[error("namespace `{namespace_id}` already exists")]
NamespaceAlreadyExists { namespace_id: NamespaceId },
#[error("namespace `{namespace_id}` is deleted and its id is retired")]
NamespaceDeleted { namespace_id: NamespaceId },
#[error(transparent)]
Head(#[from] ControlObjectLoadError),
#[error(transparent)]
Core(#[from] CoreError),
}
impl BootstrapNamespaceError {
pub fn code(&self) -> ErrorCode {
match self {
BootstrapNamespaceError::EmptyHolderId => ErrorCode::InvalidRequest,
BootstrapNamespaceError::NamespaceAlreadyExists { .. } => ErrorCode::NamespaceExists,
BootstrapNamespaceError::NamespaceDeleted { .. } => ErrorCode::NamespaceDeleted,
BootstrapNamespaceError::Head(_) => ErrorCode::ServerError,
BootstrapNamespaceError::Core(error) => error.code(),
}
}
pub fn details(&self) -> Option<loonfs_api::ErrorDetails> {
match self {
BootstrapNamespaceError::Core(error) => error.details(),
BootstrapNamespaceError::EmptyHolderId
| BootstrapNamespaceError::NamespaceAlreadyExists { .. }
| BootstrapNamespaceError::NamespaceDeleted { .. }
| BootstrapNamespaceError::Head(_) => None,
}
}
}
pub(crate) async fn bootstrap_namespace<S: ObjectStore + ?Sized>(
store: &S,
namespace_id: &NamespaceId,
context: &MutationContext,
allow_existing: bool,
) -> Result<NamespaceSummary, BootstrapNamespaceError> {
if context.writer_id.trim().is_empty() {
return Err(BootstrapNamespaceError::EmptyHolderId);
}
let mut head = HeadState::initial(namespace_id.clone(), ContentStoreId::generate());
head.writer = Some(WriterBlock {
writer_id: context.writer_id.clone(),
acquired_at_ms: context.now_ms,
});
match install_namespace_head(store, namespace_id, &head).await? {
NamespaceHeadInstall::Landed => Ok(NamespaceSummary {
namespace_id: namespace_id.clone(),
}),
NamespaceHeadInstall::Exists if allow_existing => Ok(NamespaceSummary {
namespace_id: namespace_id.clone(),
}),
NamespaceHeadInstall::Exists => Err(BootstrapNamespaceError::NamespaceAlreadyExists {
namespace_id: namespace_id.clone(),
}),
NamespaceHeadInstall::Deleted => Err(BootstrapNamespaceError::NamespaceDeleted {
namespace_id: namespace_id.clone(),
}),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum NamespaceHeadInstall {
Landed,
Exists,
Deleted,
}
pub(super) async fn install_namespace_head<S: ObjectStore + ?Sized>(
store: &S,
namespace_id: &NamespaceId,
head: &HeadState,
) -> Result<NamespaceHeadInstall, CoreError> {
let object_key = wal_head(namespace_id.as_str());
let envelope = HeadStateEnvelope::from_state(ControlObjectKind::WalHead, head.clone())
.map_err(|err| CoreError::Internal(format!("failed to build head envelope: {err}")))?;
let bytes = encode_control_object(&envelope)
.map_err(|err| CoreError::Internal(format!("failed to encode head object: {err}")))?;
match store.put_if_absent(&object_key, Bytes::from(bytes)).await {
Ok(_) => Ok(NamespaceHeadInstall::Landed),
Err(ObjectStoreError::PreconditionFailed { .. }) => {
let existing = match read_head_object(store, namespace_id).await {
Ok(loaded) => loaded.envelope.state,
Err(error) => return Err(CoreError::load_head(error)),
};
if existing.state == NamespaceState::Deleted {
return Ok(NamespaceHeadInstall::Deleted);
}
Ok(NamespaceHeadInstall::Exists)
}
Err(error) => Err(CoreError::store(&object_key, &error)),
}
}
pub(crate) fn bootstrap_metadata_state() -> MetadataState {
MetadataState::from_rows(
vec![InodeRecord {
inode_id: ROOT_INODE_ID,
inode_kind: InodeKind::Directory,
created_seq: ChangeSeq(0),
}],
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
)
}