Skip to content

Commit 0d8f2e3

Browse files
authored
Insert VNIs via a next-item query (#1107)
* Better selection of Geneve VNI for a new VPC - Adds a `NextItem` query for the Geneve Virtual Network Identifier (VNI) for a new VPC. Previously, this was selected randomly and conflicts resulted in a 500. The new query starts from a random VNI, and selects the first available. - Makes the generic `NextItem` queries "wrapping". The previous implementation had a subtle problem, where the size of the search space was depdendent on the base / starting value, since we just searched from the base to the provided maximum value. This new implementation accepts a maximum _leftward_ or negative shift from the base value as well, and does a wrapping search from base, to the maximum, to the minimum, and back to the base. - Adds a call to `usdt::register_probes()` inside the test utility that sets up a test CRDB instance. This ensures that the `diesel-dtrace` probes are registered for tests. * Add test for the wrapping next-item query * Add initial slice of reserved VNIs * Maximum VNI value off by one
1 parent fb209f9 commit 0d8f2e3

File tree

12 files changed

+668
-139
lines changed

12 files changed

+668
-139
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/src/api/external/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,12 +1745,16 @@ impl JsonSchema for MacAddr {
17451745
pub struct Vni(u32);
17461746

17471747
impl Vni {
1748-
const MAX_VNI: u32 = 1 << 24;
1748+
/// Virtual Network Identifiers are constrained to be 24-bit values.
1749+
pub const MAX_VNI: u32 = 0xFF_FFFF;
1750+
1751+
/// Oxide reserves a slice of initial VNIs for its own use.
1752+
pub const MIN_GUEST_VNI: u32 = 1024;
17491753

17501754
/// Create a new random VNI.
17511755
pub fn random() -> Self {
17521756
use rand::Rng;
1753-
Self(rand::thread_rng().gen_range(0..=Self::MAX_VNI))
1757+
Self(rand::thread_rng().gen_range(Self::MIN_GUEST_VNI..=Self::MAX_VNI))
17541758
}
17551759
}
17561760

nexus/src/app/vpc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ impl super::Nexus {
5454
// which may not even happen here. Creating the vpc, its system router,
5555
// and that routers default route should all be a part of the same
5656
// transaction.
57-
let vpc = db::model::Vpc::new(
57+
let vpc = db::model::IncompleteVpc::new(
5858
vpc_id,
5959
authz_project.id(),
6060
system_router_id,

nexus/src/db/datastore.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ use crate::db::fixed_data::role_builtin::BUILTIN_ROLES;
3838
use crate::db::fixed_data::silo::DEFAULT_SILO;
3939
use crate::db::lookup::LookupPath;
4040
use crate::db::model::DatabaseString;
41+
use crate::db::model::IncompleteVpc;
42+
use crate::db::model::Vpc;
4143
use crate::db::queries::network_interface::InsertNetworkInterfaceQuery;
4244
use crate::db::queries::network_interface::NetworkInterfaceError;
45+
use crate::db::queries::vpc::InsertVpcQuery;
4346
use crate::db::queries::vpc_subnet::FilterConflictingVpcSubnetRangesQuery;
4447
use crate::db::queries::vpc_subnet::SubnetError;
4548
use crate::db::{
@@ -52,7 +55,7 @@ use crate::db::{
5255
OrganizationUpdate, OximeterInfo, ProducerEndpoint, Project,
5356
ProjectUpdate, Region, RoleAssignment, RoleBuiltin, RouterRoute,
5457
RouterRouteUpdate, Silo, SiloUser, Sled, SshKey,
55-
UpdateAvailableArtifact, UserBuiltin, Volume, Vpc, VpcFirewallRule,
58+
UpdateAvailableArtifact, UserBuiltin, Volume, VpcFirewallRule,
5659
VpcRouter, VpcRouterUpdate, VpcSubnet, VpcSubnetUpdate, VpcUpdate,
5760
Zpool,
5861
},
@@ -2019,17 +2022,21 @@ impl DataStore {
20192022
&self,
20202023
opctx: &OpContext,
20212024
authz_project: &authz::Project,
2022-
vpc: Vpc,
2025+
vpc: IncompleteVpc,
20232026
) -> Result<(authz::Vpc, Vpc), Error> {
20242027
use db::schema::vpc::dsl;
20252028

20262029
assert_eq!(authz_project.id(), vpc.project_id);
20272030
opctx.authorize(authz::Action::CreateChild, authz_project).await?;
20282031

20292032
// TODO-correctness Shouldn't this use "insert_resource"?
2030-
let name = vpc.name().clone();
2033+
//
2034+
// Note that to do so requires adding an `rcgen` column to the project
2035+
// table.
2036+
let name = vpc.identity.name.clone();
2037+
let query = InsertVpcQuery::new(vpc);
20312038
let vpc = diesel::insert_into(dsl::vpc)
2032-
.values(vpc)
2039+
.values(query)
20332040
.returning(Vpc::as_returning())
20342041
.get_result_async(self.pool())
20352042
.await

nexus/src/db/model/vpc.rs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::defaults;
1010
use crate::external_api::params;
1111
use chrono::{DateTime, Utc};
1212
use db_macros::Resource;
13+
use ipnetwork::IpNetwork;
1314
use omicron_common::api::external;
1415
use uuid::Uuid;
1516

@@ -30,28 +31,45 @@ pub struct Vpc {
3031
pub firewall_gen: Generation,
3132
}
3233

33-
impl Vpc {
34+
/// An `IncompleteVpc` is a candidate VPC, where some of the values may be
35+
/// modified and returned as part of the query inserting it into the database.
36+
/// In particular, the requested VNI may not actually be available, in which
37+
/// case the database will select an available one (if it exists).
38+
#[derive(Clone, Debug)]
39+
pub struct IncompleteVpc {
40+
pub identity: VpcIdentity,
41+
pub project_id: Uuid,
42+
pub system_router_id: Uuid,
43+
pub vni: Vni,
44+
pub ipv6_prefix: IpNetwork,
45+
pub dns_name: Name,
46+
pub firewall_gen: Generation,
47+
}
48+
49+
impl IncompleteVpc {
3450
pub fn new(
3551
vpc_id: Uuid,
3652
project_id: Uuid,
3753
system_router_id: Uuid,
3854
params: params::VpcCreate,
3955
) -> Result<Self, external::Error> {
4056
let identity = VpcIdentity::new(vpc_id, params.identity);
41-
let ipv6_prefix = match params.ipv6_prefix {
42-
None => defaults::random_vpc_ipv6_prefix(),
43-
Some(prefix) => {
44-
if prefix.is_vpc_prefix() {
45-
Ok(prefix)
46-
} else {
47-
Err(external::Error::invalid_request(
48-
"VPC IPv6 address prefixes must be in the
57+
let ipv6_prefix = IpNetwork::from(
58+
match params.ipv6_prefix {
59+
None => defaults::random_vpc_ipv6_prefix(),
60+
Some(prefix) => {
61+
if prefix.is_vpc_prefix() {
62+
Ok(prefix)
63+
} else {
64+
Err(external::Error::invalid_request(
65+
"VPC IPv6 address prefixes must be in the \
4966
Unique Local Address range `fd00::/48` (RFD 4193)",
50-
))
67+
))
68+
}
5169
}
52-
}
53-
}?
54-
.into();
70+
}?
71+
.0,
72+
);
5573
Ok(Self {
5674
identity,
5775
project_id,

nexus/src/db/queries/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
#[macro_use]
99
mod next_item;
1010
pub mod network_interface;
11-
pub mod vni;
11+
pub mod vpc;
1212
pub mod vpc_subnet;

nexus/src/db/queries/network_interface.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use crate::app::MAX_NICS_PER_INSTANCE;
88
use crate::db;
99
use crate::db::model::IncompleteNetworkInterface;
10+
use crate::db::queries::next_item::DefaultShiftGenerator;
1011
use crate::db::queries::next_item::NextItem;
1112
use crate::defaults::NUM_INITIAL_RESERVED_IP_ADDRESSES;
1213
use chrono::DateTime;
@@ -331,8 +332,10 @@ impl NextGuestIpv4Address {
331332
pub fn new(subnet: Ipv4Network, subnet_id: Uuid) -> Self {
332333
let subnet = IpNetwork::from(subnet);
333334
let net = IpNetwork::from(first_available_address(&subnet));
334-
let max_offset = last_address_offset(&subnet);
335-
Self { inner: NextItem::new_scoped(net, subnet_id, max_offset) }
335+
let max_shift = i64::from(last_address_offset(&subnet));
336+
let generator =
337+
DefaultShiftGenerator { base: net, max_shift, min_shift: 0 };
338+
Self { inner: NextItem::new_scoped(generator, subnet_id) }
336339
}
337340
}
338341

@@ -387,9 +390,12 @@ pub struct NextNicSlot {
387390

388391
impl NextNicSlot {
389392
pub fn new(instance_id: Uuid) -> Self {
390-
Self {
391-
inner: NextItem::new_scoped(0, instance_id, MAX_NICS_PER_INSTANCE),
392-
}
393+
let generator = DefaultShiftGenerator {
394+
base: 0,
395+
max_shift: i64::from(MAX_NICS_PER_INSTANCE),
396+
min_shift: 0,
397+
};
398+
Self { inner: NextItem::new_scoped(generator, instance_id) }
393399
}
394400
}
395401

0 commit comments

Comments
 (0)