@@ -11,6 +11,7 @@ use crate::db;
11
11
use crate :: db:: collection_insert:: AsyncInsertError ;
12
12
use crate :: db:: collection_insert:: DatastoreCollection ;
13
13
use crate :: db:: collection_insert:: SyncInsertError ;
14
+ use crate :: db:: error:: diesel_pool_result_optional;
14
15
use crate :: db:: error:: public_error_from_diesel_pool;
15
16
use crate :: db:: error:: ErrorHandler ;
16
17
use crate :: db:: error:: TransactionError ;
@@ -43,6 +44,7 @@ use omicron_common::api::external::ListResultVec;
43
44
use omicron_common:: api:: external:: LookupType ;
44
45
use omicron_common:: api:: external:: ResourceType ;
45
46
use omicron_common:: api:: external:: UpdateResult ;
47
+ use uuid:: Uuid ;
46
48
47
49
impl DataStore {
48
50
pub async fn project_list_vpcs (
@@ -128,33 +130,80 @@ impl DataStore {
128
130
pub async fn project_delete_vpc (
129
131
& self ,
130
132
opctx : & OpContext ,
133
+ db_vpc : & Vpc ,
131
134
authz_vpc : & authz:: Vpc ,
132
135
) -> DeleteResult {
133
136
opctx. authorize ( authz:: Action :: Delete , authz_vpc) . await ?;
134
137
135
138
use db:: schema:: vpc:: dsl;
139
+ use db:: schema:: vpc_subnet;
136
140
137
141
// Note that we don't ensure the firewall rules are empty here, because
138
142
// we allow deleting VPCs with firewall rules present. Inserting new
139
143
// rules is serialized with respect to the deletion by the row lock
140
144
// associated with the VPC row, since we use the collection insert CTE
141
145
// pattern to add firewall rules.
142
146
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.
143
179
let now = Utc :: now ( ) ;
144
- diesel:: update ( dsl:: vpc)
180
+ println ! ( "ABOUT TO UPDATE" ) ;
181
+ let updated_rows = diesel:: update ( dsl:: vpc)
145
182
. filter ( dsl:: time_deleted. is_null ( ) )
146
183
. filter ( dsl:: id. eq ( authz_vpc. id ( ) ) )
184
+ . filter ( dsl:: subnet_gen. eq ( db_vpc. subnet_gen ) )
147
185
. 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 ?)
150
187
. await
151
188
. map_err ( |e| {
189
+ println ! ( "FAILED TO UPDATE" ) ;
152
190
public_error_from_diesel_pool (
153
191
e,
154
192
ErrorHandler :: NotFoundByResource ( authz_vpc) ,
155
193
)
156
194
} ) ?;
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
+ }
158
207
}
159
208
160
209
pub async fn vpc_list_firewall_rules (
@@ -328,26 +377,59 @@ impl DataStore {
328
377
pub async fn vpc_delete_subnet (
329
378
& self ,
330
379
opctx : & OpContext ,
380
+ db_subnet : & VpcSubnet ,
331
381
authz_subnet : & authz:: VpcSubnet ,
332
382
) -> DeleteResult {
333
383
opctx. authorize ( authz:: Action :: Delete , authz_subnet) . await ?;
334
384
385
+ use db:: schema:: network_interface;
335
386
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.
336
410
let now = Utc :: now ( ) ;
337
- diesel:: update ( dsl:: vpc_subnet)
411
+ let updated_rows = diesel:: update ( dsl:: vpc_subnet)
338
412
. filter ( dsl:: time_deleted. is_null ( ) )
339
413
. filter ( dsl:: id. eq ( authz_subnet. id ( ) ) )
414
+ . filter ( dsl:: rcgen. eq ( db_subnet. rcgen ) )
340
415
. 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 ?)
343
417
. await
344
418
. map_err ( |e| {
345
419
public_error_from_diesel_pool (
346
420
e,
347
421
ErrorHandler :: NotFoundByResource ( authz_subnet) ,
348
422
)
349
423
} ) ?;
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
+ }
351
433
}
352
434
353
435
pub async fn vpc_update_subnet (
@@ -463,8 +545,7 @@ impl DataStore {
463
545
. filter ( dsl:: time_deleted. is_null ( ) )
464
546
. filter ( dsl:: id. eq ( authz_router. id ( ) ) )
465
547
. set ( dsl:: time_deleted. eq ( now) )
466
- . returning ( VpcRouter :: as_returning ( ) )
467
- . get_result_async ( self . pool ( ) )
548
+ . execute_async ( self . pool ( ) )
468
549
. await
469
550
. map_err ( |e| {
470
551
public_error_from_diesel_pool (
0 commit comments