use std::{collections::BTreeSet, io, sync::Arc};
use bamboo_domain::{
ProjectId, Storage, SupervisorBootstrapReceipt, SupervisorLinkObservation,
SupervisorManagementMutation, SupervisorManagementReceipt, SupervisorManagementRequest,
SupervisorReference, SupervisorScopeObservation,
};
#[derive(Clone)]
pub struct SupervisorSessionService {
storage: Arc<dyn Storage>,
}
impl SupervisorSessionService {
pub fn new(storage: Arc<dyn Storage>) -> Self {
Self { storage }
}
pub async fn get_or_create_default(
&self,
initial_model: &str,
) -> io::Result<SupervisorBootstrapReceipt> {
self.storage
.get_or_create_default_supervisor(initial_model)
.await
}
pub async fn inspect_scope(
&self,
supervisor: &SupervisorReference,
) -> io::Result<SupervisorScopeObservation> {
self.storage.inspect_supervisor_scope(supervisor).await
}
pub async fn configure_project_scope(
&self,
supervisor: &SupervisorReference,
expected_state_revision: u64,
allowed_projects: BTreeSet<ProjectId>,
) -> io::Result<SupervisorManagementReceipt> {
self.mutate(
supervisor,
expected_state_revision,
SupervisorManagementMutation::ConfigureProjectScope { allowed_projects },
)
.await
}
pub async fn attach(
&self,
supervisor: &SupervisorReference,
expected_state_revision: u64,
target_session_id: &str,
) -> io::Result<SupervisorManagementReceipt> {
self.mutate(
supervisor,
expected_state_revision,
SupervisorManagementMutation::Attach {
target_session_id: target_session_id.to_string(),
},
)
.await
}
pub async fn detach(
&self,
supervisor: &SupervisorReference,
expected_state_revision: u64,
target_session_id: &str,
) -> io::Result<SupervisorManagementReceipt> {
self.mutate(
supervisor,
expected_state_revision,
SupervisorManagementMutation::Detach {
target_session_id: target_session_id.to_string(),
},
)
.await
}
pub async fn inspect_link(
&self,
supervisor: &SupervisorReference,
target_session_id: &str,
) -> io::Result<SupervisorLinkObservation> {
self.storage
.inspect_supervisor_link(supervisor, target_session_id)
.await
}
async fn mutate(
&self,
supervisor: &SupervisorReference,
expected_state_revision: u64,
mutation: SupervisorManagementMutation,
) -> io::Result<SupervisorManagementReceipt> {
self.storage
.mutate_supervisor_management(&SupervisorManagementRequest {
supervisor: supervisor.clone(),
expected_state_revision,
mutation,
})
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use bamboo_domain::Session;
struct UnsupportedStore;
#[async_trait::async_trait]
impl Storage for UnsupportedStore {
async fn save_session(&self, _: &Session) -> io::Result<()> {
panic!("bootstrap must not fall back to ordinary save")
}
async fn load_session(&self, _: &str) -> io::Result<Option<Session>> {
panic!("authority must not fall back to ordinary load")
}
async fn delete_session(&self, _: &str) -> io::Result<bool> {
panic!("bootstrap must not delete sessions")
}
}
#[tokio::test]
async fn unsupported_authority_ports_fail_without_ordinary_fallback() {
let storage: Arc<dyn Storage> = Arc::new(UnsupportedStore);
let service = SupervisorSessionService::new(storage.clone());
assert_eq!(
service
.get_or_create_default("model")
.await
.unwrap_err()
.kind(),
io::ErrorKind::Unsupported
);
assert_eq!(
storage
.load_root_authority("root")
.await
.unwrap_err()
.kind(),
io::ErrorKind::Unsupported
);
let supervisor = SupervisorReference {
session_id: bamboo_domain::DEFAULT_SUPERVISOR_SESSION_ID.into(),
incarnation_id: uuid::Uuid::new_v4(),
};
assert_eq!(
service.inspect_scope(&supervisor).await.unwrap_err().kind(),
io::ErrorKind::Unsupported
);
assert_eq!(
service
.configure_project_scope(&supervisor, 0, BTreeSet::new())
.await
.unwrap_err()
.kind(),
io::ErrorKind::Unsupported
);
assert_eq!(
service
.attach(&supervisor, 0, "target")
.await
.unwrap_err()
.kind(),
io::ErrorKind::Unsupported
);
assert_eq!(
service
.detach(&supervisor, 0, "target")
.await
.unwrap_err()
.kind(),
io::ErrorKind::Unsupported
);
assert_eq!(
service
.inspect_link(&supervisor, "target")
.await
.unwrap_err()
.kind(),
io::ErrorKind::Unsupported
);
}
}