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

add cancel action API #11

Merged
merged 5 commits into from
Jun 29, 2021
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
14 changes: 14 additions & 0 deletions hawkbit/examples/polling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,20 @@ async fn main() -> Result<()> {
.await?;
}

if let Some(cancel_action) = reply.cancel_action() {
println!("Action to cancel: {}", cancel_action.id().await?);

cancel_action
.send_feedback(Execution::Proceeding, Finished::None, vec!["Cancelling"])
.await?;

cancel_action
.send_feedback(Execution::Closed, Finished::Success, vec![])
.await?;

println!("Action cancelled");
}

let t = reply.polling_sleep()?;
sleep(t).await;
}
Expand Down
2 changes: 2 additions & 0 deletions hawkbit/src/ddi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@

// FIXME: set link to hawbit/examples/polling.rs once we have the final public repo

mod cancel_action;
mod client;
mod common;
mod config_data;
mod deployment_base;
mod feedback;
mod poll;

pub use cancel_action::CancelAction;
pub use client::{Client, Error};
pub use common::{Execution, Finished};
pub use config_data::{ConfigRequest, Mode};
Expand Down
77 changes: 77 additions & 0 deletions hawkbit/src/ddi/cancel_action.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2021, Collabora Ltd.
// SPDX-License-Identifier: MIT OR Apache-2.0

// Cancelled operation

use reqwest::Client;
use serde::Deserialize;

use crate::ddi::client::Error;
use crate::ddi::common::{send_feedback_internal, Execution, Finished};

/// A request from the server to cancel an update.
///
/// Call [`CancelAction::id()`] to retrieve the ID of the action to cancel.
///
/// Cancel actions need to be closed by sending feedback to the server using
/// [`CancelAction::send_feedback`] with either
/// [`Finished::Success`] or [`Finished::Failure`].
#[derive(Debug)]
pub struct CancelAction {
client: Client,
url: String,
}

impl CancelAction {
pub(crate) fn new(client: Client, url: String) -> Self {
Self { client, url }
}

/// Retrieve the id of the action to cancel.
pub async fn id(&self) -> Result<String, Error> {
let reply = self.client.get(&self.url).send().await?;
reply.error_for_status_ref()?;

let reply = reply.json::<CancelReply>().await?;
Ok(reply.cancel_action.stop_id)
}

/// Send feedback to server about this cancel action.
///
/// # Arguments
/// * `execution`: status of the action execution.
/// * `finished`: defined status of the result. The action will be kept open on the server until the controller on the device reports either [`Finished::Success`] or [`Finished::Failure`].
/// * `details`: list of details message information.
pub async fn send_feedback(
&self,
execution: Execution,
finished: Finished,
details: Vec<&str>,
) -> Result<(), Error> {
let id = self.id().await?;

send_feedback_internal::<bool>(
&self.client,
&self.url,
&id,
execution,
finished,
None,
details,
)
.await
}
}

#[derive(Debug, Deserialize)]
struct CancelReply {
id: String,
#[serde(rename = "cancelAction")]
cancel_action: CancelActionReply,
}

#[derive(Debug, Deserialize)]
struct CancelActionReply {
#[serde(rename = "stopId")]
stop_id: String,
}
32 changes: 32 additions & 0 deletions hawkbit/src/ddi/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@

use std::fmt;

use reqwest::Client;
use serde::{Deserialize, Serialize};
use url::Url;

use crate::ddi::client::Error;
use crate::ddi::feedback::Feedback;

