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

[wicket] add rack-update clear #4451

Merged
merged 5 commits into from
Nov 8, 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
2 changes: 2 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 @@ -228,6 +228,7 @@ lazy_static = "1.4.0"
libc = "0.2.150"
linear-map = "1.2.0"
macaddr = { version = "1.0.1", features = ["serde_std"] }
maplit = "1.0.2"
mime_guess = "2.0.4"
mockall = "0.11"
newtype_derive = "0.1.6"
Expand Down
3 changes: 3 additions & 0 deletions clients/wicketd-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ progenitor::generate_api!(
Ipv6Network = ipnetwork::Ipv6Network,
IpNetwork = ipnetwork::IpNetwork,
PutRssUserConfigInsensitive = wicket_common::rack_setup::PutRssUserConfigInsensitive,
ClearUpdateStateResponse = wicket_common::rack_update::ClearUpdateStateResponse,
SpIdentifier = wicket_common::rack_update::SpIdentifier,
SpType = wicket_common::rack_update::SpType,
EventReportForWicketdEngineSpec = wicket_common::update_events::EventReport,
StepEventForWicketdEngineSpec = wicket_common::update_events::StepEvent,
ProgressEventForWicketdEngineSpec = wicket_common::update_events::ProgressEvent,
Expand Down
2 changes: 1 addition & 1 deletion update-engine/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ impl<S: StepSpec> StepContext<S> {
component: failed_step.info.component.clone(),
id: failed_step.info.id.clone(),
description: failed_step.info.description.clone(),
error: NestedError::new(
error: NestedError::from_message_and_causes(
message.clone(),
causes.clone(),
),
Expand Down
54 changes: 53 additions & 1 deletion update-engine/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,19 @@ pub struct NestedError {
}

impl NestedError {
/// Creates a new `NestedError` from an error.
pub fn new(error: &dyn std::error::Error) -> Self {
Self {
message: format!("{}", error),
source: error.source().map(|s| Box::new(Self::new(s))),
}
}

/// Creates a new `NestedError` from a message and a list of causes.
pub fn new(message: String, causes: Vec<String>) -> Self {
pub fn from_message_and_causes(
message: String,
causes: Vec<String>,
) -> Self {
// Yes, this is an actual singly-linked list. You rarely ever see them
// in Rust but they're required to implement Error::source.
let mut next = None;
Expand All @@ -174,6 +185,47 @@ impl std::error::Error for NestedError {
}
}

mod nested_error_serde {
use super::*;
use serde::Deserialize;

#[derive(Serialize, Deserialize)]
struct SerializedNestedError {
message: String,
causes: Vec<String>,
}

impl Serialize for NestedError {
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
let mut causes = Vec::new();
let mut cause = self.source.as_ref();
while let Some(c) = cause {
causes.push(c.message.clone());
cause = c.source.as_ref();
}

let serialized =
SerializedNestedError { message: self.message.clone(), causes };
serialized.serialize(serializer)
}
}

impl<'de> Deserialize<'de> for NestedError {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
let serialized = SerializedNestedError::deserialize(deserializer)?;
Ok(NestedError::from_message_and_causes(
serialized.message,
serialized.causes,
))
}
}
}

impl AsError for NestedError {
fn as_error(&self) -> &(dyn std::error::Error + 'static) {
self
Expand Down
1 change: 1 addition & 0 deletions wicket-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
// Copyright 2023 Oxide Computer Company

pub mod rack_setup;
pub mod rack_update;
pub mod update_events;
70 changes: 70 additions & 0 deletions wicket-common/src/rack_update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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/.

// Copyright 2023 Oxide Computer Company

use std::{collections::BTreeSet, fmt};

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

// TODO: unify this with the one in gateway http_entrypoints.rs.
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
JsonSchema,
)]
pub struct SpIdentifier {
#[serde(rename = "type")]
pub type_: SpType,
pub slot: u32,
}

#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
JsonSchema,
)]
#[serde(rename_all = "lowercase")]
pub enum SpType {
Switch,
Sled,
Power,
}

impl fmt::Display for SpType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SpType::Switch => write!(f, "switch"),
SpType::Sled => write!(f, "sled"),
SpType::Power => write!(f, "power"),
}
}
}

#[derive(
Clone, Debug, Default, PartialEq, Eq, JsonSchema, Serialize, Deserialize,
)]
pub struct ClearUpdateStateResponse {
/// The SPs for which update data was cleared.
pub cleared: BTreeSet<SpIdentifier>,

/// The SPs that had no update state to clear.
pub no_update_data: BTreeSet<SpIdentifier>,
}
1 change: 0 additions & 1 deletion wicket/src/cli/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use super::{
};

pub(crate) struct CommandOutput<'a> {
#[allow(dead_code)]
pub(crate) stdout: &'a mut dyn std::io::Write,
pub(crate) stderr: &'a mut dyn std::io::Write,
}
Expand Down
4 changes: 2 additions & 2 deletions wicket/src/cli/rack_setup/config_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ use toml_edit::InlineTable;
use toml_edit::Item;
use toml_edit::Table;
use toml_edit::Value;
use wicket_common::rack_update::SpType;
use wicketd_client::types::BootstrapSledDescription;
use wicketd_client::types::CurrentRssUserConfigInsensitive;
use wicketd_client::types::IpRange;
use wicketd_client::types::RackNetworkConfigV1;
use wicketd_client::types::SpType;

static TEMPLATE: &str = include_str!("config_template.toml");

