Skip to content

Commit 817e24d

Browse files
committed
rebase on main
Created using spr 1.3.6-beta.1
2 parents 8b91e26 + c06ac52 commit 817e24d

9 files changed

Lines changed: 435 additions & 42 deletions

File tree

nexus/db-model/src/external_ip.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,18 @@ impl TryFrom<ExternalIp> for SourceNatConfigGeneric {
236236
}
237237
}
238238

239+
/// The resource an external IP is being allocated for.
240+
#[derive(Debug, Clone)]
241+
pub enum IpAllocationTarget {
242+
/// A silo-owned external IP: instance SNAT / ephemeral, probe ephemeral, or
243+
/// a floating IP. Allocation verifies the pool is still linked to this silo
244+
/// and assigned to silos.
245+
Silo { silo_id: Uuid },
246+
/// A system-service external IP, i.e., an Omicron zone. Allocation verifies
247+
/// the pool is still assigned to system services.
248+
SystemService,
249+
}
250+
239251
/// An incomplete external IP, used to store state required for issuing the
240252
/// database query that selects an available IP and stores the resulting record.
241253
#[derive(Debug, Clone)]
@@ -245,7 +257,7 @@ pub struct IncompleteExternalIp {
245257
description: Option<String>,
246258
time_created: DateTime<Utc>,
247259
kind: IpKind,
248-
is_service: bool,
260+
target: IpAllocationTarget,
249261
is_probe: bool,
250262
parent_id: Option<Uuid>,
251263
pool_id: Uuid,
@@ -262,6 +274,7 @@ impl IncompleteExternalIp {
262274
id: Uuid,
263275
instance_id: Uuid,
264276
pool_id: Uuid,
277+
silo_id: Uuid,
265278
) -> Self {
266279
let kind = IpKind::SNat;
267280
Self {
@@ -270,7 +283,7 @@ impl IncompleteExternalIp {
270283
description: None,
271284
time_created: Utc::now(),
272285
kind,
273-
is_service: false,
286+
target: IpAllocationTarget::Silo { silo_id },
274287
is_probe: false,
275288
parent_id: Some(instance_id),
276289
pool_id,
@@ -281,15 +294,15 @@ impl IncompleteExternalIp {
281294
}
282295
}
283296

284-
pub fn for_ephemeral(id: Uuid, pool_id: Uuid) -> Self {
297+
pub fn for_ephemeral(id: Uuid, pool_id: Uuid, silo_id: Uuid) -> Self {
285298
let kind = IpKind::Ephemeral;
286299
Self {
287300
id,
288301
name: None,
289302
description: None,
290303
time_created: Utc::now(),
291304
kind,
292-
is_service: false,
305+
target: IpAllocationTarget::Silo { silo_id },
293306
parent_id: None,
294307
is_probe: false,
295308
pool_id,
@@ -304,6 +317,7 @@ impl IncompleteExternalIp {
304317
id: Uuid,
305318
instance_id: Uuid,
306319
pool_id: Uuid,
320+
silo_id: Uuid,
307321
) -> Self {
308322
let kind = IpKind::Ephemeral;
309323
Self {
@@ -312,7 +326,7 @@ impl IncompleteExternalIp {
312326
description: None,
313327
time_created: Utc::now(),
314328
kind: IpKind::Ephemeral,
315-
is_service: false,
329+
target: IpAllocationTarget::Silo { silo_id },
316330
is_probe: true,
317331
parent_id: Some(instance_id),
318332
pool_id,
@@ -329,6 +343,7 @@ impl IncompleteExternalIp {
329343
description: &str,
330344
project_id: Uuid,
331345
pool_id: Uuid,
346+
silo_id: Uuid,
332347
) -> Self {
333348
let kind = IpKind::Floating;
334349
Self {
@@ -337,7 +352,7 @@ impl IncompleteExternalIp {
337352
description: Some(description.to_string()),
338353
time_created: Utc::now(),
339354
kind,
340-
is_service: false,
355+
target: IpAllocationTarget::Silo { silo_id },
341356
is_probe: false,
342357
parent_id: None,
343358
pool_id,
@@ -355,6 +370,7 @@ impl IncompleteExternalIp {
355370
project_id: Uuid,
356371
explicit_ip: IpAddr,
357372
pool_id: Uuid,
373+
silo_id: Uuid,
358374
) -> Self {
359375
let kind = IpKind::Floating;
360376
Self {
@@ -363,7 +379,7 @@ impl IncompleteExternalIp {
363379
description: Some(description.to_string()),
364380
time_created: Utc::now(),
365381
kind,
366-
is_service: false,
382+
target: IpAllocationTarget::Silo { silo_id },
367383
is_probe: false,
368384
parent_id: None,
369385
pool_id,
@@ -424,7 +440,7 @@ impl IncompleteExternalIp {
424440
description,
425441
time_created: Utc::now(),
426442
kind,
427-
is_service: true,
443+
target: IpAllocationTarget::SystemService,
428444
is_probe: false,
429445
parent_id: Some(zone_id.into_untyped_uuid()),
430446
pool_id,
@@ -455,8 +471,21 @@ impl IncompleteExternalIp {
455471
&self.kind
456472
}
457473

458-
pub fn is_service(&self) -> &bool {
459-
&self.is_service
474+
pub fn is_service(&self) -> bool {
475+
match self.target {
476+
IpAllocationTarget::Silo { .. } => false,
477+
IpAllocationTarget::SystemService => true,
478+
}
479+
}
480+
481+
/// The silo that owns this external IP, if it is a silo-owned IP.
482+
///
483+
/// Returns `None` for system-service IPs.
484+
pub fn silo_id(&self) -> Option<&Uuid> {
485+
match &self.target {
486+
IpAllocationTarget::Silo { silo_id } => Some(silo_id),
487+
IpAllocationTarget::SystemService => None,
488+
}
460489
}
461490

462491
pub fn is_probe(&self) -> &bool {

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

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use super::DataStore;
88
use super::SQL_BATCH_SIZE;
99
use crate::authz;
10+
use crate::authz::ApiResource;
1011
use crate::context::OpContext;
1112
use crate::db::collection_attach::AttachError;
1213
use crate::db::collection_attach::DatastoreAttachTarget;
@@ -52,6 +53,7 @@ use omicron_common::api::external::Error;
5253
use omicron_common::api::external::IdentityMetadataCreateParams;
5354
use omicron_common::api::external::ListResultVec;
5455
use omicron_common::api::external::LookupResult;
56+
use omicron_common::api::external::LookupType;
5557
use omicron_common::api::external::ResourceType;
5658
use omicron_common::api::external::UpdateResult;
5759
use omicron_common::api::external::http_pagination::PaginatedBy;
@@ -136,12 +138,14 @@ impl DataStore {
136138
instance_id: InstanceUuid,
137139
pool_id: Uuid,
138140
) -> CreateResult<ExternalIp> {
141+
let silo_id = opctx.authn.silo_required()?.id();
139142
let data = IncompleteExternalIp::for_instance_source_nat(
140143
ip_id,
141144
instance_id.into_untyped_uuid(),
142145
pool_id,
146+
silo_id,
143147
);
144-
self.allocate_external_ip(opctx, data).await
148+
self.allocate_external_ip(opctx, data, LookupType::ById(pool_id)).await
145149
}
146150

147151
/// Create an Ephemeral IP address for a probe.
@@ -161,12 +165,15 @@ impl DataStore {
161165
ip_version,
162166
)
163167
.await?;
168+
let silo_id = opctx.authn.silo_required()?.id();
169+
let pool_lookup = authz_pool.lookup_type().clone();
164170
let data = IncompleteExternalIp::for_ephemeral_probe(
165171
ip_id,
166172
probe_id,
167173
authz_pool.id(),
174+
silo_id,
168175
);
169-
self.allocate_external_ip(opctx, data).await
176+
self.allocate_external_ip(opctx, data, pool_lookup).await
170177
}
171178

172179
/// Create an Ephemeral IP address for an instance.
@@ -206,11 +213,17 @@ impl DataStore {
206213
)
207214
.await?;
208215

209-
let data = IncompleteExternalIp::for_ephemeral(ip_id, authz_pool.id());
216+
let silo_id = opctx.authn.silo_required()?.id();
217+
let pool_lookup = authz_pool.lookup_type().clone();
218+
let data = IncompleteExternalIp::for_ephemeral(
219+
ip_id,
220+
authz_pool.id(),
221+
silo_id,
222+
);
210223

211224
// We might not be able to acquire a new IP, but in the event of an
212225
// idempotent or double attach this failure is allowed.
213-
let temp_ip = self.allocate_external_ip(opctx, data).await;
226+
let temp_ip = self.allocate_external_ip(opctx, data, pool_lookup).await;
214227
if let Err(e) = temp_ip {
215228
// Use the pool's version for lookup when the request didn't
216229
// specify one. This handles the case where an explicit pool was
@@ -327,6 +340,7 @@ impl DataStore {
327340
"project_id" => %project_id,
328341
);
329342

343+
let silo_id = opctx.authn.silo_required()?.id();
330344
let data = if let Some(ip) = explicit_ip {
331345
IncompleteExternalIp::for_floating_explicit(
332346
ip_id,
@@ -335,6 +349,7 @@ impl DataStore {
335349
project_id,
336350
ip,
337351
authz_pool.id(),
352+
silo_id,
338353
)
339354
} else {
340355
IncompleteExternalIp::for_floating(
@@ -343,29 +358,39 @@ impl DataStore {
343358
&identity.description,
344359
project_id,
345360
authz_pool.id(),
361+
silo_id,
346362
)
347363
};
348364

349-
self.allocate_external_ip(opctx, data).await
365+
let pool_lookup = authz_pool.lookup_type().clone();
366+
self.allocate_external_ip(opctx, data, pool_lookup).await
350367
}
351368

352369
async fn allocate_external_ip(
353370
&self,
354371
opctx: &OpContext,
355372
data: IncompleteExternalIp,
373+
pool_lookup: LookupType,
356374
) -> CreateResult<ExternalIp> {
357375
let conn = self.pool_connection_authorized(opctx).await?;
358-
let ip = Self::allocate_external_ip_on_connection(&conn, data)
359-
.await
360-
.map_err(|err| err.into_public_ignore_retries())?;
376+
let ip =
377+
Self::allocate_external_ip_on_connection(&conn, data, pool_lookup)
378+
.await
379+
.map_err(|err| err.into_public_ignore_retries())?;
361380
Ok(ip)
362381
}
363382

364383
/// Variant of [Self::allocate_external_ip] which may be called from a
365384
/// transaction context.
385+
///
386+
/// `pool_lookup` describes how the pool being allocated from was looked up
387+
/// originally. This is used to generate a correct error message when the
388+
/// pool isn't found or is no longer linked, which depends on how the API
389+
/// request specifed the pool in the first place.
366390
pub(crate) async fn allocate_external_ip_on_connection(
367391
conn: &async_bb8_diesel::Connection<DbConnection>,
368392
data: IncompleteExternalIp,
393+
pool_lookup: LookupType,
369394
) -> Result<ExternalIp, TransactionError<Error>> {
370395
// Name needs to be cloned out here (if present) to give users a
371396
// sensible error message on name collision.
@@ -426,7 +451,10 @@ impl DataStore {
426451
))
427452
} else {
428453
TransactionError::CustomError(
429-
crate::db::queries::external_ip::from_diesel(e),
454+
crate::db::queries::external_ip::from_diesel(
455+
e,
456+
pool_lookup.clone(),
457+
),
430458
)
431459
}
432460
}
@@ -435,7 +463,10 @@ impl DataStore {
435463
return TransactionError::Database(e);
436464
}
437465
TransactionError::CustomError(
438-
crate::db::queries::external_ip::from_diesel(e),
466+
crate::db::queries::external_ip::from_diesel(
467+
e,
468+
pool_lookup.clone(),
469+
),
439470
)
440471
}
441472
}
@@ -460,7 +491,8 @@ impl DataStore {
460491
zone_id,
461492
zone_kind,
462493
);
463-
self.allocate_external_ip(opctx, data).await
494+
self.allocate_external_ip(opctx, data, LookupType::ById(pool.id()))
495+
.await
464496
}
465497

466498
/// Variant of [Self::external_ip_allocate_omicron_zone] which may be called
@@ -479,7 +511,12 @@ impl DataStore {
479511
zone_id,
480512
zone_kind,
481513
);
482-
Self::allocate_external_ip_on_connection(conn, data).await
514+
Self::allocate_external_ip_on_connection(
515+
conn,
516+
data,
517+
LookupType::ById(service_pool.id()),
518+
)
519+
.await
483520
}
484521

485522
/// List one page of all external IPs allocated to internal services

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -582,10 +582,10 @@ impl DataStore {
582582

583583
let (authz_pool, pool_version) = match pool {
584584
Some(authz_pool) => {
585-
self.ip_pool_fetch_link(opctx, authz_pool.id())
586-
.await
587-
.map_err(|_| authz_pool.not_found())?;
588-
585+
// We intentionally skip checking the silo / pool link here.
586+
// That's now done in the `NextExternalIp` allocation query
587+
// itself, which has a CTE arm that ensures the link exists. We
588+
// do still fetch the pool to validate its type and IP version.
589589
let pool_record = {
590590
ip_pool::table
591591
.filter(ip_pool::id.eq(authz_pool.id()))

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

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -667,18 +667,22 @@ impl DataStore {
667667
zone_config.id,
668668
zone_config.zone_type.kind(),
669669
);
670-
Self::allocate_external_ip_on_connection(conn, db_ip).await.map_err(
671-
|err| {
672-
error!(
673-
log,
674-
"Initializing Rack: Failed to allocate \
675-
IP address for {}",
676-
zone_report_str;
677-
"err" => %err,
678-
);
679-
RackInitError::AddingIp(err.into_public_ignore_retries())
680-
},
681-
)?;
670+
Self::allocate_external_ip_on_connection(
671+
conn,
672+
db_ip,
673+
LookupType::ById(service_pool.db_pool.id()),
674+
)
675+
.await
676+
.map_err(|err| {
677+
error!(
678+
log,
679+
"Initializing Rack: Failed to allocate \
680+
IP address for {}",
681+
zone_report_str;
682+
"err" => %err,
683+
);
684+
RackInitError::AddingIp(err.into_public_ignore_retries())
685+
})?;
682686

683687
self.create_network_interface_raw_conn(conn, db_nic)
684688
.await

0 commit comments

Comments
 (0)