feat(console): add deployment-routed gateway
- persist service-to-revision deployments in libSQL with an additive migration - restore active actors and atomically switch revisions - expose an isolated raw-byte gateway on a second listener with stable errors - cover routing, isolation, revision switching, and restart restoration
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
//! Toasty models and transactional libSQL snapshots for Console control data.
|
||||
//!
|
||||
//! Component bytes and WIT packages remain filesystem artifacts. This module
|
||||
//! persists only service manifests, metrics, and the bounded event history.
|
||||
//! persists only service manifests, deployments, metrics, and the bounded
|
||||
//! event history.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
@@ -31,31 +32,62 @@ pub(crate) struct StoredEvent {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, toasty::Model)]
|
||||
pub(crate) struct StoredDeployment {
|
||||
#[key]
|
||||
pub service_id: String,
|
||||
pub active_revision: String,
|
||||
pub updated_at_ms: u64,
|
||||
}
|
||||
|
||||
/// Serialized access to the Toasty database connection.
|
||||
pub(crate) struct Persistence {
|
||||
db: Mutex<Db>,
|
||||
}
|
||||
|
||||
impl Persistence {
|
||||
/// Opens the database and installs the schema for a new file.
|
||||
/// Opens the database and installs or advances its schema.
|
||||
pub async fn open(path: &Path) -> toasty::Result<Self> {
|
||||
let is_new_database = !path.exists();
|
||||
let db = Db::builder()
|
||||
.models(toasty::models!(StoredService, StoredEvent))
|
||||
.models(toasty::models!(
|
||||
StoredService,
|
||||
StoredEvent,
|
||||
StoredDeployment
|
||||
))
|
||||
.build(Turso::file(path))
|
||||
.await?;
|
||||
if is_new_database {
|
||||
db.push_schema().await?;
|
||||
} else {
|
||||
// This project predates Toasty's migration history. Keep the
|
||||
// additive bootstrap explicit so existing databases can be opened
|
||||
// without recreating the service and event tables.
|
||||
let mut db = db;
|
||||
toasty::sql::statement(
|
||||
r#"CREATE TABLE IF NOT EXISTS "stored_deployments" (
|
||||
"service_id" TEXT NOT NULL,
|
||||
"active_revision" TEXT NOT NULL,
|
||||
"updated_at_ms" INTEGER NOT NULL,
|
||||
PRIMARY KEY ("service_id")
|
||||
)"#,
|
||||
)
|
||||
.exec(&mut db)
|
||||
.await?;
|
||||
return Ok(Self { db: Mutex::new(db) });
|
||||
}
|
||||
Ok(Self { db: Mutex::new(db) })
|
||||
}
|
||||
|
||||
/// Loads the complete service set and retained event history.
|
||||
pub async fn load(&self) -> toasty::Result<(Vec<StoredService>, Vec<StoredEvent>)> {
|
||||
/// Loads the complete service and deployment sets plus retained events.
|
||||
pub async fn load(
|
||||
&self,
|
||||
) -> toasty::Result<(Vec<StoredService>, Vec<StoredEvent>, Vec<StoredDeployment>)> {
|
||||
let mut db = self.db.lock().await;
|
||||
let services = StoredService::all().exec(&mut *db).await?;
|
||||
let events = StoredEvent::all().exec(&mut *db).await?;
|
||||
Ok((services, events))
|
||||
let deployments = StoredDeployment::all().exec(&mut *db).await?;
|
||||
Ok((services, events, deployments))
|
||||
}
|
||||
|
||||
/// Atomically upserts a Console snapshot and prunes expired events.
|
||||
@@ -63,6 +95,7 @@ impl Persistence {
|
||||
&self,
|
||||
services: Vec<StoredService>,
|
||||
events: Vec<StoredEvent>,
|
||||
deployments: Vec<StoredDeployment>,
|
||||
) -> toasty::Result<()> {
|
||||
let mut db = self.db.lock().await;
|
||||
let mut tx = db.transaction().await?;
|
||||
@@ -96,6 +129,14 @@ impl Persistence {
|
||||
.await?;
|
||||
}
|
||||
|
||||
for deployment in deployments {
|
||||
StoredDeployment::upsert_by_service_id(deployment.service_id)
|
||||
.active_revision(deployment.active_revision)
|
||||
.updated_at_ms(deployment.updated_at_ms)
|
||||
.exec(&mut tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user