Skip to content

Commit db702a2

Browse files
authored
Small IP pools fixes (#4007)
Followup to #3985, closes #4005. - Add `is_default` to IP pool response - Inline `ip_pools_fetch` into the one callsite and delete it (per discussion #3985 (comment)) - Other small test tweaks suggested by @luqmana
1 parent 01e730a commit db702a2

File tree

7 files changed

+47
-49
lines changed

7 files changed

+47
-49
lines changed

nexus/db-model/src/ip_pool.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ impl IpPool {
6666

6767
impl From<IpPool> for views::IpPool {
6868
fn from(pool: IpPool) -> Self {
69-
Self { identity: pool.identity(), silo_id: pool.silo_id }
69+
Self {
70+
identity: pool.identity(),
71+
silo_id: pool.silo_id,
72+
is_default: pool.is_default,
73+
}
7074
}
7175
}
7276

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

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
//! [`DataStore`] methods on [`ExternalIp`]s.
66
77
use super::DataStore;
8+
use crate::authz;
9+
use crate::authz::ApiResource;
810
use crate::context::OpContext;
911
use crate::db;
1012
use crate::db::error::public_error_from_diesel_pool;
1113
use crate::db::error::ErrorHandler;
14+
use crate::db::lookup::LookupPath;
1215
use crate::db::model::ExternalIp;
1316
use crate::db::model::IncompleteExternalIp;
1417
use crate::db::model::IpKind;
@@ -52,11 +55,29 @@ impl DataStore {
5255
instance_id: Uuid,
5356
pool_name: Option<Name>,
5457
) -> CreateResult<ExternalIp> {
55-
// If we have a pool name, look up the pool by name and return it
56-
// as long as its scopes don't conflict with the current scope.
57-
// Otherwise, not found.
5858
let pool = match pool_name {
59-
Some(name) => self.ip_pools_fetch(&opctx, &name).await?,
59+
Some(name) => {
60+
let (.., authz_pool, pool) = LookupPath::new(opctx, &self)
61+
.ip_pool_name(&name)
62+
// any authenticated user can CreateChild on an IP pool. this is
63+
// meant to represent allocating an IP
64+
.fetch_for(authz::Action::CreateChild)
65+
.await?;
66+
67+
// If the named pool conflicts with user's current scope, i.e.,
68+
// if it has a silo and it's different from the current silo,
69+
// then as far as IP allocation is concerned, that pool doesn't
70+
// exist. If the pool has no silo, it's fleet-scoped and can
71+
// always be used.
72+
let authz_silo_id = opctx.authn.silo_required()?.id();
73+
if let Some(pool_silo_id) = pool.silo_id {
74+
if pool_silo_id != authz_silo_id {
75+
return Err(authz_pool.not_found());
76+
}
77+
}
78+
79+
pool
80+
}
6081
// If no name given, use the default logic
6182
None => self.ip_pools_fetch_default(&opctx).await?,
6283
};

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

Lines changed: 6 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
77
use super::DataStore;
88
use crate::authz;
9-
use crate::authz::ApiResource;
109
use crate::context::OpContext;
1110
use crate::db;
1211
use crate::db::collection_insert::AsyncInsertError;
@@ -16,7 +15,6 @@ use crate::db::error::public_error_from_diesel_pool;
1615
use crate::db::error::ErrorHandler;
1716
use crate::db::fixed_data::silo::INTERNAL_SILO_ID;
1817
use crate::db::identity::Resource;
19-
use crate::db::lookup::LookupPath;
2018
use crate::db::model::IpPool;
2119
use crate::db::model::IpPoolRange;
2220
use crate::db::model::IpPoolUpdate;
@@ -111,32 +109,6 @@ impl DataStore {
111109
.map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server))
112110
}
113111

114-
/// Looks up an IP pool by name if it does not conflict with your current scope.
115-
pub(crate) async fn ip_pools_fetch(
116-
&self,
117-
opctx: &OpContext,
118-
name: &Name,
119-
) -> LookupResult<IpPool> {
120-
let authz_silo_id = opctx.authn.silo_required()?.id();
121-
122-
let (.., authz_pool, pool) = LookupPath::new(opctx, &self)
123-
.ip_pool_name(&name)
124-
// any authenticated user can CreateChild on an IP pool. this is
125-
// meant to represent allocating an IP
126-
.fetch_for(authz::Action::CreateChild)
127-
.await?;
128-
129-
// You can't look up a pool by name if it conflicts with your current
130-
// scope, i.e., if it has a silo it is different from your current silo
131-
if let Some(pool_silo_id) = pool.silo_id {
132-
if pool_silo_id != authz_silo_id {
133-
return Err(authz_pool.not_found());
134-
}
135-
}
136-
137-
Ok(pool)
138-
}
139-
140112
/// Looks up an IP pool intended for internal services.
141113
///
142114
/// This method may require an index by Availability Zone in the future.
@@ -465,7 +437,6 @@ mod test {
465437
use crate::db::datastore::datastore_test;
466438
use crate::db::model::IpPool;
467439
use assert_matches::assert_matches;
468-
use nexus_db_model::Name;
469440
use nexus_test_utils::db::test_setup_database;
470441
use nexus_types::identity::Resource;
471442
use omicron_common::api::external::{Error, IdentityMetadataCreateParams};
@@ -510,12 +481,13 @@ mod test {
510481
name: "non-default-for-silo".parse().unwrap(),
511482
description: "".to_string(),
512483
};
513-
let _ = datastore
484+
datastore
514485
.ip_pool_create(
515486
&opctx,
516487
IpPool::new(&identity, Some(silo_id), /*default= */ false),
517488
)
518-
.await;
489+
.await
490+
.expect("Failed to create silo non-default IP pool");
519491

520492
// because that one was not a default, when we ask for the silo default
521493
// pool, we still get the fleet default
@@ -530,9 +502,10 @@ mod test {
530502
name: "default-for-silo".parse().unwrap(),
531503
description: "".to_string(),
532504
};
533-
let _ = datastore
505+
datastore
534506
.ip_pool_create(&opctx, IpPool::new(&identity, Some(silo_id), true))
535-
.await;
507+
.await
508+
.expect("Failed to create silo default IP pool");
536509

537510
// now when we ask for the default pool, we get the one we just made
538511
let ip_pool = datastore
@@ -541,13 +514,6 @@ mod test {
541514
.expect("Failed to get silo's default IP pool");
542515
assert_eq!(ip_pool.name().as_str(), "default-for-silo");
543516

544-
// if we ask for the fleet default by name, we can still get that one
545-
let ip_pool = datastore
546-
.ip_pools_fetch(&opctx, &Name("default".parse().unwrap()))
547-
.await
548-
.expect("Failed to get fleet default IP pool");
549-
assert_eq!(ip_pool.id(), fleet_default_pool.id());
550-
551517
// and we can't create a second default pool for the silo
552518
let identity = IdentityMetadataCreateParams {
553519
name: "second-default-for-silo".parse().unwrap(),

nexus/tests/integration_tests/ip_pools.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ async fn test_ip_pool_basic_crud(cptestctx: &ControlPlaneTestContext) {
6161
assert_eq!(ip_pools.len(), 1, "Expected to see default IP pool");
6262

6363
assert_eq!(ip_pools[0].identity.name, "default",);
64+
assert_eq!(ip_pools[0].silo_id, None);
65+
assert!(ip_pools[0].is_default);
6466

6567
// Verify 404 if the pool doesn't exist yet, both for creating or deleting
6668
let error: HttpErrorResponseBody = NexusRequest::expect_failure(
@@ -288,9 +290,9 @@ async fn test_ip_pool_with_silo(cptestctx: &ControlPlaneTestContext) {
288290
};
289291
let created_pool = create_pool(client, &params).await;
290292
assert_eq!(created_pool.identity.name, "p0");
291-
assert!(created_pool.silo_id.is_some());
292293

293-
let silo_id = created_pool.silo_id.unwrap();
294+
let silo_id =
295+
created_pool.silo_id.expect("Expected pool to have a silo_id");
294296

295297
// now we'll create another IP pool using that silo ID
296298
let params = IpPoolCreate {

nexus/types/src/external_api/params.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ pub struct IpPoolCreate {
736736
/// silo can draw from that pool.
737737
pub silo: Option<NameOrId>,
738738

739-
/// Whether the IP pool is considered a default pool for its scope (fleet,
739+
/// Whether the IP pool is considered a default pool for its scope (fleet
740740
/// or silo). If a pool is marked default and is associated with a silo,
741741
/// instances created in that silo will draw IPs from that pool unless
742742
/// another pool is specified at instance create time.

nexus/types/src/external_api/views.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ pub struct IpPool {
245245
#[serde(flatten)]
246246
pub identity: IdentityMetadata,
247247
pub silo_id: Option<Uuid>,
248+
pub is_default: bool,
248249
}
249250

250251
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema)]

openapi/nexus.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10133,6 +10133,9 @@
1013310133
"type": "string",
1013410134
"format": "uuid"
1013510135
},
10136+
"is_default": {
10137+
"type": "boolean"
10138+
},
1013610139
"name": {
1013710140
"description": "unique, mutable, user-controlled identifier for each resource",
1013810141
"allOf": [
@@ -10160,6 +10163,7 @@
1016010163
"required": [
1016110164
"description",
1016210165
"id",
10166+
"is_default",
1016310167
"name",
1016410168
"time_created",
1016510169
"time_modified"
@@ -10173,7 +10177,7 @@
1017310177
"type": "string"
1017410178
},
1017510179
"is_default": {
10176-
"description": "Whether the IP pool is considered a default pool for its scope (fleet, or silo). If a pool is marked default and is associated with a silo, instances created in that silo will draw IPs from that pool unless another pool is specified at instance create time.",
10180+
"description": "Whether the IP pool is considered a default pool for its scope (fleet or silo). If a pool is marked default and is associated with a silo, instances created in that silo will draw IPs from that pool unless another pool is specified at instance create time.",
1017710181
"default": false,
1017810182
"type": "boolean"
1017910183
},

0 commit comments

Comments
 (0)