Skip to content

Commit

Permalink
NAT RPW for instance networking
Browse files Browse the repository at this point in the history
* Add db table for tracking nat entries
* Add endpoint for retrieving changesets
* Update instance sagas to update table and trigger RPW
* Periodically cleanup soft-deleted entries that no longer
  need to be sync'd by dendrite.

The other half of the RPW lives in Dendrite. It will periodically
check for a changeset, or check for a changeset when the trigger
endpoint is called by the relevant saga / nexus operation.
  • Loading branch information
internet-diglett committed Oct 16, 2023
1 parent 9700d44 commit 32e53be
Show file tree
Hide file tree
Showing 28 changed files with 1,229 additions and 83 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions common/src/api/external/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ pub enum ResourceType {
UpdateableComponent,
UserBuiltin,
Zpool,
Ipv4NatEntry,
}

// IDENTITY METADATA
Expand Down
19 changes: 17 additions & 2 deletions common/src/nexus_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ pub struct BackgroundTaskConfig {
pub dns_external: DnsTasksConfig,
/// configuration for external endpoint list watcher
pub external_endpoints: ExternalEndpointsConfig,
/// configuration for nat table garbage collector
pub nat_cleanup: NatCleanupConfig,
}

#[serde_as]
Expand Down Expand Up @@ -345,6 +347,14 @@ pub struct ExternalEndpointsConfig {
// allow/disallow wildcard certs, don't serve expired certs, etc.)
}

#[serde_as]
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct NatCleanupConfig {
/// period (in seconds) for periodic activations of this background task
#[serde_as(as = "DurationSeconds<u64>")]
pub period_secs: Duration,
}

/// Configuration for a nexus server
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct PackageConfig {
Expand Down Expand Up @@ -448,7 +458,7 @@ mod test {
use crate::nexus_config::{
BackgroundTaskConfig, ConfigDropshotWithTls, Database,
DeploymentConfig, DnsTasksConfig, DpdConfig, ExternalEndpointsConfig,
InternalDns, LoadErrorKind,
InternalDns, LoadErrorKind, NatCleanupConfig,
};
use dropshot::ConfigDropshot;
use dropshot::ConfigLogging;
Expand Down Expand Up @@ -594,6 +604,7 @@ mod test {
dns_external.period_secs_propagation = 7
dns_external.max_concurrent_server_updates = 8
external_endpoints.period_secs = 9
nat_cleanup.period_secs = 30
"##,
)
.unwrap();
Expand Down Expand Up @@ -675,7 +686,10 @@ mod test {
},
external_endpoints: ExternalEndpointsConfig {
period_secs: Duration::from_secs(9),
}
},
nat_cleanup: NatCleanupConfig {
period_secs: Duration::from_secs(30),
},
},
},
}
Expand Down Expand Up @@ -724,6 +738,7 @@ mod test {
dns_external.period_secs_propagation = 7
dns_external.max_concurrent_server_updates = 8
external_endpoints.period_secs = 9
nat_cleanup.period_secs = 30
"##,
)
.unwrap();
Expand Down
116 changes: 116 additions & 0 deletions nexus/db-model/src/ipv4_nat_entry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use std::net::{Ipv4Addr, Ipv6Addr};

use super::MacAddr;
use crate::{
schema::{ipv4_nat_entry, nat_gen},
SqlU16, SqlU32, Vni,
};
use chrono::{DateTime, Utc};
use omicron_common::api::external;
use schemars::JsonSchema;
use serde::Serialize;
use uuid::Uuid;

// TODO correctness
// If we're not going to store ipv4 and ipv6
// NAT entries in the same table, and we don't
// need any of the special properties of the IpNetwork
// column type, does it make sense to use a different
// column type?
/// Database representation of an Ipv4 NAT Entry.
#[derive(Insertable, Debug, Clone)]
#[diesel(table_name = ipv4_nat_entry)]
pub struct Ipv4NatValues {
pub external_address: ipnetwork::IpNetwork,
pub first_port: SqlU16,
pub last_port: SqlU16,
pub sled_address: ipnetwork::IpNetwork,
pub vni: Vni,
pub mac: MacAddr,
}

