Skip to content

Commit 1121324

Browse files
[rfd-599] Implement skeleton subnet pools and external subnets APIs (#9634)
This introduces a skeleton `/v1/system/subnet-pools` and `/v1/external-subnets` REST API for allocating external subnets from subnet pools, and attaching them to instances, as described in [RFD 599](https://rfd.shared.oxide.computer/rfd/0599).
1 parent 915ab1e commit 1121324

File tree

15 files changed

+33350
-3
lines changed

15 files changed

+33350
-3
lines changed

common/src/api/external/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,7 @@ pub enum ResourceType {
953953
DeviceAccessToken,
954954
DeviceAuthRequest,
955955
Disk,
956+
ExternalSubnet,
956957
Fleet,
957958
FloatingIp,
958959
IdentityProvider,
@@ -996,6 +997,8 @@ pub enum ResourceType {
996997
Snapshot,
997998
SshKey,
998999
SupportBundle,
1000+
SubnetPool,
1001+
SubnetPoolMember,
9991002
Switch,
10001003
SwitchPort,
10011004
SwitchPortSettings,

nexus/external-api/output/nexus_tags.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ support_bundle_update PUT /experimental/v1/system/suppor
7171
support_bundle_view GET /experimental/v1/system/support-bundles/{bundle_id}
7272
timeseries_query POST /v1/timeseries/query
7373

74+
API operations found with tag "external-subnets"
75+
OPERATION ID METHOD URL PATH
76+
external_subnet_attach POST /v1/external-subnets/{external_subnet}/attach
77+
external_subnet_create POST /v1/external-subnets
78+
external_subnet_delete DELETE /v1/external-subnets/{external_subnet}
79+
external_subnet_detach POST /v1/external-subnets/{external_subnet}/detach
80+
external_subnet_list GET /v1/external-subnets
81+
external_subnet_update PUT /v1/external-subnets/{external_subnet}
82+
external_subnet_view GET /v1/external-subnets/{external_subnet}
83+
7484
API operations found with tag "floating-ips"
7585
OPERATION ID METHOD URL PATH
7686
floating_ip_attach POST /v1/floating-ips/{floating_ip}/attach
@@ -304,6 +314,22 @@ API operations found with tag "system/status"
304314
OPERATION ID METHOD URL PATH
305315
ping GET /v1/ping
306316

317+
API operations found with tag "system/subnet-pools"
318+
OPERATION ID METHOD URL PATH
319+
subnet_pool_create POST /v1/system/subnet-pools
320+
subnet_pool_delete DELETE /v1/system/subnet-pools/{pool}
321+
subnet_pool_list GET /v1/system/subnet-pools
322+
subnet_pool_member_add POST /v1/system/subnet-pools/{pool}/members/add
323+
subnet_pool_member_list GET /v1/system/subnet-pools/{pool}/members
324+
subnet_pool_member_remove POST /v1/system/subnet-pools/{pool}/members/remove
325+
subnet_pool_silo_link POST /v1/system/subnet-pools/{pool}/silos
326+
subnet_pool_silo_list GET /v1/system/subnet-pools/{pool}/silos
327+
subnet_pool_silo_unlink DELETE /v1/system/subnet-pools/{pool}/silos/{silo}
328+
subnet_pool_silo_update PUT /v1/system/subnet-pools/{pool}/silos/{silo}
329+
subnet_pool_update PUT /v1/system/subnet-pools/{pool}
330+
subnet_pool_utilization_view GET /v1/system/subnet-pools/{pool}/utilization
331+
subnet_pool_view GET /v1/system/subnet-pools/{pool}
332+
307333
API operations found with tag "system/update"
308334
OPERATION ID METHOD URL PATH
309335
system_update_repository_list GET /v1/system/update/repositories

nexus/external-api/src/lib.rs

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ api_versions!([
7474
// | date-based version should be at the top of the list.
7575
// v
7676
// (next_yyyymmddnn, IDENT),
77+
(2026011601, EXTERNAL_SUBNET_ATTACHMENT),
7778
(2026011600, RENAME_ADDRESS_SELECTOR_TO_ADDRESS_ALLOCATOR),
7879
(2026011501, AUDIT_LOG_CREDENTIAL_ID),
7980
(2026011500, AUDIT_LOG_AUTH_METHOD_ENUM),
@@ -187,6 +188,12 @@ const PUT_UPDATE_REPOSITORY_MAX_BYTES: usize = 4 * GIB;
187188
url = "http://docs.oxide.computer/api/floating-ips"
188189
}
189190
},
191+
"external-subnets" = {
192+
description = "External subnets that can be attached to instances.",
193+
external_docs = {
194+
url = "http://docs.oxide.computer/api/external-subnets"
195+
}
196+
},
190197
"images" = {
191198
description = "Images are read-only virtual disks that may be used to boot virtual machines.",
192199
external_docs = {
@@ -295,6 +302,15 @@ const PUT_UPDATE_REPOSITORY_MAX_BYTES: usize = 4 * GIB;
295302
url = "http://docs.oxide.computer/api/system-ip-pools"
296303
}
297304
},
305+
"system/subnet-pools" = {
306+
description = "Subnet pools are collections of IP subnets \
307+
that can be assigned to silos. When a pool is linked to \
308+
a silo, users in that silo can allocate external subnets \
309+
from the pool.",
310+
external_docs = {
311+
url = "http://docs.oxide.computer/api/system-subnet-pools"
312+
}
313+
},
298314
"system/networking" = {
299315
description = "This provides rack-level network configuration.",
300316
external_docs = {
@@ -1277,6 +1293,272 @@ pub trait NexusExternalApi {
12771293
range_params: TypedBody<shared::IpRange>,
12781294
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
12791295

1296+
// Subnet Pools
1297+
1298+
/// List subnet pools
1299+
#[endpoint {
1300+
method = GET,
1301+
path = "/v1/system/subnet-pools",
1302+
tags = ["system/subnet-pools"],
1303+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1304+
}]
1305+
async fn subnet_pool_list(
1306+
rqctx: RequestContext<Self::Context>,
1307+
query_params: Query<PaginatedByNameOrId>,
1308+
) -> Result<HttpResponseOk<ResultsPage<views::SubnetPool>>, HttpError>;
1309+
1310+
/// Create a subnet pool
1311+
#[endpoint {
1312+
method = POST,
1313+
path = "/v1/system/subnet-pools",
1314+
tags = ["system/subnet-pools"],
1315+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1316+
}]
1317+
async fn subnet_pool_create(
1318+
rqctx: RequestContext<Self::Context>,
1319+
pool_params: TypedBody<params::SubnetPoolCreate>,
1320+
) -> Result<HttpResponseCreated<views::SubnetPool>, HttpError>;
1321+
1322+
/// Fetch a subnet pool
1323+
#[endpoint {
1324+
method = GET,
1325+
path = "/v1/system/subnet-pools/{pool}",
1326+
tags = ["system/subnet-pools"],
1327+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1328+
}]
1329+
async fn subnet_pool_view(
1330+
rqctx: RequestContext<Self::Context>,
1331+
path_params: Path<params::SubnetPoolPath>,
1332+
) -> Result<HttpResponseOk<views::SubnetPool>, HttpError>;
1333+
1334+
/// Update a subnet pool
1335+
#[endpoint {
1336+
method = PUT,
1337+
path = "/v1/system/subnet-pools/{pool}",
1338+
tags = ["system/subnet-pools"],
1339+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1340+
}]
1341+
async fn subnet_pool_update(
1342+
rqctx: RequestContext<Self::Context>,
1343+
path_params: Path<params::SubnetPoolPath>,
1344+
updates: TypedBody<params::SubnetPoolUpdate>,
1345+
) -> Result<HttpResponseOk<views::SubnetPool>, HttpError>;
1346+
1347+
/// Delete a subnet pool
1348+
#[endpoint {
1349+
method = DELETE,
1350+
path = "/v1/system/subnet-pools/{pool}",
1351+
tags = ["system/subnet-pools"],
1352+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1353+
}]
1354+
async fn subnet_pool_delete(
1355+
rqctx: RequestContext<Self::Context>,
1356+
path_params: Path<params::SubnetPoolPath>,
1357+
) -> Result<HttpResponseDeleted, HttpError>;
1358+
1359+
/// List members in a subnet pool
1360+
#[endpoint {
1361+
method = GET,
1362+
path = "/v1/system/subnet-pools/{pool}/members",
1363+
tags = ["system/subnet-pools"],
1364+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1365+
}]
1366+
async fn subnet_pool_member_list(
1367+
rqctx: RequestContext<Self::Context>,
1368+
path_params: Path<params::SubnetPoolPath>,
1369+
query_params: Query<PaginatedByNameOrId>,
1370+
) -> Result<HttpResponseOk<ResultsPage<views::SubnetPoolMember>>, HttpError>;
1371+
1372+
/// Add a member to a subnet pool
1373+
#[endpoint {
1374+
method = POST,
1375+
path = "/v1/system/subnet-pools/{pool}/members/add",
1376+
tags = ["system/subnet-pools"],
1377+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1378+
}]
1379+
async fn subnet_pool_member_add(
1380+
rqctx: RequestContext<Self::Context>,
1381+
path_params: Path<params::SubnetPoolPath>,
1382+
subnet_params: TypedBody<params::SubnetPoolMemberAdd>,
1383+
) -> Result<HttpResponseCreated<views::SubnetPoolMember>, HttpError>;
1384+
1385+
/// Remove a member from a subnet pool
1386+
#[endpoint {
1387+
method = POST,
1388+
path = "/v1/system/subnet-pools/{pool}/members/remove",
1389+
tags = ["system/subnet-pools"],
1390+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1391+
}]
1392+
async fn subnet_pool_member_remove(
1393+
rqctx: RequestContext<Self::Context>,
1394+
path_params: Path<params::SubnetPoolPath>,
1395+
subnet_params: TypedBody<params::SubnetPoolMemberRemove>,
1396+
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
1397+
1398+
/// List silos linked to a subnet pool
1399+
#[endpoint {
1400+
method = GET,
1401+
path = "/v1/system/subnet-pools/{pool}/silos",
1402+
tags = ["system/subnet-pools"],
1403+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1404+
}]
1405+
async fn subnet_pool_silo_list(
1406+
rqctx: RequestContext<Self::Context>,
1407+
path_params: Path<params::SubnetPoolPath>,
1408+
query_params: Query<PaginatedById>,
1409+
) -> Result<HttpResponseOk<ResultsPage<views::SubnetPoolSiloLink>>, HttpError>;
1410+
1411+
/// Link a subnet pool to a silo
1412+
#[endpoint {
1413+
method = POST,
1414+
path = "/v1/system/subnet-pools/{pool}/silos",
1415+
tags = ["system/subnet-pools"],
1416+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1417+
}]
1418+
async fn subnet_pool_silo_link(
1419+
rqctx: RequestContext<Self::Context>,
1420+
path_params: Path<params::SubnetPoolPath>,
1421+
silo_link: TypedBody<params::SubnetPoolLinkSilo>,
1422+
) -> Result<HttpResponseCreated<views::SubnetPoolSiloLink>, HttpError>;
1423+
1424+
/// Update a subnet pool's link to a silo
1425+
#[endpoint {
1426+
method = PUT,
1427+
path = "/v1/system/subnet-pools/{pool}/silos/{silo}",
1428+
tags = ["system/subnet-pools"],
1429+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1430+
}]
1431+
async fn subnet_pool_silo_update(
1432+
rqctx: RequestContext<Self::Context>,
1433+
path_params: Path<params::SubnetPoolSiloPath>,
1434+
update: TypedBody<params::SubnetPoolSiloUpdate>,
1435+
) -> Result<HttpResponseOk<views::SubnetPoolSiloLink>, HttpError>;
1436+
1437+
/// Unlink a subnet pool from a silo
1438+
#[endpoint {
1439+
method = DELETE,
1440+
path = "/v1/system/subnet-pools/{pool}/silos/{silo}",
1441+
tags = ["system/subnet-pools"],
1442+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1443+
}]
1444+
async fn subnet_pool_silo_unlink(
1445+
rqctx: RequestContext<Self::Context>,
1446+
path_params: Path<params::SubnetPoolSiloPath>,
1447+
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
1448+
1449+
/// Fetch subnet pool utilization
1450+
#[endpoint {
1451+
method = GET,
1452+
path = "/v1/system/subnet-pools/{pool}/utilization",
1453+
tags = ["system/subnet-pools"],
1454+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1455+
}]
1456+
async fn subnet_pool_utilization_view(
1457+
rqctx: RequestContext<Self::Context>,
1458+
path_params: Path<params::SubnetPoolPath>,
1459+
) -> Result<HttpResponseOk<views::SubnetPoolUtilization>, HttpError>;
1460+
1461+
// External Subnets
1462+
1463+
/// List external subnets in a project
1464+
#[endpoint {
1465+
method = GET,
1466+
path = "/v1/external-subnets",
1467+
tags = ["external-subnets"],
1468+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1469+
}]
1470+
async fn external_subnet_list(
1471+
rqctx: RequestContext<Self::Context>,
1472+
query_params: Query<PaginatedByNameOrId<params::ProjectSelector>>,
1473+
) -> Result<HttpResponseOk<ResultsPage<views::ExternalSubnet>>, HttpError>;
1474+
1475+
/// Create an external subnet
1476+
#[endpoint {
1477+
method = POST,
1478+
path = "/v1/external-subnets",
1479+
tags = ["external-subnets"],
1480+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1481+
}]
1482+
async fn external_subnet_create(
1483+
rqctx: RequestContext<Self::Context>,
1484+
query_params: Query<params::ProjectSelector>,
1485+
subnet_params: TypedBody<params::ExternalSubnetCreate>,
1486+
) -> Result<HttpResponseCreated<views::ExternalSubnet>, HttpError>;
1487+
1488+
/// Fetch an external subnet
1489+
#[endpoint {
1490+
method = GET,
1491+
path = "/v1/external-subnets/{external_subnet}",
1492+
tags = ["external-subnets"],
1493+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1494+
}]
1495+
async fn external_subnet_view(
1496+
rqctx: RequestContext<Self::Context>,
1497+
path_params: Path<params::ExternalSubnetPath>,
1498+
query_params: Query<params::OptionalProjectSelector>,
1499+
) -> Result<HttpResponseOk<views::ExternalSubnet>, HttpError>;
1500+
1501+
/// Update an external subnet
1502+
#[endpoint {
1503+
method = PUT,
1504+
path = "/v1/external-subnets/{external_subnet}",
1505+
tags = ["external-subnets"],
1506+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1507+
}]
1508+
async fn external_subnet_update(
1509+
rqctx: RequestContext<Self::Context>,
1510+
path_params: Path<params::ExternalSubnetPath>,
1511+
query_params: Query<params::OptionalProjectSelector>,
1512+
subnet_params: TypedBody<params::ExternalSubnetUpdate>,
1513+
) -> Result<HttpResponseOk<views::ExternalSubnet>, HttpError>;
1514+
1515+
/// Delete an external subnet
1516+
#[endpoint {
1517+
method = DELETE,
1518+
path = "/v1/external-subnets/{external_subnet}",
1519+
tags = ["external-subnets"],
1520+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1521+
}]
1522+
async fn external_subnet_delete(
1523+
rqctx: RequestContext<Self::Context>,
1524+
path_params: Path<params::ExternalSubnetPath>,
1525+
query_params: Query<params::OptionalProjectSelector>,
1526+
) -> Result<HttpResponseDeleted, HttpError>;
1527+
1528+
/// Attach an external subnet to an instance
1529+
///
1530+
/// Begins an asynchronous attach operation. Returns the subnet with
1531+
/// `instance_id` set to the target instance. The attach completes
1532+
/// asynchronously; poll the subnet to check completion.
1533+
#[endpoint {
1534+
method = POST,
1535+
path = "/v1/external-subnets/{external_subnet}/attach",
1536+
tags = ["external-subnets"],
1537+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1538+
}]
1539+
async fn external_subnet_attach(
1540+
rqctx: RequestContext<Self::Context>,
1541+
path_params: Path<params::ExternalSubnetPath>,
1542+
query_params: Query<params::OptionalProjectSelector>,
1543+
attach_params: TypedBody<params::ExternalSubnetAttach>,
1544+
) -> Result<HttpResponseAccepted<views::ExternalSubnet>, HttpError>;
1545+
1546+
/// Detach an external subnet from an instance
1547+
///
1548+
/// Begins an asynchronous detach operation. Returns the subnet with
1549+
/// `instance_id` cleared. The detach completes asynchronously.
1550+
#[endpoint {
1551+
method = POST,
1552+
path = "/v1/external-subnets/{external_subnet}/detach",
1553+
tags = ["external-subnets"],
1554+
versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..,
1555+
}]
1556+
async fn external_subnet_detach(
1557+
rqctx: RequestContext<Self::Context>,
1558+
path_params: Path<params::ExternalSubnetPath>,
1559+
query_params: Query<params::OptionalProjectSelector>,
1560+
) -> Result<HttpResponseAccepted<views::ExternalSubnet>, HttpError>;
1561+
12801562
// Floating IP Addresses
12811563

12821564
/// List floating IPs

0 commit comments

Comments
 (0)