Skip to content

Commit

Permalink
Merge pull request #2771 from rina23q/improve/2654/c8y-mapper-support…
Browse files Browse the repository at this point in the history
…s-advanced-software-type

c8y-mapper sends software list to advanced software management endpoint
  • Loading branch information
rina23q authored Mar 21, 2024
2 parents fd7a532 + 667652a commit 4109ef6
Show file tree
Hide file tree
Showing 10 changed files with 406 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use std::str::FromStr;
use strum::Display;

/// A flag that switches legacy or advanced software management API.
/// Can be set to auto in the future, see #2778.
#[derive(
Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, doku::Document, Display,
)]
#[strum(serialize_all = "camelCase")]
pub enum SoftwareManagementApiFlag {
Legacy,
Advanced,
}

#[derive(thiserror::Error, Debug)]
#[error("Failed to parse flag: {input}. Supported values are: legacy, advanced")]
pub struct InvalidSoftwareManagementApiFlag {
input: String,
}

impl FromStr for SoftwareManagementApiFlag {
type Err = InvalidSoftwareManagementApiFlag;

fn from_str(input: &str) -> Result<Self, Self::Err> {
match input {
"legacy" => Ok(SoftwareManagementApiFlag::Legacy),
"advanced" => Ok(SoftwareManagementApiFlag::Advanced),
_ => Err(InvalidSoftwareManagementApiFlag {
input: input.to_string(),
}),
}
}
}
2 changes: 2 additions & 0 deletions crates/common/tedge_config/src/tedge_config_cli/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod apt_config;
pub mod auto;
pub mod c8y_software_management;
pub mod connect_url;
pub mod flag;
pub mod host_port;
Expand All @@ -15,6 +16,7 @@ pub const MQTT_TLS_PORT: u16 = 8883;

pub use self::apt_config::*;
pub use self::auto::*;
pub use self::c8y_software_management::*;
pub use self::connect_url::*;
pub use self::flag::*;
#[doc(inline)]
Expand Down
11 changes: 11 additions & 0 deletions crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::AutoFlag;
use crate::ConnectUrl;
use crate::HostPort;
use crate::Seconds;
use crate::SoftwareManagementApiFlag;
use crate::TEdgeConfigLocation;
use crate::TemplatesSet;
use crate::HTTPS_PORT;
Expand Down Expand Up @@ -475,6 +476,16 @@ define_tedge_config! {
#[tedge_config(example = "true", default(value = true))]
clean_start: bool,
},

software_management: {
/// Switch legacy or advanced software management API to use. Value: legacy or advanced
#[tedge_config(example = "advanced", default(variable = "SoftwareManagementApiFlag::Legacy"))]
api: SoftwareManagementApiFlag,

/// Enable publishing c8y_SupportedSoftwareTypes fragment to the c8y inventory API
#[tedge_config(example = "true", default(value = false))]
with_types: bool,
},
},

#[tedge_config(deprecated_name = "azure")] // for 0.1.0 compatibility
Expand Down
168 changes: 163 additions & 5 deletions crates/core/c8y_api/src/smartrest/smartrest_serializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ use crate::smartrest::topic::C8yTopic;
use csv::StringRecord;
use mqtt_channel::Message;
use serde::ser::SerializeSeq;
use serde::Deserialize;
use serde::Serialize;
use serde::Serializer;
use tedge_api::SoftwareListCommand;
use tedge_api::SoftwareModule;
use tedge_config::TopicPrefix;
use tracing::warn;

Expand Down Expand Up @@ -105,11 +106,93 @@ pub fn declare_supported_operations(ops: &[&str]) -> String {
format!("114,{}", fields_to_csv_string(ops))
}

#[derive(Debug, Deserialize, Serialize, Eq, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct SmartRestSoftwareModuleItem {
pub software: String,
pub version: Option<String>,
pub url: Option<String>,
pub name: String,
pub version: String,
pub software_type: String,
pub url: String,
}

impl From<SoftwareModule> for SmartRestSoftwareModuleItem {
fn from(module: SoftwareModule) -> Self {
let url = match module.url {
None => "".to_string(),
Some(download_info) => download_info.url,
};

Self {
name: module.name,
version: module.version.unwrap_or_default(),
software_type: module.module_type.unwrap_or(SoftwareModule::default_type()),
url,
}
}
}

pub enum AdvancedSoftwareList {
Set(Vec<SmartRestSoftwareModuleItem>),
Append(Vec<SmartRestSoftwareModuleItem>),
}

impl AdvancedSoftwareList {
fn smartrest_payload(self) -> String {
let vec = match self {
AdvancedSoftwareList::Set(items) => Self::create_software_list("140", items),
AdvancedSoftwareList::Append(items) => Self::create_software_list("141", items),
};
let list: Vec<&str> = vec.iter().map(std::ops::Deref::deref).collect();

fields_to_csv_string(list.as_slice())
}

fn create_software_list(id: &str, items: Vec<SmartRestSoftwareModuleItem>) -> Vec<String> {
if items.is_empty() {
vec![id.into(), "".into(), "".into(), "".into(), "".into()]
} else {
let mut vec = vec![id.to_string()];
for item in items {
vec.push(item.name);
vec.push(item.version);
vec.push(item.software_type);
vec.push(item.url);
}
vec
}
}
}

