Skip to content

Commit 5465ccf

Browse files
authored
[10/n] [sled-agent] validate zone images as written in zone manifest (#8190)
Included in this PR: * Add validation for zone images * Write tests for success and failure cases * Extend the `test_installinator_fetch` wicketd integration test to also ensure installinator + sled-agent have a coherent view of the world The last point forced me to expose a bunch of the status via a public interface. We'll be able to reuse this information (though probably not in this full form) and send it up as part of the inventory.
1 parent 6c93e44 commit 5465ccf

File tree

15 files changed

+2264
-613
lines changed

15 files changed

+2264
-613
lines changed

Cargo.lock

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

common/src/update/zone_manifest.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5+
use std::fmt;
6+
57
use iddqd::{IdOrdItem, IdOrdMap, id_upcast};
68
use omicron_uuid_kinds::MupdateUuid;
79
use serde::{Deserialize, Serialize};
@@ -10,9 +12,8 @@ use tufaceous_artifact::ArtifactHash;
1012
/// Describes the set of Omicron zones written out into an install dataset.
1113
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
1214
pub struct OmicronZoneManifest {
13-
/// The UUID of the mupdate which created this manifest. Intended primarily
14-
/// for checking equality.
15-
pub mupdate_id: MupdateUuid,
15+
/// The source of the manifest.
16+
pub source: OmicronZoneManifestSource,
1617

1718
/// Omicron zone file names and hashes.
1819
pub zones: IdOrdMap<OmicronZoneFileMetadata>,
@@ -23,6 +24,34 @@ impl OmicronZoneManifest {
2324
pub const FILE_NAME: &str = "zones.json";
2425
}
2526

27+
/// The source of truth for an Omicron zone manifest.
28+
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
29+
pub enum OmicronZoneManifestSource {
30+
/// The manifest was written out by installinator and the mupdate process.
31+
Installinator {
32+
/// The UUID of the mupdate.
33+
mupdate_id: MupdateUuid,
34+
},
35+
36+
/// The zone manifest was not found during the install process. A synthetic
37+
/// zone manifest was generated by Sled Agent by looking at all the
38+
/// `.tar.gz` files in the install dataset.
39+
SledAgent,
40+
}
41+
42+
impl fmt::Display for OmicronZoneManifestSource {
43+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44+
match self {
45+
OmicronZoneManifestSource::Installinator { mupdate_id } => {
46+
write!(f, "installinator (mupdate ID: {})", mupdate_id)
47+
}
48+
OmicronZoneManifestSource::SledAgent => {
49+
write!(f, "sled-agent")
50+
}
51+
}
52+
}
53+
}
54+
2655
/// Information about an Omicron zone file written out to the install dataset.
2756
///
2857
/// Part of [`OmicronZoneManifest`].

installinator/src/write.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use omicron_common::{
2525
disk::M2Slot,
2626
update::{
2727
MupdateOverrideInfo, OmicronZoneFileMetadata, OmicronZoneManifest,
28+
OmicronZoneManifestSource,
2829
},
2930
};
3031
use omicron_uuid_kinds::{MupdateOverrideUuid, MupdateUuid};
@@ -63,7 +64,8 @@ struct ArtifactDestination {
6364

6465
impl ArtifactDestination {
6566
fn from_directory(dir: &Utf8Path) -> Result<Self> {
66-
let control_plane_dir = dir.join("zones");
67+
// The install dataset goes into a directory called "install".
68+
let control_plane_dir = dir.join("install");
6769
std::fs::create_dir_all(&control_plane_dir)
6870
.with_context(|| format!("error creating directories at {dir}"))?;
6971

@@ -827,8 +829,12 @@ impl ControlPlaneZoneWriteContext<'_> {
827829
async fn omicron_zone_manifest_artifact(&self) -> BufList {
828830
let zones = compute_zone_hashes(&self.zones).await;
829831

830-
let omicron_zone_manifest =
831-
OmicronZoneManifest { mupdate_id: self.mupdate_id, zones };
832+
let omicron_zone_manifest = OmicronZoneManifest {
833+
source: OmicronZoneManifestSource::Installinator {
834+
mupdate_id: self.mupdate_id,
835+
},
836+
zones,
837+
};
832838
let json_bytes = serde_json::to_vec(&omicron_zone_manifest)
833839
.expect("this serialization is infallible");
834840
BufList::from(json_bytes)

sled-agent/zone-images/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,21 @@ illumos-utils.workspace = true
1515
nexus-sled-agent-shared.workspace = true
1616
omicron-common.workspace = true
1717
omicron-workspace-hack.workspace = true
18+
rayon.workspace = true
19+
serde.workspace = true
1820
serde_json.workspace = true
21+
sha2.workspace = true
1922
sled-agent-config-reconciler.workspace = true
2023
sled-storage.workspace = true
2124
slog.workspace = true
2225
slog-error-chain.workspace = true
2326
thiserror.workspace = true
27+
tufaceous-artifact.workspace = true
2428

2529
[dev-dependencies]
2630
camino-tempfile-ext.workspace = true
2731
dropshot.workspace = true
32+
expectorate.workspace = true
2833
omicron-uuid-kinds.workspace = true
2934
pretty_assertions.workspace = true
3035
sled-agent-config-reconciler = { workspace = true, features = ["testing"] }

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
use std::{io, sync::Arc};
6+
use thiserror::Error;
7+
8+
/// An `io::Error` wrapper that implements `Clone` and `PartialEq`.
9+
#[derive(Clone, Debug, Error)]
10+
#[error(transparent)]
11+
pub struct ArcIoError(pub Arc<io::Error>);
12+
13+
impl ArcIoError {
14+
pub(crate) fn new(error: io::Error) -> Self {
15+
Self(Arc::new(error))
16+
}
17+
}
18+
19+
/// Testing aid.
20+
impl PartialEq for ArcIoError {
21+
fn eq(&self, other: &Self) -> bool {
22+
// Simply comparing io::ErrorKind is good enough for tests.
23+
self.0.kind() == other.0.kind()
24+
}
25+
}
26+
27+
/// A `serde_json::Error` that implements `Clone` and `PartialEq`.
28+
#[derive(Clone, Debug, Error)]
29+
#[error(transparent)]
30+
pub struct ArcSerdeJsonError(pub Arc<serde_json::Error>);
31+
32+
impl ArcSerdeJsonError {
33+
pub(crate) fn new(error: serde_json::Error) -> Self {
34+
Self(Arc::new(error))
35+
}
36+
}
37+
38+
/// Testing aid.
39+
impl PartialEq for ArcSerdeJsonError {
40+
fn eq(&self, other: &Self) -> bool {
41+
// Simply comparing line/column/category is good enough for tests.
42+
self.0.line() == other.0.line()
43+
&& self.0.column() == other.0.column()
44+
&& self.0.classify() == other.0.classify()
45+
}
46+
}

0 commit comments

Comments
 (0)