Skip to content

Commit 56f300a

Browse files
committed
Fix listing, add listing test
1 parent 9ad2313 commit 56f300a

File tree

2 files changed

+185
-12
lines changed

2 files changed

+185
-12
lines changed

common/src/api/external/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1337,7 +1337,9 @@ impl SimpleIdentity for AffinityGroupMember {
13371337
///
13381338
/// Membership in a group is not exclusive - members may belong to multiple
13391339
/// affinity / anti-affinity groups.
1340-
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
1340+
#[derive(
1341+
Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Hash, Eq,
1342+
)]
13411343
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
13421344
pub enum AntiAffinityGroupMember {
13431345
/// An affinity group belonging to this group, identified by UUID.

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

Lines changed: 182 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,14 @@ impl DataStore {
406406
) -> ListResultVec<external::AntiAffinityGroupMember> {
407407
opctx.authorize(authz::Action::Read, authz_anti_affinity_group).await?;
408408

409+
let asc = match pagparams.direction {
410+
dropshot::PaginationOrder::Ascending => true,
411+
dropshot::PaginationOrder::Descending => false,
412+
};
413+
409414
let mut query = QueryBuilder::new()
410415
.sql(
411-
"
416+
"SELECT id,label FROM (
412417
SELECT instance_id as id, 'instance' as label
413418
FROM anti_affinity_group_instance_membership
414419
WHERE group_id = ",
@@ -424,24 +429,23 @@ impl DataStore {
424429
)
425430
.param()
426431
.bind::<diesel::sql_types::Uuid, _>(authz_anti_affinity_group.id())
427-
.sql(" ");
428-
429-
let (sort, cmp) = match pagparams.direction {
430-
dropshot::PaginationOrder::Ascending => (" ORDER BY id ASC ", ">"),
431-
dropshot::PaginationOrder::Descending => {
432-
(" ORDER BY id DESC ", "<")
433-
}
434-
};
432+
.sql(") ");
435433
if let Some(id) = pagparams.marker {
436434
query = query
437435
.sql("WHERE id ")
438-
.sql(cmp)
436+
.sql(if asc { ">" } else { "<" })
439437
.sql(" ")
440438
.param()
441439
.bind::<diesel::sql_types::Uuid, _>(*id);
442440
};
443441

444-
query = query.sql(sort);
442+
query = query.sql(" ORDER BY id ");
443+
if asc {
444+
query = query.sql("ASC ");
445+
} else {
446+
query = query.sql("DESC ");
447+
}
448+
445449
query =
446450
query.sql(" LIMIT ").param().bind::<diesel::sql_types::BigInt, _>(
447451
i64::from(pagparams.limit.get()),
@@ -1230,6 +1234,7 @@ mod tests {
12301234
use nexus_types::external_api::params;
12311235
use omicron_common::api::external::{
12321236
self, ByteCount, DataPageParams, IdentityMetadataCreateParams,
1237+
SimpleIdentity,
12331238
};
12341239
use omicron_test_utils::dev;
12351240
use omicron_uuid_kinds::GenericUuid;
@@ -1890,6 +1895,172 @@ mod tests {
18901895
logctx.cleanup_successful();
18911896
}
18921897

1898+
// Anti-affinity group member listing has a slightly more complicated
1899+
// implementation, because it queries multiple tables and UNIONs them
1900+
// together.
1901+
//
1902+
// This test exists to validate that manual implementation.
1903+
#[tokio::test]
1904+
async fn anti_affinity_group_membership_list_extended() {
1905+
// Setup
1906+
let logctx =
1907+
dev::test_setup_log("anti_affinity_group_membership_list_extended");
1908+
let db = TestDatabase::new_with_datastore(&logctx.log).await;
1909+
let (opctx, datastore) = (db.opctx(), db.datastore());
1910+
1911+
// Create a project and a group
1912+
let (authz_project, ..) =
1913+
create_project(&opctx, &datastore, "my-project").await;
1914+
let group = create_anti_affinity_group(
1915+
&opctx,
1916+
&datastore,
1917+
&authz_project,
1918+
"my-group",
1919+
)
1920+
.await
1921+
.unwrap();
1922+
1923+
let (.., authz_aa_group) = LookupPath::new(opctx, datastore)
1924+
.anti_affinity_group_id(group.id())
1925+
.lookup_for(authz::Action::Modify)
1926+
.await
1927+
.unwrap();
1928+
1929+
// A new group should have no members
1930+
let pagparams = DataPageParams {
1931+
marker: None,
1932+
limit: NonZeroU32::new(100).unwrap(),
1933+
direction: dropshot::PaginationOrder::Ascending,
1934+
};
1935+
let members = datastore
1936+
.anti_affinity_group_member_list(
1937+
&opctx,
1938+
&authz_aa_group,
1939+
&pagparams,
1940+
)
1941+
.await
1942+
.unwrap();
1943+
assert!(members.is_empty());
1944+
1945+
// Add some groups and instances, so we have data to list over.
1946+
1947+
const INSTANCE_COUNT: usize = 3;
1948+
const AFFINITY_GROUP_COUNT: usize = 3;
1949+
1950+
let mut members = Vec::new();
1951+
1952+
for i in 0..INSTANCE_COUNT {
1953+
let instance = create_stopped_instance_record(
1954+
&opctx,
1955+
&datastore,
1956+
&authz_project,
1957+
&format!("instance-{i}"),
1958+
)
1959+
.await;
1960+
1961+
// Add the instance as a member to the group
1962+
let member = external::AntiAffinityGroupMember::Instance(instance);
1963+
datastore
1964+
.anti_affinity_group_member_add(
1965+
&opctx,
1966+
&authz_aa_group,
1967+
member.clone(),
1968+
)
1969+
.await
1970+
.unwrap();
1971+
members.push(member);
1972+
}
1973+
1974+
for i in 0..AFFINITY_GROUP_COUNT {
1975+
let affinity_group = create_affinity_group(
1976+
&opctx,
1977+
&datastore,
1978+
&authz_project,
1979+
&format!("affinity-{i}"),
1980+
)
1981+
.await
1982+
.unwrap();
1983+
1984+
// Add the instance as a member to the group
1985+
let member = external::AntiAffinityGroupMember::AffinityGroup(
1986+
AffinityGroupUuid::from_untyped_uuid(affinity_group.id()),
1987+
);
1988+
datastore
1989+
.anti_affinity_group_member_add(
1990+
&opctx,
1991+
&authz_aa_group,
1992+
member.clone(),
1993+
)
1994+
.await
1995+
.unwrap();
1996+
members.push(member);
1997+
}
1998+
1999+
// Order by UUID, regardless of member type
2000+
members.sort_unstable_by(|m1, m2| m1.id().cmp(&m2.id()));
2001+
2002+
// We can list all members
2003+
let mut pagparams = DataPageParams {
2004+
marker: None,
2005+
limit: NonZeroU32::new(100).unwrap(),
2006+
direction: dropshot::PaginationOrder::Ascending,
2007+
};
2008+
let observed_members = datastore
2009+
.anti_affinity_group_member_list(
2010+
&opctx,
2011+
&authz_aa_group,
2012+
&pagparams,
2013+
)
2014+
.await
2015+
.unwrap();
2016+
assert_eq!(observed_members, members);
2017+
2018+
// We can paginate over the results
2019+
let marker = members[2].id();
2020+
pagparams.marker = Some(&marker);
2021+
let observed_members = datastore
2022+
.anti_affinity_group_member_list(
2023+
&opctx,
2024+
&authz_aa_group,
2025+
&pagparams,
2026+
)
2027+
.await
2028+
.unwrap();
2029+
assert_eq!(observed_members, members[3..]);
2030+
2031+
// We can list limited results
2032+
pagparams.marker = Some(&marker);
2033+
pagparams.limit = NonZeroU32::new(2).unwrap();
2034+
let observed_members = datastore
2035+
.anti_affinity_group_member_list(
2036+
&opctx,
2037+
&authz_aa_group,
2038+
&pagparams,
2039+
)
2040+
.await
2041+
.unwrap();
2042+
assert_eq!(observed_members, members[3..5]);
2043+
2044+
// We can list in descending order too
2045+
members.reverse();
2046+
pagparams.marker = None;
2047+
pagparams.limit = NonZeroU32::new(100).unwrap();
2048+
pagparams.direction = dropshot::PaginationOrder::Descending;
2049+
let observed_members = datastore
2050+
.anti_affinity_group_member_list(
2051+
&opctx,
2052+
&authz_aa_group,
2053+
&pagparams,
2054+
)
2055+
.await
2056+
.unwrap();
2057+
assert_eq!(observed_members, members);
2058+
2059+
// Clean up.
2060+
db.terminate().await;
2061+
logctx.cleanup_successful();
2062+
}
2063+
18932064
#[tokio::test]
18942065
async fn affinity_group_membership_add_remove_instance_with_vmm() {
18952066
// Setup

0 commit comments

Comments
 (0)