// TODO correctness
// If we're not going to store ipv4 and ipv6
// NAT entries in the same table, we should probably
// make the types more restrictive to prevent an
// accidental ipv6 entry from being created.
#[derive(Queryable, Debug, Clone, Selectable)]
#[diesel(table_name = ipv4_nat_entry)]
pub struct Ipv4NatEntry {
pub id: Uuid,
pub external_address: ipnetwork::IpNetwork,
pub first_port: SqlU16,
pub last_port: SqlU16,
pub sled_address: ipnetwork::IpNetwork,
pub vni: Vni,
pub mac: MacAddr,
pub gen: SqlU32,
pub time_created: DateTime<Utc>,
pub time_deleted: Option<DateTime<Utc>>,
}

impl Ipv4NatEntry {
pub fn first_port(&self) -> u16 {
self.first_port.into()
}

pub fn last_port(&self) -> u16 {
self.last_port.into()
}

pub fn gen(&self) -> u32 {
self.gen.into()
}
}

#[derive(Queryable, Debug, Clone, Selectable)]
#[diesel(table_name = nat_gen)]
pub struct Ipv4NatGen {
pub last_value: SqlU32,
pub log_cnt: SqlU32,
pub is_called: bool,
}

/// NAT Record
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub struct Ipv4NatEntryView {
pub external_address: Ipv4Addr,
pub first_port: u16,
pub last_port: u16,
pub sled_address: Ipv6Addr,
pub vni: external::Vni,
pub mac: external::MacAddr,
pub gen: u32,
pub deleted: bool,
}

impl From<Ipv4NatEntry> for Ipv4NatEntryView {
fn from(value: Ipv4NatEntry) -> Self {
let external_address = match value.external_address.ip() {
std::net::IpAddr::V4(a) => a,
std::net::IpAddr::V6(_) => unreachable!(),
};

let sled_address = match value.sled_address.ip() {
std::net::IpAddr::V4(_) => unreachable!(),
std::net::IpAddr::V6(a) => a,
};

Self {
external_address,
first_port: value.first_port(),
last_port: value.last_port(),
sled_address,
vni: value.vni.0,
mac: *value.mac,
gen: value.gen(),
deleted: value.time_deleted.is_some(),
}
}
}

/// NAT Generation
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub struct Ipv4NatGenView {
pub gen: u32,
}
2 changes: 2 additions & 0 deletions nexus/db-model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ mod system_update;
// These actually represent subqueries, not real table.
// However, they must be defined in the same crate as our tables
// for join-based marker trait generation.
mod ipv4_nat_entry;
pub mod queries;
mod rack;
mod region;
Expand Down Expand Up @@ -119,6 +120,7 @@ pub use instance::*;
pub use instance_cpu_count::*;
pub use instance_state::*;
pub use ip_pool::*;
pub use ipv4_nat_entry::*;
pub use ipv4net::*;
pub use ipv6::*;
pub use ipv6net::*;
Expand Down
27 changes: 26 additions & 1 deletion nexus/db-model/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,31 @@ table! {
}
}

table! {
ipv4_nat_entry (id) {
id -> Uuid,
external_address -> Inet,
first_port -> Int4,
last_port -> Int4,
sled_address -> Inet,
vni -> Int4,
mac -> Int8,
gen -> Int8,
time_created -> Timestamptz,
time_deleted -> Nullable<Timestamptz>,
}
}

// This is the sequence used for the generation number
// in ipv4_nat_entry.
table! {
nat_gen (last_value) {
last_value -> Int8,
log_cnt -> Int8,
is_called -> Bool,
}
}

table! {
external_ip (id) {
id -> Uuid,
Expand Down Expand Up @@ -1130,7 +1155,7 @@ table! {
///
/// This should be updated whenever the schema is changed. For more details,
/// refer to: schema/crdb/README.adoc
pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(4, 0, 0);
pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(4, 0, 1);

allow_tables_to_appear_in_same_query!(
system_update,
Expand Down
Loading

0 comments on commit 32e53be

Please sign in to comment.