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 6c1d8688f29..478d7f5383d 100644 --- a/crates/extensions/c8y_mapper_ext/src/tests.rs +++ b/crates/extensions/c8y_mapper_ext/src/tests.rs @@ -36,6 +36,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; @@ -288,6 +289,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 @@ -2473,6 +2545,8 @@ pub(crate) async fn spawn_c8y_mapper_actor( true, "c8y".into(), false, + SoftwareManagementApiFlag::Advanced, + true, ); let mut mqtt_builder: SimpleMessageBoxBuilder = 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