Skip to content

Commit 18e317b

Browse files
authored
(1/5) [nexus] Add Affinity/Anti-Affinity Groups to API (unimplemented) (#7443)
Pulled out of #7076 Exposes Affinity/Anti-Affinity Groups in the API, but as unimplemented.
1 parent ff6d24a commit 18e317b

File tree

8 files changed

+2154
-76
lines changed

8 files changed

+2154
-76
lines changed

common/src/api/external/mod.rs

+69
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ use dropshot::HttpError;
2222
pub use dropshot::PaginationOrder;
2323
pub use error::*;
2424
use futures::stream::BoxStream;
25+
use omicron_uuid_kinds::GenericUuid;
26+
use omicron_uuid_kinds::InstanceUuid;
2527
use oxnet::IpNet;
2628
use oxnet::Ipv4Net;
2729
use parse_display::Display;
@@ -994,6 +996,10 @@ impl JsonSchema for Hostname {
994996
pub enum ResourceType {
995997
AddressLot,
996998
AddressLotBlock,
999+
AffinityGroup,
1000+
AffinityGroupMember,
1001+
AntiAffinityGroup,
1002+
AntiAffinityGroupMember,
9971003
AllowList,
9981004
BackgroundTask,
9991005
BgpConfig,
@@ -1324,6 +1330,69 @@ pub enum InstanceAutoRestartPolicy {
13241330
BestEffort,
13251331
}
13261332

1333+
// AFFINITY GROUPS
1334+
1335+
/// Affinity policy used to describe "what to do when a request cannot be satisfied"
1336+
///
1337+
/// Used for both Affinity and Anti-Affinity Groups
1338+
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
1339+
#[serde(rename_all = "snake_case")]
1340+
pub enum AffinityPolicy {
1341+
/// If the affinity request cannot be satisfied, allow it anyway.
1342+
///
1343+
/// This enables a "best-effort" attempt to satisfy the affinity policy.
1344+
Allow,
1345+
1346+
/// If the affinity request cannot be satisfied, fail explicitly.
1347+
Fail,
1348+
}
1349+
1350+
/// Describes the scope of affinity for the purposes of co-location.
1351+
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
1352+
#[serde(rename_all = "snake_case")]
1353+
pub enum FailureDomain {
1354+
/// Instances are considered co-located if they are on the same sled
1355+
Sled,
1356+
}
1357+
1358+
/// A member of an Affinity Group
1359+
///
1360+
/// Membership in a group is not exclusive - members may belong to multiple
1361+
/// affinity / anti-affinity groups.
1362+
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
1363+
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
1364+
pub enum AffinityGroupMember {
1365+
/// An instance belonging to this group, identified by UUID.
1366+
Instance(InstanceUuid),
1367+
}
1368+
1369+
impl SimpleIdentity for AffinityGroupMember {
1370+
fn id(&self) -> Uuid {
1371+
match self {
1372+
AffinityGroupMember::Instance(id) => *id.as_untyped_uuid(),
1373+
}
1374+
}
1375+
}
1376+
1377+
/// A member of an Anti-Affinity Group
1378+
///
1379+
/// Membership in a group is not exclusive - members may belong to multiple
1380+
/// affinity / anti-affinity groups.
1381+
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
1382+
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
1383+
pub enum AntiAffinityGroupMember {
1384+
/// An instance belonging to this group, identified by UUID.
1385+
Instance(InstanceUuid),
1386+
}
1387+
1388+
impl SimpleIdentity for AntiAffinityGroupMember {
1389+
fn id(&self) -> Uuid {
1390+
match self {
1391+
AntiAffinityGroupMember::Instance(id) => *id.as_untyped_uuid(),
1392+
}
1393+
}
1394+
}
1395+
13271396
// DISKS
13281397

13291398
/// View of a Disk

nexus/external-api/output/nexus_tags.txt

+21
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
API operations found with tag "affinity"
2+
OPERATION ID METHOD URL PATH
3+
affinity_group_create POST /v1/affinity-groups
4+
affinity_group_delete DELETE /v1/affinity-groups/{affinity_group}
5+
affinity_group_list GET /v1/affinity-groups
6+
affinity_group_member_instance_add POST /v1/affinity-groups/{affinity_group}/members/instance/{instance}
7+
affinity_group_member_instance_delete DELETE /v1/affinity-groups/{affinity_group}/members/instance/{instance}
8+
affinity_group_member_instance_view GET /v1/affinity-groups/{affinity_group}/members/instance/{instance}
9+
affinity_group_member_list GET /v1/affinity-groups/{affinity_group}/members
10+
affinity_group_update PUT /v1/affinity-groups/{affinity_group}
11+
affinity_group_view GET /v1/affinity-groups/{affinity_group}
12+
anti_affinity_group_create POST /v1/anti-affinity-groups
13+
anti_affinity_group_delete DELETE /v1/anti-affinity-groups/{anti_affinity_group}
14+
anti_affinity_group_list GET /v1/anti-affinity-groups
15+
anti_affinity_group_member_instance_add POST /v1/anti-affinity-groups/{anti_affinity_group}/members/instance/{instance}
16+
anti_affinity_group_member_instance_delete DELETE /v1/anti-affinity-groups/{anti_affinity_group}/members/instance/{instance}
17+
anti_affinity_group_member_instance_view GET /v1/anti-affinity-groups/{anti_affinity_group}/members/instance/{instance}
18+
anti_affinity_group_member_list GET /v1/anti-affinity-groups/{anti_affinity_group}/members
19+
anti_affinity_group_update PUT /v1/anti-affinity-groups/{anti_affinity_group}
20+
anti_affinity_group_view GET /v1/anti-affinity-groups/{anti_affinity_group}
21+
122
API operations found with tag "disks"
223
OPERATION ID METHOD URL PATH
324
disk_bulk_write_import POST /v1/disks/{disk}/bulk-write

nexus/external-api/src/lib.rs

+225
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ const PUT_UPDATE_REPOSITORY_MAX_BYTES: usize = 4 * GIB;
7474
allow_other_tags = false,
7575
policy = EndpointTagPolicy::ExactlyOne,
7676
tags = {
77+
"affinity" = {
78+
description = "Affinity and anti-affinity groups give control over instance placement.",
79+
external_docs = {
80+
url = "http://docs.oxide.computer/api/affinity"
81+
}
82+
83+
},
7784
"disks" = {
7885
description = "Virtual disks are used to store instance-local data which includes the operating system.",
7986
external_docs = {
@@ -1257,6 +1264,224 @@ pub trait NexusExternalApi {
12571264
disk_to_detach: TypedBody<params::DiskPath>,
12581265
) -> Result<HttpResponseAccepted<Disk>, HttpError>;
12591266

1267+
// Affinity Groups
1268+
1269+
/// List affinity groups
1270+
#[endpoint {
1271+
method = GET,
1272+
path = "/v1/affinity-groups",
1273+
tags = ["affinity"],
1274+
}]
1275+
async fn affinity_group_list(
1276+
rqctx: RequestContext<Self::Context>,
1277+
query_params: Query<PaginatedByNameOrId<params::ProjectSelector>>,
1278+
) -> Result<HttpResponseOk<ResultsPage<views::AffinityGroup>>, HttpError>;
1279+
1280+
/// Fetch an affinity group
1281+
#[endpoint {
1282+
method = GET,
1283+
path = "/v1/affinity-groups/{affinity_group}",
1284+
tags = ["affinity"],
1285+
}]
1286+
async fn affinity_group_view(
1287+
rqctx: RequestContext<Self::Context>,
1288+
query_params: Query<params::OptionalProjectSelector>,
1289+
path_params: Path<params::AffinityGroupPath>,
1290+
) -> Result<HttpResponseOk<views::AffinityGroup>, HttpError>;
1291+
1292+
/// List members of an affinity group
1293+
#[endpoint {
1294+
method = GET,
1295+
path = "/v1/affinity-groups/{affinity_group}/members",
1296+
tags = ["affinity"],
1297+
}]
1298+
async fn affinity_group_member_list(
1299+
rqctx: RequestContext<Self::Context>,
1300+
query_params: Query<PaginatedById<params::OptionalProjectSelector>>,
1301+
path_params: Path<params::AffinityGroupPath>,
1302+
) -> Result<HttpResponseOk<ResultsPage<AffinityGroupMember>>, HttpError>;
1303+
1304+
/// Fetch an affinity group member
1305+
#[endpoint {
1306+
method = GET,
1307+
path = "/v1/affinity-groups/{affinity_group}/members/instance/{instance}",
1308+
tags = ["affinity"],
1309+
}]
1310+
async fn affinity_group_member_instance_view(
1311+
rqctx: RequestContext<Self::Context>,
1312+
query_params: Query<params::OptionalProjectSelector>,
1313+
path_params: Path<params::AffinityInstanceGroupMemberPath>,
1314+
) -> Result<HttpResponseOk<AffinityGroupMember>, HttpError>;
1315+
1316+
/// Add a member to an affinity group
1317+
#[endpoint {
1318+
method = POST,
1319+
path = "/v1/affinity-groups/{affinity_group}/members/instance/{instance}",
1320+
tags = ["affinity"],
1321+
}]
1322+
async fn affinity_group_member_instance_add(
1323+
rqctx: RequestContext<Self::Context>,
1324+
query_params: Query<params::OptionalProjectSelector>,
1325+
path_params: Path<params::AffinityInstanceGroupMemberPath>,
1326+
) -> Result<HttpResponseCreated<AffinityGroupMember>, HttpError>;
1327+
1328+
/// Remove a member from an affinity group
1329+
#[endpoint {
1330+
method = DELETE,
1331+
path = "/v1/affinity-groups/{affinity_group}/members/instance/{instance}",
1332+
tags = ["affinity"],
1333+
}]
1334+
async fn affinity_group_member_instance_delete(
1335+
rqctx: RequestContext<Self::Context>,
1336+
query_params: Query<params::OptionalProjectSelector>,
1337+
path_params: Path<params::AffinityInstanceGroupMemberPath>,
1338+
) -> Result<HttpResponseDeleted, HttpError>;
1339+
1340+
/// Create an affinity group
1341+
#[endpoint {
1342+
method = POST,
1343+
path = "/v1/affinity-groups",
1344+
tags = ["affinity"],
1345+
}]
1346+
async fn affinity_group_create(
1347+
rqctx: RequestContext<Self::Context>,
1348+
query_params: Query<params::ProjectSelector>,
1349+
new_affinity_group_params: TypedBody<params::AffinityGroupCreate>,
1350+
) -> Result<HttpResponseCreated<views::AffinityGroup>, HttpError>;
1351+
1352+
/// Update an affinity group
1353+
#[endpoint {
1354+
method = PUT,
1355+
path = "/v1/affinity-groups/{affinity_group}",
1356+
tags = ["affinity"],
1357+
}]
1358+
async fn affinity_group_update(
1359+
rqctx: RequestContext<Self::Context>,
1360+
query_params: Query<params::OptionalProjectSelector>,
1361+
path_params: Path<params::AffinityGroupPath>,
1362+
updated_group: TypedBody<params::AffinityGroupUpdate>,
1363+
) -> Result<HttpResponseOk<views::AffinityGroup>, HttpError>;
1364+
1365+
/// Delete an affinity group
1366+
#[endpoint {
1367+
method = DELETE,
1368+
path = "/v1/affinity-groups/{affinity_group}",
1369+
tags = ["affinity"],
1370+
}]
1371+
async fn affinity_group_delete(
1372+
rqctx: RequestContext<Self::Context>,
1373+
query_params: Query<params::OptionalProjectSelector>,
1374+
path_params: Path<params::AffinityGroupPath>,
1375+
) -> Result<HttpResponseDeleted, HttpError>;
1376+
1377+
/// List anti-affinity groups
1378+
#[endpoint {
1379+
method = GET,
1380+
path = "/v1/anti-affinity-groups",
1381+
tags = ["affinity"],
1382+
}]
1383+
async fn anti_affinity_group_list(
1384+
rqctx: RequestContext<Self::Context>,
1385+
query_params: Query<PaginatedByNameOrId<params::ProjectSelector>>,
1386+
) -> Result<HttpResponseOk<ResultsPage<views::AntiAffinityGroup>>, HttpError>;
1387+
1388+
/// Fetch an anti-affinity group
1389+
#[endpoint {
1390+
method = GET,
1391+
path = "/v1/anti-affinity-groups/{anti_affinity_group}",
1392+
tags = ["affinity"],
1393+
}]
1394+
async fn anti_affinity_group_view(
1395+
rqctx: RequestContext<Self::Context>,
1396+
query_params: Query<params::OptionalProjectSelector>,
1397+
path_params: Path<params::AntiAffinityGroupPath>,
1398+
) -> Result<HttpResponseOk<views::AntiAffinityGroup>, HttpError>;
1399+
1400+
/// List members of an anti-affinity group
1401+
#[endpoint {
1402+
method = GET,
1403+
path = "/v1/anti-affinity-groups/{anti_affinity_group}/members",
1404+
tags = ["affinity"],
1405+
}]
1406+
async fn anti_affinity_group_member_list(
1407+
rqctx: RequestContext<Self::Context>,
1408+
query_params: Query<PaginatedById<params::OptionalProjectSelector>>,
1409+
path_params: Path<params::AntiAffinityGroupPath>,
1410+
) -> Result<HttpResponseOk<ResultsPage<AntiAffinityGroupMember>>, HttpError>;
1411+
1412+
/// Fetch an anti-affinity group member
1413+
#[endpoint {
1414+
method = GET,
1415+
path = "/v1/anti-affinity-groups/{anti_affinity_group}/members/instance/{instance}",
1416+
tags = ["affinity"],
1417+
}]
1418+
async fn anti_affinity_group_member_instance_view(
1419+
rqctx: RequestContext<Self::Context>,
1420+
query_params: Query<params::OptionalProjectSelector>,
1421+
path_params: Path<params::AntiAffinityInstanceGroupMemberPath>,
1422+
) -> Result<HttpResponseOk<AntiAffinityGroupMember>, HttpError>;
1423+
1424+
/// Add a member to an anti-affinity group
1425+
#[endpoint {
1426+
method = POST,
1427+
path = "/v1/anti-affinity-groups/{anti_affinity_group}/members/instance/{instance}",
1428+
tags = ["affinity"],
1429+
}]
1430+
async fn anti_affinity_group_member_instance_add(
1431+
rqctx: RequestContext<Self::Context>,
1432+
query_params: Query<params::OptionalProjectSelector>,
1433+
path_params: Path<params::AntiAffinityInstanceGroupMemberPath>,
1434+
) -> Result<HttpResponseCreated<AntiAffinityGroupMember>, HttpError>;
1435+
1436+
/// Remove a member from an anti-affinity group
1437+
#[endpoint {
1438+
method = DELETE,
1439+
path = "/v1/anti-affinity-groups/{anti_affinity_group}/members/instance/{instance}",
1440+
tags = ["affinity"],
1441+
}]
1442+
async fn anti_affinity_group_member_instance_delete(
1443+
rqctx: RequestContext<Self::Context>,
1444+
query_params: Query<params::OptionalProjectSelector>,
1445+
path_params: Path<params::AntiAffinityInstanceGroupMemberPath>,
1446+
) -> Result<HttpResponseDeleted, HttpError>;
1447+
1448+
/// Create an anti-affinity group
1449+
#[endpoint {
1450+
method = POST,
1451+
path = "/v1/anti-affinity-groups",
1452+
tags = ["affinity"],
1453+
}]
1454+
async fn anti_affinity_group_create(
1455+
rqctx: RequestContext<Self::Context>,
1456+
query_params: Query<params::ProjectSelector>,
1457+
new_affinity_group_params: TypedBody<params::AntiAffinityGroupCreate>,
1458+
) -> Result<HttpResponseCreated<views::AntiAffinityGroup>, HttpError>;
1459+
1460+
/// Update an anti-affinity group
1461+
#[endpoint {
1462+
method = PUT,
1463+
path = "/v1/anti-affinity-groups/{anti_affinity_group}",
1464+
tags = ["affinity"],
1465+
}]
1466+
async fn anti_affinity_group_update(
1467+
rqctx: RequestContext<Self::Context>,
1468+
query_params: Query<params::OptionalProjectSelector>,
1469+
path_params: Path<params::AntiAffinityGroupPath>,
1470+
updated_group: TypedBody<params::AntiAffinityGroupUpdate>,
1471+
) -> Result<HttpResponseOk<views::AntiAffinityGroup>, HttpError>;
1472+
1473+
/// Delete an anti-affinity group
1474+
#[endpoint {
1475+
method = DELETE,
1476+
path = "/v1/anti-affinity-groups/{anti_affinity_group}",
1477+
tags = ["affinity"],
1478+
}]
1479+
async fn anti_affinity_group_delete(
1480+
rqctx: RequestContext<Self::Context>,
1481+
query_params: Query<params::OptionalProjectSelector>,
1482+
path_params: Path<params::AntiAffinityGroupPath>,
1483+
) -> Result<HttpResponseDeleted, HttpError>;
1484+
12601485
// Certificates
12611486

12621487
/// List certificates for external endpoints

0 commit comments

Comments
 (0)