Skip to content

Commit

Permalink
[wicketd] make clear_update_state take multiple SPs (#4430)
Browse files Browse the repository at this point in the history
This ensures atomicity and will be used for a new CLI interface.
  • Loading branch information
sunshowers authored Nov 6, 2023
1 parent 5229459 commit ebb25eb
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 60 deletions.
85 changes: 61 additions & 24 deletions openapi/wicketd.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,44 +131,31 @@
}
}
},
"/clear-update-state/{type}/{slot}": {
"/clear-update-state": {
"post": {
"summary": "Resets update state for a sled.",
"description": "Use this to clear update state after a failed update.",
"operationId": "post_clear_update_state",
"parameters": [
{
"in": "path",
"name": "slot",
"required": true,
"schema": {
"type": "integer",
"format": "uint32",
"minimum": 0
}
},
{
"in": "path",
"name": "type",
"required": true,
"schema": {
"$ref": "#/components/schemas/SpType"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ClearUpdateStateOptions"
"$ref": "#/components/schemas/ClearUpdateStateParams"
}
}
},
"required": true
},
"responses": {
"204": {
"description": "resource updated"
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ClearUpdateStateResponse"
}
}
}
},
"4XX": {
"$ref": "#/components/responses/Error"
Expand Down Expand Up @@ -1014,6 +1001,56 @@
}
}
},
"ClearUpdateStateParams": {
"type": "object",
"properties": {
"options": {
"description": "Options for clearing update state",
"allOf": [
{
"$ref": "#/components/schemas/ClearUpdateStateOptions"
}
]
},
"targets": {
"description": "The SP identifiers to clear the update state for. Must be non-empty.",
"type": "array",
"items": {
"$ref": "#/components/schemas/SpIdentifier"
},
"uniqueItems": true
}
},
"required": [
"options",
"targets"
]
},
"ClearUpdateStateResponse": {
"type": "object",
"properties": {
"cleared": {
"description": "The SPs for which update data was cleared.",
"type": "array",
"items": {
"$ref": "#/components/schemas/SpIdentifier"
},
"uniqueItems": true
},
"no_update_data": {
"description": "The SPs that had no update state to clear.",
"type": "array",
"items": {
"$ref": "#/components/schemas/SpIdentifier"
},
"uniqueItems": true
}
},
"required": [
"cleared",
"no_update_data"
]
},
"CurrentRssUserConfig": {
"type": "object",
"properties": {
Expand Down
22 changes: 12 additions & 10 deletions wicket/src/wicketd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ use std::net::SocketAddrV6;
use tokio::sync::mpsc::{self, Sender, UnboundedSender};
use tokio::time::{interval, Duration, MissedTickBehavior};
use wicketd_client::types::{
AbortUpdateOptions, ClearUpdateStateOptions, GetInventoryParams,
GetInventoryResponse, GetLocationResponse, IgnitionCommand, SpIdentifier,
SpType, StartUpdateOptions, StartUpdateParams,
AbortUpdateOptions, ClearUpdateStateOptions, ClearUpdateStateParams,
GetInventoryParams, GetInventoryResponse, GetLocationResponse,
IgnitionCommand, SpIdentifier, SpType, StartUpdateOptions,
StartUpdateParams,
};

use crate::events::EventReportMap;
Expand Down Expand Up @@ -229,14 +230,15 @@ impl WicketdManager {
tokio::spawn(async move {
let update_client =
create_wicketd_client(&log, addr, WICKETD_TIMEOUT);
let sp: SpIdentifier = component_id.into();
let response = match update_client
.post_clear_update_state(sp.type_, sp.slot, &options)
.await
{
Ok(_) => Ok(()),
Err(error) => Err(error.to_string()),
let params = ClearUpdateStateParams {
targets: vec![component_id.into()],
options,
};
let response =
match update_client.post_clear_update_state(&params).await {
Ok(_) => Ok(()),
Err(error) => Err(error.to_string()),
};

slog::info!(
log,
Expand Down
42 changes: 33 additions & 9 deletions wicketd/src/http_entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,13 +708,31 @@ pub(crate) enum UpdateSimulatedResult {
Failure,
}

#[derive(Clone, Debug, JsonSchema, Deserialize)]
pub(crate) struct ClearUpdateStateParams {
/// The SP identifiers to clear the update state for. Must be non-empty.
pub(crate) targets: BTreeSet<SpIdentifier>,

/// Options for clearing update state
pub(crate) options: ClearUpdateStateOptions,
}

#[derive(Clone, Debug, JsonSchema, Deserialize)]
pub(crate) struct ClearUpdateStateOptions {
/// If passed in, fails the clear update state operation with a simulated
/// error.
pub(crate) test_error: Option<UpdateTestError>,
}

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

/// The SPs that had no update state to clear.
pub(crate) no_update_data: BTreeSet<SpIdentifier>,
}

#[derive(Clone, Debug, JsonSchema, Deserialize)]
pub(crate) struct AbortUpdateOptions {
/// The message to abort the update with.
Expand Down Expand Up @@ -1080,25 +1098,31 @@ async fn post_abort_update(
/// Use this to clear update state after a failed update.
#[endpoint {
method = POST,
path = "/clear-update-state/{type}/{slot}",
path = "/clear-update-state",
}]
async fn post_clear_update_state(
rqctx: RequestContext<ServerContext>,
target: Path<SpIdentifier>,
opts: TypedBody<ClearUpdateStateOptions>,
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
params: TypedBody<ClearUpdateStateParams>,
) -> Result<HttpResponseOk<ClearUpdateStateResponse>, HttpError> {
let log = &rqctx.log;
let target = target.into_inner();
let rqctx = rqctx.context();
let params = params.into_inner();

let opts = opts.into_inner();
if let Some(test_error) = opts.test_error {
if params.targets.is_empty() {
return Err(HttpError::for_bad_request(
None,
"No targets specified".into(),
));
}

if let Some(test_error) = params.options.test_error {
return Err(test_error
.into_http_error(log, "clearing update state")
.await);
}

match rqctx.context().update_tracker.clear_update_state(target).await {
Ok(()) => Ok(HttpResponseUpdatedNoContent {}),
match rqctx.update_tracker.clear_update_state(params.targets).await {
Ok(response) => Ok(HttpResponseOk(response)),
Err(err) => Err(err.to_http_error()),
}
}
Expand Down
54 changes: 37 additions & 17 deletions wicketd/src/update_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::artifacts::ArtifactIdData;
use crate::artifacts::UpdatePlan;
use crate::artifacts::WicketdArtifactStore;
use crate::helpers::sps_to_string;
use crate::http_entrypoints::ClearUpdateStateResponse;
use crate::http_entrypoints::GetArtifactsAndEventReportsResponse;
use crate::http_entrypoints::StartUpdateOptions;
use crate::http_entrypoints::UpdateSimulatedResult;
Expand Down Expand Up @@ -184,10 +185,10 @@ impl UpdateTracker {

pub(crate) async fn clear_update_state(
&self,
sp: SpIdentifier,
) -> Result<(), ClearUpdateStateError> {
sps: BTreeSet<SpIdentifier>,
) -> Result<ClearUpdateStateResponse, ClearUpdateStateError> {
let mut update_data = self.sp_update_data.lock().await;
update_data.clear_update_state(sp)
update_data.clear_update_state(&sps)
}

pub(crate) async fn abort_update(
Expand Down Expand Up @@ -609,19 +610,38 @@ impl UpdateTrackerData {

fn clear_update_state(
&mut self,
sp: SpIdentifier,
) -> Result<(), ClearUpdateStateError> {
// Is an update currently running? If so, then reject the request.
let is_running = self
.sp_update_data
.get(&sp)
.map_or(false, |update_data| !update_data.task.is_finished());
if is_running {
return Err(ClearUpdateStateError::UpdateInProgress);
sps: &BTreeSet<SpIdentifier>,
) -> Result<ClearUpdateStateResponse, ClearUpdateStateError> {
// Are any updates currently running? If so, then reject the request.
let in_progress_updates = sps
.iter()
.filter_map(|sp| {
self.sp_update_data
.get(sp)
.map_or(false, |update_data| {
!update_data.task.is_finished()
})
.then(|| *sp)
})
.collect::<Vec<_>>();

if !in_progress_updates.is_empty() {
return Err(ClearUpdateStateError::UpdateInProgress(
in_progress_updates,
));
}

self.sp_update_data.remove(&sp);
Ok(())
let mut resp = ClearUpdateStateResponse::default();

for sp in sps {
if self.sp_update_data.remove(sp).is_some() {
resp.cleared.insert(*sp);
} else {
resp.no_update_data.insert(*sp);
}
}

Ok(resp)
}

async fn abort_update(
Expand Down Expand Up @@ -695,16 +715,16 @@ pub enum StartUpdateError {

#[derive(Debug, Clone, Error, Eq, PartialEq)]
pub enum ClearUpdateStateError {
#[error("target is currently being updated")]
UpdateInProgress,
#[error("targets are currently being updated: {}", sps_to_string(.0))]
UpdateInProgress(Vec<SpIdentifier>),
}

impl ClearUpdateStateError {
pub(crate) fn to_http_error(&self) -> HttpError {
let message = DisplayErrorChain::new(self).to_string();

match self {
ClearUpdateStateError::UpdateInProgress => {
ClearUpdateStateError::UpdateInProgress(_) => {
HttpError::for_bad_request(None, message)
}
}
Expand Down

0 comments on commit ebb25eb

Please sign in to comment.