From 808a48808a1c7cfe8ad79a7e945ee70bbafadc2e Mon Sep 17 00:00:00 2001 From: Rina Fujino Date: Wed, 13 Mar 2024 00:08:15 +0100 Subject: [PATCH 1/2] c8y-mapper sends software list to advanced software management endpoint Since Cumulocity 10.14, it is recommended to send software list to the advanced software management endpoint. By default, the mapper publishes its software list via SmartREST. If `c8y.software_management.api` is set to 'true', the mapper also sends c8y_SupportedSoftwareTypes to inventory via JSON over MQTT. To keep backwards compatibility, if `c8y.software_management.api` is set to "legacy", the mapper posts to c8y_SoftwareList fragment to the inventory API via HTTP. Signed-off-by: Rina Fujino --- .../models/c8y_software_management.rs | 33 ++++ .../src/tedge_config_cli/models/mod.rs | 2 + .../src/tedge_config_cli/tedge_config.rs | 11 ++ .../src/smartrest/smartrest_serializer.rs | 168 +++++++++++++++++- .../extensions/c8y_mapper_ext/src/config.rs | 12 ++ .../c8y_mapper_ext/src/converter.rs | 67 +++++-- .../c8y_mapper_ext/src/inventory.rs | 18 +- crates/extensions/c8y_mapper_ext/src/tests.rs | 74 ++++++++ 8 files changed, 362 insertions(+), 23 deletions(-) create mode 100644 crates/common/tedge_config/src/tedge_config_cli/models/c8y_software_management.rs diff --git a/crates/common/tedge_config/src/tedge_config_cli/models/c8y_software_management.rs b/crates/common/tedge_config/src/tedge_config_cli/models/c8y_software_management.rs new file mode 100644 index 00000000000..fc29f017701 --- /dev/null +++ b/crates/common/tedge_config/src/tedge_config_cli/models/c8y_software_management.rs @@ -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 { + match input { + "legacy" => Ok(SoftwareManagementApiFlag::Legacy), + "advanced" => Ok(SoftwareManagementApiFlag::Advanced), + _ => Err(InvalidSoftwareManagementApiFlag { + input: input.to_string(), + }), + } + } +} diff --git a/crates/common/tedge_config/src/tedge_config_cli/models/mod.rs b/crates/common/tedge_config/src/tedge_config_cli/models/mod.rs index 155b079d3c3..41ddf6dc4ee 100644 --- a/crates/common/tedge_config/src/tedge_config_cli/models/mod.rs +++ b/crates/common/tedge_config/src/tedge_config_cli/models/mod.rs @@ -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; @@ -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)] diff --git a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs index e84c620144e..9e4f3c32c7f 100644 --- a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs +++ b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs @@ -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; @@ -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 diff --git a/crates/core/c8y_api/src/smartrest/smartrest_serializer.rs b/crates/core/c8y_api/src/smartrest/smartrest_serializer.rs index 8fa2cc4e017..4e70ff0a217 100644 --- a/crates/core/c8y_api/src/smartrest/smartrest_serializer.rs +++ b/crates/core/c8y_api/src/smartrest/smartrest_serializer.rs @@ -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; @@ -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, - pub url: Option, + pub name: String, + pub version: String, + pub software_type: String, + pub url: String, +} + +impl From 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), + Append(Vec), +} + +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) -> Vec { + 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 { + let mut messages: Vec = Vec::new(); + + if software_list_cmd.modules().is_empty() { + messages.push(AdvancedSoftwareList::Set(vec![]).smartrest_payload()); + return messages; + } + + let mut items: Vec = 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 @@ -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() { @@ -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,,,,"); + } } diff --git a/crates/extensions/c8y_mapper_ext/src/config.rs b/crates/extensions/c8y_mapper_ext/src/config.rs index 3a4673cb27b..cac989668b5 100644 --- a/crates/extensions/c8y_mapper_ext/src/config.rs +++ b/crates/extensions/c8y_mapper_ext/src/config.rs @@ -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; @@ -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 { @@ -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) @@ -108,6 +113,8 @@ impl C8yMapperConfig { clean_start, c8y_prefix, bridge_in_mapper, + software_management_api, + software_management_with_types, } } @@ -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, @@ -220,6 +230,8 @@ impl C8yMapperConfig { clean_start, c8y_prefix, bridge_in_mapper, + software_management_api, + software_management_with_types, )) } diff --git a/crates/extensions/c8y_mapper_ext/src/converter.rs b/crates/extensions/c8y_mapper_ext/src/converter.rs index 85da0fb1323..ffd9b3668c8 100644 --- a/crates/extensions/c8y_mapper_ext/src/converter.rs +++ b/crates/extensions/c8y_mapper_ext/src/converter.rs @@ -35,6 +35,7 @@ use c8y_api::smartrest::operations::get_operations; use c8y_api::smartrest::operations::Operations; use c8y_api::smartrest::operations::ResultFormat; use c8y_api::smartrest::smartrest_serializer::fail_operation; +use c8y_api::smartrest::smartrest_serializer::get_advanced_software_list_payloads; use c8y_api::smartrest::smartrest_serializer::request_pending_operations; use c8y_api::smartrest::smartrest_serializer::set_operation_executing; use c8y_api::smartrest::smartrest_serializer::succeed_operation; @@ -51,6 +52,7 @@ use camino::Utf8Path; use logged_command::LoggedCommand; use plugin_sm::operation_logs::OperationLogs; use plugin_sm::operation_logs::OperationLogsError; +use serde_json::json; use serde_json::Map; use serde_json::Value; use service_monitor::convert_health_status_message; @@ -72,6 +74,7 @@ use tedge_api::event::error::ThinEdgeJsonDeserializerError; use tedge_api::event::ThinEdgeEvent; use tedge_api::messages::CommandStatus; use tedge_api::messages::RestartCommand; +use tedge_api::messages::SoftwareCommandMetadata; use tedge_api::messages::SoftwareListCommand; use tedge_api::messages::SoftwareUpdateCommand; use tedge_api::mqtt_topics::Channel; @@ -82,6 +85,8 @@ use tedge_api::mqtt_topics::OperationType; use tedge_api::pending_entity_store::PendingEntityData; use tedge_api::DownloadInfo; use tedge_api::EntityStore; +use tedge_api::Jsonify; +use tedge_config::SoftwareManagementApiFlag; use tedge_config::TEdgeConfigError; use tedge_config::TopicPrefix; use tedge_mqtt_ext::Message; @@ -107,6 +112,7 @@ const DEFAULT_EVENT_TYPE: &str = "ThinEdgeEvent"; const FORBIDDEN_ID_CHARS: [char; 3] = ['/', '+', '#']; const REQUESTER_NAME: &str = "c8y-mapper"; const EARLY_MESSAGE_BUFFER_SIZE: usize = 100; +const SOFTWARE_LIST_CHUNK_SIZE: usize = 100; #[derive(Debug)] pub struct MapperConfig { @@ -1055,7 +1061,8 @@ impl CumulocityConverter { match operation { OperationType::Restart => self.register_restart_operation(&source).await, OperationType::SoftwareList => { - self.register_software_list_operation(&source).await + self.register_software_list_operation(&source, message) + .await } OperationType::SoftwareUpdate => { self.register_software_update_operation(&source).await @@ -1469,10 +1476,20 @@ impl CumulocityConverter { async fn register_software_list_operation( &self, - _target: &EntityTopicId, + target: &EntityTopicId, + message: &Message, ) -> Result, ConversionError> { - // On c8y, "software list" is implied by "software update" - Ok(vec![]) + if !self.config.software_management_with_types { + debug!("Publishing c8y_SupportedSoftwareTypes is disabled. To enable it, run `tedge config set c8y.software_management.with_types true`."); + return Ok(vec![]); + } + + // Send c8y_SupportedSoftwareTypes, which is introduced in c8y >= 10.14 + let data = SoftwareCommandMetadata::from_json(message.payload_str()?)?; + let payload = json!({"c8y_SupportedSoftwareTypes": data.types}).to_string(); + let topic = self.get_inventory_update_topic(target)?; + + Ok(vec![Message::new(&topic, payload)]) } async fn register_software_update_operation( @@ -1568,16 +1585,37 @@ impl CumulocityConverter { match response.status() { CommandStatus::Successful => { - if let Some(device) = self.entity_store.get(target) { - let c8y_software_list: C8yUpdateSoftwareListResponse = (&response).into(); - self.http_proxy - .send_software_list_http( - c8y_software_list, - device.external_id.as_ref().to_string(), - ) - .await?; + // Send a list via HTTP to support backwards compatibility to c8y < 10.14 + if self.config.software_management_api == SoftwareManagementApiFlag::Legacy { + if let Some(device) = self.entity_store.get(target) { + let c8y_software_list: C8yUpdateSoftwareListResponse = (&response).into(); + self.http_proxy + .send_software_list_http( + c8y_software_list, + device.external_id.as_ref().to_string(), + ) + .await?; + } + return Ok(vec![response.clearing_message(&self.mqtt_schema)]); } - Ok(vec![response.clearing_message(&self.mqtt_schema)]) + + // Send a list via SmartREST, "advanced software list" feature c8y >= 10.14 + let topic = self + .entity_store + .get(target) + .and_then(|entity| { + C8yTopic::smartrest_response_topic(entity, &self.config.c8y_prefix) + }) + .ok_or_else(|| Error::UnknownEntity(target.to_string()))?; + let payloads = + get_advanced_software_list_payloads(&response, SOFTWARE_LIST_CHUNK_SIZE); + + let mut messages: Vec = Vec::new(); + for payload in payloads { + messages.push(Message::new(&topic, payload)) + } + messages.push(response.clearing_message(&self.mqtt_schema)); + Ok(messages) } CommandStatus::Failed { reason } => { @@ -1655,6 +1693,7 @@ pub(crate) mod tests { use tedge_api::mqtt_topics::MqttSchema; use tedge_api::mqtt_topics::OperationType; use tedge_api::SoftwareUpdateCommand; + use tedge_config::SoftwareManagementApiFlag; use tedge_config::TEdgeConfig; use tedge_mqtt_ext::test_helpers::assert_messages_matching; use tedge_mqtt_ext::Message; @@ -3223,6 +3262,8 @@ pub(crate) mod tests { true, "c8y".into(), false, + SoftwareManagementApiFlag::Advanced, + true, ) } diff --git a/crates/extensions/c8y_mapper_ext/src/inventory.rs b/crates/extensions/c8y_mapper_ext/src/inventory.rs index 7a0e6735838..72ac36cbcbc 100644 --- a/crates/extensions/c8y_mapper_ext/src/inventory.rs +++ b/crates/extensions/c8y_mapper_ext/src/inventory.rs @@ -113,11 +113,7 @@ impl CumulocityConverter { source: &EntityTopicId, fragment_value: JsonValue, ) -> Result { - let entity_external_id = self.entity_store.try_get(source)?.external_id.as_ref(); - let inventory_update_topic = Topic::new_unchecked(&format!( - "{prefix}/{INVENTORY_MANAGED_OBJECTS_TOPIC}/{entity_external_id}", - prefix = self.config.c8y_prefix, - )); + let inventory_update_topic = self.get_inventory_update_topic(source)?; Ok(Message::new( &inventory_update_topic, @@ -172,6 +168,18 @@ impl CumulocityConverter { info!("Read the fragments from {file_path:?} file"); Ok(json) } + + /// Returns the JSON over MQTT inventory update topic + pub fn get_inventory_update_topic( + &self, + source: &EntityTopicId, + ) -> Result { + let entity_external_id = self.entity_store.try_get(source)?.external_id.as_ref(); + Ok(Topic::new_unchecked(&format!( + "{prefix}/{INVENTORY_MANAGED_OBJECTS_TOPIC}/{entity_external_id}", + prefix = self.config.c8y_prefix, + ))) + } } #[cfg(test)] diff --git a/crates/extensions/c8y_mapper_ext/src/tests.rs b/crates/extensions/c8y_mapper_ext/src/tests.rs index c1b9aa7f0e5..7420ae23769 100644 --- a/crates/extensions/c8y_mapper_ext/src/tests.rs +++ b/crates/extensions/c8y_mapper_ext/src/tests.rs @@ -37,6 +37,7 @@ use tedge_api::mqtt_topics::MqttSchema; use tedge_api::CommandStatus; use tedge_api::SoftwareUpdateCommand; use tedge_api::MQTT_BRIDGE_UP_PAYLOAD; +use tedge_config::SoftwareManagementApiFlag; use tedge_config::TEdgeConfig; use tedge_file_system_ext::FsWatchEvent; use tedge_mqtt_ext::test_helpers::assert_received_contains_str; @@ -289,6 +290,77 @@ async fn service_registration_mapping() { .await; } +#[tokio::test] +async fn mapper_publishes_supported_software_types() { + let cfg_dir = TempTedgeDir::new(); + let (mqtt, http, _fs, _timer, _ul, _dl) = spawn_c8y_mapper_actor(&cfg_dir, true).await; + spawn_dummy_c8y_http_proxy(http); + + let mut mqtt = mqtt.with_timeout(TEST_TIMEOUT_MS); + + skip_init_messages(&mut mqtt).await; + + // Simulate software_list capability message + mqtt.send(MqttMessage::new( + &Topic::new_unchecked("te/device/main///cmd/software_list"), + json!({"types": ["apt", "docker"]}).to_string(), + )) + .await + .expect("Send failed"); + + assert_received_includes_json( + &mut mqtt, + [( + "c8y/inventory/managedObjects/update/test-device", + json!({"c8y_SupportedSoftwareTypes":["apt","docker"]}), + )], + ) + .await; +} + +#[tokio::test] +async fn mapper_publishes_advanced_software_list() { + let cfg_dir = TempTedgeDir::new(); + let (mqtt, http, _fs, _timer, _ul, _dl) = spawn_c8y_mapper_actor(&cfg_dir, true).await; + spawn_dummy_c8y_http_proxy(http); + + let mut mqtt = mqtt.with_timeout(TEST_TIMEOUT_MS); + + skip_init_messages(&mut mqtt).await; + + // Simulate software_list request + mqtt.send(MqttMessage::new( + &Topic::new_unchecked("te/device/main///cmd/software_list/c8y-mapper-1234"), + json!({ + "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"} + ]} + ]}) + .to_string(), + )) + .await + .expect("Send failed"); + + assert_received_contains_str( + &mut mqtt, + [ + ( + "c8y/s/us", + "140,a,,debian,,b,1.0,debian,,c,,debian,https://foobar.io/c.deb,d,beta,debian,https://foobar.io/d.deb,m,,apama,https://foobar.io/m.epl" + ) + ]) + .await; +} + #[tokio::test] async fn mapper_publishes_software_update_request() { // The test assures c8y mapper correctly receives software update request from JSON over MQTT @@ -2474,6 +2546,8 @@ pub(crate) async fn spawn_c8y_mapper_actor( true, "c8y".into(), false, + SoftwareManagementApiFlag::Advanced, + true, ); let mut mqtt_builder: SimpleMessageBoxBuilder = From 667652a9a23b5502aab4da8b5324319cc44f9021 Mon Sep 17 00:00:00 2001 From: Rina Fujino Date: Wed, 13 Mar 2024 14:29:19 +0000 Subject: [PATCH 2/2] Add robot tests to support advanced software management Signed-off-by: Rina Fujino --- .../software_management/software.robot | 17 ++++++++++- .../tests/tedge/call_tedge_config_list.robot | 28 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/tests/RobotFramework/tests/cumulocity/software_management/software.robot b/tests/RobotFramework/tests/cumulocity/software_management/software.robot index e53a7181961..f1c39b93e26 100644 --- a/tests/RobotFramework/tests/cumulocity/software_management/software.robot +++ b/tests/RobotFramework/tests/cumulocity/software_management/software.robot @@ -9,11 +9,26 @@ Test Teardown Custom Teardown *** Test Cases *** Supported software types should be declared during startup - [Documentation] #2654 This test will be updated once advanced software management support is implemented + [Documentation] c8y_SupportedSoftwareTypes should NOT be created by default #2654 Should Have MQTT Messages topic=te/device/main///cmd/software_list minimum=1 maximum=1 message_contains="types":["apt"] Should Have MQTT Messages topic=te/device/main///cmd/software_update minimum=1 maximum=1 message_contains="types":["apt"] + Run Keyword And Expect Error * Device Should Have Fragment Values c8y_SupportedSoftwareTypes\=["apt"] + +Supported software types and c8y_SupportedSoftwareTypes should be declared during startup + [Documentation] c8y_SupportedSoftwareTypes should be created if the relevant config is set to true #2654 + Execute Command tedge config set c8y.software_management.with_types true + Restart Service tedge-mapper-c8y + Device Should Have Fragment Values c8y_SupportedSoftwareTypes\=["apt"] Software list should be populated during startup + [Documentation] The list is sent via HTTP by default. + Device Should Have Installed Software tedge timeout=120 + +Software list should be populated during startup with advanced software management + [Documentation] The list is sent via SmartREST with advanced software management. See #2654 + Execute Command tedge config set c8y.software_management.api advanced + Restart Service tedge-mapper-c8y + Should Have MQTT Messages c8y/s/us message_contains=140, minimum=1 maximum=1 Device Should Have Installed Software tedge timeout=120 Install software via Cumulocity diff --git a/tests/RobotFramework/tests/tedge/call_tedge_config_list.robot b/tests/RobotFramework/tests/tedge/call_tedge_config_list.robot index 1a79da1d64a..b8e93062e39 100644 --- a/tests/RobotFramework/tests/tedge/call_tedge_config_list.robot +++ b/tests/RobotFramework/tests/tedge/call_tedge_config_list.robot @@ -153,6 +153,34 @@ set/unset c8y.bridge.include.local_cleansession ${unset} Execute Command tedge config list Should Contain ${unset} c8y.bridge.include.local_cleansession=auto +set/unset c8y.software_management.api + Execute Command sudo tedge config set c8y.software_management.api legacy + ${set} Execute Command tedge config list + Should Contain ${set} c8y.software_management.api=legacy + + Execute Command sudo tedge config set c8y.software_management.api advanced + ${set} Execute Command tedge config list + Should Contain ${set} c8y.software_management.api=advanced + + # Undo the change by using the 'unset' command, value returns to default one + Execute Command sudo tedge config unset c8y.software_management.api + ${set} Execute Command tedge config list + Should Contain ${set} c8y.software_management.api=legacy + +set/unset c8y.software_management.with_types + Execute Command sudo tedge config set c8y.software_management.with_types false + ${set} Execute Command tedge config list + Should Contain ${set} c8y.software_management.with_types=false + + Execute Command sudo tedge config set c8y.software_management.with_types true + ${set} Execute Command tedge config list + Should Contain ${set} c8y.software_management.with_types=true + + # Undo the change by using the 'unset' command, value returns to default one + Execute Command sudo tedge config unset c8y.software_management.with_types + ${set} Execute Command tedge config list + Should Contain ${set} c8y.software_management.with_types=false + set/unset az.root_cert_path Execute Command sudo tedge config set az.root_cert_path /etc/ssl/certs1 # Changing az.root_cert_path ${set} Execute Command tedge config list