Skip to content

Commit 00e8bed

Browse files
authored
[nexus] Split Nexus configuration (package vs deployment) (#1174)
This is a major part of #822 This PR splits the Nexus configuration into "things known at package time" and "things known at deployment". Due to hardcoded configurations, the two have historically been conflated. However, there are a number of parameters which will only be known at deployment time, including: - The addresses Nexus should use on the underlay (these are derived from the sled's /64, which is variable) - The UUID of the Nexus instance (multiple Nexus instances may be executing simultaneously) - The rack subnet - (While it is still an IP address) the IP address of CRDB
1 parent 2021dc9 commit 00e8bed

File tree

29 files changed

+987
-678
lines changed

29 files changed

+987
-678
lines changed

Cargo.lock

Lines changed: 219 additions & 318 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ steno = { git = "https://github.com/oxidecomputer/steno", branch = "main" }
2929
thiserror = "1.0"
3030
tokio = { version = "1.18", features = [ "full" ] }
3131
tokio-postgres = { version = "0.7", features = [ "with-chrono-0_4", "with-uuid-1" ] }
32+
toml = "0.5.9"
3233
uuid = { version = "1.1.0", features = [ "serde", "v4" ] }
3334
parse-display = "0.5.4"
3435
progenitor = { git = "https://github.com/oxidecomputer/progenitor" }

common/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ pub mod address;
2424
pub mod api;
2525
pub mod backoff;
2626
pub mod cmd;
27-
pub mod config;
27+
pub mod nexus_config;
28+
pub mod postgres_config;
2829

2930
#[macro_export]
3031
macro_rules! generate_logging_api {

common/src/nexus_config.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! Configuration parameters to Nexus that are usually only known
6+
//! at deployment time.
7+
8+
use super::address::{Ipv6Subnet, RACK_PREFIX};
9+
use super::postgres_config::PostgresConfigWithUrl;
10+
use dropshot::ConfigDropshot;
11+
use serde::{Deserialize, Serialize};
12+
use serde_with::serde_as;
13+
use serde_with::DisplayFromStr;
14+
use std::fmt;
15+
use std::path::{Path, PathBuf};
16+
use uuid::Uuid;
17+
18+
#[derive(Debug)]
19+
pub struct LoadError {
20+
pub path: PathBuf,
21+
pub kind: LoadErrorKind,
22+
}
23+
24+
#[derive(Debug)]
25+
pub struct InvalidTunable {
26+
pub tunable: String,
27+
pub message: String,
28+
}
29+
30+
impl std::fmt::Display for InvalidTunable {
31+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32+
write!(f, "invalid \"{}\": \"{}\"", self.tunable, self.message)
33+
}
34+
}
35+
impl std::error::Error for InvalidTunable {}
36+
37+
#[derive(Debug)]
38+
pub enum LoadErrorKind {
39+
Io(std::io::Error),
40+
Parse(toml::de::Error),
41+
InvalidTunable(InvalidTunable),
42+
}
43+
44+
impl From<(PathBuf, std::io::Error)> for LoadError {
45+
fn from((path, err): (PathBuf, std::io::Error)) -> Self {
46+
LoadError { path, kind: LoadErrorKind::Io(err) }
47+
}
48+
}
49+
50+
impl From<(PathBuf, toml::de::Error)> for LoadError {
51+
fn from((path, err): (PathBuf, toml::de::Error)) -> Self {
52+
LoadError { path, kind: LoadErrorKind::Parse(err) }
53+
}
54+
}
55+
56+
impl std::error::Error for LoadError {}
57+
58+
impl fmt::Display for LoadError {
59+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60+
match &self.kind {
61+
LoadErrorKind::Io(e) => {
62+
write!(f, "read \"{}\": {}", self.path.display(), e)
63+
}
64+
LoadErrorKind::Parse(e) => {
65+
write!(f, "parse \"{}\": {}", self.path.display(), e)
66+
}
67+
LoadErrorKind::InvalidTunable(inner) => {
68+
write!(
69+
f,
70+
"invalid tunable \"{}\": {}",
71+
self.path.display(),
72+
inner,
73+
)
74+
}
75+
}
76+
}
77+
}
78+
79+
impl std::cmp::PartialEq<std::io::Error> for LoadError {
80+
fn eq(&self, other: &std::io::Error) -> bool {
81+
if let LoadErrorKind::Io(e) = &self.kind {
82+
e.kind() == other.kind()
83+
} else {
84+
false
85+
}
86+
}
87+
}
88+
89+
#[serde_as]
90+
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
91+
#[serde(tag = "type", rename_all = "snake_case")]
92+
#[allow(clippy::large_enum_variant)]
93+
pub enum Database {
94+
FromDns,
95+
FromUrl {
96+
#[serde_as(as = "DisplayFromStr")]
97+
url: PostgresConfigWithUrl,
98+
},
99+
}
100+
101+
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
102+
pub struct DeploymentConfig {
103+
/// Uuid of the Nexus instance
104+
pub id: Uuid,
105+
/// Dropshot configuration for external API server
106+
pub dropshot_external: ConfigDropshot,
107+
/// Dropshot configuration for internal API server
108+
pub dropshot_internal: ConfigDropshot,
109+
/// Portion of the IP space to be managed by the Rack.
110+
pub subnet: Ipv6Subnet<RACK_PREFIX>,
111+
/// DB configuration.
112+
pub database: Database,
113+
}
114+
115+
impl DeploymentConfig {
116+
/// Load a `DeploymentConfig` from the given TOML file
117+
///
118+
/// This config object can then be used to create a new `Nexus`.
119+
/// The format is described in the README.
120+
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, LoadError> {
121+
let path = path.as_ref();
122+
let file_contents = std::fs::read_to_string(path)
123+
.map_err(|e| (path.to_path_buf(), e))?;
124+
let config_parsed: Self = toml::from_str(&file_contents)
125+
.map_err(|e| (path.to_path_buf(), e))?;
126+
Ok(config_parsed)
127+
}
128+
}
File renamed without changes.