#[derive(Debug, Deserialize)]
pub struct Link {
Expand Down Expand Up @@ -46,3 +51,30 @@ pub enum Finished {
/// Operation is still in-progress
None,
}

pub(crate) async fn send_feedback_internal<T: Serialize>(
client: &Client,
url: &str,
id: &str,
execution: Execution,
finished: Finished,
progress: Option<T>,
details: Vec<&str>,
) -> Result<(), Error> {
let mut url: Url = url.parse()?;
{
let mut paths = url
.path_segments_mut()
.map_err(|_| url::ParseError::SetHostOnCannotBeABaseUrl)?;
paths.push("feedback");
}
url.set_query(None);

let details = details.iter().map(|m| m.to_string()).collect();
let feedback = Feedback::new(id, execution, finished, progress, details);

let reply = client.post(&url.to_string()).json(&feedback).send().await?;
reply.error_for_status()?;

Ok(())
}
64 changes: 21 additions & 43 deletions hawkbit/src/ddi/deployment_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ use tokio::{
fs::{DirBuilder, File},
io::AsyncWriteExt,
};
use url::Url;

use crate::ddi::client::Error;
use crate::ddi::common::{Execution, Finished, Link};
use crate::ddi::feedback::Feedback;
use crate::ddi::common::{send_feedback_internal, Execution, Finished, Link};

#[derive(Debug)]
/// A pending update whose details have not been retrieved yet.
Expand Down Expand Up @@ -262,42 +260,6 @@ impl Update {
Ok(result)
}

async fn send_feedback_internal<T: Serialize>(
&self,
execution: Execution,
finished: Finished,
progress: Option<T>,
details: Vec<&str>,
) -> Result<(), Error> {
let mut url: Url = self.url.parse()?;
{
match url.path_segments_mut() {
Err(_) => {
return Err(Error::ParseUrlError(
url::ParseError::SetHostOnCannotBeABaseUrl,
))
}
Ok(mut paths) => {
paths.push("feedback");
}
}
}
url.set_query(None);

let details = details.iter().map(|m| m.to_string()).collect();
let feedback = Feedback::new(&self.info.id, execution, finished, progress, details);

let reply = self
.client
.post(&url.to_string())
.json(&feedback)
.send()
.await?;
reply.error_for_status()?;

Ok(())
}

/// Send feedback to server about this update, with custom progress information.
///
/// # Arguments
Expand All @@ -312,8 +274,16 @@ impl Update {
progress: T,
details: Vec<&str>,
) -> Result<(), Error> {
self.send_feedback_internal(execution, finished, Some(progress), details)
.await
send_feedback_internal(
&self.client,
&self.url,
&self.info.id,
execution,
finished,
Some(progress),
details,
)
.await
}

/// Send feedback to server about this update.
Expand All @@ -325,8 +295,16 @@ impl Update {
finished: Finished,
details: Vec<&str>,
) -> Result<(), Error> {
self.send_feedback_internal::<bool>(execution, finished, None, details)
.await
send_feedback_internal::<bool>(
&self.client,
&self.url,
&self.info.id,
execution,
finished,
None,
details,
)
.await
}
}

Expand Down
1 change: 1 addition & 0 deletions hawkbit/src/ddi/feedback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ struct Status<T: Serialize> {
#[derive(Debug, Serialize)]
pub struct ResultT<T: Serialize> {
finished: Finished,
#[serde(skip_serializing_if = "Option::is_none")]
progress: Option<T>,
}

Expand Down
12 changes: 12 additions & 0 deletions hawkbit/src/ddi/poll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::time::Duration;
use reqwest::Client;
use serde::Deserialize;

use crate::ddi::cancel_action::CancelAction;
use crate::ddi::client::Error;
use crate::ddi::common::Link;
use crate::ddi::config_data::ConfigRequest;
Expand Down Expand Up @@ -75,6 +76,17 @@ impl Reply {
None => None,
}
}

/// Returns pending cancel action, if any.
pub fn cancel_action(&self) -> Option<CancelAction> {
match &self.reply.links {
Some(links) => links
.cancel_action
.as_ref()
.map(|l| CancelAction::new(self.client.clone(), l.to_string())),
None => None,
}
}
}

impl Polling {
Expand Down
44 changes: 41 additions & 3 deletions hawkbit/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ async fn deployment() {
}

#[tokio::test]
async fn send_feedback() {
async fn send_deployment_feedback() {
init();

let server = ServerBuilder::default().build();
Expand All @@ -233,7 +233,7 @@ async fn send_feedback() {
let update = update.fetch().await.expect("failed to fetch update info");

// Send feedback without progress
let mut mock = target.expect_feedback(
let mut mock = target.expect_deployment_feedback(
&deploy_id,
Execution::Proceeding,
Finished::None,
Expand All @@ -250,7 +250,7 @@ async fn send_feedback() {
mock.delete();

// Send feedback with progress
let mut mock = target.expect_feedback(
let mut mock = target.expect_deployment_feedback(
&deploy_id,
Execution::Closed,
Finished::Success,
Expand Down Expand Up @@ -460,3 +460,41 @@ async fn wrong_checksums() {
}
}
}

#[tokio::test]
async fn cancel_action() {
init();

let server = ServerBuilder::default().build();
let (client, target) = add_target(&server, "Target1");
target.cancel_action("10");

let reply = client.poll().await.expect("poll failed");
assert!(reply.config_data_request().is_none());
assert!(reply.update().is_none());
let cancel_action = reply.cancel_action().expect("missing cancel action");

let id = cancel_action
.id()
.await
.expect("failed to fetch cancel action id");
assert_eq!(id, "10");

assert_eq!(target.poll_hits(), 1);
assert_eq!(target.cancel_action_hits(), 1);

let mut mock = target.expect_cancel_feedback(
&id,
Execution::Proceeding,
Finished::None,
vec!["Cancelling"],
);
assert_eq!(mock.hits(), 0);

cancel_action
.send_feedback(Execution::Proceeding, Finished::None, vec!["Cancelling"])
.await
.expect("Failed to send feedback");
assert_eq!(mock.hits(), 1);
mock.delete();
}
Loading