Skip to content

Commit 67a8984

Browse files
authored
[ls-apis] support APIs exposed by multiple servers (#7843)
Currently, `cargo xtask ls-apis` will fail when it encounters an API that is served by multiple entities. For example, when adding the `ereporter-api` in #7833, which is served by both MGS and sled-agent, `ls-apis` currently fails with an error like this one: ```console eliza@theseus ~/Code/oxide/omicron $ cargo xtask ls-apis apis Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.45s Running `target/debug/xtask ls-apis apis` Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.01s Running `target/debug/ls-apis apis` loading metadata for workspace omicron from current workspace loading metadata for workspace lldp from /home/eliza/.cargo/git/checkouts/lldp-d47de417041f191b/ce952e6/Cargo.toml loading metadata for workspace propolis from /home/eliza/.cargo/git/checkouts/propolis-d68c8bd1bc59c9bd/6b5f2af/Cargo.toml loading metadata for workspace crucible from /home/eliza/.cargo/git/checkouts/crucible-0a48bd218bc2bbbc/81a3528/Cargo.toml loading metadata for workspace maghemite from /home/eliza/.cargo/git/checkouts/maghemite-c0236f0fd3d582b6/caafd88/Cargo.toml loading metadata for workspace dendrite from /home/eliza/.cargo/git/checkouts/dendrite-ae9f1715c17fc765/a66561e/Cargo.toml note: ignoring Cargo dependency from crucible-pantry -> ... -> crucible-control-client note: ignoring Cargo dependency from omicron-sled-agent -> dns-server error: API for client ereporter-client appears to be exported by multiple components: at least omicron-sled-agent and omicron-gateway (DepPath([PackageId { repr: "path+file:///home/eliza/Code/oxide/omicron/gateway#omicron-gateway@0.1.0" }])) Error: found at least one API exported by multiple servers # ... backtrace snipped for brevity ... ``` This is unfortunate, as it would be nice to be able to have such APIs without breaking `ls-apis`. Therefore, this commit adds support for APIs with multiple server components. This change ends up being pretty straightforward; it's mainly just changing the `api_producers` map from a map of client package names to server components to a map of client package names to *a map of* server components, and updating the code that consumes this information to loop over those server components as appropriate. Now, the various `ls-apis` commands can handle the new `ereproter-api`. For example: ```console eliza@theseus ~/Code/oxide/omicron $ cargo xtask ls-apis apis Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.37s Running `target/debug/xtask ls-apis apis` Compiling omicron-ls-apis v0.1.0 (/home/eliza/Code/oxide/omicron/dev-tools/ls-apis) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.69s Running `target/debug/ls-apis apis` loading metadata for workspace omicron from current workspace loading metadata for workspace lldp from /home/eliza/.cargo/git/checkouts/lldp-d47de417041f191b/ce952e6/Cargo.toml loading metadata for workspace maghemite from /home/eliza/.cargo/git/checkouts/maghemite-c0236f0fd3d582b6/caafd88/Cargo.toml loading metadata for workspace propolis from /home/eliza/.cargo/git/checkouts/propolis-d68c8bd1bc59c9bd/6b5f2af/Cargo.toml loading metadata for workspace crucible from /home/eliza/.cargo/git/checkouts/crucible-0a48bd218bc2bbbc/81a3528/Cargo.toml loading metadata for workspace dendrite from /home/eliza/.cargo/git/checkouts/dendrite-ae9f1715c17fc765/a66561e/Cargo.toml note: ignoring Cargo dependency from crucible-pantry -> ... -> crucible-control-client note: ignoring Cargo dependency from omicron-sled-agent -> dns-server Bootstrap Agent (client: bootstrap-agent-client) consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path consumed by: wicketd (omicron/wicketd) via 2 paths # ... snipped irrelevant APIs for brevity ... Ereporter (client: ereporter-client) consumed by: omicron-nexus (omicron/nexus) via 1 path # ... snipped irrelevant APIs for brevity ... eliza@theseus ~/Code/oxide/omicron $ cargo xtask ls-apis check Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32s Running `target/debug/xtask ls-apis check` Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s Running `target/debug/ls-apis check` loading metadata for workspace omicron from current workspace loading metadata for workspace lldp from /home/eliza/.cargo/git/checkouts/lldp-d47de417041f191b/ce952e6/Cargo.toml loading metadata for workspace maghemite from /home/eliza/.cargo/git/checkouts/maghemite-c0236f0fd3d582b6/caafd88/Cargo.toml loading metadata for workspace propolis from /home/eliza/.cargo/git/checkouts/propolis-d68c8bd1bc59c9bd/6b5f2af/Cargo.toml loading metadata for workspace crucible from /home/eliza/.cargo/git/checkouts/crucible-0a48bd218bc2bbbc/81a3528/Cargo.toml loading metadata for workspace dendrite from /home/eliza/.cargo/git/checkouts/dendrite-ae9f1715c17fc765/a66561e/Cargo.toml note: ignoring Cargo dependency from crucible-pantry -> ... -> crucible-control-client note: ignoring Cargo dependency from omicron-sled-agent -> dns-server Server-managed APIs: Clickhouse Cluster Admin for Keepers (clickhouse-admin-keeper-client, exposed by omicron-clickhouse-admin) Clickhouse Cluster Admin for Servers (clickhouse-admin-server-client, exposed by omicron-clickhouse-admin) Clickhouse Single-Node Cluster Admin (clickhouse-admin-single-client, exposed by omicron-clickhouse-admin) CockroachDB Cluster Admin (cockroach-admin-client, exposed by omicron-cockroach-admin) Crucible Agent (crucible-agent-client, exposed by crucible-agent) Crucible Control (for testing only) (crucible-control-client, exposed by propolis-server) Crucible Pantry (crucible-pantry-client, exposed by crucible-pantry) Maghemite DDM Admin (ddm-admin-client, exposed by ddmd) DNS Server (dns-service-client, exposed by dns-server) Ereporter (ereporter-client, exposed by omicron-gateway, omicron-sled-agent) Management Gateway Service (gateway-client, exposed by omicron-gateway) LLDP daemon (lldpd-client, exposed by lldpd) Maghemite MG Admin (mg-admin-client, exposed by mgd) External API (oxide-client, exposed by omicron-nexus) Oximeter (oximeter-client, exposed by oximeter-collector) Propolis (propolis-client, exposed by propolis-server) Sled Agent (sled-agent-client, exposed by omicron-sled-agent) Wicketd (wicketd-client, exposed by wicketd) Client-managed API: Bootstrap Agent (bootstrap-agent-client, exposed by omicron-sled-agent) reason: depends on itself (i.e., instances call each other) Dendrite DPD (dpd-client, exposed by dpd) reason: Sled Agent calling DPD creates a circular dependency Wicketd Installinator (installinator-client, exposed by wicketd) reason: client is provided implicitly by the operator Nexus Internal API (nexus-client, exposed by omicron-nexus) reason: Circular dependencies between Nexus and other services Crucible Repair (repair-client, exposed by crucible-downstairs) reason: depends on itself (i.e., instances call each other) Repo Depot API (repo-depot-client, exposed by omicron-sled-agent) reason: depends on itself (i.e., instances call each other) APIs with unknown version management: none eliza@theseus ~/Code/oxide/omicron $ cargo xtask ls-apis servers Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.35s Running `target/debug/xtask ls-apis servers` Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s Running `target/debug/ls-apis servers` loading metadata for workspace omicron from current workspace loading metadata for workspace lldp from /home/eliza/.cargo/git/checkouts/lldp-d47de417041f191b/ce952e6/Cargo.toml loading metadata for workspace maghemite from /home/eliza/.cargo/git/checkouts/maghemite-c0236f0fd3d582b6/caafd88/Cargo.toml loading metadata for workspace propolis from /home/eliza/.cargo/git/checkouts/propolis-d68c8bd1bc59c9bd/6b5f2af/Cargo.toml loading metadata for workspace crucible from /home/eliza/.cargo/git/checkouts/crucible-0a48bd218bc2bbbc/81a3528/Cargo.toml loading metadata for workspace dendrite from /home/eliza/.cargo/git/checkouts/dendrite-ae9f1715c17fc765/a66561e/Cargo.toml note: ignoring Cargo dependency from crucible-pantry -> ... -> crucible-control-client note: ignoring Cargo dependency from omicron-sled-agent -> dns-server omicron-clickhouse-admin (omicron/clickhouse-admin) exposes: Clickhouse Cluster Admin for Keepers (client = clickhouse-admin-keeper-client) exposes: Clickhouse Cluster Admin for Servers (client = clickhouse-admin-server-client) exposes: Clickhouse Single-Node Cluster Admin (client = clickhouse-admin-single-client) omicron-cockroach-admin (omicron/cockroach-admin) exposes: CockroachDB Cluster Admin (client = cockroach-admin-client) crucible-agent (crucible/agent) exposes: Crucible Agent (client = crucible-agent-client) crucible-downstairs (crucible/downstairs) exposes: Crucible Repair (client = repair-client) consumes: repair-client crucible-pantry (crucible/pantry) exposes: Crucible Pantry (client = crucible-pantry-client) consumes: nexus-client dns-server (omicron/dns-server) exposes: DNS Server (client = dns-service-client) omicron-sled-agent (omicron/sled-agent) exposes: Bootstrap Agent (client = bootstrap-agent-client) exposes: Ereporter (client = ereporter-client) exposes: Repo Depot API (client = repo-depot-client) exposes: Sled Agent (client = sled-agent-client) consumes: bootstrap-agent-client consumes: ddm-admin-client consumes: dns-service-client consumes: dpd-client consumes: gateway-client consumes: mg-admin-client consumes: nexus-client consumes: propolis-client consumes: repo-depot-client propolis-server (propolis/bin/propolis-server) exposes: Crucible Control (for testing only) (client = crucible-control-client) exposes: Propolis (client = propolis-client) consumes: nexus-client ddmd (maghemite/ddmd) exposes: Maghemite DDM Admin (client = ddm-admin-client) consumes: dpd-client dpd (dendrite/dpd) exposes: Dendrite DPD (client = dpd-client) consumes: gateway-client consumes: nexus-client lldpd (lldp/lldpd) exposes: LLDP daemon (client = lldpd-client) mgd (maghemite/mgd) exposes: Maghemite MG Admin (client = mg-admin-client) consumes: ddm-admin-client consumes: dpd-client omicron-gateway (omicron/gateway) exposes: Ereporter (client = ereporter-client) exposes: Management Gateway Service (client = gateway-client) tfportd (dendrite/tfportd) consumes: dpd-client consumes: lldpd-client wicketd (omicron/wicketd) exposes: Wicketd Installinator (client = installinator-client) exposes: Wicketd (client = wicketd-client) consumes: bootstrap-agent-client consumes: ddm-admin-client consumes: dpd-client consumes: gateway-client installinator (omicron/installinator) consumes: ddm-admin-client consumes: installinator-client omicron-nexus (omicron/nexus) exposes: Nexus Internal API (client = nexus-client) exposes: External API (client = oxide-client) consumes: clickhouse-admin-keeper-client consumes: clickhouse-admin-server-client consumes: clickhouse-admin-single-client consumes: cockroach-admin-client consumes: crucible-agent-client consumes: crucible-pantry-client consumes: dns-service-client consumes: dpd-client consumes: ereporter-client consumes: gateway-client consumes: lldpd-client consumes: mg-admin-client consumes: oximeter-client consumes: propolis-client consumes: sled-agent-client oximeter-collector (omicron/oximeter/collector) exposes: Oximeter (client = oximeter-client) consumes: nexus-client ````
1 parent df35ee9 commit 67a8984

File tree

3 files changed

+179
-168
lines changed

3 files changed

+179
-168
lines changed

dev-tools/ls-apis/src/bin/ls-apis.rs

+26-33
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use anyhow::{Context, Result, bail};
88
use camino::Utf8PathBuf;
99
use clap::{Args, Parser, Subcommand};
1010
use omicron_ls_apis::{
11-
AllApiMetadata, ApiDependencyFilter, LoadArgs, ServerComponentName,
12-
SystemApis, VersionedHow,
11+
AllApiMetadata, ApiDependencyFilter, ApiMetadata, LoadArgs,
12+
ServerComponentName, SystemApis, VersionedHow,
1313
};
1414
use parse_display::{Display, FromStr};
1515

@@ -108,17 +108,15 @@ fn run_adoc(apis: &SystemApis) -> Result<()> {
108108

109109
let metadata = apis.api_metadata();
110110
for api in metadata.apis() {
111-
let Some(server_component) =
112-
apis.api_producer(&api.client_package_name)
113-
else {
114-
continue;
115-
};
116111
println!("// DO NOT EDIT. This table is auto-generated. See above.");
117-
println!("|{}", api.label);
118-
println!("|{}", apis.adoc_label(server_component)?);
119112
println!("|{}", apis.adoc_label(&api.client_package_name)?);
113+
120114
println!("|");
115+
for server_component in apis.api_producers(&api.client_package_name) {
116+
println!("* {}", apis.adoc_label(server_component)?);
117+
}
121118

119+
println!("|");
122120
for (c, _) in apis.api_consumers(
123121
&api.client_package_name,
124122
ApiDependencyFilter::default(),
@@ -214,12 +212,10 @@ fn print_server_components<'a>(
214212
for s in server_components.into_iter() {
215213
let (repo_name, pkg_path) = apis.package_label(s)?;
216214
println!("{}{} ({}/{})", prefix, s, repo_name, pkg_path);
217-
for api in metadata.apis().filter(|a| {
218-
matches!(
219-
apis.api_producer(&a.client_package_name),
220-
Some (name) if name == s
221-
)
222-
}) {
215+
for api in metadata
216+
.apis()
217+
.filter(|a| apis.is_producer_of(s, &a.client_package_name))
218+
{
223219
println!(
224220
"{} exposes: {} (client = {})",
225221
prefix, api.label, api.client_package_name
@@ -301,31 +297,33 @@ fn run_check(apis: &SystemApis) -> Result<()> {
301297
);
302298
}
303299

300+
fn print_api_and_producers(api: &ApiMetadata, apis: &SystemApis) {
301+
print!(" {} ({}", api.label, api.client_package_name,);
302+
let mut producers = apis.api_producers(&api.client_package_name);
303+
if let Some(producer) = producers.next() {
304+
print!(", exposed by {producer}");
305+
for producer in producers {
306+
print!(", {producer}")
307+
}
308+
}
309+
println!(")");
310+
}
311+
304312
println!("\n");
305313
println!("Server-managed APIs:\n");
306314
for api in apis
307315
.api_metadata()
308316
.apis()
309317
.filter(|f| f.deployed() && f.versioned_how == VersionedHow::Server)
310318
{
311-
println!(
312-
" {} ({}, exposed by {})",
313-
api.label,
314-
api.client_package_name,
315-
apis.api_producer(&api.client_package_name).unwrap()
316-
);
319+
print_api_and_producers(api, apis);
317320
}
318321

319322
println!("\n");
320323
println!("Client-managed API:\n");
321324
for api in apis.api_metadata().apis().filter(|f| f.deployed()) {
322325
if let VersionedHow::Client(reason) = &api.versioned_how {
323-
println!(
324-
" {} ({}, exposed by {})",
325-
api.label,
326-
api.client_package_name,
327-
apis.api_producer(&api.client_package_name).unwrap()
328-
);
326+
print_api_and_producers(api, apis);
329327
println!(" reason: {}", reason);
330328
}
331329
}
@@ -342,12 +340,7 @@ fn run_check(apis: &SystemApis) -> Result<()> {
342340
} else {
343341
println!("\n");
344342
for api in unknown {
345-
println!(
346-
" {} ({}, exposed by {})",
347-
api.label,
348-
api.client_package_name,
349-
apis.api_producer(&api.client_package_name).unwrap()
350-
);
343+
print_api_and_producers(api, apis);
351344
}
352345
bail!("at least one API has unknown version strategy (see above)");
353346
}

dev-tools/ls-apis/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod system_apis;
1010
mod workspaces;
1111

1212
pub use api_metadata::AllApiMetadata;
13+
pub use api_metadata::ApiMetadata;
1314
pub use api_metadata::VersionedHow;
1415
pub use system_apis::ApiDependencyFilter;
1516
pub use system_apis::SystemApis;

0 commit comments

Comments
 (0)