Lines
100 %
Functions
Branches
//! Two migrators running at once must not corrupt a tenant or lose it.
//!
//! The per-tenant advisory lock is taken inside that tenant's own database, so a
//! second worker either waits for the claim and then re-runs the (idempotent)
//! migrator, or reports the tenant unresolved. What must never happen is two
//! workers migrating one database concurrently, or both declaring success over a
//! tenant neither finished.
#![cfg(feature = "db")]
use server::fleet::migrate_fleet;
use tests_integration::fleet_support::{
admin_connect, admin_id, forget_tenant, is_fully_migrated, quarantine_of, register_tenant,
};
#[tokio::test]
async fn concurrent_migrators_do_not_corrupt_or_abandon_a_tenant() -> anyhow::Result<()> {
let admin = admin_connect().await;
let (id, database_name) = register_tenant(&admin, "fleet-race", true).await;
let (left, right) = tokio::join!(migrate_fleet(admin_id()), migrate_fleet(admin_id()));
let (left, right) = (left?, right?);
assert!(
is_fully_migrated(&database_name).await,
"whichever worker won, the tenant must end at the current schema"
);
assert_eq!(
quarantine_of(&admin, id).await,
None,
"losing a race is not a failure and must not quarantine the tenant"
// Every tenant must be accounted for by at least one of the two runs: an
// unresolved claim is honest, but both runs skipping it is not.
let seen_by = |r: &server::fleet::FleetReport| !r.unresolved.contains(&id);
seen_by(&left) || seen_by(&right),
"left={left:?} right={right:?}"
forget_tenant(&admin, id, &database_name).await;
Ok(())
}