From 40c95384ad2c6142f31c0226fbeeb8b2772ee2e9 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Thu, 11 Jan 2024 18:40:25 +0000 Subject: [PATCH 01/17] WIP: Add oxlog tool and library This still needs a bit of work before it is ready to go. --- Cargo.lock | 11 ++ Cargo.toml | 2 + dev-tools/oxlog/Cargo.toml | 18 ++ dev-tools/oxlog/src/bin/oxlog.rs | 116 ++++++++++++ dev-tools/oxlog/src/lib.rs | 308 +++++++++++++++++++++++++++++++ 5 files changed, 455 insertions(+) create mode 100644 dev-tools/oxlog/Cargo.toml create mode 100644 dev-tools/oxlog/src/bin/oxlog.rs create mode 100644 dev-tools/oxlog/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 7491f30dde..6cc8f7619d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5584,6 +5584,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "oxlog" +version = "0.1.0" +dependencies = [ + "anyhow", + "camino", + "chrono", + "clap 4.4.3", + "uuid", +] + [[package]] name = "p256" version = "0.13.2" diff --git a/Cargo.toml b/Cargo.toml index fbef04d3c0..aad1be4265 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "dev-tools/crdb-seed", "dev-tools/omdb", "dev-tools/omicron-dev", + "dev-tools/oxlog", "dev-tools/thing-flinger", "dev-tools/xtask", "dns-server", @@ -91,6 +92,7 @@ default-members = [ "dev-tools/crdb-seed", "dev-tools/omdb", "dev-tools/omicron-dev", + "dev-tools/oxlog", "dev-tools/thing-flinger", # Do not include xtask in the list of default members, because this causes # hakari to not work as well and build times to be longer. diff --git a/dev-tools/oxlog/Cargo.toml b/dev-tools/oxlog/Cargo.toml new file mode 100644 index 0000000000..bf86ea7587 --- /dev/null +++ b/dev-tools/oxlog/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "oxlog" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" + +[dependencies] +anyhow.workspace = true +camino.workspace = true +chrono.workspace = true +clap.workspace = true +uuid.workspace = true + +# Disable doc builds by default for our binaries to work around issue +# rust-lang/cargo#8373. These docs would not be very useful anyway. +[[bin]] +name = "oxlog" +doc = false diff --git a/dev-tools/oxlog/src/bin/oxlog.rs b/dev-tools/oxlog/src/bin/oxlog.rs new file mode 100644 index 0000000000..da76b24272 --- /dev/null +++ b/dev-tools/oxlog/src/bin/oxlog.rs @@ -0,0 +1,116 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Tool for discovering oxide related logfiles on sleds + +use clap::{Parser, Subcommand}; +use oxlog::{LogFile, Zones}; + +#[derive(Debug, Parser)] +#[command(version)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Debug, Subcommand)] +enum Commands { + /// List all zones found on the filesystem + Zones, + + /// List logs for a given service + Logs { + // The name of the zone + zone: String, + + /// The name of the service to list logs for + service: Option, + + /// Print available metadata + #[arg(short, long)] + metadata: bool, + + /// Print only the current log file + #[arg(short, long)] + current: bool, + + /// Print only the archive log files + #[arg(short, long)] + archived: bool, + + // Print only the extra log files + #[arg(short, long)] + extra: bool, + }, +} + +fn main() -> Result<(), anyhow::Error> { + let cli = Cli::parse(); + + match cli.command { + Commands::Zones => { + for zone in Zones::load()?.zones.keys() { + println!("{zone}"); + } + Ok(()) + } + Commands::Logs { + zone, + service, + metadata, + current, + archived, + extra, + } => { + let zones = Zones::load()?; + let print_metadata = |f: &LogFile| { + println!( + "{}\t{}\t{}", + f.path, + f.size.map_or_else(|| "-".to_string(), |s| s.to_string()), + f.modified + .map_or_else(|| "-".to_string(), |s| s.to_rfc3339()) + ); + }; + + let logs = zones.zone_logs(&zone); + for (svc_name, mut svc_logs) in logs { + if let Some(service) = &service { + if svc_name != service.as_str() { + continue; + } + } + svc_logs.archived.sort(); + if current { + if let Some(current) = &svc_logs.current { + if metadata { + print_metadata(current); + } else { + println!("{}", current.path); + } + } + } + if archived { + for f in &svc_logs.archived { + if metadata { + print_metadata(f); + } else { + println!("{}", f.path); + } + } + } + if extra { + for f in &svc_logs.extra { + if metadata { + print_metadata(f); + } else { + println!("{}", f.path); + } + } + } + } + Ok(()) + } + } +} diff --git a/dev-tools/oxlog/src/lib.rs b/dev-tools/oxlog/src/lib.rs new file mode 100644 index 0000000000..43967c8358 --- /dev/null +++ b/dev-tools/oxlog/src/lib.rs @@ -0,0 +1,308 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! A tool to show oxide related log file paths +//! +//! All data is based off of reading the filesystem + +use anyhow::Context; +use camino::Utf8PathBuf; +use chrono::{DateTime, Utc}; +use std::collections::BTreeMap; +use std::fs::{read_dir, DirEntry}; +use std::io; +use uuid::Uuid; + +/// Return a UUID if the `DirEntry` contains a directory that parses into a UUID. +fn get_uuid_dir(result: io::Result) -> Option { + let Ok(entry) = result else { + return None; + }; + let Ok(file_type) = entry.file_type() else { + return None; + }; + if !file_type.is_dir() { + return None; + } + let file_name = entry.file_name(); + let Some(s) = file_name.to_str() else { + return None; + }; + if let Ok(uuid) = s.parse() { + Some(uuid) + } else { + None + } +} + +#[derive(Debug)] +pub struct Pools { + pub internal: Vec, + pub external: Vec, +} + +impl Pools { + pub fn read() -> anyhow::Result { + let internal = read_dir("/pool/int/") + .context("Failed to read /pool/int")? + .filter_map(get_uuid_dir) + .collect(); + let external = read_dir("/pool/ext/") + .context("Failed to read /pool/ext")? + .filter_map(get_uuid_dir) + .collect(); + Ok(Pools { internal, external }) + } +} + +/// Path and metadata about a logfile +/// We use options for metadata as retrieval is fallible +#[derive(Debug, Clone, Eq)] +pub struct LogFile { + pub path: Utf8PathBuf, + pub size: Option, + pub modified: Option>, +} + +impl PartialEq for LogFile { + fn eq(&self, other: &Self) -> bool { + self.path == other.path + } +} + +impl PartialOrd for LogFile { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for LogFile { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.path.cmp(&other.path) + } +} + +impl LogFile { + fn new(path: Utf8PathBuf) -> LogFile { + LogFile { path, size: None, modified: None } + } +} + +/// All oxide logs for a given service in a given zone +#[derive(Debug, Clone, Default)] +pub struct SvcLogs { + pub current: Option, + pub archived: Vec, + + // Logs in non-standard places and logs that aren't necessarily oxide logs + pub extra: Vec, +} + +// These probably don't warrant newtypes. They are just to make the nested +// maps in `Logs` a bit easier to read. +type ZoneName = String; +type ServiceName = String; + +pub struct Paths { + pub primary: Utf8PathBuf, + pub debug: Vec, + pub extra: Vec<(&'static str, Utf8PathBuf)>, +} + +pub struct Zones { + pub zones: BTreeMap, +} + +impl Zones { + pub fn load() -> Result { + let mut zones = BTreeMap::new(); + zones.insert( + "global".to_string(), + Paths { + primary: Utf8PathBuf::from("/var/svc/log"), + debug: vec![], + extra: vec![], + }, + ); + zones.insert( + "oxz_switch".to_string(), + Paths { + primary: Utf8PathBuf::from("/zone/oxz_switch/root/var/svc/log"), + debug: vec![], + extra: vec![], + }, + ); + let pools = Pools::read()?; + + // Load the primary and extra logs + for uuid in &pools.external { + let zones_path: Utf8PathBuf = + ["/pool/ext", &uuid.to_string(), "crypt/zone"].iter().collect(); + // Find the zones on the given pool + let Ok(entries) = read_dir(zones_path.as_path()) else { + continue; + }; + for entry in entries { + let Ok(zone_entry) = entry else { + continue; + }; + let zone = zone_entry.file_name(); + let Some(zone) = zone.to_str() else { + // not utf8 + continue; + }; + // Load the current logs + let mut dir = zones_path.clone(); + dir.push(zone); + dir.push("root/var/svc/log"); + let mut paths = + Paths { primary: dir, debug: vec![], extra: vec![] }; + + // Load the extra logs + if zone.starts_with("oxz_cockroachdb") { + let mut dir = zones_path.clone(); + dir.push(zone); + dir.push("root/data/logs"); + paths.extra.push(("cockroachdb", dir)); + } + + zones.insert(zone.to_string(), paths); + } + } + + // Load the debug logs + for uuid in &pools.external { + let zones_path: Utf8PathBuf = + ["/pool/ext", &uuid.to_string(), "crypt/debug"] + .iter() + .collect(); + // Find the zones on the given pool + let Ok(entries) = read_dir(zones_path.as_path()) else { + continue; + }; + for entry in entries { + let Ok(zone_entry) = entry else { + continue; + }; + let zone = zone_entry.file_name(); + let Some(zone) = zone.to_str() else { + // not utf8 + continue; + }; + let mut dir = zones_path.clone(); + dir.push(zone); + + // We only add debug paths if the zones have primary paths + zones.get_mut(zone).map(|paths| paths.debug.push(dir)); + } + } + + Ok(Zones { zones }) + } + + pub fn zone_logs(&self, zone: &str) -> BTreeMap { + let mut output = BTreeMap::new(); + let Some(paths) = self.zones.get(zone) else { + return BTreeMap::new(); + }; + load_svc_logs(paths.primary.clone(), &mut output); + for dir in paths.debug.clone() { + load_svc_logs(dir, &mut output); + } + for (svc_name, dir) in paths.extra.clone() { + load_extra_logs(dir, svc_name, &mut output); + } + output + } +} + +fn load_svc_logs(dir: Utf8PathBuf, logs: &mut BTreeMap) { + let Ok(entries) = read_dir(dir.as_path()) else { + return; + }; + for entry in entries { + let Ok(entry) = entry else { + continue; + }; + let filename = entry.file_name(); + let Some(filename) = filename.to_str() else { + continue; + }; + if filename.starts_with("oxide-") { + let mut path = dir.clone(); + path.push(filename); + let mut logfile = LogFile::new(path); + // If we can't find the service name, then skip the log + let Some((prefix, _suffix)) = filename.split_once(':') else { + continue; + }; + // We already look for this prefix above + let svc_name = prefix.strip_prefix("oxide-").unwrap().to_string(); + if let Ok(metadata) = entry.metadata() { + if metadata.len() == 0 { + // skip 0 size files + continue; + } + logfile.size = Some(metadata.len()); + if let Ok(modified) = metadata.modified() { + logfile.modified = Some(modified.into()); + } + } + + let is_current = filename.ends_with(".log"); + + let svc_logs = + logs.entry(svc_name.clone()).or_insert(SvcLogs::default()); + + if is_current { + svc_logs.current = Some(logfile.clone()); + } else { + svc_logs.archived.push(logfile.clone()); + } + } + } +} + +fn load_extra_logs( + dir: Utf8PathBuf, + svc_name: &str, + logs: &mut BTreeMap, +) { + let Ok(entries) = read_dir(dir.as_path()) else { + return; + }; + + // We only insert extra files if we have already collected + // related current and archived files. + // This should always be the case unless the files are + // for zones that no longer exist. + let Some(svc_logs) = logs.get_mut(svc_name) else { + return; + }; + + for entry in entries { + let Ok(entry) = entry else { + continue; + }; + let filename = entry.file_name(); + let Some(filename) = filename.to_str() else { + continue; + }; + let mut path = dir.clone(); + path.push(filename); + let mut logfile = LogFile::new(path); + if let Ok(metadata) = entry.metadata() { + if metadata.len() == 0 { + // skip 0 size files + continue; + } + logfile.size = Some(metadata.len()); + if let Ok(modified) = metadata.modified() { + logfile.modified = Some(modified.into()); + } + } + + svc_logs.extra.push(logfile); + } +} From a644413dcbcf440538e48d31a3b3c4ea7b5460e9 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Thu, 18 Jan 2024 00:35:07 +0000 Subject: [PATCH 02/17] minor cleanup --- dev-tools/oxlog/src/bin/oxlog.rs | 2 +- dev-tools/oxlog/src/lib.rs | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/dev-tools/oxlog/src/bin/oxlog.rs b/dev-tools/oxlog/src/bin/oxlog.rs index da76b24272..d1639209c3 100644 --- a/dev-tools/oxlog/src/bin/oxlog.rs +++ b/dev-tools/oxlog/src/bin/oxlog.rs @@ -35,7 +35,7 @@ enum Commands { #[arg(short, long)] current: bool, - /// Print only the archive log files + /// Print only the archived log files #[arg(short, long)] archived: bool, diff --git a/dev-tools/oxlog/src/lib.rs b/dev-tools/oxlog/src/lib.rs index 43967c8358..c930795a02 100644 --- a/dev-tools/oxlog/src/lib.rs +++ b/dev-tools/oxlog/src/lib.rs @@ -99,8 +99,8 @@ pub struct SvcLogs { pub extra: Vec, } -// These probably don't warrant newtypes. They are just to make the nested -// maps in `Logs` a bit easier to read. +// These probably don't warrant newtypes. They are just to make the +// keys in maps a bit easier to read. type ZoneName = String; type ServiceName = String; @@ -117,6 +117,8 @@ pub struct Zones { impl Zones { pub fn load() -> Result { let mut zones = BTreeMap::new(); + + // Describe where to find logs for the global zone zones.insert( "global".to_string(), Paths { @@ -125,17 +127,22 @@ impl Zones { extra: vec![], }, ); + + // Describe where to find logs for the switch zone zones.insert( "oxz_switch".to_string(), Paths { primary: Utf8PathBuf::from("/zone/oxz_switch/root/var/svc/log"), debug: vec![], - extra: vec![], + extra: vec![( + "dendrite", + "/zone/oxz_switch/root/var/dendrite".into(), + )], }, ); + // Find the directories containing the primary and extra log files + // for all zones on external storage pools. let pools = Pools::read()?; - - // Load the primary and extra logs for uuid in &pools.external { let zones_path: Utf8PathBuf = ["/pool/ext", &uuid.to_string(), "crypt/zone"].iter().collect(); @@ -152,14 +159,14 @@ impl Zones { // not utf8 continue; }; - // Load the current logs + // Add the path to the current logs for the zone let mut dir = zones_path.clone(); dir.push(zone); dir.push("root/var/svc/log"); let mut paths = Paths { primary: dir, debug: vec![], extra: vec![] }; - // Load the extra logs + // Add the path to the extra logs for the zone if zone.starts_with("oxz_cockroachdb") { let mut dir = zones_path.clone(); dir.push(zone); @@ -171,7 +178,7 @@ impl Zones { } } - // Load the debug logs + // Find the directories containing the debug log files for uuid in &pools.external { let zones_path: Utf8PathBuf = ["/pool/ext", &uuid.to_string(), "crypt/debug"] From 908d4431d79367664036ebaa82e5f83cd2be0754 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Thu, 18 Jan 2024 01:47:01 +0000 Subject: [PATCH 03/17] Move filtering into library --- dev-tools/oxlog/src/bin/oxlog.rs | 53 +++++++++++++++++--------------- dev-tools/oxlog/src/lib.rs | 32 ++++++++++++++++--- 2 files changed, 56 insertions(+), 29 deletions(-) diff --git a/dev-tools/oxlog/src/bin/oxlog.rs b/dev-tools/oxlog/src/bin/oxlog.rs index d1639209c3..ef79605dda 100644 --- a/dev-tools/oxlog/src/bin/oxlog.rs +++ b/dev-tools/oxlog/src/bin/oxlog.rs @@ -4,8 +4,8 @@ //! Tool for discovering oxide related logfiles on sleds -use clap::{Parser, Subcommand}; -use oxlog::{LogFile, Zones}; +use clap::{Args, Parser, Subcommand}; +use oxlog::{Filter, LogFile, Zones}; #[derive(Debug, Parser)] #[command(version)] @@ -31,18 +31,25 @@ enum Commands { #[arg(short, long)] metadata: bool, - /// Print only the current log file - #[arg(short, long)] - current: bool, + #[command(flatten)] + filter: FilterArgs, + }, +} - /// Print only the archived log files - #[arg(short, long)] - archived: bool, +#[derive(Args, Debug)] +#[group(required = true, multiple = true)] +struct FilterArgs { + /// Print only the current log file + #[arg(short, long)] + current: bool, - // Print only the extra log files - #[arg(short, long)] - extra: bool, - }, + /// Print only the archived log files + #[arg(short, long)] + archived: bool, + + // Print only the extra log files + #[arg(short, long)] + extra: bool, } fn main() -> Result<(), anyhow::Error> { @@ -55,15 +62,13 @@ fn main() -> Result<(), anyhow::Error> { } Ok(()) } - Commands::Logs { - zone, - service, - metadata, - current, - archived, - extra, - } => { + Commands::Logs { zone, service, metadata, filter } => { let zones = Zones::load()?; + let filter = Filter { + current: filter.current, + archived: filter.archived, + extra: filter.extra, + }; let print_metadata = |f: &LogFile| { println!( "{}\t{}\t{}", @@ -74,7 +79,7 @@ fn main() -> Result<(), anyhow::Error> { ); }; - let logs = zones.zone_logs(&zone); + let logs = zones.zone_logs(&zone, filter); for (svc_name, mut svc_logs) in logs { if let Some(service) = &service { if svc_name != service.as_str() { @@ -82,7 +87,7 @@ fn main() -> Result<(), anyhow::Error> { } } svc_logs.archived.sort(); - if current { + if filter.current { if let Some(current) = &svc_logs.current { if metadata { print_metadata(current); @@ -91,7 +96,7 @@ fn main() -> Result<(), anyhow::Error> { } } } - if archived { + if filter.archived { for f in &svc_logs.archived { if metadata { print_metadata(f); @@ -100,7 +105,7 @@ fn main() -> Result<(), anyhow::Error> { } } } - if extra { + if filter.extra { for f in &svc_logs.extra { if metadata { print_metadata(f); diff --git a/dev-tools/oxlog/src/lib.rs b/dev-tools/oxlog/src/lib.rs index c930795a02..f3b0018920 100644 --- a/dev-tools/oxlog/src/lib.rs +++ b/dev-tools/oxlog/src/lib.rs @@ -56,6 +56,19 @@ impl Pools { } } +/// Filter which logs to search for in a given zone +/// +/// Each field in the filter is additive. +/// +/// The filter was added to the library and not just the CLI because in some +/// cases searching for archived logs is pretty expensive. +#[derive(Clone, Copy, Debug)] +pub struct Filter { + pub current: bool, + pub archived: bool, + pub extra: bool, +} + /// Path and metadata about a logfile /// We use options for metadata as retrieval is fallible #[derive(Debug, Clone, Eq)] @@ -208,17 +221,26 @@ impl Zones { Ok(Zones { zones }) } - pub fn zone_logs(&self, zone: &str) -> BTreeMap { + pub fn zone_logs( + &self, + zone: &str, + filter: Filter, + ) -> BTreeMap { let mut output = BTreeMap::new(); let Some(paths) = self.zones.get(zone) else { return BTreeMap::new(); }; load_svc_logs(paths.primary.clone(), &mut output); - for dir in paths.debug.clone() { - load_svc_logs(dir, &mut output); + + if filter.archived { + for dir in paths.debug.clone() { + load_svc_logs(dir, &mut output); + } } - for (svc_name, dir) in paths.extra.clone() { - load_extra_logs(dir, svc_name, &mut output); + if filter.extra { + for (svc_name, dir) in paths.extra.clone() { + load_extra_logs(dir, svc_name, &mut output); + } } output } From c38d7e4c1f036546bf302d44e5c036d7fc278baf Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Thu, 18 Jan 2024 04:35:59 +0000 Subject: [PATCH 04/17] Also grab files prefixed with "system-illumos-" --- dev-tools/oxlog/src/lib.rs | 129 +++++++++++++++++++++++++++++++++---- 1 file changed, 116 insertions(+), 13 deletions(-) diff --git a/dev-tools/oxlog/src/lib.rs b/dev-tools/oxlog/src/lib.rs index f3b0018920..4bf193dfd1 100644 --- a/dev-tools/oxlog/src/lib.rs +++ b/dev-tools/oxlog/src/lib.rs @@ -153,6 +153,7 @@ impl Zones { )], }, ); + // Find the directories containing the primary and extra log files // for all zones on external storage pools. let pools = Pools::read()?; @@ -221,6 +222,7 @@ impl Zones { Ok(Zones { zones }) } + /// Return log files organized by service name pub fn zone_logs( &self, zone: &str, @@ -246,6 +248,45 @@ impl Zones { } } +const OX_SMF_PREFIXES: [&str; 2] = ["oxide-", "system-illumos-"]; + +/// Return true if the provided file name appears to be a valid log file for an +/// Oxide-managed SMF service. +/// +/// Note that this operates on the _file name_. Any leading path components will +/// cause this check to return `false`. +pub fn is_oxide_smf_log_file(filename: impl AsRef) -> bool { + // Log files are named by the SMF services, with the `/` in the FMRI + // translated to a `-`. + let filename = filename.as_ref(); + OX_SMF_PREFIXES + .iter() + .any(|prefix| filename.starts_with(prefix) && filename.contains(".log")) +} + +// Parse an oxide smf log file name and return the name of the underlying +// service. +// +// If parsing fails for some reason, return `None`. +pub fn oxide_smf_service_name_from_log_file_name( + filename: &str, +) -> Option<&str> { + let Some((prefix, _suffix)) = filename.split_once(':') else { + // No ':' found + return None; + }; + + for ox_prefix in OX_SMF_PREFIXES { + if let Some(svc_name) = prefix.strip_prefix(ox_prefix) { + return Some(svc_name); + } + } + + None +} + +// Given a directory, find all oxide specific SMF service logs and return them +// mapped to their inferred service name. fn load_svc_logs(dir: Utf8PathBuf, logs: &mut BTreeMap) { let Ok(entries) = read_dir(dir.as_path()) else { return; @@ -258,16 +299,19 @@ fn load_svc_logs(dir: Utf8PathBuf, logs: &mut BTreeMap) { let Some(filename) = filename.to_str() else { continue; }; - if filename.starts_with("oxide-") { + + // Is this a log file we care about? + if is_oxide_smf_log_file(filename) { let mut path = dir.clone(); path.push(filename); let mut logfile = LogFile::new(path); - // If we can't find the service name, then skip the log - let Some((prefix, _suffix)) = filename.split_once(':') else { + + let Some(svc_name) = + oxide_smf_service_name_from_log_file_name(filename) + else { continue; }; - // We already look for this prefix above - let svc_name = prefix.strip_prefix("oxide-").unwrap().to_string(); + if let Ok(metadata) = entry.metadata() { if metadata.len() == 0 { // skip 0 size files @@ -282,7 +326,7 @@ fn load_svc_logs(dir: Utf8PathBuf, logs: &mut BTreeMap) { let is_current = filename.ends_with(".log"); let svc_logs = - logs.entry(svc_name.clone()).or_insert(SvcLogs::default()); + logs.entry(svc_name.to_string()).or_insert(SvcLogs::default()); if is_current { svc_logs.current = Some(logfile.clone()); @@ -293,6 +337,8 @@ fn load_svc_logs(dir: Utf8PathBuf, logs: &mut BTreeMap) { } } +// Load any logs in non-standard paths. We grab all logs in `dir` and +// don't filter based on filename prefix as in `load_svc_logs`. fn load_extra_logs( dir: Utf8PathBuf, svc_name: &str, @@ -302,13 +348,8 @@ fn load_extra_logs( return; }; - // We only insert extra files if we have already collected - // related current and archived files. - // This should always be the case unless the files are - // for zones that no longer exist. - let Some(svc_logs) = logs.get_mut(svc_name) else { - return; - }; + let svc_logs = + logs.entry(svc_name.to_string()).or_insert(SvcLogs::default()); for entry in entries { let Ok(entry) = entry else { @@ -335,3 +376,65 @@ fn load_extra_logs( svc_logs.extra.push(logfile); } } + +#[cfg(test)] +mod tests { + pub use super::is_oxide_smf_log_file; + pub use super::oxide_smf_service_name_from_log_file_name; + + #[test] + fn test_is_oxide_smf_log_file() { + assert!(is_oxide_smf_log_file("oxide-blah:default.log")); + assert!(is_oxide_smf_log_file("oxide-blah:default.log.0")); + assert!(is_oxide_smf_log_file("oxide-blah:default.log.1111")); + assert!(is_oxide_smf_log_file("system-illumos-blah:default.log")); + assert!(is_oxide_smf_log_file("system-illumos-blah:default.log.0")); + assert!(!is_oxide_smf_log_file("not-oxide-blah:default.log")); + assert!(!is_oxide_smf_log_file("not-system-illumos-blah:default.log")); + assert!(!is_oxide_smf_log_file("system-blah:default.log")); + } + + #[test] + fn test_oxide_smf_service_name_from_log_file_name() { + assert_eq!( + Some("blah"), + oxide_smf_service_name_from_log_file_name("oxide-blah:default.log") + ); + assert_eq!( + Some("blah"), + oxide_smf_service_name_from_log_file_name( + "oxide-blah:default.log.0" + ) + ); + assert_eq!( + Some("blah"), + oxide_smf_service_name_from_log_file_name( + "oxide-blah:default.log.1111" + ) + ); + assert_eq!( + Some("blah"), + oxide_smf_service_name_from_log_file_name( + "system-illumos-blah:default.log" + ) + ); + assert_eq!( + Some("blah"), + oxide_smf_service_name_from_log_file_name( + "system-illumos-blah:default.log.0" + ) + ); + assert!(!oxide_smf_service_name_from_log_file_name( + "not-oxide-blah:default.log" + ) + .is_none()); + assert!(!oxide_smf_service_name_from_log_file_name( + "not-system-illumos-blah:default.log" + ) + .is_none()); + assert!(!oxide_smf_service_name_from_log_file_name( + "system-blah:default.log" + ) + .is_none()); + } +} From 403b8790c4053b888bd6982e8917480be6c9baa2 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Thu, 18 Jan 2024 04:38:52 +0000 Subject: [PATCH 05/17] fix tests --- dev-tools/oxlog/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dev-tools/oxlog/src/lib.rs b/dev-tools/oxlog/src/lib.rs index 4bf193dfd1..5b91a90fb5 100644 --- a/dev-tools/oxlog/src/lib.rs +++ b/dev-tools/oxlog/src/lib.rs @@ -424,15 +424,15 @@ mod tests { "system-illumos-blah:default.log.0" ) ); - assert!(!oxide_smf_service_name_from_log_file_name( + assert!(oxide_smf_service_name_from_log_file_name( "not-oxide-blah:default.log" ) .is_none()); - assert!(!oxide_smf_service_name_from_log_file_name( + assert!(oxide_smf_service_name_from_log_file_name( "not-system-illumos-blah:default.log" ) .is_none()); - assert!(!oxide_smf_service_name_from_log_file_name( + assert!(oxide_smf_service_name_from_log_file_name( "system-blah:default.log" ) .is_none()); From 4d01a2cfc2013d5e7c38f0f30700ffd864c50104 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Thu, 18 Jan 2024 04:42:23 +0000 Subject: [PATCH 06/17] feed hakari --- Cargo.lock | 1 + workspace-hack/Cargo.toml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 6cc8f7619d..ebfd4ba689 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5087,6 +5087,7 @@ dependencies = [ "bstr 1.6.0", "byteorder", "bytes", + "camino", "chrono", "cipher", "clap 4.4.3", diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 5688e133c0..390a2f7c6b 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -25,6 +25,7 @@ bstr-6f8ce4dd05d13bba = { package = "bstr", version = "0.2.17" } bstr-dff4ba8e3ae991db = { package = "bstr", version = "1.6.0" } byteorder = { version = "1.5.0" } bytes = { version = "1.5.0", features = ["serde"] } +camino = { version = "1.1.6", default-features = false, features = ["serde1"] } chrono = { version = "0.4.31", features = ["alloc", "serde"] } cipher = { version = "0.4.4", default-features = false, features = ["block-padding", "zeroize"] } clap = { version = "4.4.3", features = ["derive", "env", "wrap_help"] } @@ -128,6 +129,7 @@ bstr-6f8ce4dd05d13bba = { package = "bstr", version = "0.2.17" } bstr-dff4ba8e3ae991db = { package = "bstr", version = "1.6.0" } byteorder = { version = "1.5.0" } bytes = { version = "1.5.0", features = ["serde"] } +camino = { version = "1.1.6", default-features = false, features = ["serde1"] } chrono = { version = "0.4.31", features = ["alloc", "serde"] } cipher = { version = "0.4.4", default-features = false, features = ["block-padding", "zeroize"] } clap = { version = "4.4.3", features = ["derive", "env", "wrap_help"] } From 01d641f5ec2b70eb0a3d612d1cce39131c10ba2c Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Thu, 18 Jan 2024 05:32:25 +0000 Subject: [PATCH 07/17] Add oxlog to global zone packages --- package-manifest.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/package-manifest.toml b/package-manifest.toml index 6bd40c320d..592d06f300 100644 --- a/package-manifest.toml +++ b/package-manifest.toml @@ -613,3 +613,11 @@ source.packages = [ "sp-sim-softnpu.tar.gz" ] output.type = "zone" + +[package.oxlog] +service_name = "oxlog" +only_for_targets.image = "standard" +source.type = "local" +source.rust.binary_names = ["oxlog"] +source.rust.release = true +output.type = "tarball" From a933c9c920fb66012fb0ce8b069526951a58da53 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Thu, 18 Jan 2024 06:12:17 +0000 Subject: [PATCH 08/17] Cliiiiiiiiiiiipppppppppy --- dev-tools/oxlog/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dev-tools/oxlog/src/lib.rs b/dev-tools/oxlog/src/lib.rs index 5b91a90fb5..200d0fdd6c 100644 --- a/dev-tools/oxlog/src/lib.rs +++ b/dev-tools/oxlog/src/lib.rs @@ -215,7 +215,9 @@ impl Zones { dir.push(zone); // We only add debug paths if the zones have primary paths - zones.get_mut(zone).map(|paths| paths.debug.push(dir)); + if let Some(paths) = zones.get_mut(zone) { + paths.debug.push(dir); + } } } From 68434e2e1967ebdebd4a18e1603dadf0b77d4a09 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Thu, 18 Jan 2024 06:19:04 +0000 Subject: [PATCH 09/17] hakari me a river --- workspace-hack/Cargo.toml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 390a2f7c6b..fad2603240 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -28,12 +28,13 @@ bytes = { version = "1.5.0", features = ["serde"] } camino = { version = "1.1.6", default-features = false, features = ["serde1"] } chrono = { version = "0.4.31", features = ["alloc", "serde"] } cipher = { version = "0.4.4", default-features = false, features = ["block-padding", "zeroize"] } -clap = { version = "4.4.3", features = ["derive", "env", "wrap_help"] } -clap_builder = { version = "4.4.2", default-features = false, features = ["color", "env", "std", "suggestions", "usage", "wrap_help"] } -console = { version = "0.15.7" } +clap = { version = "4.4.3", features = ["cargo", "derive", "env", "wrap_help"] } +clap_builder = { version = "4.4.2", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } +console = { version = "0.15.8" } const-oid = { version = "0.9.5", default-features = false, features = ["db", "std"] } crossbeam-epoch = { version = "0.9.15" } crossbeam-utils = { version = "0.8.16" } +crossterm = { version = "0.27.0", features = ["event-stream", "serde"] } crypto-common = { version = "0.1.6", default-features = false, features = ["getrandom", "std"] } der = { version = "0.7.8", default-features = false, features = ["derive", "flagset", "oid", "pem", "std"] } diesel = { version = "2.1.4", features = ["chrono", "i-implement-a-third-party-backend-and-opt-into-breaking-changes", "network-address", "postgres", "r2d2", "serde_json", "uuid"] } @@ -77,7 +78,7 @@ pem-rfc7468 = { version = "0.7.0", default-features = false, features = ["std"] petgraph = { version = "0.6.4", features = ["serde-1"] } postgres-types = { version = "0.2.6", default-features = false, features = ["with-chrono-0_4", "with-serde_json-1", "with-uuid-1"] } ppv-lite86 = { version = "0.2.17", default-features = false, features = ["simd", "std"] } -predicates = { version = "3.0.4" } +predicates = { version = "3.1.0" } proc-macro2 = { version = "1.0.74" } rand = { version = "0.8.5" } rand_chacha = { version = "0.3.1", default-features = false, features = ["std"] } @@ -88,7 +89,7 @@ reqwest = { version = "0.11.22", features = ["blocking", "json", "rustls-tls", " ring = { version = "0.17.7", features = ["std"] } schemars = { version = "0.8.16", features = ["bytes", "chrono", "uuid1"] } semver = { version = "1.0.21", features = ["serde"] } -serde = { version = "1.0.194", features = ["alloc", "derive", "rc"] } +serde = { version = "1.0.195", features = ["alloc", "derive", "rc"] } serde_json = { version = "1.0.111", features = ["raw_value", "unbounded_depth"] } sha2 = { version = "0.10.8", features = ["oid"] } similar = { version = "2.3.0", features = ["inline", "unicode"] } @@ -132,12 +133,13 @@ bytes = { version = "1.5.0", features = ["serde"] } camino = { version = "1.1.6", default-features = false, features = ["serde1"] } chrono = { version = "0.4.31", features = ["alloc", "serde"] } cipher = { version = "0.4.4", default-features = false, features = ["block-padding", "zeroize"] } -clap = { version = "4.4.3", features = ["derive", "env", "wrap_help"] } -clap_builder = { version = "4.4.2", default-features = false, features = ["color", "env", "std", "suggestions", "usage", "wrap_help"] } -console = { version = "0.15.7" } +clap = { version = "4.4.3", features = ["cargo", "derive", "env", "wrap_help"] } +clap_builder = { version = "4.4.2", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } +console = { version = "0.15.8" } const-oid = { version = "0.9.5", default-features = false, features = ["db", "std"] } crossbeam-epoch = { version = "0.9.15" } crossbeam-utils = { version = "0.8.16" } +crossterm = { version = "0.27.0", features = ["event-stream", "serde"] } crypto-common = { version = "0.1.6", default-features = false, features = ["getrandom", "std"] } der = { version = "0.7.8", default-features = false, features = ["derive", "flagset", "oid", "pem", "std"] } diesel = { version = "2.1.4", features = ["chrono", "i-implement-a-third-party-backend-and-opt-into-breaking-changes", "network-address", "postgres", "r2d2", "serde_json", "uuid"] } @@ -181,7 +183,7 @@ pem-rfc7468 = { version = "0.7.0", default-features = false, features = ["std"] petgraph = { version = "0.6.4", features = ["serde-1"] } postgres-types = { version = "0.2.6", default-features = false, features = ["with-chrono-0_4", "with-serde_json-1", "with-uuid-1"] } ppv-lite86 = { version = "0.2.17", default-features = false, features = ["simd", "std"] } -predicates = { version = "3.0.4" } +predicates = { version = "3.1.0" } proc-macro2 = { version = "1.0.74" } rand = { version = "0.8.5" } rand_chacha = { version = "0.3.1", default-features = false, features = ["std"] } @@ -192,7 +194,7 @@ reqwest = { version = "0.11.22", features = ["blocking", "json", "rustls-tls", " ring = { version = "0.17.7", features = ["std"] } schemars = { version = "0.8.16", features = ["bytes", "chrono", "uuid1"] } semver = { version = "1.0.21", features = ["serde"] } -serde = { version = "1.0.194", features = ["alloc", "derive", "rc"] } +serde = { version = "1.0.195", features = ["alloc", "derive", "rc"] } serde_json = { version = "1.0.111", features = ["raw_value", "unbounded_depth"] } sha2 = { version = "0.10.8", features = ["oid"] } similar = { version = "2.3.0", features = ["inline", "unicode"] } From 2366c68e5c628684a3029d5abe9126b2e0f3cc2e Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Thu, 18 Jan 2024 06:57:08 +0000 Subject: [PATCH 10/17] hakari --- Cargo.lock | 1 + dev-tools/oxlog/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 073af257c9..2ed96cfaa5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5585,6 +5585,7 @@ dependencies = [ "camino", "chrono", "clap 4.4.3", + "omicron-workspace-hack", "uuid", ] diff --git a/dev-tools/oxlog/Cargo.toml b/dev-tools/oxlog/Cargo.toml index bf86ea7587..cb9f19e897 100644 --- a/dev-tools/oxlog/Cargo.toml +++ b/dev-tools/oxlog/Cargo.toml @@ -10,6 +10,7 @@ camino.workspace = true chrono.workspace = true clap.workspace = true uuid.workspace = true +omicron-workspace-hack.workspace = true # Disable doc builds by default for our binaries to work around issue # rust-lang/cargo#8373. These docs would not be very useful anyway. From c16c2bdee88e1eff41f4a12532ab99775ced0e45 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Fri, 19 Jan 2024 22:34:06 +0000 Subject: [PATCH 11/17] minor dedup --- dev-tools/oxlog/src/lib.rs | 39 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/dev-tools/oxlog/src/lib.rs b/dev-tools/oxlog/src/lib.rs index 200d0fdd6c..713bd07c66 100644 --- a/dev-tools/oxlog/src/lib.rs +++ b/dev-tools/oxlog/src/lib.rs @@ -78,6 +78,17 @@ pub struct LogFile { pub modified: Option>, } +impl LogFile { + pub fn read_metadata(&mut self, entry: DirEntry) { + if let Ok(metadata) = entry.metadata() { + self.size = Some(metadata.len()); + if let Ok(modified) = metadata.modified() { + self.modified = Some(modified.into()); + } + } + } +} + impl PartialEq for LogFile { fn eq(&self, other: &Self) -> bool { self.path == other.path @@ -311,18 +322,14 @@ fn load_svc_logs(dir: Utf8PathBuf, logs: &mut BTreeMap) { let Some(svc_name) = oxide_smf_service_name_from_log_file_name(filename) else { + // parsing failed continue; }; - if let Ok(metadata) = entry.metadata() { - if metadata.len() == 0 { - // skip 0 size files - continue; - } - logfile.size = Some(metadata.len()); - if let Ok(modified) = metadata.modified() { - logfile.modified = Some(modified.into()); - } + logfile.read_metadata(entry); + if logfile.size == Some(0) { + // skip 0 size files + continue; } let is_current = filename.ends_with(".log"); @@ -364,17 +371,11 @@ fn load_extra_logs( let mut path = dir.clone(); path.push(filename); let mut logfile = LogFile::new(path); - if let Ok(metadata) = entry.metadata() { - if metadata.len() == 0 { - // skip 0 size files - continue; - } - logfile.size = Some(metadata.len()); - if let Ok(modified) = metadata.modified() { - logfile.modified = Some(modified.into()); - } + logfile.read_metadata(entry); + if logfile.size == Some(0) { + // skip 0 size files + continue; } - svc_logs.extra.push(logfile); } } From f2b2f8a245aebd32243937a72ae2ef0603f15f15 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Fri, 19 Jan 2024 22:41:31 +0000 Subject: [PATCH 12/17] Add a filter condition --- dev-tools/oxlog/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dev-tools/oxlog/src/lib.rs b/dev-tools/oxlog/src/lib.rs index 713bd07c66..7e20833164 100644 --- a/dev-tools/oxlog/src/lib.rs +++ b/dev-tools/oxlog/src/lib.rs @@ -245,7 +245,12 @@ impl Zones { let Some(paths) = self.zones.get(zone) else { return BTreeMap::new(); }; - load_svc_logs(paths.primary.clone(), &mut output); + // Some rotated files exist in `paths.primary` that we track as + // 'archived'. These files have not yet been migrated into the debug + // directory. + if filter.current || filter.archived { + load_svc_logs(paths.primary.clone(), &mut output); + } if filter.archived { for dir in paths.debug.clone() { From 09ad1b5580a5865c0d4dce5b672fb1994155b108 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Fri, 19 Jan 2024 23:34:06 +0000 Subject: [PATCH 13/17] Add oxlog to build-global-zone-packages.sh --- tools/build-global-zone-packages.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/build-global-zone-packages.sh b/tools/build-global-zone-packages.sh index fc1ab42ade..b989e6a543 100755 --- a/tools/build-global-zone-packages.sh +++ b/tools/build-global-zone-packages.sh @@ -15,6 +15,7 @@ deps=( "$tarball_src_dir/mg-ddm-gz.tar" "$tarball_src_dir/propolis-server.tar.gz" "$tarball_src_dir/overlay.tar.gz" + "$tarball_src_dir/oxlog.tar" ) for dep in "${deps[@]}"; do if [[ ! -e $dep ]]; then @@ -48,6 +49,12 @@ mkdir -p "$pkg_dir" cd "$pkg_dir" tar -xvfz "$tarball_src_dir/mg-ddm-gz.tar" cd - +# Extract the oxlog tarball for re-packaging into the layered GZ archive. +pkg_dir="$tmp_gz/root/opt/oxide/oxlog" +mkdir -p "$pkg_dir" +cd "$pkg_dir" +tar -xvfz "$tarball_src_dir/oxlog.tar" +cd - # propolis should be bundled with this OS: Put the propolis-server zone image # under /opt/oxide in the gz. From caf3cd5f74be8313f00d617d9cdb4d08d11a9c41 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Fri, 19 Jan 2024 23:43:58 +0000 Subject: [PATCH 14/17] Call into oxlog from illumos-utils --- Cargo.lock | 1 + Cargo.toml | 1 + illumos-utils/Cargo.toml | 1 + illumos-utils/src/running_zone.rs | 28 +--------------------------- 4 files changed, 4 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f24549473..2a9020c661 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3197,6 +3197,7 @@ dependencies = [ "omicron-workspace-hack", "opte-ioctl", "oxide-vpc", + "oxlog", "regress", "schemars", "serde", diff --git a/Cargo.toml b/Cargo.toml index 7821d0ddd8..c7310f317d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -254,6 +254,7 @@ nexus-inventory = { path = "nexus/inventory" } omicron-certificates = { path = "certificates" } omicron-passwords = { path = "passwords" } omicron-workspace-hack = "0.1.0" +oxlog = { path = "dev-tools/oxlog" } nexus-test-interface = { path = "nexus/test-interface" } nexus-test-utils-macros = { path = "nexus/test-utils-macros" } nexus-test-utils = { path = "nexus/test-utils" } diff --git a/illumos-utils/Cargo.toml b/illumos-utils/Cargo.toml index 8296eace5c..e4a99095fd 100644 --- a/illumos-utils/Cargo.toml +++ b/illumos-utils/Cargo.toml @@ -20,6 +20,7 @@ libc.workspace = true macaddr.workspace = true omicron-common.workspace = true oxide-vpc.workspace = true +oxlog.workspace = true schemars.workspace = true serde.workspace = true slog.workspace = true diff --git a/illumos-utils/src/running_zone.rs b/illumos-utils/src/running_zone.rs index ea80a6d34b..4b4107f529 100644 --- a/illumos-utils/src/running_zone.rs +++ b/illumos-utils/src/running_zone.rs @@ -14,6 +14,7 @@ use camino::{Utf8Path, Utf8PathBuf}; use camino_tempfile::Utf8TempDir; use ipnetwork::IpNetwork; use omicron_common::backoff; +pub use oxlog::is_oxide_smf_log_file; use slog::{error, info, o, warn, Logger}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::sync::Arc; @@ -1411,24 +1412,8 @@ pub fn is_oxide_smf_service(fmri: impl AsRef) -> bool { SMF_SERVICE_PREFIXES.iter().any(|prefix| fmri.starts_with(prefix)) } -/// Return true if the provided file name appears to be a valid log file for an -/// Oxide-managed SMF service. -/// -/// Note that this operates on the _file name_. Any leading path components will -/// cause this check to return `false`. -pub fn is_oxide_smf_log_file(filename: impl AsRef) -> bool { - // Log files are named by the SMF services, with the `/` in the FMRI - // translated to a `-`. - const PREFIXES: [&str; 2] = ["oxide-", "system-illumos-"]; - let filename = filename.as_ref(); - PREFIXES - .iter() - .any(|prefix| filename.starts_with(prefix) && filename.contains(".log")) -} - #[cfg(test)] mod tests { - use super::is_oxide_smf_log_file; use super::is_oxide_smf_service; #[test] @@ -1438,15 +1423,4 @@ mod tests { assert!(!is_oxide_smf_service("svc:/system/blah:default")); assert!(!is_oxide_smf_service("svc:/not/oxide/blah:default")); } - - #[test] - fn test_is_oxide_smf_log_file() { - assert!(is_oxide_smf_log_file("oxide-blah:default.log")); - assert!(is_oxide_smf_log_file("oxide-blah:default.log.0")); - assert!(is_oxide_smf_log_file("oxide-blah:default.log.1111")); - assert!(is_oxide_smf_log_file("system-illumos-blah:default.log")); - assert!(is_oxide_smf_log_file("system-illumos-blah:default.log.0")); - assert!(!is_oxide_smf_log_file("not-oxide-blah:default.log")); - assert!(!is_oxide_smf_log_file("not-system-illumos-blah:default.log")); - } } From 835d0cd021ec0d63b779fb16542b7a250a7a8130 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Tue, 23 Jan 2024 01:15:30 +0000 Subject: [PATCH 15/17] some review fixes --- dev-tools/oxlog/Cargo.toml | 3 --- dev-tools/oxlog/src/lib.rs | 26 +++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/dev-tools/oxlog/Cargo.toml b/dev-tools/oxlog/Cargo.toml index cb9f19e897..5d7cfaf5c1 100644 --- a/dev-tools/oxlog/Cargo.toml +++ b/dev-tools/oxlog/Cargo.toml @@ -12,8 +12,5 @@ clap.workspace = true uuid.workspace = true omicron-workspace-hack.workspace = true -# Disable doc builds by default for our binaries to work around issue -# rust-lang/cargo#8373. These docs would not be very useful anyway. [[bin]] name = "oxlog" -doc = false diff --git a/dev-tools/oxlog/src/lib.rs b/dev-tools/oxlog/src/lib.rs index 7e20833164..231af2e7bb 100644 --- a/dev-tools/oxlog/src/lib.rs +++ b/dev-tools/oxlog/src/lib.rs @@ -64,8 +64,18 @@ impl Pools { /// cases searching for archived logs is pretty expensive. #[derive(Clone, Copy, Debug)] pub struct Filter { + /// The current logfile for a service. + /// e.g. `/var/svc/log/oxide-sled-agent:default.log` pub current: bool, + + /// Any rotated log files in the default service directory or archived to + /// a debug directory. e.g. `/var/svc/log/oxide-sled-agent:default.log.0` + /// or `/pool/ext/021afd19-2f87-4def-9284-ab7add1dd6ae/crypt/debug/global/oxide-sled-agent:default.log.1697509861` pub archived: bool, + + /// Any files of special interest for a given service that don't reside in + /// standard paths or don't follow the naming conventions of SMF service + /// files. e.g. `/pool/ext/e12f29b8-1ab8-431e-bc96-1c1298947980/crypt/zone/oxz_cockroachdb_8bbea076-ff60-4330-8302-383e18140ef3/root/data/logs/cockroach.log` pub extra: bool, } @@ -116,10 +126,18 @@ impl LogFile { /// All oxide logs for a given service in a given zone #[derive(Debug, Clone, Default)] pub struct SvcLogs { + /// The current logfile for a service. + /// e.g. `/var/svc/log/oxide-sled-agent:default.log` pub current: Option, + + /// Any rotated log files in the default service directory or archived to + /// a debug directory. e.g. `/var/svc/log/oxide-sled-agent:default.log.0` + /// or `/pool/ext/021afd19-2f87-4def-9284-ab7add1dd6ae/crypt/debug/global/oxide-sled-agent:default.log.1697509861` pub archived: Vec, - // Logs in non-standard places and logs that aren't necessarily oxide logs + /// Any files of special interest for a given service that don't reside in + /// standard paths or don't follow the naming conventions of SMF service + /// files. e.g. `/pool/ext/e12f29b8-1ab8-431e-bc96-1c1298947980/crypt/zone/oxz_cockroachdb_8bbea076-ff60-4330-8302-383e18140ef3/root/data/logs/cockroach.log` pub extra: Vec, } @@ -129,8 +147,14 @@ type ZoneName = String; type ServiceName = String; pub struct Paths { + /// Links to the location of current and rotated log files for a given service pub primary: Utf8PathBuf, + + /// Links to debug directories containing archived log files pub debug: Vec, + + /// Links to directories containing extra files such as cockroachdb logs + /// that reside outside our SMF log and debug service log paths. pub extra: Vec<(&'static str, Utf8PathBuf)>, } From 5e48e4814f0c0d5a6beebc81f123e0bd7d86faf8 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Tue, 23 Jan 2024 01:31:09 +0000 Subject: [PATCH 16/17] Use camino utf8_read_dir --- dev-tools/oxlog/src/lib.rs | 45 +++++++++++++------------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/dev-tools/oxlog/src/lib.rs b/dev-tools/oxlog/src/lib.rs index 231af2e7bb..589b113928 100644 --- a/dev-tools/oxlog/src/lib.rs +++ b/dev-tools/oxlog/src/lib.rs @@ -7,15 +7,14 @@ //! All data is based off of reading the filesystem use anyhow::Context; -use camino::Utf8PathBuf; +use camino::{Utf8DirEntry, Utf8Path, Utf8PathBuf}; use chrono::{DateTime, Utc}; use std::collections::BTreeMap; -use std::fs::{read_dir, DirEntry}; use std::io; use uuid::Uuid; /// Return a UUID if the `DirEntry` contains a directory that parses into a UUID. -fn get_uuid_dir(result: io::Result) -> Option { +fn get_uuid_dir(result: io::Result) -> Option { let Ok(entry) = result else { return None; }; @@ -26,10 +25,7 @@ fn get_uuid_dir(result: io::Result) -> Option { return None; } let file_name = entry.file_name(); - let Some(s) = file_name.to_str() else { - return None; - }; - if let Ok(uuid) = s.parse() { + if let Ok(uuid) = file_name.parse() { Some(uuid) } else { None @@ -44,11 +40,13 @@ pub struct Pools { impl Pools { pub fn read() -> anyhow::Result { - let internal = read_dir("/pool/int/") + let internal = Utf8Path::new("/pool/int/") + .read_dir_utf8() .context("Failed to read /pool/int")? .filter_map(get_uuid_dir) .collect(); - let external = read_dir("/pool/ext/") + let external = Utf8Path::new("/pool/ext/") + .read_dir_utf8() .context("Failed to read /pool/ext")? .filter_map(get_uuid_dir) .collect(); @@ -89,7 +87,7 @@ pub struct LogFile { } impl LogFile { - pub fn read_metadata(&mut self, entry: DirEntry) { + pub fn read_metadata(&mut self, entry: &Utf8DirEntry) { if let Ok(metadata) = entry.metadata() { self.size = Some(metadata.len()); if let Ok(modified) = metadata.modified() { @@ -196,7 +194,7 @@ impl Zones { let zones_path: Utf8PathBuf = ["/pool/ext", &uuid.to_string(), "crypt/zone"].iter().collect(); // Find the zones on the given pool - let Ok(entries) = read_dir(zones_path.as_path()) else { + let Ok(entries) = zones_path.read_dir_utf8() else { continue; }; for entry in entries { @@ -204,10 +202,7 @@ impl Zones { continue; }; let zone = zone_entry.file_name(); - let Some(zone) = zone.to_str() else { - // not utf8 - continue; - }; + // Add the path to the current logs for the zone let mut dir = zones_path.clone(); dir.push(zone); @@ -234,7 +229,7 @@ impl Zones { .iter() .collect(); // Find the zones on the given pool - let Ok(entries) = read_dir(zones_path.as_path()) else { + let Ok(entries) = zones_path.read_dir_utf8() else { continue; }; for entry in entries { @@ -242,10 +237,6 @@ impl Zones { continue; }; let zone = zone_entry.file_name(); - let Some(zone) = zone.to_str() else { - // not utf8 - continue; - }; let mut dir = zones_path.clone(); dir.push(zone); @@ -330,7 +321,7 @@ pub fn oxide_smf_service_name_from_log_file_name( // Given a directory, find all oxide specific SMF service logs and return them // mapped to their inferred service name. fn load_svc_logs(dir: Utf8PathBuf, logs: &mut BTreeMap) { - let Ok(entries) = read_dir(dir.as_path()) else { + let Ok(entries) = dir.read_dir_utf8() else { return; }; for entry in entries { @@ -338,9 +329,6 @@ fn load_svc_logs(dir: Utf8PathBuf, logs: &mut BTreeMap) { continue; }; let filename = entry.file_name(); - let Some(filename) = filename.to_str() else { - continue; - }; // Is this a log file we care about? if is_oxide_smf_log_file(filename) { @@ -355,7 +343,7 @@ fn load_svc_logs(dir: Utf8PathBuf, logs: &mut BTreeMap) { continue; }; - logfile.read_metadata(entry); + logfile.read_metadata(&entry); if logfile.size == Some(0) { // skip 0 size files continue; @@ -382,7 +370,7 @@ fn load_extra_logs( svc_name: &str, logs: &mut BTreeMap, ) { - let Ok(entries) = read_dir(dir.as_path()) else { + let Ok(entries) = dir.read_dir_utf8() else { return; }; @@ -394,13 +382,10 @@ fn load_extra_logs( continue; }; let filename = entry.file_name(); - let Some(filename) = filename.to_str() else { - continue; - }; let mut path = dir.clone(); path.push(filename); let mut logfile = LogFile::new(path); - logfile.read_metadata(entry); + logfile.read_metadata(&entry); if logfile.size == Some(0) { // skip 0 size files continue; From a35b003b05ee45e9291abd8d2b21d2ec519b60a3 Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Tue, 23 Jan 2024 20:12:08 +0000 Subject: [PATCH 17/17] stampy --- .github/buildomat/jobs/package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/buildomat/jobs/package.sh b/.github/buildomat/jobs/package.sh index 350ab37233..5f0c67dd4e 100755 --- a/.github/buildomat/jobs/package.sh +++ b/.github/buildomat/jobs/package.sh @@ -91,7 +91,7 @@ ptime -m cargo run --locked --release --bin omicron-package -- \ -t host target create -i standard -m gimlet -s asic -r multi-sled ptime -m cargo run --locked --release --bin omicron-package -- \ -t host package -stamp_packages omicron-sled-agent mg-ddm-gz propolis-server overlay +stamp_packages omicron-sled-agent mg-ddm-gz propolis-server overlay oxlog # Create global zone package @ /work/global-zone-packages.tar.gz ptime -m ./tools/build-global-zone-packages.sh "$tarball_src_dir" /work