Expand Down Expand Up @@ -317,14 +317,14 @@ mod tests {
use omicron_common::api::internal::shared::RackNetworkConfigV1 as InternalRackNetworkConfig;
use std::net::Ipv6Addr;
use wicket_common::rack_setup::PutRssUserConfigInsensitive;
use wicket_common::rack_update::SpIdentifier;
use wicketd_client::types::Baseboard;
use wicketd_client::types::BgpConfig;
use wicketd_client::types::BgpPeerConfig;
use wicketd_client::types::PortConfigV1;
use wicketd_client::types::PortFec;
use wicketd_client::types::PortSpeed;
use wicketd_client::types::RouteConfig;
use wicketd_client::types::SpIdentifier;
use wicketd_client::types::SwitchLocation;

fn put_config_from_current_config(
Expand Down
126 changes: 121 additions & 5 deletions wicket/src/cli/rack_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,24 @@ use std::{
};

use anyhow::{anyhow, bail, Context, Result};
use clap::{Args, Subcommand};
use clap::{Args, Subcommand, ValueEnum};
use slog::Logger;
use tokio::{sync::watch, task::JoinHandle};
use update_engine::display::{GroupDisplay, LineDisplayStyles};
use wicket_common::update_events::EventReport;
use wicketd_client::types::StartUpdateParams;
use update_engine::{
display::{GroupDisplay, LineDisplayStyles},
NestedError,
};
use wicket_common::{
rack_update::ClearUpdateStateResponse, update_events::EventReport,
};
use wicketd_client::types::{ClearUpdateStateParams, StartUpdateParams};

use crate::{
cli::GlobalOpts,
state::{parse_event_report_map, ComponentId, CreateStartUpdateOptions},
state::{
parse_event_report_map, ComponentId, CreateClearUpdateStateOptions,
CreateStartUpdateOptions,
},
wicketd::{create_wicketd_client, WICKETD_TIMEOUT},
};

Expand All @@ -34,6 +42,8 @@ pub(crate) enum RackUpdateArgs {
Start(StartRackUpdateArgs),
/// Attach to one or more running updates.
Attach(AttachArgs),
/// Clear updates.
Clear(ClearArgs),
}

impl RackUpdateArgs {
Expand All @@ -51,6 +61,9 @@ impl RackUpdateArgs {
RackUpdateArgs::Attach(args) => {
args.exec(log, wicketd_addr, global_opts, output).await
}
RackUpdateArgs::Clear(args) => {
args.exec(log, wicketd_addr, global_opts, output).await
}
}
}
}
Expand Down Expand Up @@ -268,6 +281,109 @@ async fn start_fetch_reports_task(
(rx, handle)
}

#[derive(Debug, Args)]
pub(crate) struct ClearArgs {
#[clap(flatten)]
component_ids: ComponentIdSelector,
#[clap(long, value_name = "FORMAT", value_enum, default_value_t = MessageFormat::Human)]
message_format: MessageFormat,
sunshowers marked this conversation as resolved.
Show resolved Hide resolved
}

impl ClearArgs {
async fn exec(
self,
log: Logger,
wicketd_addr: SocketAddrV6,
global_opts: GlobalOpts,
output: CommandOutput<'_>,
) -> Result<()> {
let client = create_wicketd_client(&log, wicketd_addr, WICKETD_TIMEOUT);

let update_ids = self.component_ids.to_component_ids()?;
let response =
do_clear_update_state(client, update_ids, global_opts).await;

match self.message_format {
MessageFormat::Human => {
let response = response?;
let cleared = response
.cleared
.iter()
.map(|sp| {
ComponentId::from_sp_type_and_slot(sp.type_, sp.slot)
.map(|id| id.to_string())
})
.collect::<Result<Vec<_>>>()
.context("unknown component ID returned in response")?;
let no_update_data = response
.no_update_data
.iter()
.map(|sp| {
ComponentId::from_sp_type_and_slot(sp.type_, sp.slot)
.map(|id| id.to_string())
})
.collect::<Result<Vec<_>>>()
.context("unknown component ID returned in response")?;

if !cleared.is_empty() {
slog::info!(
log,
"cleared update state for {} components: {}",
cleared.len(),
cleared.join(", ")
);
}
if !no_update_data.is_empty() {
slog::info!(
log,
"no update data found for {} components: {}",
no_update_data.len(),
no_update_data.join(", ")
);
}
}
MessageFormat::Json => {
let response =
response.map_err(|error| NestedError::new(error.as_ref()));
// Return the response as a JSON object.
serde_json::to_writer_pretty(output.stdout, &response)
.context("error writing to output")?;
if response.is_err() {
bail!("error clearing update state");
}
}
}

Ok(())
}
}

async fn do_clear_update_state(
client: wicketd_client::Client,
update_ids: BTreeSet<ComponentId>,
_global_opts: GlobalOpts,
) -> Result<ClearUpdateStateResponse> {
let options =
CreateClearUpdateStateOptions {}.to_clear_update_state_options()?;
let params = ClearUpdateStateParams {
targets: update_ids.iter().copied().map(Into::into).collect(),
options,
};

let result = client
.post_clear_update_state(&params)
.await
.context("error calling clear_update_state")?;
let response = result.into_inner();
Ok(response)
}

#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug, ValueEnum)]
enum MessageFormat {
Human,
Json,
}

/// Command-line arguments for selecting component IDs.
#[derive(Debug, Args)]
#[clap(next_help_heading = "Component selectors")]
Expand Down
Loading