Skip to content

Commit fb9d759

Browse files
committed
fix unique index preventing multiple defaults per scope
1 parent 6455ec3 commit fb9d759

File tree

2 files changed

+32
-3
lines changed

2 files changed

+32
-3
lines changed

nexus/db-queries/src/db/datastore/ip_pool.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,9 +457,10 @@ mod test {
457457
use crate::authz;
458458
use crate::db::datastore::datastore_test;
459459
use crate::db::model::IpPool;
460+
use assert_matches::assert_matches;
460461
use nexus_test_utils::db::test_setup_database;
461462
use nexus_types::identity::Resource;
462-
use omicron_common::api::external::IdentityMetadataCreateParams;
463+
use omicron_common::api::external::{Error, IdentityMetadataCreateParams};
463464
use omicron_test_utils::dev;
464465

465466
#[tokio::test]
@@ -482,6 +483,20 @@ mod test {
482483
assert_eq!(fleet_default_pool.silo_id, None);
483484
assert_eq!(fleet_default_pool.project_id, None);
484485

486+
// unique index prevents second fleet-level default
487+
let identity = IdentityMetadataCreateParams {
488+
name: "another-fleet-default".parse().unwrap(),
489+
description: "".to_string(),
490+
};
491+
let err = datastore
492+
.ip_pool_create(
493+
&opctx,
494+
IpPool::new(&identity, None, /*default= */ true),
495+
)
496+
.await
497+
.expect_err("Failed to fail to create a second default fleet pool");
498+
assert_matches!(err, Error::ObjectAlreadyExists { .. });
499+
485500
// now the interesting thing is that when we fetch the default pool for
486501
// a particular silo or a particular project, if those scopes do not
487502
// have a default IP pool, we will still get back the fleet default
@@ -538,6 +553,17 @@ mod test {
538553
.expect("Failed to get fleet default IP pool");
539554
assert_eq!(ip_pool.id(), fleet_default_pool.id());
540555

556+
// and we can't create a second default pool for the silo
557+
let identity = IdentityMetadataCreateParams {
558+
name: "second-default-for-silo".parse().unwrap(),
559+
description: "".to_string(),
560+
};
561+
let err = datastore
562+
.ip_pool_create(&opctx, IpPool::new(&identity, Some(silo_id), true))
563+
.await
564+
.expect_err("Failed to fail to create second default pool");
565+
assert_matches!(err, Error::ObjectAlreadyExists { .. });
566+
541567
db.cleanup().await.unwrap();
542568
logctx.cleanup_successful();
543569
}

schema/crdb/dbinit.sql

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1500,10 +1500,13 @@ CREATE TABLE IF NOT EXISTS omicron.public.ip_pool (
15001500
);
15011501

15021502
/*
1503-
* Ensure there can only be one default pool for the fleet or a given silo or project
1503+
* Ensure there can only be one default pool for the fleet or a given silo or
1504+
* project. Coalesce is needed because otherwise different nulls are considered
1505+
* to be distinct from each other.
15041506
*/
15051507
CREATE UNIQUE INDEX IF NOT EXISTS one_default_pool_per_scope ON omicron.public.ip_pool (
1506-
silo_id, project_id
1508+
COALESCE(silo_id, '00000000-0000-0000-0000-000000000000'::uuid),
1509+
COALESCE(project_id, '00000000-0000-0000-0000-000000000000'::uuid)
15071510
) WHERE
15081511
"default" = true AND time_deleted IS NULL;
15091512

0 commit comments

Comments
 (0)