nexus/benches/setup_benchmark.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ async fn do_full_setup() {
1919
// Wraps exclusively the CockroachDB portion of setup/teardown.
2020
async fn do_crdb_setup() {
2121
let cfg = nexus_test_utils::load_test_config();
22-
let logctx = LogContext::new("crdb_setup", &cfg.log);
22+
let logctx = LogContext::new("crdb_setup", &cfg.pkg.log);
2323
let mut db = test_setup_database(&logctx.log).await;
2424
db.cleanup().await.unwrap();
2525
}

nexus/examples/config.toml

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
# Oxide API: example configuration file
33
#
44

5-
# Identifier for this instance of Nexus
6-
id = "e6bff1ff-24fb-49dc-a54e-c6a350cd4d6c"
7-
85
[console]
96
# Directory for static assets. Absolute path or relative to CWD.
107
static_dir = "nexus/static" # TODO: figure out value
@@ -20,21 +17,6 @@ session_absolute_timeout_minutes = 480
2017
# TODO(https://github.com/oxidecomputer/omicron/issues/372): Remove "spoof".
2118
schemes_external = ["spoof", "session_cookie"]
2219

23-
[database]
24-
# URL for connecting to the database
25-
url = "postgresql://root@127.0.0.1:32221/omicron?sslmode=disable"
26-
27-
[dropshot_external]
28-
# IP address and TCP port on which to listen for the external API
29-
bind_address = "127.0.0.1:12220"
30-
# Allow larger request bodies (1MiB) to accomodate firewall endpoints (one
31-
# rule is ~500 bytes)
32-
request_body_max_bytes = 1048576
33-
34-
[dropshot_internal]
35-
# IP address and TCP port on which to listen for the internal API
36-
bind_address = "127.0.0.1:12221"
37-
3820
[log]
3921
# Show log messages of this level and more severe
4022
level = "info"
@@ -51,6 +33,29 @@ mode = "stderr-terminal"
5133
[timeseries_db]
5234
address = "[::1]:8123"
5335

36+
[deployment]
37+
# Identifier for this instance of Nexus
38+
id = "e6bff1ff-24fb-49dc-a54e-c6a350cd4d6c"
39+
40+
[deployment.dropshot_external]
41+
# IP address and TCP port on which to listen for the external API
42+
bind_address = "127.0.0.1:12220"
43+
# Allow larger request bodies (1MiB) to accomodate firewall endpoints (one
44+
# rule is ~500 bytes)
45+
request_body_max_bytes = 1048576
46+
47+
[deployment.dropshot_internal]
48+
# IP address and TCP port on which to listen for the internal API
49+
bind_address = "127.0.0.1:12221"
50+
51+
[deployment.subnet]
52+
net = "fd00:1122:3344:0100::/56"
53+
54+
[deployment.database]
55+
# URL for connecting to the database
56+
type = "from_url"
57+
url = "postgresql://root@127.0.0.1:32221/omicron?sslmode=disable"
58+
5459
# Tunable configuration parameters, for testing or experimentation
5560
[tunables]
5661

nexus/src/app/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ impl Nexus {
112112
authz: Arc<authz::Authz>,
113113
) -> Arc<Nexus> {
114114
let pool = Arc::new(pool);
115-
let my_sec_id = db::SecId::from(config.id);
115+
let my_sec_id = db::SecId::from(config.deployment.id);
116116
let db_datastore = Arc::new(db::DataStore::new(Arc::clone(&pool)));
117117
let sec_store = Arc::new(db::CockroachDbSecStore::new(
118118
my_sec_id,
@@ -127,7 +127,7 @@ impl Nexus {
127127
sec_store,
128128
));
129129
let timeseries_client =
130-
oximeter_db::Client::new(config.timeseries_db.address, &log);
130+
oximeter_db::Client::new(config.pkg.timeseries_db.address, &log);
131131

132132
// TODO-cleanup We may want a first-class subsystem for managing startup
133133
// background tasks. It could use a Future for each one, a status enum
@@ -143,7 +143,7 @@ impl Nexus {
143143
populate_start(populate_ctx, Arc::clone(&db_datastore));
144144

145145
let nexus = Nexus {
146-
id: config.id,
146+
id: config.deployment.id,
147147
rack_id,
148148
log: log.new(o!()),
149149
api_rack_identity: db::model::RackIdentity::new(rack_id),
@@ -153,8 +153,8 @@ impl Nexus {
153153
recovery_task: std::sync::Mutex::new(None),
154154
populate_status,
155155
timeseries_client,
156-
updates_config: config.updates.clone(),
157-
tunables: config.tunables.clone(),
156+
updates_config: config.pkg.updates.clone(),
157+
tunables: config.pkg.tunables.clone(),
158158
opctx_alloc: OpContext::for_background(
159159
log.new(o!("component" => "InstanceAllocator")),
160160
Arc::clone(&authz),

0 commit comments

Comments
 (0)