Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 12 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"dev-tools/downloader",
"dev-tools/dropshot-apis",
"dev-tools/ls-apis",
"dev-tools/ls-apis-shared",
"dev-tools/mgs-dev",
"dev-tools/omdb",
"dev-tools/omicron-dev",
Expand Down Expand Up @@ -217,6 +218,7 @@ default-members = [
"dev-tools/downloader",
"dev-tools/dropshot-apis",
"dev-tools/ls-apis",
"dev-tools/ls-apis-shared",
"dev-tools/mgs-dev",
"dev-tools/omdb",
"dev-tools/omicron-dev",
Expand Down Expand Up @@ -579,6 +581,7 @@ linear-map = "1.2.0"
live-tests-macros = { path = "live-tests/macros" }
lldpd_client = { git = "https://github.com/oxidecomputer/lldp", package = "lldpd-client" }
lldp_protocol = { git = "https://github.com/oxidecomputer/lldp", package = "protocol" }
ls-apis-shared = { path = "dev-tools/ls-apis-shared" }
macaddr = { version = "1.0.1", features = ["serde_std"] }
maplit = "1.0.2"
newtype_derive = "0.1.6"
Expand Down
13 changes: 13 additions & 0 deletions dev-tools/ls-apis-shared/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "ls-apis-shared"
version = "0.1.0"
edition.workspace = true
license = "MPL-2.0"

[lints]
workspace = true

[dependencies]
newtype_derive.workspace = true
omicron-workspace-hack.workspace = true
serde.workspace = true
43 changes: 43 additions & 0 deletions dev-tools/ls-apis-shared/src/deployment_unit_dag.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/.

use serde::{Deserialize, Serialize};

/// The path to omicron-ls-apis relative to the workspace root.
pub const OMICRON_LS_APIS_PATH: &str = "dev-tools/ls-apis";

/// The path to the `deployment_unit_dag.toml` file relative to the
/// omicron-ls-apis directory.
pub const DEPLOYMENT_UNIT_DAG_PATH: &str =
"tests/output/deployment-unit-dag.toml";

/// Short, machine-friendly identifier for a deployment unit
/// (e.g. "nexus", "dns_server").
#[derive(
Clone, Deserialize, Serialize, Hash, Ord, PartialOrd, Eq, PartialEq,
)]
#[serde(transparent)]
pub struct DeploymentUnitId(String);
NewtypeDebug! { () pub struct DeploymentUnitId(String); }
NewtypeDeref! { () pub struct DeploymentUnitId(String); }
NewtypeDisplay! { () pub struct DeploymentUnitId(String); }
NewtypeFrom! { () pub struct DeploymentUnitId(String); }

/// A single directed edge in the deployment unit dependency DAG.
///
/// `consumer` depends on `producer`: the producer must be fully updated
/// before the consumer starts updating.
#[derive(
Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Ord, PartialOrd,
)]
pub struct DagEdge {
pub consumer: DeploymentUnitId,
pub producer: DeploymentUnitId,
}

/// Top-level structure of the `deployment_unit_dag.toml` file.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DagEdgesFile {
pub edges: Vec<DagEdge>,
}
16 changes: 16 additions & 0 deletions dev-tools/ls-apis-shared/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// 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/.

//! Types shared between `omicron-ls-apis` and consumers of its output
//! (e.g. the reconfigurator planner tests).

#[macro_use]
extern crate newtype_derive;

mod deployment_unit_dag;

