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
227 changes: 50 additions & 177 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 1 addition & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -582,11 +582,10 @@ kstat-rs = "0.2.4"
libc = "0.2.174"
linkme = "0.3"
libipcc = { git = "https://github.com/oxidecomputer/ipcc-rs", rev = "524eb8f125003dff50b9703900c6b323f00f9e1b" }
libfalcon = { git = "https://github.com/oxidecomputer/falcon", branch = "main" }
libnvme = { git = "https://github.com/oxidecomputer/libnvme", rev = "dd5bb221d327a1bc9287961718c3c10d6bd37da0" }
linear-map = "1.2.0"
live-tests-macros = { path = "live-tests/macros" }
lldpd_client = { git = "https://github.com/oxidecomputer/lldp", package = "lldpd-client" }
lldpd_client = { git = "https://github.com/oxidecomputer/lldp", rev = "c3305fd1a7ea7aba31f3834757a6b931e4f59fe6", package = "lldpd-client" }
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of our other packages have specific Git commits in Cargo.toml and that seems appropriate so I've done that here. In practice, this also updates this dependency. I now see that was a breaking change so there's more work here to be done. I'll split this into a separate PR.

lldp_protocol = { git = "https://github.com/oxidecomputer/lldp", package = "protocol" }
macaddr = { version = "1.0.1", features = ["serde_std"] }
maplit = "1.0.2"
Expand Down Expand Up @@ -1066,9 +1065,6 @@ opt-level = 3
# [patch."https://github.com/oxidecomputer/diesel-dtrace"]
# diesel-dtrace = { path = "../diesel-dtrace" }

# [patch."https://github.com/oxidecomputer/falcon"]
# libfalcon = { path = "../falcon/lib" }

# [patch."https://github.com/oxidecomputer/propolis"]
# propolis-client = { path = "../propolis/lib/propolis-client" }
# propolis-mock-server = { path = "../propolis/bin/mock-server" }
Expand Down
1 change: 1 addition & 0 deletions dev-tools/ls-apis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ iddqd.workspace = true
indent_write.workspace = true
itertools.workspace = true
newtype_derive.workspace = true
omicron-zone-package.workspace = true
parse-display.workspace = true
petgraph.workspace = true
serde.workspace = true
Expand Down
9 changes: 8 additions & 1 deletion dev-tools/ls-apis/src/bin/ls-apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,11 +299,18 @@ impl TryFrom<&LsApis> for LoadArgs {
let self_manifest_dir_str = std::env::var("CARGO_MANIFEST_DIR")
.context("expected CARGO_MANIFEST_DIR in environment")?;
let self_manifest_dir = Utf8PathBuf::from(self_manifest_dir_str);
// The API manifest is at the root of this particular package.
let api_manifest_path = args
.api_manifest
.clone()
.unwrap_or_else(|| self_manifest_dir.join("api-manifest.toml"));
Ok(LoadArgs { api_manifest_path })

// This package is two levels down from the workspace root.
let mut workspace_root = self_manifest_dir;
workspace_root.pop();
workspace_root.pop();

Ok(LoadArgs { workspace_root, api_manifest_path })
}
}

