Skip to content

Commit

Permalink
rebase
Browse files Browse the repository at this point in the history
Created using spr 1.3.6-beta.1
  • Loading branch information
sunshowers committed Jul 15, 2024
2 parents 408274f + 88246d9 commit d33ed8e
Show file tree
Hide file tree
Showing 24 changed files with 572 additions and 285 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/hakari.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
with:
toolchain: stable
- name: Install cargo-hakari
uses: taiki-e/install-action@0256b3ea9ae3d751755a35cbb0608979a842f1d2 # v2
uses: taiki-e/install-action@996330bfc2ff267dc45a3d59354705b61547df0b # v2
with:
tool: cargo-hakari
- name: Check workspace-hack Cargo.toml is up-to-date
Expand Down
30 changes: 21 additions & 9 deletions Cargo.lock

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

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ members = [
"dev-tools/releng",
"dev-tools/xtask",
"dns-server",
"dns-server-api",
"end-to-end-tests",
"gateway-cli",
"gateway-test-utils",
Expand Down Expand Up @@ -119,6 +120,7 @@ default-members = [
# hakari to not work as well and build times to be longer.
# See omicron#4392.
"dns-server",
"dns-server-api",
# Do not include end-to-end-tests in the list of default members, as its
# tests only work on a deployed control plane.
"gateway-cli",
Expand Down Expand Up @@ -279,6 +281,7 @@ derive-where = "1.2.7"
diesel = { version = "2.1.6", features = ["postgres", "r2d2", "chrono", "serde_json", "network-address", "uuid"] }
diesel-dtrace = { git = "https://github.com/oxidecomputer/diesel-dtrace", branch = "main" }
dns-server = { path = "dns-server" }
dns-server-api = { path = "dns-server-api" }
dns-service-client = { path = "clients/dns-service-client" }
dpd-client = { path = "clients/dpd-client" }
dropshot = { git = "https://github.com/oxidecomputer/dropshot", branch = "main", features = [ "usdt-probes" ] }
Expand Down Expand Up @@ -480,7 +483,7 @@ sp-sim = { path = "sp-sim" }
sprockets-common = { git = "https://github.com/oxidecomputer/sprockets", rev = "77df31efa5619d0767ffc837ef7468101608aee9" }
sprockets-host = { git = "https://github.com/oxidecomputer/sprockets", rev = "77df31efa5619d0767ffc837ef7468101608aee9" }
sprockets-rot = { git = "https://github.com/oxidecomputer/sprockets", rev = "77df31efa5619d0767ffc837ef7468101608aee9" }
sqlformat = "0.2.3"
sqlformat = "0.2.4"
sqlparser = { version = "0.45.0", features = [ "visitor" ] }
static_assertions = "1.1.0"
# Please do not change the Steno version to a Git dependency. It makes it
Expand Down
1 change: 1 addition & 0 deletions dev-tools/openapi-manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ anyhow.workspace = true
atomicwrites.workspace = true
camino.workspace = true
clap.workspace = true
dns-server-api.workspace = true
dropshot.workspace = true
fs-err.workspace = true
indent_write.workspace = true
Expand Down
35 changes: 22 additions & 13 deletions dev-tools/openapi-manager/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,34 @@ use openapiv3::OpenAPI;
pub fn all_apis() -> Vec<ApiSpec> {
vec![
ApiSpec {
title: "Installinator API".to_string(),
version: "0.0.1".to_string(),
title: "Internal DNS",
version: "0.0.1",
description: "API for the internal DNS server",
boundary: ApiBoundary::Internal,
api_description:
dns_server_api::dns_server_api::stub_api_description,
filename: "dns-server.json",
extra_validation: None,
},
ApiSpec {
title: "Installinator API",
version: "0.0.1",
description: "API for installinator to fetch artifacts \
and report progress"
.to_string(),
and report progress",
boundary: ApiBoundary::Internal,
api_description:
installinator_api::installinator_api::stub_api_description,
filename: "installinator.json".to_string(),
filename: "installinator.json",
extra_validation: None,
},
ApiSpec {
title: "Nexus internal API".to_string(),
version: "0.0.1".to_string(),
description: "Nexus internal API".to_string(),
title: "Nexus internal API",
version: "0.0.1",
description: "Nexus internal API",
boundary: ApiBoundary::Internal,
api_description:
nexus_internal_api::nexus_internal_api_mod::stub_api_description,
filename: "nexus-internal.json".to_string(),
filename: "nexus-internal.json",
extra_validation: None,
},
// Add your APIs here! Please keep this list sorted by filename.
Expand All @@ -42,13 +51,13 @@ pub fn all_apis() -> Vec<ApiSpec> {

pub struct ApiSpec {
/// The title.
pub title: String,
pub title: &'static str,

/// The version.
pub version: String,
pub version: &'static str,

/// The description string.
pub description: String,
pub description: &'static str,

/// Whether this API is internal or external.
pub boundary: ApiBoundary,
Expand All @@ -59,7 +68,7 @@ pub struct ApiSpec {
fn() -> Result<ApiDescription<StubContext>, ApiDescriptionBuildErrors>,

/// The JSON filename to write the API description to.
pub filename: String,
pub filename: &'static str,

/// Extra validation to perform on the OpenAPI spec, if any.
pub extra_validation: Option<fn(&OpenAPI) -> anyhow::Result<()>>,
Expand Down
15 changes: 15 additions & 0 deletions dns-server-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "dns-server-api"
version = "0.1.0"
edition = "2021"
license = "MPL-2.0"

[lints]
workspace = true

[dependencies]
chrono.workspace = true
dropshot.workspace = true
omicron-workspace-hack.workspace = true
schemars.workspace = true
serde.workspace = true
160 changes: 160 additions & 0 deletions dns-server-api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// 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/.

//! Dropshot API for configuring DNS namespace.
//!
//! ## Shape of the API
//!
//! The DNS configuration API has just two endpoints: PUT and GET of the entire
//! DNS configuration. This is pretty anti-REST. But it's important to think
//! about how this server fits into the rest of the system. When changes are
//! made to DNS data, they're grouped together and assigned a monotonically
//! increasing generation number. The DNS data is first stored into CockroachDB
//! and then propagated from a distributed fleet of Nexus instances to a
//! distributed fleet of these DNS servers. If we accepted individual updates to
//! DNS names, then propagating a particular change would be non-atomic, and
//! Nexus would have to do a lot more work to ensure (1) that all changes were
//! propagated (even if it crashes) and (2) that they were propagated in the
//! correct order (even if two Nexus instances concurrently propagate separate
//! changes).
//!
//! This DNS server supports hosting multiple zones. We could imagine supporting
//! separate endpoints to update the DNS data for a particular zone. That feels
//! nicer (although it's not clear what it would buy us). But as with updates to
//! multiple names, Nexus's job is potentially much easier if the entire state
//! for all zones is updated at once. (Otherwise, imagine how Nexus would
//! implement _renaming_ one zone to another without loss of service. With
//! a combined endpoint and generation number for all zones, all that's necessary
//! is to configure a new zone with all the same names, and then remove the old
//! zone later in another update. That can be managed by the same mechanism in
//! Nexus that manages regular name updates. On the other hand, if there were
//! separate endpoints with separate generation numbers, then Nexus has more to
//! keep track of in order to do the rename safely.)
//!
//! See RFD 367 for more on DNS propagation.
//!
//! ## ETags and Conditional Requests
//!
//! It's idiomatic in HTTP use ETags and conditional requests to provide
//! synchronization. We could define an ETag to be just the current generation
//! number of the server and honor standard `if-match` headers to fail requests
//! where the generation number doesn't match what the client expects. This
//! would be fine, but it's rather annoying:
//!
//! 1. When the client wants to propagate generation X, the client would have
//! make an extra request just to fetch the current ETag, just so it can put
//! it into the conditional request.
//!
//! 2. If some other client changes the configuration in the meantime, the
//! conditional request would fail and the client would have to take another
//! lap (fetching the current config and potentially making another
//! conditional PUT).
//!
//! 3. This approach would make synchronization opt-in. If a client (or just
//! one errant code path) neglected to set the if-match header, we could do
//! the wrong thing and cause the system to come to rest with the wrong DNS
//! data.
//!
//! Since the semantics here are so simple (we only ever want to move the
//! generation number forward), we don't bother with ETags or conditional
//! requests. Instead we have the server implement the behavior we want, which
//! is that when a request comes in to update DNS data to generation X, the
//! server replies with one of:
//!
//! (1) the update has been applied and the server is now running generation X
//! (client treats this as success)
//!
//! (2) the update was not applied because the server is already at generation X
//! (client treats this as success)
//!
//! (3) the update was not applied because the server is already at a newer
//! generation
//! (client probably starts the whole propagation process over because its
//! current view of the world is out of date)
//!
//! This way, the DNS data can never move backwards and the client only ever has
//! to make one request.
//!
//! ## Concurrent updates
//!
//! Given that we've got just one API to update the all DNS zones, and given
//! that might therefore take a minute for a large zone, and also that there may
//! be multiple Nexus instances trying to do it at the same time, we need to
//! think a bit about what should happen if two Nexus do try to do it at the same
//! time. Spoiler: we immediately fail any request to update the DNS data if
//! there's already an update in progress.
//!
//! What else could we do? We could queue the incoming request behind the
//! in-progress one. How large do we allow that queue to grow? At some point
//! we'll need to stop queueing them. So why bother at all?
use std::{
collections::HashMap,
net::{Ipv4Addr, Ipv6Addr},
};

use dropshot::{HttpError, HttpResponseOk, RequestContext};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[dropshot::api_description]
pub trait DnsServerApi {
type Context;

#[endpoint(
method = GET,
path = "/config",
)]
async fn dns_config_get(
rqctx: RequestContext<Self::Context>,
) -> Result<HttpResponseOk<DnsConfig>, HttpError>;

#[endpoint(
method = PUT,
path = "/config",
)]
async fn dns_config_put(
rqctx: RequestContext<Self::Context>,
rq: dropshot::TypedBody<DnsConfigParams>,
) -> Result<dropshot::HttpResponseUpdatedNoContent, dropshot::HttpError>;
}

#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct DnsConfigParams {
pub generation: u64,
pub time_created: chrono::DateTime<chrono::Utc>,
pub zones: Vec<DnsConfigZone>,
}

#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct DnsConfig {
pub generation: u64,
pub time_created: chrono::DateTime<chrono::Utc>,
pub time_applied: chrono::DateTime<chrono::Utc>,
pub zones: Vec<DnsConfigZone>,
}

#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct DnsConfigZone {
pub zone_name: String,
pub records: HashMap<String, Vec<DnsRecord>>,
}

#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(tag = "type", content = "data")]
pub enum DnsRecord {
A(Ipv4Addr),
AAAA(Ipv6Addr),
SRV(SRV),
}

#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(rename = "Srv")]
pub struct SRV {
pub prio: u16,
pub weight: u16,
pub port: u16,
pub target: String,
}
1 change: 1 addition & 0 deletions dns-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ anyhow.workspace = true
camino.workspace = true
chrono.workspace = true
clap.workspace = true
dns-server-api.workspace = true
dns-service-client.workspace = true
dropshot.workspace = true
http.workspace = true
Expand Down
Loading

0 comments on commit d33ed8e

Please sign in to comment.