@@ -11,6 +11,7 @@ use crate::db;
1111use crate :: db:: collection_insert:: AsyncInsertError ;
1212use crate :: db:: collection_insert:: DatastoreCollection ;
1313use crate :: db:: collection_insert:: SyncInsertError ;
14+ use crate :: db:: error:: diesel_pool_result_optional;
1415use crate :: db:: error:: public_error_from_diesel_pool;
1516use crate :: db:: error:: ErrorHandler ;
1617use crate :: db:: error:: TransactionError ;
@@ -43,6 +44,7 @@ use omicron_common::api::external::ListResultVec;
4344use omicron_common:: api:: external:: LookupType ;
4445use omicron_common:: api:: external:: ResourceType ;
4546use omicron_common:: api:: external:: UpdateResult ;
47+ use uuid:: Uuid ;
4648
4749impl DataStore {
4850 pub async fn project_list_vpcs (
@@ -128,33 +130,80 @@ impl DataStore {
128130 pub async fn project_delete_vpc (
129131 & self ,
130132 opctx : & OpContext ,
133+ db_vpc : & Vpc ,
131134 authz_vpc : & authz:: Vpc ,
132135 ) -> DeleteResult {
133136 opctx. authorize ( authz:: Action :: Delete , authz_vpc) . await ?;
134137
135138 use db:: schema:: vpc:: dsl;
139+ use db:: schema:: vpc_subnet;
136140
137141 // Note that we don't ensure the firewall rules are empty here, because
138142 // we allow deleting VPCs with firewall rules present. Inserting new
139143 // rules is serialized with respect to the deletion by the row lock
140144 // associated with the VPC row, since we use the collection insert CTE
141145 // pattern to add firewall rules.
142146
147+ // We _do_ need to check for the existence of subnets. VPC Subnets
148+ // cannot be deleted while there are network interfaces in them
149+ // (associations between an instance and a VPC Subnet). Because VPC
150+ // Subnets are themselves containers for resources that we don't want to
151+ // auto-delete (now, anyway), we've got to check there aren't any. We
152+ // _might_ be able to make this a check for NICs, rather than subnets,
153+ // but we can't have NICs be a child of both tables at this point, and
154+ // we need to prevent VPC Subnets from being deleted while they have
155+ // NICs in them as well.
156+ println ! ( "ABOUT TO SEARCH FOR SUBNETS" ) ;
157+ if diesel_pool_result_optional (
158+ vpc_subnet:: dsl:: vpc_subnet
159+ . filter ( vpc_subnet:: dsl:: vpc_id. eq ( authz_vpc. id ( ) ) )
160+ . filter ( vpc_subnet:: dsl:: time_deleted. is_null ( ) )
161+ . select ( vpc_subnet:: dsl:: id)
162+ . limit ( 1 )
163+ . first_async :: < Uuid > ( self . pool_authorized ( opctx) . await ?)
164+ . await ,
165+ )
166+ . map_err ( |e| public_error_from_diesel_pool ( e, ErrorHandler :: Server ) ) ?
167+ . is_some ( )
168+ {
169+ println ! ( "FOUND SOME SUBNETS" ) ;
170+ return Err ( Error :: InvalidRequest {
171+ message : String :: from (
172+ "VPC cannot be deleted while VPC Subnets exist" ,
173+ ) ,
174+ } ) ;
175+ }
176+ println ! ( "FOUND NO SUBNETS" ) ;
177+
178+ // Delete the VPC, conditional on the subnet_gen not having changed.
143179 let now = Utc :: now ( ) ;
144- diesel:: update ( dsl:: vpc)
180+ println ! ( "ABOUT TO UPDATE" ) ;
181+ let updated_rows = diesel:: update ( dsl:: vpc)
145182 . filter ( dsl:: time_deleted. is_null ( ) )
146183 . filter ( dsl:: id. eq ( authz_vpc. id ( ) ) )
184+ . filter ( dsl:: subnet_gen. eq ( db_vpc. subnet_gen ) )
147185 . set ( dsl:: time_deleted. eq ( now) )
148- . returning ( Vpc :: as_returning ( ) )
149- . get_result_async ( self . pool_authorized ( opctx) . await ?)
186+ . execute_async ( self . pool_authorized ( opctx) . await ?)
150187 . await
151188 . map_err ( |e| {
189+ println ! ( "FAILED TO UPDATE" ) ;
152190 public_error_from_diesel_pool (
153191 e,
154192 ErrorHandler :: NotFoundByResource ( authz_vpc) ,
155193 )
156194 } ) ?;
157- Ok ( ( ) )
195+ println ! ( "FINISHED UPDATE" ) ;
196+ if updated_rows == 0 {
197+ println ! ( "NO ROWS UPDATED" ) ;
198+ Err ( Error :: InvalidRequest {
199+ message : String :: from (
200+ "deletion failed to to concurrent modification" ,
201+ ) ,
202+ } )
203+ } else {
204+ println ! ( "SOME ROWS UPDATED" ) ;
205+ Ok ( ( ) )
206+ }
158207 }
159208
160209 pub async fn vpc_list_firewall_rules (
@@ -328,26 +377,59 @@ impl DataStore {
328377 pub async fn vpc_delete_subnet (
329378 & self ,
330379 opctx : & OpContext ,
380+ db_subnet : & VpcSubnet ,
331381 authz_subnet : & authz:: VpcSubnet ,
332382 ) -> DeleteResult {
333383 opctx. authorize ( authz:: Action :: Delete , authz_subnet) . await ?;
334384
385+ use db:: schema:: network_interface;
335386 use db:: schema:: vpc_subnet:: dsl;
387+
388+ // Verify there are no child network interfaces in this VPC Subnet
389+ if diesel_pool_result_optional (
390+ network_interface:: dsl:: network_interface
391+ . filter ( network_interface:: dsl:: subnet_id. eq ( authz_subnet. id ( ) ) )
392+ . filter ( network_interface:: dsl:: time_deleted. is_null ( ) )
393+ . select ( network_interface:: dsl:: id)
394+ . limit ( 1 )
395+ . first_async :: < Uuid > ( self . pool_authorized ( opctx) . await ?)
396+ . await ,
397+ )
398+ . map_err ( |e| public_error_from_diesel_pool ( e, ErrorHandler :: Server ) ) ?
399+ . is_some ( )
400+ {
401+ return Err ( Error :: InvalidRequest {
402+ message : String :: from (
403+ "VPC Subnet cannot be deleted while instances \
404+ with network interfaces in the subnet exist",
405+ ) ,
406+ } ) ;
407+ }
408+
409+ // Delete the subnet, conditional on the rcgen not having changed.
336410 let now = Utc :: now ( ) ;
337- diesel:: update ( dsl:: vpc_subnet)
411+ let updated_rows = diesel:: update ( dsl:: vpc_subnet)
338412 . filter ( dsl:: time_deleted. is_null ( ) )
339413 . filter ( dsl:: id. eq ( authz_subnet. id ( ) ) )
414+ . filter ( dsl:: rcgen. eq ( db_subnet. rcgen ) )
340415 . set ( dsl:: time_deleted. eq ( now) )
341- . returning ( VpcSubnet :: as_returning ( ) )
342- . get_result_async ( self . pool_authorized ( opctx) . await ?)
416+ . execute_async ( self . pool_authorized ( opctx) . await ?)
343417 . await
344418 . map_err ( |e| {
345419 public_error_from_diesel_pool (
346420 e,
347421 ErrorHandler :: NotFoundByResource ( authz_subnet) ,
348422 )
349423 } ) ?;
350- Ok ( ( ) )
424+ if updated_rows == 0 {
425+ return Err ( Error :: InvalidRequest {
426+ message : String :: from (
427+ "deletion failed to to concurrent modification" ,
428+ ) ,
429+ } ) ;
430+ } else {
431+ Ok ( ( ) )
432+ }
351433 }
352434
353435 pub async fn vpc_update_subnet (
@@ -463,8 +545,7 @@ impl DataStore {
463545 . filter ( dsl:: time_deleted. is_null ( ) )
464546 . filter ( dsl:: id. eq ( authz_router. id ( ) ) )
465547 . set ( dsl:: time_deleted. eq ( now) )
466- . returning ( VpcRouter :: as_returning ( ) )
467- . get_result_async ( self . pool ( ) )
548+ . execute_async ( self . pool ( ) )
468549 . await
469550 . map_err ( |e| {
470551 public_error_from_diesel_pool (
0 commit comments