Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,100 @@
|
||||
use super::*;
|
||||
use crate::database::{Database, Lease, Options, Parameter};
|
||||
use futures_util::FutureExt;
|
||||
use std::panic::AssertUnwindSafe;
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires explicit isolated postgres_tenant_test template and CREATEDB"]
|
||||
fn physical_databases_are_explicit_isolated_idempotent_and_fail_closed() {
|
||||
let directory = Directory::new();
|
||||
runtime().run(async |scope| {
|
||||
let document = Document::load(&PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../../config/setting.json")).unwrap();
|
||||
let settings: serde_json::Value = document.decode().unwrap();
|
||||
let admin: Options = serde_json::from_value(settings["database"]["postgres_test"].clone()).unwrap();
|
||||
let mut options: Options = serde_json::from_value(settings["database"]["postgres_tenant_test"].clone()).unwrap();
|
||||
let template = options.url.as_ref().unwrap().parse::<tokio_postgres::Config>().unwrap();
|
||||
assert!(template.get_dbname().unwrap().contains("{case}"));
|
||||
assert_eq!(options.url.as_ref().unwrap().matches("{case}").count(), 1);
|
||||
let token = crate::database::scalar::generated_uuid().unwrap().to_string().replace('-', "");
|
||||
options.url = Some(options.url.unwrap().replace("{case}", &token));
|
||||
let control_name = options.url.as_ref().unwrap().parse::<tokio_postgres::Config>().unwrap().get_dbname().unwrap().to_owned();
|
||||
assert!((1..=63).contains(&control_name.len()));
|
||||
assert!(control_name.bytes().all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_'));
|
||||
let prefix = format!("dv_{token}");
|
||||
options.tenant_database_prefix = Some(prefix.clone());
|
||||
options.max_connections = 2;
|
||||
options.max_page_size = 1;
|
||||
let tenant_options = options.clone();
|
||||
let admin = Database::open(&directory.0, admin, scope).await.unwrap();
|
||||
let mut admin_executor = admin.executor().await.unwrap();
|
||||
let Lease::Postgres(lease) = &mut admin_executor.lease else { unreachable!() };
|
||||
lease.call(async |client| client.batch_execute(&format!("CREATE DATABASE \"{control_name}\"")).await.map_err(Into::into)).await.unwrap();
|
||||
let mut database = std::collections::BTreeMap::new();
|
||||
database.insert("default".to_owned(), options);
|
||||
let config = Settings(database, Some(crate::database::TenantOptions { database: "default".to_owned(), max_pools: 2, idle_timeout_ms: 100 }));
|
||||
let mut registry = None;
|
||||
let result = AssertUnwindSafe(async {
|
||||
registry = Some(Registry::prepare(&directory.0, config, &bindings(), scope).await.unwrap());
|
||||
let registry = registry.as_ref().unwrap();
|
||||
let mut absent = Context::new(registry.clone());
|
||||
assert_eq!(absent.select_tenant(scope, 1).await.unwrap_err().kind, Kind::Migration);
|
||||
assert_eq!(admin_executor.query(scope, Sql::same("SELECT COUNT(*) FROM pg_database WHERE datname=$1"), vec![Parameter::Text(format!("{prefix}_1"))], 1, |row| row.get::<_, i64>(0)).await.unwrap(), [0]);
|
||||
for id in [1, 2, 1] { registry.migrate_tenant(scope, id).await.unwrap(); }
|
||||
assert_eq!(registry.queue_tenants(0, scope).await.unwrap(), [Some(1), Some(2)]);
|
||||
for id in [1, 2] {
|
||||
let mut context = Context::new(registry.clone());
|
||||
context.select_tenant(scope, id).await.unwrap();
|
||||
let owner = context.enter();
|
||||
context.on(0, scope).await.unwrap().execute(scope, Sql::same("INSERT INTO items(id,value) VALUES(1,$1)"), vec![Parameter::Text(format!("tenant {id}"))]).await.unwrap();
|
||||
context.on(2, scope).await.unwrap().execute(scope, "INSERT INTO roles(id) VALUES(1)", vec![]).await.unwrap();
|
||||
assert!(context.on(1, scope).await.is_err());
|
||||
context.finish(scope, owner, true).await.unwrap();
|
||||
assert_eq!(context.on(0, scope).await.unwrap().query(scope, "SELECT value FROM items", vec![], 1, |row| row.get::<_, String>(0)).await.unwrap(), [format!("tenant {id}")]);
|
||||
assert_eq!(context.control(scope).await.unwrap().query(scope, "SELECT COUNT(*) FROM roles", vec![], 1, |row| row.get::<_, i64>(0)).await.unwrap(), [0]);
|
||||
}
|
||||
// 冷租户的标记被真实表锁阻塞时,另一租户仍能进入;取消后可重新准备。
|
||||
tokio::time::sleep(std::time::Duration::from_millis(110)).await;
|
||||
Context::new(registry.clone()).select_tenant(scope, 1).await.unwrap();
|
||||
let held = Database::open_existing(&directory.0, tenant_options.clone(), Some(&format!("{prefix}_2")), scope).await.unwrap();
|
||||
let mut held_executor = held.executor().await.unwrap();
|
||||
held_executor.begin(scope).await.unwrap();
|
||||
held_executor.initialize(scope, "LOCK TABLE _dever_tenant_ready IN ACCESS EXCLUSIVE MODE").await.unwrap();
|
||||
let copy = registry.clone();
|
||||
let blocked = scope.spawn(move |child| async move { Context::new(copy).select_tenant(&child, 2).await }).unwrap();
|
||||
let started = tokio::time::Instant::now();
|
||||
loop {
|
||||
let waiting = admin_executor.query(scope, Sql::same("SELECT COUNT(*) FROM pg_stat_activity WHERE datname=$1 AND wait_event_type='Lock'"), vec![Parameter::Text(format!("{prefix}_2"))], 1, |row| row.get::<_, i64>(0)).await.unwrap();
|
||||
if waiting[0] > 0 { break; }
|
||||
assert!(started.elapsed() < std::time::Duration::from_secs(2));
|
||||
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
|
||||
}
|
||||
tokio::time::timeout(std::time::Duration::from_millis(500), Context::new(registry.clone()).select_tenant(scope, 1)).await.unwrap().unwrap();
|
||||
assert!(blocked.stop().await.unwrap().is_none());
|
||||
held_executor.finish(scope, true, false).await.unwrap();
|
||||
drop(held_executor);
|
||||
held.close();
|
||||
Context::new(registry.clone()).select_tenant(scope, 2).await.unwrap();
|
||||
registry.set_component(scope, 1, "shop", false).await.unwrap();
|
||||
let mut context = Context::new(registry.clone());
|
||||
context.select_tenant(scope, 1).await.unwrap();
|
||||
assert!(!context.components_enabled(scope, &["shop"]).await.unwrap());
|
||||
assert_eq!(registry.queue_tenants(0, scope).await.unwrap(), [Some(1), Some(2)]);
|
||||
context.on(0, scope).await.unwrap().execute(scope, "UPDATE _dever_tenant_ready SET ready=0", vec![]).await.unwrap();
|
||||
drop(context);
|
||||
assert_eq!(Context::new(registry.clone()).select_tenant(scope, 1).await.unwrap_err().kind, Kind::Migration);
|
||||
}).catch_unwind().await;
|
||||
if let Some(registry) = registry.take() {
|
||||
registry.close().await;
|
||||
drop(registry);
|
||||
}
|
||||
// Pool::close 释放驱动句柄后,让同一 runtime 的连接任务完成退出。
|
||||
tokio::time::sleep(std::time::Duration::from_millis(30)).await;
|
||||
let Lease::Postgres(lease) = &mut admin_executor.lease else { unreachable!() };
|
||||
for name in [format!("{prefix}_1"), format!("{prefix}_2"), control_name] {
|
||||
lease.call(async |client| client.batch_execute(&format!("DROP DATABASE IF EXISTS \"{name}\"")).await.map_err(Into::into)).await.unwrap();
|
||||
}
|
||||
drop(admin_executor);
|
||||
admin.close();
|
||||
if let Err(panic) = result { std::panic::resume_unwind(panic); }
|
||||
}).unwrap();
|
||||
}
|
||||
Reference in new issue
Block a user