pub use deployment_unit_dag::{
DEPLOYMENT_UNIT_DAG_PATH, DagEdge, DagEdgesFile, DeploymentUnitId,
OMICRON_LS_APIS_PATH,
};
1 change: 1 addition & 0 deletions dev-tools/ls-apis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ clap.workspace = true
iddqd.workspace = true
indent_write.workspace = true
itertools.workspace = true
ls-apis-shared.workspace = true
newtype_derive.workspace = true
parse-display.workspace = true
petgraph.workspace = true
Expand Down
10 changes: 10 additions & 0 deletions dev-tools/ls-apis/api-manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ ignored_non_clients = [
# The host OS includes Sled Agent, Propolis, and all the components that get
# bundled into the switch zone.
[[deployment_units]]
id = "host_os"
label = "Host OS"
packages = [
"omicron-sled-agent",
Expand All @@ -83,43 +84,52 @@ packages = [

# Installinator gets packaged into its own host OS image.
[[deployment_units]]
id = "installinator"
label = "Installinator"
packages = [ "installinator" ]

# The rest of these get bundled by standard control plane zone images.

[[deployment_units]]
id = "crucible"
label = "Crucible"
packages = [ "crucible-agent", "crucible-downstairs" ]

[[deployment_units]]
id = "crucible_pantry"
label = "Crucible Pantry"
packages = [ "crucible-pantry" ]

[[deployment_units]]
id = "cockroach"
label = "Cockroach"
packages = [ "omicron-cockroach-admin" ]

# These are really three distinct deployment units, but they behave the same for
# our purposes, and the tooling doesn't support multiple deployment units
# that each expose a particular service.
[[deployment_units]]
id = "clickhouse"
label = "Clickhouse (single-node) / Clickhouse Server (multi-node) / Clickhouse Keeper (multi-node)"
packages = [ "omicron-clickhouse-admin" ]

[[deployment_units]]
id = "dns_server"
label = "DNS Server"
packages = [ "dns-server" ]

[[deployment_units]]
id = "nexus"
label = "Nexus"
packages = [ "omicron-nexus" ]

[[deployment_units]]
id = "ntp"
label = "NTP"
packages = [ "omicron-ntp-admin" ]

[[deployment_units]]
id = "oximeter"
label = "Oximeter"
packages = [ "oximeter-collector" ]

Expand Down
39 changes: 28 additions & 11 deletions dev-tools/ls-apis/src/api_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use anyhow::{Result, bail};
use iddqd::IdOrdItem;
use iddqd::IdOrdMap;
use iddqd::id_upcast;
use ls_apis_shared::DeploymentUnitId;
use serde::Deserialize;
use std::borrow::Borrow;
use std::collections::BTreeMap;
Expand All @@ -27,7 +28,7 @@ use std::collections::BTreeSet;
#[serde(try_from = "RawApiMetadata")]
pub struct AllApiMetadata {
apis: BTreeMap<ClientPackageName, ApiMetadata>,
deployment_units: BTreeMap<DeploymentUnitName, DeploymentUnitInfo>,
deployment_units: IdOrdMap<DeploymentUnitInfo>,
dependency_rules: BTreeMap<ClientPackageName, Vec<DependencyFilterRule>>,
ignored_non_clients: BTreeSet<ClientPackageName>,
intra_deployment_unit_only_edges: Vec<IntraDeploymentUnitOnlyEdge>,
Expand All @@ -42,10 +43,18 @@ impl AllApiMetadata {
/// Iterate over the deployment units defined in the metadata
pub fn deployment_units(
&self,
) -> impl Iterator<Item = (&DeploymentUnitName, &DeploymentUnitInfo)> {
) -> impl Iterator<Item = &DeploymentUnitInfo> {
self.deployment_units.iter()
}

/// Look up a deployment unit's info by its ID
pub fn deployment_unit_info(
&self,
id: &DeploymentUnitId,
) -> Option<&DeploymentUnitInfo> {
self.deployment_units.get(id)
}

/// Iterate over the package names for all the APIs' clients
pub fn client_pkgnames(&self) -> impl Iterator<Item = &ClientPackageName> {
self.apis.keys()
Expand All @@ -55,7 +64,7 @@ impl AllApiMetadata {
pub fn server_components(
&self,
) -> impl Iterator<Item = &ServerComponentName> {
self.deployment_units.values().flat_map(|d| d.packages.iter())
self.deployment_units.iter().flat_map(|d| d.packages.iter())
}

/// Look up details about an API based on its client package name
Expand Down Expand Up @@ -166,14 +175,12 @@ impl TryFrom<RawApiMetadata> for AllApiMetadata {
}
}

let mut deployment_units = BTreeMap::new();
let mut deployment_units = IdOrdMap::new();
for info in raw.deployment_units {
if let Some(previous) =
deployment_units.insert(info.label.clone(), info)
{
if let Err(e) = deployment_units.insert_unique(info) {
bail!(
"duplicate deployment unit in API metadata: {}",
&previous.label,
"duplicate deployment unit id in API metadata: {}",
e.new_item().id,
);
}
}
Expand Down Expand Up @@ -206,7 +213,7 @@ impl TryFrom<RawApiMetadata> for AllApiMetadata {
// Validate that IDU-only edges reference only known server components
// and APIs.
let known_components: BTreeSet<_> =
deployment_units.values().flat_map(|u| u.packages.iter()).collect();
deployment_units.iter().flat_map(|u| u.packages.iter()).collect();
for edge in &raw.intra_deployment_unit_only_edges {
if !known_components.contains(&edge.server) {
bail!(
Expand Down Expand Up @@ -411,12 +418,22 @@ pub enum ApiConsumerStatus {
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DeploymentUnitInfo {
/// human-readable label, also used as primary key
/// short, machine-friendly identifier (e.g. "nexus", "dns_server")
pub id: DeploymentUnitId,
/// human-readable label for display
pub label: DeploymentUnitName,
/// list of Rust packages that are shipped in this unit
pub packages: Vec<ServerComponentName>,
}

impl IdOrdItem for DeploymentUnitInfo {
type Key<'a> = &'a DeploymentUnitId;
fn key(&self) -> Self::Key<'_> {
&self.id
}
id_upcast!();
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DependencyFilterRule {
Expand Down
28 changes: 25 additions & 3 deletions dev-tools/ls-apis/src/bin/ls-apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use anyhow::{Context, Result, bail};
use camino::Utf8PathBuf;
use clap::{Args, Parser, Subcommand};
use indent_write::indentable::Indentable;
use ls_apis_shared::DagEdgesFile;
use omicron_ls_apis::{
AllApiMetadata, ApiConsumerStatus, ApiDependencyFilter, ApiMetadata,
FailedConsumerCheck, LoadArgs, ServerComponentName, SystemApis,
Expand Down Expand Up @@ -38,6 +39,8 @@ enum Cmds {
Apis(ShowDepsArgs),
/// check the update DAG and propose changes
Check,
/// print deployment unit DAG edges as TOML
DagEdges,
/// print out APIs exported and consumed by each deployment unit
DeploymentUnits(DotArgs),
/// print out APIs exported and consumed, by server component
Expand Down Expand Up @@ -86,11 +89,26 @@ fn main() -> Result<()> {
Cmds::Adoc => run_adoc(&apis),
Cmds::Apis(args) => run_apis(&apis, args),
Cmds::Check => run_check(&apis),
Cmds::DagEdges => run_dag_edges(&apis),
Cmds::DeploymentUnits(args) => run_deployment_units(&apis, args),
Cmds::Servers(args) => run_servers(&apis, args),
}
}

fn run_dag_edges(apis: &SystemApis) -> Result<()> {
let edges = apis.deployment_unit_dag_edges()?;
let output = DagEdgesFile { edges };
let toml_str = toml::to_string_pretty(&output)
.context("serializing DAG edges as TOML")?;
print!(
"# BEGIN @generated server-side deployment unit DAG edges.\n\
# To regenerate, run `EXPECTORATE=overwrite cargo nextest run -p omicron-ls-apis`.\n\
\n\
{toml_str}"
);
Ok(())
}

fn run_adoc(apis: &SystemApis) -> Result<()> {
println!("// BEGIN auto-generated by Omicron's `cargo xtask ls-apis adoc`");
println!("// DO NOT EDIT.");
Expand Down Expand Up @@ -212,9 +230,13 @@ fn run_deployment_units(apis: &SystemApis, args: DotArgs) -> Result<()> {
OutputFormat::Dot => println!("{}", apis.dot_by_unit(args.filter)?),
OutputFormat::Text => {
let metadata = apis.api_metadata();
for unit in apis.deployment_units() {
let server_components = apis.deployment_unit_servers(unit)?;
println!("{}", unit);
for unit_id in apis.deployment_units() {
let info = metadata
.deployment_unit_info(unit_id)
.expect("deployment unit info exists");
let server_components =
apis.deployment_unit_servers(unit_id)?;
println!("{}", info.label);
print_server_components(
apis,
metadata,
Expand Down
Loading
Loading