Expand Down
3 changes: 3 additions & 0 deletions dev-tools/ls-apis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ impl Borrow<String> for ServerPackageName {

/// Parameters for loading information about system APIs
pub struct LoadArgs {
/// path to the root of the Omicron workspace
pub workspace_root: Utf8PathBuf,

/// path to developer-maintained API metadata
pub api_manifest_path: Utf8PathBuf,
}
Expand Down
3 changes: 2 additions & 1 deletion dev-tools/ls-apis/src/system_apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ impl SystemApis {
let api_metadata: AllApiMetadata =
parse_toml_file(&args.api_manifest_path)?;
// Load Cargo metadata and validate it against the manifest.
let (workspaces, warnings) = Workspaces::load(&api_metadata)?;
let (workspaces, warnings) =
Workspaces::load(&api_metadata, &args.workspace_root)?;
if !warnings.is_empty() {
// We treat these warnings as fatal here.
for e in warnings {
Expand Down
251 changes: 179 additions & 72 deletions dev-tools/ls-apis/src/workspaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,59 @@
use crate::ClientPackageName;
use crate::api_metadata::AllApiMetadata;
use crate::cargo::Workspace;
use anyhow::{Context, Result, anyhow, ensure};
use anyhow::{Context, Result, anyhow, bail, ensure};
use camino::Utf8Path;
use cargo_metadata::CargoOpt;
use cargo_metadata::Package;
use cargo_metadata::PackageId;
use omicron_zone_package::package::PackageSource;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::sync::Arc;
use std::sync::LazyLock;

struct RelatedRepoConfig {
repo_name: &'static str,
expected_pkg_name: &'static str,
extra_cargo_features: Option<CargoOpt>,
}

static RELATED_REPOS: LazyLock<[RelatedRepoConfig; 5]> = LazyLock::new(|| {
[
RelatedRepoConfig {
repo_name: "crucible",
expected_pkg_name: "crucible-agent-client",
extra_cargo_features: None,
},
RelatedRepoConfig {
repo_name: "dendrite",
expected_pkg_name: "dpd-client",
extra_cargo_features: None,
},
RelatedRepoConfig {
repo_name: "propolis",
expected_pkg_name: "propolis-client",
// The artifacts shipped from the Propolis repo (particularly,
// `propolis-server`) are built with the `omicron-build`
// feature, which is not enabled by default. Enable this
// feature when loading the Propolis repo metadata so that we
// see the dependency tree that a shipping system will have.
extra_cargo_features: Some(CargoOpt::SomeFeatures(vec![
String::from("omicron-build"),
])),
},
RelatedRepoConfig {
repo_name: "maghemite",
expected_pkg_name: "mg-admin-client",
extra_cargo_features: None,
},
RelatedRepoConfig {
repo_name: "lldp",
expected_pkg_name: "lldpd-client",
extra_cargo_features: None,
},
]
});

/// Thin wrapper around a list of workspaces that makes it easy to query which
/// workspace has which package
Expand All @@ -32,6 +77,7 @@ impl Workspaces {
/// of inconsistencies between API metadata and Cargo metadata.
pub fn load(
api_metadata: &AllApiMetadata,
workspace_root: &Utf8Path,
) -> Result<(Workspaces, Vec<anyhow::Error>)> {
// First, load information about the "omicron" workspace. This is the
// current workspace so we don't need to provide the path to it.
Expand All @@ -43,6 +89,23 @@ impl Workspaces {
ignored_non_clients,
)?);

// Next, load the top level package manifest. This will tell us for
// each related component (like Crucible, Maghemite, etc.), which commit
// of that component's repo Omicron actually deploys to running systems.
let package_manifest_path =
workspace_root.join("package-manifest.toml");
let package_manifest =
omicron_zone_package::config::parse(&package_manifest_path)
.with_context(|| {
format!("parsing {package_manifest_path:?}")
})?;
let mut related_repo_commits = BTreeMap::new();
for related_repo in &*RELATED_REPOS {
let commit =
find_repo_commit(&package_manifest, related_repo.repo_name)?;
related_repo_commits.insert(related_repo.repo_name, commit);
}

// In order to assemble this metadata, Cargo already has a clone of most
// of the other workspaces that we care about. We'll use those clones
// rather than manage our own.
Expand All @@ -58,43 +121,31 @@ impl Workspaces {
//
// If we had many more repos than this, we'd probably want to limit the
// concurrency.
let handles: Vec<_> = [
// To find this repo ... look up this package in Omicron
// | | +---- and enable these extra
// | | | features when loading
// v v v
("crucible", "crucible-agent-client", None),
("dendrite", "dpd-client", None),
(
"propolis",
"propolis-client",
// The artifacts shipped from the Propolis repo (particularly,
// `propolis-server`) are built with the `omicron-build`
// feature, which is not enabled by default. Enable this
// feature when loading the Propolis repo metadata so that we
// see the dependency tree that a shipping system will have.
Some(CargoOpt::SomeFeatures(vec![String::from(
"omicron-build",
)])),
),
("maghemite", "mg-admin-client", None),
("lldp", "lldpd-client", None),
]
.into_iter()
.map(|(repo, omicron_pkg, extra_features)| {
let mine = omicron.clone();
let my_ignored = ignored_non_clients.clone();
std::thread::spawn(move || {
load_dependent_repo(
&mine,
repo,
omicron_pkg,
extra_features,
my_ignored,
)
let handles: Vec<_> = RELATED_REPOS
.iter()
.map(|repo_config| {
let RelatedRepoConfig {
repo_name,
expected_pkg_name,
extra_cargo_features,
} = repo_config;
let mine = omicron.clone();
let my_ignored = ignored_non_clients.clone();
// unwrap(): we loaded a commit for each repo in the loop above
let expected_commit =
(*related_repo_commits.get(repo_name).unwrap()).clone();
std::thread::spawn(move || {
load_dependent_repo(
&mine,
repo_name,
expected_pkg_name,
extra_cargo_features.clone(),
my_ignored,
&expected_commit,
)
})
})
})
.collect();
.collect();

let mut workspaces: BTreeMap<_, _> = handles
.into_iter()
Expand All @@ -111,23 +162,6 @@ impl Workspaces {
Arc::into_inner(omicron).expect("no more Omicron Arc references"),
);

// To load Dendrite, we need to look something up in Maghemite (loaded
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to have been totally superfluous after #7907, which added "dendrite" to the block above instead.

// above).
let maghemite = workspaces
.get("maghemite")
.ok_or_else(|| anyhow!("missing maghemite workspaces"))?;

workspaces.insert(
String::from("dendrite"),
load_dependent_repo(
&maghemite,
"dendrite",
"dpd-client",
None,
ignored_non_clients.clone(),
)?,
);

// Validate the metadata against what we found in the workspaces.
let mut client_pkgnames_unused: BTreeSet<_> =
api_metadata.client_pkgnames().collect();
Expand Down Expand Up @@ -221,26 +255,61 @@ fn load_dependent_repo(
pkgname: &str,
extra_features: Option<CargoOpt>,
ignored_non_clients: BTreeSet<ClientPackageName>,
expected_commit: &str,
) -> Result<Workspace> {
// `Workspace` doesn't let us look up a non-workspace package by name
// because there may be many of them. So list all the pkgids and take any
// one of them -- any of them should work for our purpoes.
let pkgid = workspace.pkgids(pkgname).next().ok_or_else(|| {
anyhow!(
"workspace {} did not contain expected package {}",
workspace.name(),
pkgname
)
})?;
// It's possible to have more than one non-workspace package with a given
// name. For example, Omicron references `dpd-client` in multiple ways:
// from Nexus and through lldpd-client. So which version do we want? Well,
// the goal of looking up `dpd-client` is really to find the source for
// Dendrite. And we want the Dendrite that's actually going to run on a
// real system. That's the one specified by package-manifest.toml. The
// caller already figured out which commit that is and provided it in
// `expected_commit`.
//
// In summary: we're going to choose the package matching `expected_commit`.
let mut found_pkg = None;

for pkgid in workspace.pkgids(pkgname) {
let pkginfo = workspace.pkg_by_id(pkgid).with_context(|| {
format!("failed to find metadata for found package {pkgid:?}")
})?;

let Some(source) = &pkginfo.source else {
eprintln!(
"warn: looking up {pkgid:?}: unexpectedly found source `None`"
);
continue;
};

// This is cheesy, but it works okay for now and fails safely.
if source.repr.contains(expected_commit) {
found_pkg = Some(pkginfo);
break;
}

// Now we can look up the package metadata.
let pkg = workspace.pkg_by_id(pkgid).ok_or_else(|| {
anyhow!(
"workspace {}: did not contain expected package id {}",
workspace.name(),
pkgname
)
})?;
eprintln!(
"warn: looking up {pkgid:?}: looking for git commit \
{expected_commit} (based on package-manifest.toml), found \
source {source:?}"
);
eprintln!(
"If another version of package {pkgname:?} is found corresponding \
with this commit, then it may be suspicious to have multiple version \
of this package, but it will not break this tool."
);
eprintln!(
"If not, there's a mismatch between commits in package-manifest.toml \
and Cargo.toml or there is a bug in this tool."
);
}

let Some(pkg) = found_pkg else {
bail!(
"found no versions of package {pkgname:?} matching the git commit \
found in package-manifest.toml ({})",
expected_commit,
);
};

// The package metadata should show where the package's manifest file should
// be. This may be buried deep in the workspace. How do we find the root
Expand Down Expand Up @@ -284,3 +353,41 @@ fn load_dependent_repo(
&ignored_non_clients,
)
}

fn find_repo_commit(
package_manifest: &omicron_zone_package::config::Config,
repo_name: &str,
) -> Result<String> {
// We want to figure out which version of repo `repo_name` is actually
// deployed on real systems. To do that, we look at what's in the package
// manifest. All of the repos we care about are packaged into Omicron as
// prebuilt packages. The one hitch is that the same repo may appear
// multiple times in the package manifest. In that case, we require that
// they all be the same commit.
let candidates: BTreeSet<&String> = package_manifest
.packages
.iter()
.filter_map(|(_, pkg)| match &pkg.source {
PackageSource::Local { .. }
| PackageSource::Composite { .. }
| PackageSource::Manual => None,
PackageSource::Prebuilt { repo, commit, .. } => {
if repo == repo_name { Some(commit) } else { None }
}
})
.collect();

if candidates.is_empty() {
bail!("expected repo {repo_name:?} to appear in package-manifest.toml");
}

if candidates.len() > 1 {
bail!(
"expected repo {repo_name:?} to have exactly one git commit \
in package-manifest.toml, but found multiple: {candidates:?}",
);
}

// unwrap(): we already checked that there's at least one
Ok(candidates.into_iter().next().unwrap().clone())
}
2 changes: 1 addition & 1 deletion dev-tools/ls-apis/tests/api_dependencies.out
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Oximeter (client: oximeter-client)
consumed by: omicron-nexus (omicron/nexus) via 2 paths

Propolis (client: propolis-client)
consumed by: omicron-nexus (omicron/nexus) via 3 paths
consumed by: omicron-nexus (omicron/nexus) via 2 paths
consumed by: omicron-sled-agent (omicron/sled-agent) via 2 paths

Crucible Repair (client: repair-client)
Expand Down
4 changes: 2 additions & 2 deletions package-manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -720,8 +720,8 @@ output.intermediate_only = true
service_name = "lldp"
source.type = "prebuilt"
source.repo = "lldp"
source.commit = "61479b6922f9112fbe1e722414d2b8055212cb12"
source.sha256 = "8f988c0b0fa3ad4121ab0e825298601035e56c5c054bdc3a1dfb4d6c8fd5b300"
source.commit = "c3305fd1a7ea7aba31f3834757a6b931e4f59fe6"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This updates lldp to latest. I will split this into a separate PR (along with the lldpd-client dep change). I'm not sure what the implications are so I'll likely wait til the next release.

source.sha256 = "56603114fb9bce8855e80c6a8d4583eaa8fafe3edb03a5268950453dbf5c08e6"
output.type = "zone"
output.intermediate_only = true

Expand Down
Loading
Loading