Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 4 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion nexus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ internal-dns-resolver.workspace = true
internal-dns-types.workspace = true
ipnetwork.workspace = true
itertools.workspace = true
jiff.workspace = true
lldpd_client.workspace = true
macaddr.workspace = true
maplit.workspace = true
Expand Down Expand Up @@ -144,7 +145,7 @@ update-common.workspace = true
update-engine.workspace = true
omicron-workspace-hack.workspace = true
omicron-uuid-kinds.workspace = true
zip.workspace = true
zip = { workspace = true, features = ["jiff-02"] }

[dev-dependencies]
async-bb8-diesel.workspace = true
Expand Down
15 changes: 14 additions & 1 deletion nexus/src/app/background/tasks/support_bundle_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1241,10 +1241,23 @@ fn recursively_add_directory_to_zipfile(

let file_type = entry.file_type()?;
if file_type.is_file() {
let src = entry.path();

let zip_time = entry
.path()
.metadata()
.and_then(|m| m.modified())
.ok()
.and_then(|sys_time| jiff::Zoned::try_from(sys_time).ok())
.and_then(|zoned| {
zip::DateTime::try_from(zoned.datetime()).ok()
})
.unwrap_or_else(zip::DateTime::default);

let opts = FullFileOptions::default()
.last_modified_time(zip_time)
.compression_method(zip::CompressionMethod::Deflated)
.large_file(true);
let src = entry.path();

zip.start_file_from_path(dst, opts)?;
let mut file = std::fs::File::open(&src)?;
Expand Down
4 changes: 3 additions & 1 deletion sled-diagnostics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ workspace = true
anyhow.workspace = true
camino.workspace = true
cfg-if.workspace = true
chrono.workspace = true
fs-err = { workspace = true, features = ["tokio"] }
futures.workspace = true
illumos-utils.workspace = true
jiff.workspace = true
libc.workspace = true
omicron-workspace-hack.workspace = true
once_cell.workspace = true
Expand All @@ -26,7 +28,7 @@ slog.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["full"] }
zfs-test-harness.workspace = true
zip = { workspace = true, features = ["zstd"] }
zip = { workspace = true, features = ["jiff-02","zstd"] }

[dev-dependencies]
omicron-common.workspace = true
Expand Down
122 changes: 71 additions & 51 deletions sled-diagnostics/src/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,11 +451,11 @@ impl LogsHandle {
service: &str,
zip: &mut zip::ZipWriter<W>,
log_snapshots: &mut LogSnapshots,
logfile: &Utf8Path,
logfile: &LogFile,
logtype: LogType,
) -> Result<(), LogError> {
let snapshot_logfile =
self.find_log_in_snapshot(log_snapshots, logfile).await?;
self.find_log_in_snapshot(log_snapshots, &logfile.path).await?;

if logtype == LogType::Current {
// Since we are processing the current log files in a zone we need
Expand Down Expand Up @@ -501,13 +501,24 @@ impl LogsHandle {
.filter(|f| is_log_file(f.path(), filename))
{
let logfile = f.path();
let system_mtime =
f.metadata().and_then(|m| m.modified()).inspect_err(|e| {
Copy link
Contributor

Choose a reason for hiding this comment

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

metadata() here doesn't follow symlinks...which I believe is okay since we are operating from the gz context.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, this is fine today. On the one hand I don't foresee us copying over symlinks from a sled zip, but there's no harm in handling them just in case. Switched to Utf8Path::metadata which will traverse symlinks in fa07354.

warn!(&self.log, "sled-diagnostic failed to get mtime of logfile";
"error" => %e,
"logfile" => %logfile,
);
}).ok();
let mtime = system_mtime
.and_then(|m| jiff::Timestamp::try_from(m).ok());

if logfile.is_file() {
write_log_to_zip(
&self.log,
service,
zip,
LogType::Current,
logfile,
mtime,
)?;
}
}
Expand All @@ -522,6 +533,7 @@ impl LogsHandle {
zip,
logtype,
&snapshot_logfile,
logfile.modified,
)?;
}
false => {
Expand Down Expand Up @@ -612,7 +624,7 @@ impl LogsHandle {
&service,
&mut zip,
&mut log_snapshots,
&current.path,
&current,
LogType::Current,
)
.await?;
Expand All @@ -628,13 +640,13 @@ impl LogsHandle {
.archived
.into_iter()
.filter(|log| log.path.as_str().contains("crypt/debug"))
.map(|log| log.path)
.collect();

// Since these logs can be spread out across multiple U.2 devices
// we need to sort them by timestamp.
archived.sort_by_key(|log| {
log.as_str()
log.path
.as_str()
.rsplit_once(".")
.and_then(|(_, date)| date.parse::<u64>().ok())
.unwrap_or(0)
Expand Down Expand Up @@ -703,6 +715,7 @@ fn write_log_to_zip<W: Write + Seek>(
zip: &mut zip::ZipWriter<W>,
logtype: LogType,
snapshot_logfile: &Utf8Path,
mtime: Option<jiff::Timestamp>,
) -> Result<(), LogError> {
let Some(log_name) = snapshot_logfile.file_name() else {
warn!(
Expand All @@ -715,10 +728,19 @@ fn write_log_to_zip<W: Write + Seek>(
};

let mut src = File::open(&snapshot_logfile)?;

let zip_mtime = mtime
.and_then(|ts| {
let zoned = ts.in_tz("UTC").ok()?;
zip::DateTime::try_from(zoned.datetime()).ok()
})
.unwrap_or_else(zip::DateTime::default);

let zip_path = format!("{service}/{logtype}/{log_name}");
zip.start_file_from_path(
zip_path,
FullFileOptions::default()
.last_modified_time(zip_mtime)
.compression_method(zip::CompressionMethod::Zstd)
.compression_level(Some(3))
// NB: From the docs
Expand Down Expand Up @@ -757,8 +779,8 @@ enum ExtraLogKind<'a> {

#[derive(Debug, Default, PartialEq)]
struct ExtraLogs<'a> {
current: Option<&'a Utf8Path>,
rotated: Vec<&'a Utf8Path>,
current: Option<&'a LogFile>,
rotated: Vec<&'a LogFile>,
}

fn sort_extra_logs<'a>(
Expand All @@ -780,15 +802,15 @@ fn sort_extra_logs<'a>(
warn!(
logger,
"found multiple current log files for {name}";
"old" => %old_path,
"old" => %old_path.path,
"new" => %log.path,
);
}
entry.current = Some(&log.path);
entry.current = Some(&log);
}
ExtraLogKind::Rotated { name, log } => {
let entry = res.entry(name).or_default();
entry.rotated.push(&log.path);
entry.rotated.push(&log);
}
}
}
Expand Down Expand Up @@ -838,9 +860,9 @@ fn sort_cockroach_extra_logs(logs: &[LogFile]) -> HashMap<&str, ExtraLogs<'_>> {
let entry = interested.entry(prefix).or_default();

if file_name == format!("{prefix}.log") {
entry.current = Some(log.path.as_path());
entry.current = Some(log);
} else {
entry.rotated.push(log.path.as_path());
entry.rotated.push(log);
}
}
}
Expand Down Expand Up @@ -917,52 +939,30 @@ mod test {
"bogus.log",
"some/dir"
].into_iter().map(|l| {
oxlog::LogFile { path: Utf8PathBuf::from(l), size: None, modified: None }
}).collect();
oxlog::LogFile { path: Utf8PathBuf::from(l), size: None, modified: None }
}).collect();
let logs_map: HashMap<_, _> =
logs.iter().map(|l| (l.path.as_str(), l)).collect();

let mut expected: HashMap<&str, ExtraLogs<'_>> = HashMap::new();

// cockroach
expected.entry("cockroach").or_default().current =
Some(Utf8Path::new("cockroach.log"));
expected
.entry("cockroach")
.or_default()
.rotated
.push(Utf8Path::new("cockroach.oxzcockroachdba3628a56-6f85-43b5-be50-71d8f0e04877.root.2025-01-31T17_11_45Z.011435.log"));
expected
.entry("cockroach")
.or_default()
.rotated
.push(Utf8Path::new("cockroach.oxzcockroachdba3628a56-6f85-43b5-be50-71d8f0e04877.root.2025-02-01T01_51_51Z.011486.log"));
Some(&logs_map["cockroach.log"]);
expected.entry("cockroach").or_default().rotated.push(&logs_map["cockroach.oxzcockroachdba3628a56-6f85-43b5-be50-71d8f0e04877.root.2025-01-31T17_11_45Z.011435.log"]);
expected.entry("cockroach").or_default().rotated.push(&logs_map["cockroach.oxzcockroachdba3628a56-6f85-43b5-be50-71d8f0e04877.root.2025-02-01T01_51_51Z.011486.log"]);

// cockroach-health
expected.entry("cockroach-health").or_default().current =
Some(Utf8Path::new("cockroach-health.log"));
expected
.entry("cockroach-health")
.or_default()
.rotated
.push(Utf8Path::new("cockroach-health.oxzcockroachdba3628a56-6f85-43b5-be50-71d8f0e04877.root.2025-01-31T21_43_26Z.011435.log"));
expected
.entry("cockroach-health")
.or_default()
.rotated
.push(Utf8Path::new("cockroach-health.oxzcockroachdba3628a56-6f85-43b5-be50-71d8f0e04877.root.2025-02-01T01_51_53Z.011486.log"));
Some(&logs_map["cockroach-health.log"]);
expected.entry("cockroach-health").or_default().rotated.push(&logs_map["cockroach-health.oxzcockroachdba3628a56-6f85-43b5-be50-71d8f0e04877.root.2025-01-31T21_43_26Z.011435.log"]);
expected.entry("cockroach-health").or_default().rotated.push(&logs_map["cockroach-health.oxzcockroachdba3628a56-6f85-43b5-be50-71d8f0e04877.root.2025-02-01T01_51_53Z.011486.log"]);

// cockroach-stderr
expected.entry("cockroach-stderr").or_default().current =
Some(Utf8Path::new("cockroach-stderr.log"));
expected
.entry("cockroach-stderr")
.or_default()
.rotated
.push(Utf8Path::new("cockroach-stderr.oxzcockroachdba3628a56-6f85-43b5-be50-71d8f0e04877.root.2023-08-30T18_56_19Z.011950.log"));
expected
.entry("cockroach-stderr")
.or_default()
.rotated
.push(Utf8Path::new("cockroach-stderr.oxzcockroachdba3628a56-6f85-43b5-be50-71d8f0e04877.root.2023-08-31T02_59_24Z.010479.log"));
Some(&logs_map["cockroach-stderr.log"]);
expected.entry("cockroach-stderr").or_default().rotated.push(&logs_map["cockroach-stderr.oxzcockroachdba3628a56-6f85-43b5-be50-71d8f0e04877.root.2023-08-30T18_56_19Z.011950.log"]);
expected.entry("cockroach-stderr").or_default().rotated.push(&logs_map["cockroach-stderr.oxzcockroachdba3628a56-6f85-43b5-be50-71d8f0e04877.root.2023-08-31T02_59_24Z.010479.log"]);

let extra = sort_cockroach_extra_logs(logs.as_slice());
assert_eq!(
Expand Down Expand Up @@ -1177,6 +1177,12 @@ mod illumos_tests {
let mut logfile_handle =
fs_err::tokio::File::create_new(&logfile).await.unwrap();
logfile_handle.write_all(data.as_bytes()).await.unwrap();

// 2025-10-15T00:00:00
let mtime = std::time::SystemTime::UNIX_EPOCH
+ std::time::Duration::from_secs(1760486400);
let std_file = logfile_handle.into_std().await.into_file();
std_file.set_modified(mtime).unwrap();
}

// Populate some file with similar names that should be skipped over
Expand All @@ -1197,27 +1203,40 @@ mod illumos_tests {
let zipfile_path = mountpoint.join("test.zip");
let zipfile = File::create_new(&zipfile_path).unwrap();
let mut zip = ZipWriter::new(zipfile);
let log = LogFile {
path: mountpoint
.join(format!("var/svc/log/{}", logfile_to_data[0].0)),
size: None,
modified: None,
};

loghandle
.process_logs(
"mg-ddm",
&mut zip,
&mut log_snapshots,
&mountpoint
.join(format!("var/svc/log/{}", logfile_to_data[0].0)),
&log,
LogType::Current,
)
.await
.unwrap();

zip.finish().unwrap();

// Confirm the zip has our file and data
let expected_zip_mtime =
zip::DateTime::from_date_and_time(2025, 10, 15, 0, 0, 0)
.unwrap();

// Confirm the zip has our file and data with the right mtime
let mut archive =
ZipArchive::new(File::open(zipfile_path).unwrap()).unwrap();
for (name, data) in logfile_to_data {
let mut file_in_zip =
archive.by_name(&format!("mg-ddm/current/{name}")).unwrap();

let mtime = file_in_zip.last_modified().unwrap();
assert_eq!(mtime, expected_zip_mtime, "file mtime matches");

let mut contents = String::new();
file_in_zip.read_to_string(&mut contents).unwrap();

Expand Down Expand Up @@ -1283,13 +1302,14 @@ mod illumos_tests {
let zipfile_path = mountpoint.join("test.zip");
let zipfile = File::create_new(&zipfile_path).unwrap();
let mut zip = ZipWriter::new(zipfile);
let log = LogFile { path: logfile, size: None, modified: None };

loghandle
.process_logs(
"mg-ddm",
&mut zip,
&mut log_snapshots,
&logfile,
&log,
LogType::Current,
)
.await
Expand Down
4 changes: 2 additions & 2 deletions workspace-hack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ x509-cert = { version = "0.2.5" }
zerocopy-c38e5c1d305a1b54 = { package = "zerocopy", version = "0.8.27", default-features = false, features = ["derive", "simd"] }
zerocopy-ca01ad9e24f5d932 = { package = "zerocopy", version = "0.7.35", features = ["derive", "simd"] }
zeroize = { version = "1.8.1", features = ["std", "zeroize_derive"] }
zip-164d15cefe24d7eb = { package = "zip", version = "4.2.0", default-features = false, features = ["bzip2", "deflate", "zstd"] }
zip-164d15cefe24d7eb = { package = "zip", version = "4.2.0", default-features = false, features = ["bzip2", "deflate", "jiff-02", "zstd"] }
zip-3b31131e45eafb45 = { package = "zip", version = "0.6.6", default-features = false, features = ["bzip2", "deflate"] }

[build-dependencies]
Expand Down Expand Up @@ -291,7 +291,7 @@ x509-cert = { version = "0.2.5" }
zerocopy-c38e5c1d305a1b54 = { package = "zerocopy", version = "0.8.27", default-features = false, features = ["derive", "simd"] }
zerocopy-ca01ad9e24f5d932 = { package = "zerocopy", version = "0.7.35", features = ["derive", "simd"] }
zeroize = { version = "1.8.1", features = ["std", "zeroize_derive"] }
zip-164d15cefe24d7eb = { package = "zip", version = "4.2.0", default-features = false, features = ["bzip2", "deflate", "zstd"] }
zip-164d15cefe24d7eb = { package = "zip", version = "4.2.0", default-features = false, features = ["bzip2", "deflate", "jiff-02", "zstd"] }
zip-3b31131e45eafb45 = { package = "zip", version = "0.6.6", default-features = false, features = ["bzip2", "deflate"] }

[target.x86_64-unknown-linux-gnu.dependencies]
Expand Down
Loading