Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 81 additions & 8 deletions common/src/sql/dbinit.sql
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,9 @@ CREATE TABLE omicron.public.ip_pool (
time_modified TIMESTAMPTZ NOT NULL,
time_deleted TIMESTAMPTZ,

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

/* The collection's child-resource generation number */
rcgen INT8 NOT NULL
);
Expand All @@ -984,6 +987,15 @@ CREATE TABLE omicron.public.ip_pool_range (
/* The range is inclusive of the last address. */
last_address INET NOT NULL,
ip_pool_id UUID NOT NULL,
/* Optional ID of the project for which this range is reserved.
*
* NOTE: This denormalizes the tables a bit, since the project_id is
* duplicated here and in the parent `ip_pool` table. We're allowing this
* for now, since it reduces the complexity of the already-bad IP allocation
* query, but we may want to revisit that, and JOIN with the parent table
* instead.
*/
project_id UUID,
/* Tracks child resources, IP addresses allocated out of this range. */
rcgen INT8 NOT NULL
);
Expand All @@ -1005,14 +1017,48 @@ STORING (first_address)
WHERE time_deleted IS NULL;

/*
* External IP addresses used for instance source NAT.
* Index supporting allocation of IPs out of a Pool reserved for a project.
*/
CREATE INDEX ON omicron.public.ip_pool_range (
project_id
) WHERE
time_deleted IS NULL;


/* The kind of external IP address. */
CREATE TYPE omicron.public.ip_kind AS ENUM (
/* Automatic source NAT provided to all guests by default */
'snat',

/*
* An ephemeral IP is a fixed, known address whose lifetime is the same as
* the instance to which it is attached.
*/
'ephemeral',

/*
* A floating IP is an independent, named API resource. It is a fixed,
* known address that can be moved between instances. Its lifetime is not
* fixed to any instance.
*/
'floating'
);

