@@ -848,6 +848,14 @@ pub async fn reset<S: NodeDelegate + ControlStateDelegate + Authorization>(
848848 ctx. authorize_action ( auth. claims . identity , database. database_identity , Action :: ResetDatabase )
849849 . await ?;
850850
851+ if ctx. is_database_locked ( & database_identity) . await . map_err ( log_and_500) ? {
852+ return Err ( (
853+ StatusCode :: FORBIDDEN ,
854+ "Database is locked and cannot be reset with --delete-data. Run `spacetime unlock` first." ,
855+ )
856+ . into ( ) ) ;
857+ }
858+
851859 let num_replicas = num_replicas. map ( validate_replication_factor) . transpose ( ) ?. flatten ( ) ;
852860 ctx. reset_database (
853861 & auth. claims . identity ,
@@ -1323,13 +1331,62 @@ pub async fn delete_database<S: ControlStateDelegate + Authorization>(
13231331
13241332 ctx. authorize_action ( auth. claims . identity , database_identity, Action :: DeleteDatabase )
13251333 . await ?;
1334+
1335+ if ctx. is_database_locked ( & database_identity) . await . map_err ( log_and_500) ? {
1336+ return Err ( (
1337+ StatusCode :: FORBIDDEN ,
1338+ "Database is locked and cannot be deleted. Run `spacetime unlock` first." ,
1339+ )
1340+ . into ( ) ) ;
1341+ }
1342+
13261343 ctx. delete_database ( & auth. claims . identity , & database_identity)
13271344 . await
13281345 . map_err ( log_and_500) ?;
13291346
13301347 Ok ( ( ) )
13311348}
13321349
1350+ pub async fn lock_database < S : ControlStateDelegate + Authorization > (
1351+ State ( ctx) : State < S > ,
1352+ Path ( DeleteDatabaseParams { name_or_identity } ) : Path < DeleteDatabaseParams > ,
1353+ Extension ( auth) : Extension < SpacetimeAuth > ,
1354+ ) -> axum:: response:: Result < impl IntoResponse > {
1355+ let database_identity = name_or_identity. resolve ( & ctx) . await ?;
1356+ let Some ( _database) = worker_ctx_find_database ( & ctx, & database_identity) . await ? else {
1357+ return Err ( StatusCode :: NOT_FOUND . into ( ) ) ;
1358+ } ;
1359+
1360+ ctx. authorize_action ( auth. claims . identity , database_identity, Action :: DeleteDatabase )
1361+ . await ?;
1362+
1363+ ctx. set_database_lock ( & auth. claims . identity , & database_identity, true )
1364+ . await
1365+ . map_err ( log_and_500) ?;
1366+
1367+ Ok ( ( ) )
1368+ }
1369+
1370+ pub async fn unlock_database < S : ControlStateDelegate + Authorization > (
1371+ State ( ctx) : State < S > ,
1372+ Path ( DeleteDatabaseParams { name_or_identity } ) : Path < DeleteDatabaseParams > ,
1373+ Extension ( auth) : Extension < SpacetimeAuth > ,
1374+ ) -> axum:: response:: Result < impl IntoResponse > {
1375+ let database_identity = name_or_identity. resolve ( & ctx) . await ?;
1376+ let Some ( _database) = worker_ctx_find_database ( & ctx, & database_identity) . await ? else {
1377+ return Err ( StatusCode :: NOT_FOUND . into ( ) ) ;
1378+ } ;
1379+
1380+ ctx. authorize_action ( auth. claims . identity , database_identity, Action :: DeleteDatabase )
1381+ . await ?;
1382+
1383+ ctx. set_database_lock ( & auth. claims . identity , & database_identity, false )
1384+ . await
1385+ . map_err ( log_and_500) ?;
1386+
1387+ Ok ( ( ) )
1388+ }
1389+
13331390#[ derive( Deserialize ) ]
13341391pub struct AddNameParams {
13351392 name_or_identity : NameOrIdentity ,
@@ -1498,6 +1555,10 @@ pub struct DatabaseRoutes<S> {
14981555 pub db_reset : MethodRouter < S > ,
14991556 /// GET: /database/: name_or_identity/unstable/timestamp
15001557 pub timestamp_get : MethodRouter < S > ,
1558+ /// POST: /database/:name_or_identity/lock
1559+ pub lock_post : MethodRouter < S > ,
1560+ /// POST: /database/:name_or_identity/unlock
1561+ pub unlock_post : MethodRouter < S > ,
15011562 /// ANY: /database/:name_or_identity/route
15021563 pub http_route_root : MethodRouter < S > ,
15031564 /// ANY: /database/:name_or_identity/route/
@@ -1529,6 +1590,8 @@ where
15291590 pre_publish : post ( pre_publish :: < S > ) ,
15301591 db_reset : put ( reset :: < S > ) ,
15311592 timestamp_get : get ( get_timestamp :: < S > ) ,
1593+ lock_post : post ( lock_database :: < S > ) ,
1594+ unlock_post : post ( unlock_database :: < S > ) ,
15321595 http_route_root : any ( handle_http_route_root :: < S > ) ,
15331596 http_route_root_slash : any ( handle_http_route_root_slash :: < S > ) ,
15341597 http_route : any ( handle_http_route :: < S > ) ,
@@ -1556,7 +1619,9 @@ where
15561619 . route ( "/sql" , self . sql_post )
15571620 . route ( "/unstable/timestamp" , self . timestamp_get )
15581621 . route ( "/pre_publish" , self . pre_publish )
1559- . route ( "/reset" , self . db_reset ) ;
1622+ . route ( "/reset" , self . db_reset )
1623+ . route ( "/lock" , self . lock_post )
1624+ . route ( "/unlock" , self . unlock_post ) ;
15601625
15611626 let authed_root_router = axum:: Router :: new ( ) . route (
15621627 "/" ,
@@ -1761,6 +1826,10 @@ mod tests {
17611826 async fn lookup_namespace_owner ( & self , _name : & str ) -> anyhow:: Result < Option < Identity > > {
17621827 Ok ( None )
17631828 }
1829+
1830+ async fn is_database_locked ( & self , _database_identity : & Identity ) -> anyhow:: Result < bool > {
1831+ Ok ( false )
1832+ }
17641833 }
17651834
17661835 #[ async_trait]
@@ -1790,6 +1859,15 @@ mod tests {
17901859 Err ( anyhow:: anyhow!( "unused" ) )
17911860 }
17921861
1862+ async fn set_database_lock (
1863+ & self ,
1864+ _caller_identity : & Identity ,
1865+ _database_identity : & Identity ,
1866+ _locked : bool ,
1867+ ) -> anyhow:: Result < ( ) > {
1868+ Err ( anyhow:: anyhow!( "unused" ) )
1869+ }
1870+
17931871 async fn reset_database ( & self , _caller_identity : & Identity , _spec : DatabaseResetDef ) -> anyhow:: Result < ( ) > {
17941872 Err ( anyhow:: anyhow!( "unused" ) )
17951873 }
0 commit comments