Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mgs] Use slog-error-chain to clean up error types and logging #4717

Merged
merged 2 commits into from
Dec 19, 2023
Merged
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
21 changes: 21 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ slog = { version = "2.7", features = [ "dynamic-keys", "max_level_trace", "relea
slog-async = "2.8"
slog-dtrace = "0.2"
slog-envlogger = "2.2"
slog-error-chain = { git = "https://github.com/oxidecomputer/slog-error-chain", branch = "main", features = ["derive"] }
slog-term = "2.9"
smf = "0.2"
snafu = "0.7"
Expand Down
2 changes: 2 additions & 0 deletions gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ license = "MPL-2.0"
[dependencies]
anyhow.workspace = true
base64.workspace = true
camino.workspace = true
clap.workspace = true
dropshot.workspace = true
futures.workspace = true
Expand All @@ -25,6 +26,7 @@ signal-hook.workspace = true
signal-hook-tokio.workspace = true
slog.workspace = true
slog-dtrace.workspace = true
slog-error-chain.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["full"] }
tokio-stream.workspace = true
Expand Down
8 changes: 3 additions & 5 deletions gateway/src/bin/mgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
//! Executable program to run gateway, the management gateway service

use anyhow::{anyhow, Context};
use camino::Utf8PathBuf;
use clap::Parser;
use futures::StreamExt;
use omicron_common::cmd::{fatal, CmdError};
use omicron_gateway::{run_openapi, start_server, Config, MgsArguments};
use signal_hook::consts::signal;
use signal_hook_tokio::Signals;
use std::net::SocketAddrV6;
use std::path::PathBuf;
use uuid::Uuid;

#[derive(Debug, Parser)]
Expand All @@ -24,7 +24,7 @@ enum Args {
/// Start an MGS server
Run {
#[clap(name = "CONFIG_FILE_PATH", action)]
config_file_path: PathBuf,
config_file_path: Utf8PathBuf,

/// Read server ID and address(es) for dropshot server from our SMF
/// properties (only valid when running as a service on illumos)
Expand Down Expand Up @@ -81,9 +81,7 @@ async fn do_run() -> Result<(), CmdError> {
address,
} => {
let config = Config::from_file(&config_file_path)
.with_context(|| {
format!("failed to parse {}", config_file_path.display())
})
.map_err(anyhow::Error::new)
.map_err(CmdError::Failure)?;

let mut signals = Signals::new([signal::SIGUSR1])
Expand Down
53 changes: 19 additions & 34 deletions gateway/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
//! configuration

use crate::management_switch::SwitchConfig;
use camino::Utf8Path;
use camino::Utf8PathBuf;
use dropshot::ConfigLogging;
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::path::PathBuf;
use slog_error_chain::SlogInlineError;
use thiserror::Error;

/// Configuration for a gateway server
Expand All @@ -30,13 +31,11 @@ impl Config {
/// Load a `Config` from the given TOML file
///
/// This config object can then be used to create a new gateway server.
// The format is described in the README. // TODO add a README
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config, LoadError> {
let path = path.as_ref();
pub fn from_file(path: &Utf8Path) -> Result<Config, LoadError> {
let file_contents = std::fs::read_to_string(path)
.map_err(|e| (path.to_path_buf(), e))?;
.map_err(|err| LoadError::Io { path: path.into(), err })?;
let config_parsed: Config = toml::from_str(&file_contents)
.map_err(|e| (path.to_path_buf(), e))?;
.map_err(|err| LoadError::Parse { path: path.into(), err })?;
Ok(config_parsed)
}
}
Expand All @@ -46,32 +45,18 @@ pub struct PartialDropshotConfig {
pub request_body_max_bytes: usize,
}

#[derive(Debug, Error)]
#[derive(Debug, Error, SlogInlineError)]
pub enum LoadError {
#[error("error reading \"{}\": {}", path.display(), err)]
Io { path: PathBuf, err: std::io::Error },
#[error("error parsing \"{}\": {}", path.display(), err)]
Parse { path: PathBuf, err: toml::de::Error },
}

impl From<(PathBuf, std::io::Error)> for LoadError {
fn from((path, err): (PathBuf, std::io::Error)) -> Self {
LoadError::Io { path, err }
}
}

impl From<(PathBuf, toml::de::Error)> for LoadError {
fn from((path, err): (PathBuf, toml::de::Error)) -> Self {
LoadError::Parse { path, err }
}
}

impl std::cmp::PartialEq<std::io::Error> for LoadError {
fn eq(&self, other: &std::io::Error) -> bool {
if let LoadError::Io { err, .. } = self {
err.kind() == other.kind()
} else {
false
}
}
#[error("error reading \"{path}\"")]
Io {
path: Utf8PathBuf,
#[source]
err: std::io::Error,
},
#[error("error parsing \"{path}\"")]
Parse {
path: Utf8PathBuf,
#[source]
err: toml::de::Error,
},
}
148 changes: 85 additions & 63 deletions gateway/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
//! Error handling facilities for the management gateway.

use crate::management_switch::SpIdentifier;
use anyhow::anyhow;
use dropshot::HttpError;
use gateway_messages::SpError;
pub use gateway_sp_comms::error::CommunicationError;
use gateway_sp_comms::error::UpdateError;
use gateway_sp_comms::BindError;
use slog_error_chain::InlineErrorChain;
use slog_error_chain::SlogInlineError;
use std::time::Duration;
use thiserror::Error;

#[derive(Debug, Error)]
#[derive(Debug, Error, SlogInlineError)]
pub enum StartupError {
#[error("invalid configuration file: {}", .reasons.join(", "))]
InvalidConfig { reasons: Vec<String> },
Expand All @@ -23,116 +24,137 @@ pub enum StartupError {
BindError(#[from] BindError),
}

#[derive(Debug, Error)]
#[derive(Debug, Error, SlogInlineError)]
pub enum SpCommsError {
#[error("discovery process not yet complete")]
DiscoveryNotYetComplete,
#[error("location discovery failed: {reason}")]
DiscoveryFailed { reason: String },
#[error("nonexistent SP (type {:?}, slot {})", .0.typ, .0.slot)]
#[error("nonexistent SP {0:?}")]
SpDoesNotExist(SpIdentifier),
#[error(
"unknown socket address for SP (type {:?}, slot {})",
.0.typ,
.0.slot,
)]
#[error("unknown socket address for SP {0:?}")]
SpAddressUnknown(SpIdentifier),
#[error(
"timeout ({timeout:?}) elapsed communicating with {sp:?} on port {port}"
)]
Timeout { timeout: Duration, port: usize, sp: Option<SpIdentifier> },
#[error("error communicating with SP: {0}")]
SpCommunicationFailed(#[from] CommunicationError),
#[error("updating SP failed: {0}")]
UpdateFailed(#[from] UpdateError),
#[error("error communicating with SP {sp:?}")]
SpCommunicationFailed {
sp: SpIdentifier,
#[source]
err: CommunicationError,
},
#[error("updating SP {sp:?} failed")]
UpdateFailed {
sp: SpIdentifier,
#[source]
err: UpdateError,
},
}

impl From<SpCommsError> for HttpError {
fn from(err: SpCommsError) -> Self {
match err {
fn from(error: SpCommsError) -> Self {
match error {
SpCommsError::SpDoesNotExist(_) => HttpError::for_bad_request(
Some("InvalidSp".to_string()),
format!("{:#}", anyhow!(err)),
InlineErrorChain::new(&error).to_string(),
),
SpCommsError::SpCommunicationFailed(
CommunicationError::SpError(
SpError::SerialConsoleAlreadyAttached,
),
) => HttpError::for_bad_request(
SpCommsError::SpCommunicationFailed {
err:
CommunicationError::SpError(
SpError::SerialConsoleAlreadyAttached,
),
..
} => HttpError::for_bad_request(
Some("SerialConsoleAttached".to_string()),
format!("{:#}", anyhow!(err)),
InlineErrorChain::new(&error).to_string(),
),
SpCommsError::SpCommunicationFailed(
CommunicationError::SpError(SpError::RequestUnsupportedForSp),
) => HttpError::for_bad_request(
SpCommsError::SpCommunicationFailed {
err:
CommunicationError::SpError(SpError::RequestUnsupportedForSp),
..
} => HttpError::for_bad_request(
Some("RequestUnsupportedForSp".to_string()),
format!("{:#}", anyhow!(err)),
InlineErrorChain::new(&error).to_string(),
),
SpCommsError::SpCommunicationFailed(
CommunicationError::SpError(
SpError::RequestUnsupportedForComponent,
),
) => HttpError::for_bad_request(
SpCommsError::SpCommunicationFailed {
err:
CommunicationError::SpError(
SpError::RequestUnsupportedForComponent,
),
..
} => HttpError::for_bad_request(
Some("RequestUnsupportedForComponent".to_string()),
format!("{:#}", anyhow!(err)),
InlineErrorChain::new(&error).to_string(),
),
SpCommsError::SpCommunicationFailed(
CommunicationError::SpError(SpError::InvalidSlotForComponent),
) => HttpError::for_bad_request(
SpCommsError::SpCommunicationFailed {
err:
CommunicationError::SpError(SpError::InvalidSlotForComponent),
..
} => HttpError::for_bad_request(
Some("InvalidSlotForComponent".to_string()),
format!("{:#}", anyhow!(err)),
InlineErrorChain::new(&error).to_string(),
),
SpCommsError::UpdateFailed(UpdateError::ImageTooLarge) => {
HttpError::for_bad_request(
Some("ImageTooLarge".to_string()),
format!("{:#}", anyhow!(err)),
)
}
SpCommsError::UpdateFailed(UpdateError::Communication(
CommunicationError::SpError(SpError::UpdateSlotBusy),
)) => http_err_with_message(
SpCommsError::UpdateFailed {
err: UpdateError::ImageTooLarge,
..
} => HttpError::for_bad_request(
Some("ImageTooLarge".to_string()),
InlineErrorChain::new(&error).to_string(),
),
SpCommsError::UpdateFailed {
err:
UpdateError::Communication(CommunicationError::SpError(
SpError::UpdateSlotBusy,
)),
..
} => http_err_with_message(
http::StatusCode::SERVICE_UNAVAILABLE,
"UpdateSlotBusy",
format!("{:#}", anyhow!(err)),
InlineErrorChain::new(&error).to_string(),
),
SpCommsError::UpdateFailed(UpdateError::Communication(
CommunicationError::SpError(SpError::UpdateInProgress {
..
}),
)) => http_err_with_message(
SpCommsError::UpdateFailed {
err:
UpdateError::Communication(CommunicationError::SpError(
SpError::UpdateInProgress { .. },
)),
..
} => http_err_with_message(
http::StatusCode::SERVICE_UNAVAILABLE,
"UpdateInProgress",
format!("{:#}", anyhow!(err)),
InlineErrorChain::new(&error).to_string(),
),
SpCommsError::DiscoveryNotYetComplete => http_err_with_message(
http::StatusCode::SERVICE_UNAVAILABLE,
"DiscoveryNotYetComplete",
format!("{:#}", anyhow!(err)),
InlineErrorChain::new(&error).to_string(),
),
SpCommsError::SpAddressUnknown(_) => http_err_with_message(
http::StatusCode::SERVICE_UNAVAILABLE,
"SpAddressUnknown",
format!("{:#}", anyhow!(err)),
InlineErrorChain::new(&error).to_string(),
),
SpCommsError::DiscoveryFailed { .. } => http_err_with_message(
http::StatusCode::SERVICE_UNAVAILABLE,
"DiscoveryFailed ",
format!("{:#}", anyhow!(err)),
InlineErrorChain::new(&error).to_string(),
),
SpCommsError::Timeout { .. } => http_err_with_message(
http::StatusCode::SERVICE_UNAVAILABLE,
"Timeout ",
format!("{:#}", anyhow!(err)),
InlineErrorChain::new(&error).to_string(),
),
SpCommsError::SpCommunicationFailed(_) => http_err_with_message(
http::StatusCode::SERVICE_UNAVAILABLE,
"SpCommunicationFailed",
format!("{:#}", anyhow!(err)),
),
SpCommsError::UpdateFailed(_) => http_err_with_message(
SpCommsError::SpCommunicationFailed { .. } => {
http_err_with_message(
http::StatusCode::SERVICE_UNAVAILABLE,
"SpCommunicationFailed",
InlineErrorChain::new(&error).to_string(),
)
}
SpCommsError::UpdateFailed { .. } => http_err_with_message(
http::StatusCode::SERVICE_UNAVAILABLE,
"UpdateFailed",
format!("{:#}", anyhow!(err)),
InlineErrorChain::new(&error).to_string(),
),
}
}
Expand Down
Loading
Loading