/*
* External IP addresses used for guest instances
*
* NOTE: This currently stores only address and port information for the
* automatic source NAT supplied for all guest instances. It does not currently
* store information about ephemeral or floating IPs.
* This includes information about
*/
CREATE TABLE omicron.public.instance_external_ip (
/* Identity metadata */
id UUID PRIMARY KEY,

/* Name for floating IPs. See the constraints below. */
name STRING(128),

/* Description for floating IPs. See the constraints below. */
description STRING(512),

time_created TIMESTAMPTZ NOT NULL,
time_modified TIMESTAMPTZ NOT NULL,
time_deleted TIMESTAMPTZ,
Expand All @@ -1023,8 +1069,14 @@ CREATE TABLE omicron.public.instance_external_ip (
/* FK to the `ip_pool_range` table. */
ip_pool_range_id UUID NOT NULL,

/* FK to the `instance` table. */
instance_id UUID NOT NULL,
/* FK to the `project` table. */
project_id UUID NOT NULL,

/* FK to the `instance` table. See the constraints below. */
instance_id UUID,

/* The kind of external address, e.g., ephemeral. */
kind omicron.public.ip_kind NOT NULL,

/* The actual external IP address. */
ip INET NOT NULL,
Expand All @@ -1033,7 +1085,28 @@ CREATE TABLE omicron.public.instance_external_ip (
first_port INT4 NOT NULL,

/* The last port in the allowed range, also inclusive. */
last_port INT4 NOT NULL
last_port INT4 NOT NULL,

/* The name must be non-NULL iff this is a floating IP. */
CONSTRAINT null_fip_name CHECK (
(kind != 'floating' AND name IS NULL) OR
(kind = 'floating' AND name IS NOT NULL)
),

/* The description must be non-NULL iff this is a floating IP. */
CONSTRAINT null_fip_description CHECK (
(kind != 'floating' AND description IS NULL) OR
(kind = 'floating' AND description IS NOT NULL)
),

/*
* Only nullable if this is a floating IP, which may exist not attached
* to any instance.
*/
CONSTRAINT null_non_fip_instance_id CHECK (
(kind != 'floating' AND instance_id IS NOT NULL) OR
(kind = 'floating')
)
);

/*
Expand Down Expand Up @@ -1062,7 +1135,7 @@ CREATE UNIQUE INDEX ON omicron.public.instance_external_ip (
CREATE INDEX ON omicron.public.instance_external_ip (
instance_id
)
WHERE time_deleted IS NULL;
WHERE instance_id IS NOT NULL AND time_deleted IS NULL;

/*******************************************************************/

Expand Down
43 changes: 43 additions & 0 deletions nexus/src/app/external_ip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! External IP addresses for instances

use crate::authz;
use crate::context::OpContext;
use crate::db::lookup::LookupPath;
use crate::db::model::IpKind;
use crate::db::model::Name;
use crate::external_api::views::ExternalIp;
use omicron_common::api::external::ListResultVec;

impl super::Nexus {
pub async fn instance_list_external_ips(
&self,
opctx: &OpContext,
organization_name: &Name,
project_name: &Name,
instance_name: &Name,
) -> ListResultVec<ExternalIp> {
let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore)
.organization_name(organization_name)
.project_name(project_name)
.instance_name(instance_name)
.lookup_for(authz::Action::Read)
.await?;
Ok(self
.db_datastore
.instance_lookup_external_ips(opctx, authz_instance.id())
.await?
.into_iter()
.filter_map(|ip| {
if ip.kind == IpKind::SNat {
None
} else {
Some(ip.try_into().unwrap())
}
})
.collect::<Vec<_>>())
}
}
23 changes: 18 additions & 5 deletions nexus/src/app/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::context::OpContext;
use crate::db;
use crate::db::identity::Resource;
use crate::db::lookup::LookupPath;
use crate::db::model::IpKind;
use crate::db::model::Name;
use crate::db::queries::network_interface;
use crate::external_api::params;
Expand Down Expand Up @@ -216,12 +217,15 @@ impl super::Nexus {
self.db_datastore
.project_delete_instance(opctx, &authz_instance)
.await?;
// Ignore the count of addresses deleted, since we're not sure how many
// exist at this point.
self.db_datastore
.deallocate_instance_external_ip_by_instance_id(
opctx,
authz_instance.id(),
)
.await
.await?;
Ok(())
}

pub async fn project_instance_migrate(
Expand Down Expand Up @@ -488,11 +492,20 @@ impl super::Nexus {
.derive_guest_network_interface_info(&opctx, &authz_instance)
.await?;

let external_ip = self
// Collect the external IPs for the instance.
//
// For now, we take the Ephemeral IP, if it exists, or the SNAT IP if not.
// TODO-correctness: Handle multiple IP addresses
// TODO-correctness: Handle Floating IPs
let (ephemeral_ips, snat_ip): (Vec<_>, Vec<_>) = self
.db_datastore
.instance_lookup_external_ip(&opctx, authz_instance.id())
.await
.map(ExternalIp::from)?;
.instance_lookup_external_ips(&opctx, authz_instance.id())
.await?
.into_iter()
.partition(|ip| ip.kind == IpKind::Ephemeral);
let external_ip = ExternalIp::from(
ephemeral_ips.into_iter().chain(snat_ip).next().unwrap(),
);

// Gather the SSH public keys of the actor make the request so
// that they may be injected into the new image via cloud-init.
Expand Down
13 changes: 8 additions & 5 deletions nexus/src/app/ip_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,14 @@ impl super::Nexus {
pool_name: &Name,
range: &IpRange,
) -> UpdateResult<db::model::IpPoolRange> {
let (.., authz_pool) = LookupPath::new(opctx, &self.db_datastore)
.ip_pool_name(pool_name)
.lookup_for(authz::Action::Modify)
.await?;
self.db_datastore.ip_pool_add_range(opctx, &authz_pool, range).await
let (.., authz_pool, db_pool) =
LookupPath::new(opctx, &self.db_datastore)
.ip_pool_name(pool_name)
.fetch_for(authz::Action::Modify)
.await?;
self.db_datastore
.ip_pool_add_range(opctx, &authz_pool, &db_pool, range)
.await
}

pub async fn ip_pool_delete_range(
Expand Down
4 changes: 4 additions & 0 deletions nexus/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use uuid::Uuid;
// by resource.
mod device_auth;
mod disk;
mod external_ip;
mod iam;
mod image;
mod instance;
Expand Down Expand Up @@ -53,6 +54,9 @@ pub(crate) const MAX_DISKS_PER_INSTANCE: u32 = 8;

pub(crate) const MAX_NICS_PER_INSTANCE: u32 = 8;

// TODO-completness: Support multiple Ephemeral IPs
pub(crate) const MAX_EPHEMERAL_IPS_PER_INSTANCE: u32 = 1;

/// Manages an Oxide fleet -- the heart of the control plane
pub struct Nexus {
/// uuid for this nexus instance.
Expand Down
Loading