Skip to content

Commit ecfab33

Browse files
authored
[sled-agent] move zone image source resolver into a new crate (#8079)
1 parent dfa4df0 commit ecfab33

File tree

7 files changed

+161
-55
lines changed

7 files changed

+161
-55
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ members = [
127127
"sled-agent/config-reconciler",
128128
"sled-agent/repo-depot-api",
129129
"sled-agent/types",
130+
"sled-agent/zone-images",
130131
"sled-diagnostics",
131132
"sled-hardware",
132133
"sled-hardware/types",
@@ -279,6 +280,7 @@ default-members = [
279280
"sled-agent/config-reconciler",
280281
"sled-agent/repo-depot-api",
281282
"sled-agent/types",
283+
"sled-agent/zone-images",
282284
"sled-diagnostics",
283285
"sled-hardware",
284286
"sled-hardware/types",
@@ -666,6 +668,7 @@ sled-agent-api = { path = "sled-agent/api" }
666668
sled-agent-client = { path = "clients/sled-agent-client" }
667669
sled-agent-config-reconciler = { path = "sled-agent/config-reconciler" }
668670
sled-agent-types = { path = "sled-agent/types" }
671+
sled-agent-zone-images = { path = "sled-agent/zone-images" }
669672
sled-diagnostics = { path = "sled-diagnostics" }
670673
sled-hardware = { path = "sled-hardware" }
671674
sled-hardware-types = { path = "sled-hardware/types" }

sled-agent/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ sled-agent-api.workspace = true
8585
sled-agent-client.workspace = true
8686
sled-agent-config-reconciler.workspace = true
8787
sled-agent-types.workspace = true
88+
sled-agent-zone-images.workspace = true
8889
sled-diagnostics.workspace = true
8990
sled-hardware.workspace = true
9091
sled-hardware-types.workspace = true

sled-agent/src/services.rs

Lines changed: 11 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,13 @@ use sled_agent_types::{
102102
sled::SWITCH_ZONE_BASEBOARD_FILE, time_sync::TimeSync,
103103
zone_bundle::ZoneBundleCause,
104104
};
105+
use sled_agent_zone_images::ZoneImageSourceResolver;
105106
use sled_hardware::SledMode;
106107
use sled_hardware::is_gimlet;
107108
use sled_hardware::underlay;
108109
use sled_hardware_types::Baseboard;
109110
use sled_storage::config::MountConfig;
110-
use sled_storage::dataset::{
111-
CONFIG_DATASET, INSTALL_DATASET, M2_ARTIFACT_DATASET, ZONE_DATASET,
112-
};
111+
use sled_storage::dataset::{CONFIG_DATASET, ZONE_DATASET};
113112
use sled_storage::manager::StorageHandle;
114113
use slog::Logger;
115114
use slog_error_chain::InlineErrorChain;
@@ -779,8 +778,8 @@ pub struct ServiceManagerInner {
779778
switch_zone_bootstrap_address: Ipv6Addr,
780779
storage: StorageHandle,
781780
zone_bundler: ZoneBundler,
781+
zone_image_resolver: ZoneImageSourceResolver,
782782
ledger_directory_override: OnceLock<Utf8PathBuf>,
783-
image_directory_override: OnceLock<Utf8PathBuf>,
784783
system_api: Box<dyn SystemApi>,
785784
}
786785

@@ -991,8 +990,8 @@ impl ServiceManager {
991990
.switch_zone_bootstrap_ip,
992991
storage,
993992
zone_bundler,
993+
zone_image_resolver: ZoneImageSourceResolver::new(),
994994
ledger_directory_override: OnceLock::new(),
995-
image_directory_override: OnceLock::new(),
996995
system_api,
997996
}),
998997
}
@@ -1005,7 +1004,7 @@ impl ServiceManager {
10051004

10061005
#[cfg(all(test, target_os = "illumos"))]
10071006
fn override_image_directory(&self, path: Utf8PathBuf) {
1008-
self.inner.image_directory_override.set(path).unwrap();
1007+
self.inner.zone_image_resolver.override_image_directory(path);
10091008
}
10101009

10111010
pub(crate) fn ddm_reconciler(&self) -> &DdmReconciler {
@@ -1725,54 +1724,11 @@ impl ServiceManager {
17251724
ZoneArgs::Omicron(zone_config) => &zone_config.zone.image_source,
17261725
ZoneArgs::Switch(_) => &OmicronZoneImageSource::InstallDataset,
17271726
};
1728-
let zone_image_file_name = match image_source {
1729-
OmicronZoneImageSource::InstallDataset => None,
1730-
OmicronZoneImageSource::Artifact { hash } => Some(hash.to_string()),
1731-
};
17321727
let all_disks = self.inner.storage.get_latest_disks().await;
1733-
let zone_image_paths = match image_source {
1734-
OmicronZoneImageSource::InstallDataset => {
1735-
// Look for the image in the ramdisk first
1736-
let mut zone_image_paths =
1737-
vec![Utf8PathBuf::from("/opt/oxide")];
1738-
// Inject an image path if requested by a test.
1739-
if let Some(path) = self.inner.image_directory_override.get() {
1740-
zone_image_paths.push(path.clone());
1741-
};
1742-
1743-
// If the boot disk exists, look for the image in the "install"
1744-
// dataset there too.
1745-
if let Some((_, boot_zpool)) = all_disks.boot_disk() {
1746-
zone_image_paths.push(boot_zpool.dataset_mountpoint(
1747-
&all_disks.mount_config().root,
1748-
INSTALL_DATASET,
1749-
));
1750-
}
1751-
1752-
zone_image_paths
1753-
}
1754-
OmicronZoneImageSource::Artifact { .. } => {
1755-
// Search both artifact datasets, but look on the boot disk first.
1756-
let boot_zpool =
1757-
all_disks.boot_disk().map(|(_, boot_zpool)| boot_zpool);
1758-
// This iterator starts with the zpool for the boot disk (if it
1759-
// exists), and then is followed by all other zpools.
1760-
let zpool_iter = boot_zpool.into_iter().chain(
1761-
all_disks
1762-
.all_m2_zpools()
1763-
.into_iter()
1764-
.filter(|zpool| Some(zpool) != boot_zpool.as_ref()),
1765-
);
1766-
zpool_iter
1767-
.map(|zpool| {
1768-
zpool.dataset_mountpoint(
1769-
&all_disks.mount_config().root,
1770-
M2_ARTIFACT_DATASET,
1771-
)
1772-
})
1773-
.collect()
1774-
}
1775-
};
1728+
let file_source = self
1729+
.inner
1730+
.zone_image_resolver
1731+
.file_source_for(image_source, &all_disks);
17761732

17771733
let zone_type_str = match &request {
17781734
ZoneArgs::Omicron(zone_config) => {
@@ -1796,14 +1752,14 @@ impl ServiceManager {
17961752
if let Some(vnic) = bootstrap_vnic {
17971753
zone_builder = zone_builder.with_bootstrap_vnic(vnic);
17981754
}
1799-
if let Some(file_name) = &zone_image_file_name {
1755+
if let Some(file_name) = &file_source.file_name {
18001756
zone_builder = zone_builder.with_zone_image_file_name(file_name);
18011757
}
18021758
let installed_zone = zone_builder
18031759
.with_log(self.inner.log.clone())
18041760
.with_underlay_vnic_allocator(&self.inner.underlay_vnic_allocator)
18051761
.with_zone_root_path(zone_root_path)
1806-
.with_zone_image_paths(zone_image_paths.as_slice())
1762+
.with_zone_image_paths(file_source.search_paths.as_slice())
18071763
.with_zone_type(zone_type_str)
18081764
.with_datasets(datasets.as_slice())
18091765
.with_filesystems(&filesystems)

sled-agent/zone-images/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "sled-agent-zone-images"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "MPL-2.0"
6+
7+
[lints]
8+
workspace = true
9+
10+
[dependencies]
11+
anyhow.workspace = true
12+
camino.workspace = true
13+
nexus-sled-agent-shared.workspace = true
14+
omicron-workspace-hack.workspace = true
15+
sled-storage.workspace = true

sled-agent/zone-images/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
//! Management of Omicron zone images within sled-agent.
6+
//!
7+
//! This contains a subset of zone image code at the moment: you're encouraged
8+
//! to move more code into this crate as appropriate.
9+
10+
mod source_resolver;
11+
12+
pub use source_resolver::*;
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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+
//! Zone image lookup.
6+
7+
use camino::Utf8PathBuf;
8+
use nexus_sled_agent_shared::inventory::OmicronZoneImageSource;
9+
use sled_storage::dataset::INSTALL_DATASET;
10+
use sled_storage::dataset::M2_ARTIFACT_DATASET;
11+
use sled_storage::resources::AllDisks;
12+
use std::sync::OnceLock;
13+
14+
/// Places to look for an Omicron zone image.
15+
pub struct ZoneImageFileSource {
16+
/// A custom file name to look for, if provided.
17+
///
18+
/// The default file name is `<zone_type>.tar.gz`.
19+
pub file_name: Option<String>,
20+
21+
/// The paths to look for the zone image in.
22+
///
23+
/// This represents a high-confidence belief, but not a guarantee, that the
24+
/// zone image will be found in one of these locations.
25+
pub search_paths: Vec<Utf8PathBuf>,
26+
}
27+
28+
/// Resolves [`OmicronZoneImageSource`] instances into file names and search
29+
/// paths.
30+
pub struct ZoneImageSourceResolver {
31+
image_directory_override: OnceLock<Utf8PathBuf>,
32+
}
33+
34+
impl ZoneImageSourceResolver {
35+
pub fn new() -> Self {
36+
ZoneImageSourceResolver { image_directory_override: OnceLock::new() }
37+
}
38+
39+
/// Overrides the image directory with another one.
40+
///
41+
/// Intended for testing.
42+
pub fn override_image_directory(&self, path: Utf8PathBuf) {
43+
self.image_directory_override.set(path).unwrap();
44+
}
45+
46+
/// Returns a [`ZoneImageFileSource`] consisting of the file name, plus a
47+
/// list of potential paths to search, for a zone image.
48+
pub fn file_source_for(
49+
&self,
50+
image_source: &OmicronZoneImageSource,
51+
all_disks: &AllDisks,
52+
) -> ZoneImageFileSource {
53+
let file_name = match image_source {
54+
OmicronZoneImageSource::InstallDataset => {
55+
// Use the default file name for install-dataset lookups.
56+
None
57+
}
58+
OmicronZoneImageSource::Artifact { hash } => Some(hash.to_string()),
59+
};
60+
61+
let search_paths = match image_source {
62+
OmicronZoneImageSource::InstallDataset => {
63+
// Look for the image in the ramdisk first
64+
let mut zone_image_paths =
65+
vec![Utf8PathBuf::from("/opt/oxide")];
66+
// Inject an image path if requested by a test.
67+
if let Some(path) = self.image_directory_override.get() {
68+
zone_image_paths.push(path.clone());
69+
};
70+
71+
// If the boot disk exists, look for the image in the "install"
72+
// dataset there too.
73+
if let Some((_, boot_zpool)) = all_disks.boot_disk() {
74+
zone_image_paths.push(boot_zpool.dataset_mountpoint(
75+
&all_disks.mount_config().root,
76+
INSTALL_DATASET,
77+
));
78+
}
79+
80+
zone_image_paths
81+
}
82+
OmicronZoneImageSource::Artifact { .. } => {
83+
// Search both artifact datasets, but look on the boot disk first.
84+
let boot_zpool =
85+
all_disks.boot_disk().map(|(_, boot_zpool)| boot_zpool);
86+
// This iterator starts with the zpool for the boot disk (if it
87+
// exists), and then is followed by all other zpools.
88+
let zpool_iter = boot_zpool.into_iter().chain(
89+
all_disks
90+
.all_m2_zpools()
91+
.into_iter()
92+
.filter(|zpool| Some(zpool) != boot_zpool.as_ref()),
93+
);
94+
zpool_iter
95+
.map(|zpool| {
96+
zpool.dataset_mountpoint(
97+
&all_disks.mount_config().root,
98+
M2_ARTIFACT_DATASET,
99+
)
100+
})
101+
.collect()
102+
}
103+
};
104+
105+
ZoneImageFileSource { file_name, search_paths }
106+
}
107+
}

0 commit comments

Comments
 (0)