Skip to content

Commit f47aefc

Browse files
authored
[nexus] Remove project_id, rack_id from IP pools (#2056)
## Before this PR - IP Pools could exist in at most one project. IP allocation during instance creation occurred by [either by requesting an IP pool belonging to a project, or by "just looking for any unreserved IP Pool"](https://github.com/oxidecomputer/omicron/blob/79765a4e3b39a29bc9940c0e4a49c4364fbcc9e3/nexus/src/db/queries/external_ip.rs#L186-L212). As discussed in #2055 , our intention is for IP pools to be used across multiple projects, and for projects to be able to use multiple IP pools. - "Service" IP pools were indexed by rack ID, though (as documented in #1276 ), they should probably be accessed by AZ instead. ## This PR - Adds a default IP pool named `default`, which is used for address allocation unless a more specific IP pool is provided - Removes "project ID" from IP pools (and external IP addresses) - Removes "rack ID" from IP pool API and DB representation ## In the future - This PR doesn't provide the many-to-many connection between projects and IP pools that we eventually want, where projects can be configured to use different IP pools for different purposes. However, by removing the not-quite-accurate relationship that an IP pool must belong to a *single* project, the API moves closer towards this direction. - We probably should access the `service_ip_pool` API with the AZ UUID used for the query, but since AZs don't exist in the API yet, this has been omitted. Part of #2055
1 parent 3593342 commit f47aefc

File tree

32 files changed

+330
-913
lines changed

32 files changed

+330
-913
lines changed

common/src/sql/dbinit.sql

Lines changed: 3 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,16 +1112,9 @@ CREATE TABLE omicron.public.ip_pool (
11121112
time_modified TIMESTAMPTZ NOT NULL,
11131113
time_deleted TIMESTAMPTZ,
11141114

1115-
/* Optional ID of the project for which this pool is reserved. */
1116-
project_id UUID,
11171115

1118-
/*
1119-
* Optional rack ID, indicating this is a reserved pool for internal
1120-
* services on a specific rack.
1121-
* TODO(https://github.com/oxidecomputer/omicron/issues/1276): This
1122-
* should probably point to an AZ or fleet, not a rack.
1123-
*/
1124-
rack_id UUID,
1116+
/* Identifies if the IP Pool is dedicated to Control Plane services */
1117+
internal BOOL NOT NULL,
11251118

11261119
/* The collection's child-resource generation number */
11271120
rcgen INT8 NOT NULL
@@ -1135,15 +1128,6 @@ CREATE UNIQUE INDEX ON omicron.public.ip_pool (
11351128
) WHERE
11361129
time_deleted IS NULL;
11371130

1138-
/*
1139-
* Index ensuring uniqueness of IP pools by rack ID
1140-
*/
1141-
CREATE UNIQUE INDEX ON omicron.public.ip_pool (
1142-
rack_id
1143-
) WHERE
1144-
rack_id IS NOT NULL AND
1145-
time_deleted IS NULL;
1146-
11471131
/*
11481132
* IP Pools are made up of a set of IP ranges, which are start/stop addresses.
11491133
* Note that these need not be CIDR blocks or well-behaved subnets with a
@@ -1158,20 +1142,11 @@ CREATE TABLE omicron.public.ip_pool_range (
11581142
/* The range is inclusive of the last address. */
11591143
last_address INET NOT NULL,
11601144
ip_pool_id UUID NOT NULL,
1161-
/* Optional ID of the project for which this range is reserved.
1162-
*
1163-
* NOTE: This denormalizes the tables a bit, since the project_id is
1164-
* duplicated here and in the parent `ip_pool` table. We're allowing this
1165-
* for now, since it reduces the complexity of the already-bad IP allocation
1166-
* query, but we may want to revisit that, and JOIN with the parent table
1167-
* instead.
1168-
*/
1169-
project_id UUID,
11701145
/* Tracks child resources, IP addresses allocated out of this range. */
11711146
rcgen INT8 NOT NULL
11721147
);
11731148

1174-
/*
1149+
/*
11751150
* These help Nexus enforce that the ranges within an IP Pool do not overlap
11761151
* with any other ranges. See `nexus/src/db/queries/ip_pool.rs` for the actual
11771152
* query which does that.
@@ -1187,14 +1162,6 @@ CREATE UNIQUE INDEX ON omicron.public.ip_pool_range (
11871162
STORING (first_address)
11881163
WHERE time_deleted IS NULL;
11891164

1190-
/*
1191-
* Index supporting allocation of IPs out of a Pool reserved for a project.
1192-
*/
1193-
CREATE INDEX ON omicron.public.ip_pool_range (
1194-
project_id
1195-
) WHERE
1196-
time_deleted IS NULL;
1197-
11981165

11991166
/* The kind of external IP address. */
12001167
CREATE TYPE omicron.public.ip_kind AS ENUM (
@@ -1245,9 +1212,6 @@ CREATE TABLE omicron.public.external_ip (
12451212
/* FK to the `ip_pool_range` table. */
12461213
ip_pool_range_id UUID NOT NULL,
12471214

1248-
/* FK to the `project` table. */
1249-
project_id UUID,
1250-
12511215
/* FK to the `instance` table. See the constraints below. */
12521216
instance_id UUID,
12531217

@@ -1263,15 +1227,6 @@ CREATE TABLE omicron.public.external_ip (
12631227
/* The last port in the allowed range, also inclusive. */
12641228
last_port INT4 NOT NULL,
12651229

1266-
/*
1267-
* The project can only be NULL for service IPs.
1268-
* Additionally, the project MUST be NULL for service IPs.
1269-
*/
1270-
CONSTRAINT null_project CHECK(
1271-
(kind != 'service' AND project_id IS NOT NULL) OR
1272-
(kind = 'service' AND project_id IS NULL)
1273-
),
1274-
12751230
/* The name must be non-NULL iff this is a floating IP. */
12761231
CONSTRAINT null_fip_name CHECK (
12771232
(kind != 'floating' AND name IS NULL) OR

end-to-end-tests/src/bin/bootstrap.rs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use end_to_end_tests::helpers::ctx::{build_client, Context};
33
use end_to_end_tests::helpers::{generate_name, get_system_ip_pool};
44
use omicron_test_utils::dev::poll::{wait_for_condition, CondCheckError};
55
use oxide_client::types::{
6-
ByteCount, DiskCreate, DiskSource, IpPoolCreate, IpRange, Ipv4Range,
6+
ByteCount, DiskCreate, DiskSource, IpRange, Ipv4Range,
77
};
88
use oxide_client::{ClientDisksExt, ClientOrganizationsExt, ClientSystemExt};
99
use std::time::Duration;
@@ -30,21 +30,9 @@ async fn main() -> Result<()> {
3030
// ===== CREATE IP POOL ===== //
3131
eprintln!("creating IP pool...");
3232
let (first, last) = get_system_ip_pool()?;
33-
let pool_name = client
34-
.ip_pool_create()
35-
.body(IpPoolCreate {
36-
name: generate_name("ip-pool")?,
37-
description: String::new(),
38-
organization: None,
39-
project: None,
40-
})
41-
.send()
42-
.await?
43-
.name
44-
.clone();
4533
client
4634
.ip_pool_range_add()
47-
.pool_name(pool_name)
35+
.pool_name("default")
4836
.body(IpRange::V4(Ipv4Range { first, last }))
4937
.send()
5038
.await?;

nexus/db-model/src/external_ip.rs

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ pub struct ExternalIp {
5858
pub time_deleted: Option<DateTime<Utc>>,
5959
pub ip_pool_id: Uuid,
6060
pub ip_pool_range_id: Uuid,
61-
pub project_id: Option<Uuid>,
6261
// This is Some(_) for:
6362
// - all instance SNAT IPs
6463
// - all ephemeral IPs
@@ -80,18 +79,6 @@ impl From<ExternalIp> for sled_agent_client::types::SourceNatConfig {
8079
}
8180
}
8281

83-
/// Describes where the IP candidates for allocation come from: either
84-
/// from an IP pool, or from a project.
85-
///
86-
/// This ensures that a source is always specified, and a caller cannot
87-
/// request an external IP allocation without providing at least one of
88-
/// these options.
89-
#[derive(Debug, Clone, Copy)]
90-
pub enum IpSource {
91-
Instance { project_id: Uuid, pool_id: Option<Uuid> },
92-
Service { pool_id: Uuid },
93-
}
94-
9582
/// An incomplete external IP, used to store state required for issuing the
9683
/// database query that selects an available IP and stores the resulting record.
9784
#[derive(Debug, Clone)]
@@ -102,15 +89,14 @@ pub struct IncompleteExternalIp {
10289
time_created: DateTime<Utc>,
10390
kind: IpKind,
10491
instance_id: Option<Uuid>,
105-
source: IpSource,
92+
pool_id: Uuid,
10693
}
10794

10895
impl IncompleteExternalIp {
10996
pub fn for_instance_source_nat(
11097
id: Uuid,
111-
project_id: Uuid,
11298
instance_id: Uuid,
113-
pool_id: Option<Uuid>,
99+
pool_id: Uuid,
114100
) -> Self {
115101
Self {
116102
id,
@@ -119,33 +105,27 @@ impl IncompleteExternalIp {
119105
time_created: Utc::now(),
120106
kind: IpKind::SNat,
121107
instance_id: Some(instance_id),
122-
source: IpSource::Instance { project_id, pool_id },
108+
pool_id,
123109
}
124110
}
125111

126-
pub fn for_ephemeral(
127-
id: Uuid,
128-
project_id: Uuid,
129-
instance_id: Uuid,
130-
pool_id: Option<Uuid>,
131-
) -> Self {
112+
pub fn for_ephemeral(id: Uuid, instance_id: Uuid, pool_id: Uuid) -> Self {
132113
Self {
133114
id,
134115
name: None,
135116
description: None,
136117
time_created: Utc::now(),
137118
kind: IpKind::Ephemeral,
138119
instance_id: Some(instance_id),
139-
source: IpSource::Instance { project_id, pool_id },
120+
pool_id,
140121
}
141122
}
142123

143124
pub fn for_floating(
144125
id: Uuid,
145126
name: &Name,
146127
description: &str,
147-
project_id: Uuid,
148-
pool_id: Option<Uuid>,
128+
pool_id: Uuid,
149129
) -> Self {
150130
Self {
151131
id,
@@ -154,7 +134,7 @@ impl IncompleteExternalIp {
154134
time_created: Utc::now(),
155135
kind: IpKind::Floating,
156136
instance_id: None,
157-
source: IpSource::Instance { project_id, pool_id },
137+
pool_id,
158138
}
159139
}
160140

@@ -166,7 +146,7 @@ impl IncompleteExternalIp {
166146
time_created: Utc::now(),
167147
kind: IpKind::Service,
168148
instance_id: None,
169-
source: IpSource::Service { pool_id },
149+
pool_id,
170150
}
171151
}
172152

@@ -194,8 +174,8 @@ impl IncompleteExternalIp {
194174
&self.instance_id
195175
}
196176

197-
pub fn source(&self) -> &IpSource {
198-
&self.source
177+
pub fn pool_id(&self) -> &Uuid {
178+
&self.pool_id
199179
}
200180
}
201181

nexus/db-model/src/ip_pool.rs

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,11 @@ pub struct IpPool {
2828
#[diesel(embed)]
2929
pub identity: IpPoolIdentity,
3030

31-
/// An optional ID of the project for which this pool is reserved.
32-
pub project_id: Option<Uuid>,
33-
34-
/// An optional ID of the rack for which this pool is reserved.
35-
// TODO(https://github.com/oxidecomputer/omicron/issues/1276): This
36-
// should probably point to an AZ or fleet, not a rack.
37-
pub rack_id: Option<Uuid>,
31+
/// If true, identifies that this IP pool is dedicated to "Control-Plane
32+
/// Services", such as Nexus.
33+
///
34+
/// Otherwise, this IP pool is intended for usage by customer VMs.
35+
pub internal: bool,
3836

3937
/// Child resource generation number, for optimistic concurrency control of
4038
/// the contained ranges.
@@ -44,24 +42,22 @@ pub struct IpPool {
4442
impl IpPool {
4543
pub fn new(
4644
pool_identity: &external::IdentityMetadataCreateParams,
47-
project_id: Option<Uuid>,
48-
rack_id: Option<Uuid>,
45+
internal: bool,
4946
) -> Self {
5047
Self {
5148
identity: IpPoolIdentity::new(
5249
Uuid::new_v4(),
5350
pool_identity.clone(),
5451
),
55-
project_id,
56-
rack_id,
52+
internal,
5753
rcgen: 0,
5854
}
5955
}
6056
}
6157

6258
impl From<IpPool> for views::IpPool {
6359
fn from(pool: IpPool) -> Self {
64-
Self { identity: pool.identity(), project_id: pool.project_id }
60+
Self { identity: pool.identity() }
6561
}
6662
}
6763

@@ -98,20 +94,13 @@ pub struct IpPoolRange {
9894
pub last_address: IpNetwork,
9995
/// Foreign-key to the `ip_pool` table with the parent pool for this range
10096
pub ip_pool_id: Uuid,
101-
/// Foreign-key to the `project` table, with the Project to which this range
102-
/// is restricted, if any (derived from the `ip_pool` table).
103-
pub project_id: Option<Uuid>,
10497
/// The child resource generation number, tracking IP addresses allocated or
10598
/// used from this range.
10699
pub rcgen: i64,
107100
}
108101

109102
impl IpPoolRange {
110-
pub fn new(
111-
range: &IpRange,
112-
ip_pool_id: Uuid,
113-
project_id: Option<Uuid>,
114-
) -> Self {
103+
pub fn new(range: &IpRange, ip_pool_id: Uuid) -> Self {
115104
let now = Utc::now();
116105
let first_address = range.first_address();
117106
let last_address = range.last_address();
@@ -129,7 +118,6 @@ impl IpPoolRange {
129118
first_address: IpNetwork::from(range.first_address()),
130119
last_address: IpNetwork::from(range.last_address()),
131120
ip_pool_id,
132-
project_id,
133121
rcgen: 0,
134122
}
135123
}

nexus/db-model/src/project.rs

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,9 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
use super::{
6-
Disk, ExternalIp, Generation, Image, Instance, IpPool, Name, Snapshot, Vpc,
7-
};
5+
use super::{Disk, Generation, Image, Instance, Name, Snapshot, Vpc};
86
use crate::collection::DatastoreCollectionConfig;
9-
use crate::schema::{
10-
disk, external_ip, image, instance, ip_pool, project, snapshot, vpc,
11-
};
7+
use crate::schema::{disk, image, instance, project, snapshot, vpc};
128
use chrono::{DateTime, Utc};
139
use db_macros::Resource;
1410
use nexus_types::external_api::params;
@@ -83,26 +79,6 @@ impl DatastoreCollectionConfig<Vpc> for Project {
8379
type CollectionIdColumn = vpc::dsl::project_id;
8480
}
8581

86-
// NOTE: "IpPoolRange" also contains a reference to "project_id", but
87-
// ranges should only exist within IP Pools.
88-
impl DatastoreCollectionConfig<IpPool> for Project {
89-
type CollectionId = Uuid;
90-
type GenerationNumberColumn = project::dsl::rcgen;
91-
type CollectionTimeDeletedColumn = project::dsl::time_deleted;
92-
type CollectionIdColumn = ip_pool::dsl::project_id;
93-
}
94-
95-
// TODO(https://github.com/oxidecomputer/omicron/issues/1482): Not yet utilized,
96-
// but needed for project deletion safety.
97-
// TODO(https://github.com/oxidecomputer/omicron/issues/1334): Cannot be
98-
// utilized until floating IPs are implemented.
99-
impl DatastoreCollectionConfig<ExternalIp> for Project {
100-
type CollectionId = Uuid;
101-
type GenerationNumberColumn = project::dsl::rcgen;
102-
type CollectionTimeDeletedColumn = project::dsl::time_deleted;
103-
type CollectionIdColumn = external_ip::dsl::project_id;
104-
}
105-
10682
/// Describes a set of updates for the [`Project`] model.
10783
#[derive(AsChangeset)]
10884
#[diesel(table_name = project)]

nexus/db-model/src/schema.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,7 @@ table! {
149149
time_created -> Timestamptz,
150150
time_modified -> Timestamptz,
151151
time_deleted -> Nullable<Timestamptz>,
152-
project_id -> Nullable<Uuid>,
153-
rack_id -> Nullable<Uuid>,
152+
internal -> Bool,
154153
rcgen -> Int8,
155154
}
156155
}
@@ -164,7 +163,6 @@ table! {
164163
first_address -> Inet,
165164
last_address -> Inet,
166165
ip_pool_id -> Uuid,
167-
project_id -> Nullable<Uuid>,
168166
rcgen -> Int8,
169167
}
170168
}
@@ -179,7 +177,6 @@ table! {
179177
time_deleted -> Nullable<Timestamptz>,
180178
ip_pool_id -> Uuid,
181179
ip_pool_range_id -> Uuid,
182-
project_id -> Nullable<Uuid>,
183180
instance_id -> Nullable<Uuid>,
184181
kind -> crate::IpKindEnum,
185182
ip -> Inet,

0 commit comments

Comments
 (0)