Skip to content

Commit df37612

Browse files
committed
use one unique partial index for silo_group
1 parent 6e79986 commit df37612

File tree

3 files changed

+77
-9
lines changed

3 files changed

+77
-9
lines changed

common/src/sql/dbinit.sql

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,15 +288,11 @@ CREATE TABLE omicron.public.silo_group (
288288
external_id TEXT NOT NULL
289289
);
290290

291-
CREATE INDEX ON omicron.public.silo_group (
292-
silo_id
293-
) WHERE
294-
time_deleted IS NULL;
295-
296291
CREATE UNIQUE INDEX ON omicron.public.silo_group (
297292
silo_id,
298293
external_id
299-
);
294+
) WHERE
295+
time_deleted IS NULL;
300296

301297
/*
302298
* Silo group membership

nexus/src/db/datastore/silo_group.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,30 @@ impl DataStore {
3636
opctx.authorize(authz::Action::CreateChild, authz_silo).await?;
3737

3838
use db::schema::silo_group::dsl;
39+
40+
// Without the on_conflict below, simultaneous attempts to ensure the
41+
// same SiloGroup will see one of them fail. This can occur if two users
42+
// are logging in at the same time, and both are part of IdP groups that
43+
// do not yet exist in our database.
44+
//
45+
// Currently there is a unique partial index on silo_group, which
46+
// because it is partial has a WHERE clause. diesel does not support
47+
// creating queries that contain:
48+
//
49+
// ON CONFLICT (...) WHERE ... DO NOTHING
50+
//
51+
// meaning we have to use `on_conflict_do_nothing`. In the future when
52+
// diesel does support this, uncomment (and probably fix) the code
53+
// below.
54+
//
55+
// Issue https://github.com/oxidecomputer/omicron/issues/1545 was opened
56+
// to track this.
3957
Ok(diesel::insert_into(dsl::silo_group)
4058
.values(silo_group)
41-
.on_conflict((dsl::silo_id, dsl::external_id))
42-
.do_nothing()
59+
//.on_conflict((dsl::silo_id, dsl::external_id))
60+
// .where(dsl::time_deleted.is_null())
61+
//.do_nothing()
62+
.on_conflict_do_nothing()
4363
.returning(SiloGroup::as_returning()))
4464
}
4565

nexus/tests/integration_tests/silos.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1310,7 +1310,6 @@ async fn test_silo_delete_clean_up_groups(cptestctx: &ControlPlaneTestContext) {
13101310
nexus.datastore().clone(),
13111311
);
13121312

1313-
// Create an admin user
13141313
let (authz_silo, db_silo) = LookupPath::new(&opctx, &nexus.datastore())
13151314
.silo_name(&silo.identity.name.into())
13161315
.fetch()
@@ -1363,3 +1362,56 @@ async fn test_silo_delete_clean_up_groups(cptestctx: &ControlPlaneTestContext) {
13631362
.await
13641363
.expect_err("user found");
13651364
}
1365+
1366+
// Test ensuring the same group from different users
1367+
#[nexus_test]
1368+
async fn test_ensure_same_silo_group(cptestctx: &ControlPlaneTestContext) {
1369+
let client = &cptestctx.external_client;
1370+
let nexus = &cptestctx.server.apictx.nexus;
1371+
1372+
// Create a silo
1373+
let silo =
1374+
create_silo(&client, "test-silo", true, shared::UserProvisionType::Jit)
1375+
.await;
1376+
1377+
let opctx = OpContext::for_tests(
1378+
cptestctx.logctx.log.new(o!()),
1379+
nexus.datastore().clone(),
1380+
);
1381+
1382+
let (authz_silo, db_silo) = LookupPath::new(&opctx, &nexus.datastore())
1383+
.silo_name(&silo.identity.name.into())
1384+
.fetch()
1385+
.await
1386+
.unwrap();
1387+
1388+
// Add the first user with a group membership
1389+
let _silo_user_1 = nexus
1390+
.silo_user_from_authenticated_subject(
1391+
&nexus.opctx_external_authn(),
1392+
&authz_silo,
1393+
&db_silo,
1394+
&AuthenticatedSubject {
1395+
external_id: "user1@company.com".into(),
1396+
groups: vec!["sre".into()],
1397+
},
1398+
)
1399+
.await
1400+
.expect("silo_user_from_authenticated_subject 1")
1401+
.unwrap();
1402+
1403+
// Add the first user with a group membership
1404+
let _silo_user_2 = nexus
1405+
.silo_user_from_authenticated_subject(
1406+
&nexus.opctx_external_authn(),
1407+
&authz_silo,
1408+
&db_silo,
1409+
&AuthenticatedSubject {
1410+
external_id: "user2@company.com".into(),
1411+
groups: vec!["sre".into()],
1412+
},
1413+
)
1414+
.await
1415+
.expect("silo_user_from_authenticated_subject 2")
1416+
.unwrap();
1417+
}

0 commit comments

Comments
 (0)