pub fn get_advanced_software_list_payloads(
software_list_cmd: &SoftwareListCommand,
chunk_size: usize,
) -> Vec<String> {
let mut messages: Vec<String> = Vec::new();

if software_list_cmd.modules().is_empty() {
messages.push(AdvancedSoftwareList::Set(vec![]).smartrest_payload());
return messages;
}

let mut items: Vec<SmartRestSoftwareModuleItem> = Vec::new();
software_list_cmd
.modules()
.into_iter()
.for_each(|software_module| {
let c8y_software_module: SmartRestSoftwareModuleItem = software_module.into();
items.push(c8y_software_module);
});

let mut first = true;
for chunk in items.chunks(chunk_size) {
if first {
messages.push(AdvancedSoftwareList::Set(chunk.to_vec()).smartrest_payload());
first = false;
} else {
messages.push(AdvancedSoftwareList::Append(chunk.to_vec()).smartrest_payload());
}
}

messages
}

/// A supported operation of the thin-edge device, used in status updates via SmartREST
Expand Down Expand Up @@ -230,6 +313,9 @@ pub trait OperationStatusMessage {
#[cfg(test)]
mod tests {
use super::*;
use tedge_api::messages::SoftwareListCommandPayload;
use tedge_api::mqtt_topics::EntityTopicId;
use tedge_api::Jsonify;

#[test]
fn serialize_smartrest_supported_operations() {
Expand Down Expand Up @@ -351,4 +437,76 @@ mod tests {
let smartrest = fail_operation(CumulocitySupportedOperations::C8ySoftwareUpdate, "");
assert_eq!(smartrest, "502,c8y_SoftwareUpdate,");
}

#[test]
fn from_software_module_to_smartrest_software_module_item() {
let software_module = SoftwareModule {
module_type: Some("a".into()),
name: "b".into(),
version: Some("c".into()),
url: Some("".into()),
file_path: None,
};

let expected_c8y_item = SmartRestSoftwareModuleItem {
name: "b".into(),
version: "c".into(),
software_type: "a".to_string(),
url: "".into(),
};

let converted: SmartRestSoftwareModuleItem = software_module.into();
assert_eq!(converted, expected_c8y_item);
}

#[test]
fn from_thin_edge_json_to_advanced_software_list() {
let input_json = r#"{
"id":"1",
"status":"successful",
"currentSoftwareList":[
{"type":"debian", "modules":[
{"name":"a"},
{"name":"b","version":"1.0"},
{"name":"c","url":"https://foobar.io/c.deb"},
{"name":"d","version":"beta","url":"https://foobar.io/d.deb"}
]},
{"type":"apama","modules":[
{"name":"m","url":"https://foobar.io/m.epl"}
]}
]}"#;

let command = SoftwareListCommand {
target: EntityTopicId::default_main_device(),
cmd_id: "1".to_string(),
payload: SoftwareListCommandPayload::from_json(input_json).unwrap(),
};

let advanced_sw_list = get_advanced_software_list_payloads(&command, 2);

assert_eq!(advanced_sw_list[0], "140,a,,debian,,b,1.0,debian,");
assert_eq!(
advanced_sw_list[1],
"141,c,,debian,https://foobar.io/c.deb,d,beta,debian,https://foobar.io/d.deb"
);
assert_eq!(advanced_sw_list[2], "141,m,,apama,https://foobar.io/m.epl");
}

#[test]
fn empty_to_advanced_list() {
let input_json = r#"{
"id":"1",
"status":"successful",
"currentSoftwareList":[]
}"#;

let command = &SoftwareListCommand {
target: EntityTopicId::default_main_device(),
cmd_id: "1".to_string(),
payload: SoftwareListCommandPayload::from_json(input_json).unwrap(),
};

let advanced_sw_list = get_advanced_software_list_payloads(command, 2);
assert_eq!(advanced_sw_list[0], "140,,,,");
}
}
12 changes: 12 additions & 0 deletions crates/extensions/c8y_mapper_ext/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use tedge_api::mqtt_topics::TopicIdError;
use tedge_api::path::DataDir;
use tedge_config::ConfigNotSet;
use tedge_config::ReadError;
use tedge_config::SoftwareManagementApiFlag;
use tedge_config::TEdgeConfig;
use tedge_config::TEdgeConfigReaderService;
use tedge_config::TopicPrefix;
Expand Down Expand Up @@ -54,6 +55,8 @@ pub struct C8yMapperConfig {
pub clean_start: bool,
pub c8y_prefix: TopicPrefix,
pub bridge_in_mapper: bool,
pub software_management_api: SoftwareManagementApiFlag,
pub software_management_with_types: bool,
}

impl C8yMapperConfig {
Expand All @@ -79,6 +82,8 @@ impl C8yMapperConfig {
clean_start: bool,
c8y_prefix: TopicPrefix,
bridge_in_mapper: bool,
software_management_api: SoftwareManagementApiFlag,
software_management_with_types: bool,
) -> Self {
let ops_dir = config_dir
.join(SUPPORTED_OPERATIONS_DIRECTORY)
Expand Down Expand Up @@ -108,6 +113,8 @@ impl C8yMapperConfig {
clean_start,
c8y_prefix,
bridge_in_mapper,
software_management_api,
software_management_with_types,
}
}

Expand Down Expand Up @@ -161,6 +168,9 @@ impl C8yMapperConfig {
let enable_auto_register = tedge_config.c8y.entity_store.auto_register;
let clean_start = tedge_config.c8y.entity_store.clean_start;

let software_management_api = tedge_config.c8y.software_management.api.clone();
let software_management_with_types = tedge_config.c8y.software_management.with_types;

// Add feature topic filters
for cmd in [
OperationType::Restart,
Expand Down Expand Up @@ -220,6 +230,8 @@ impl C8yMapperConfig {
clean_start,
c8y_prefix,
bridge_in_mapper,
software_management_api,
software_management_with_types,
))
}

Expand Down
Loading

0 comments on commit 4109ef6

Please sign in to comment.