Skip to content

Commit 981feaa

Browse files
feat: support multiple configs (NR-424837) (#1708)
* feat: support multiple configs * refactor: remove optional configuration in OpampRemoteConfig
1 parent fd95804 commit 981feaa

File tree

9 files changed

+130
-149
lines changed

9 files changed

+130
-149
lines changed

agent-control/src/agent_control.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ where
431431
.into());
432432
}
433433

434-
let remote_config_value = opamp_remote_config.get_unique()?;
434+
let remote_config_value = opamp_remote_config.get_default()?;
435435

436436
let new_dynamic_config = if remote_config_value.is_empty() {
437437
// Use the local configuration if the content of the remote config is empty.
@@ -583,7 +583,9 @@ mod tests {
583583
use crate::health::with_start_time::HealthWithStartTime;
584584
use crate::opamp::client_builder::tests::MockStartedOpAMPClient;
585585
use crate::opamp::remote_config::hash::{ConfigState, Hash};
586-
use crate::opamp::remote_config::{ConfigurationMap, OpampRemoteConfig};
586+
use crate::opamp::remote_config::{
587+
ConfigurationMap, DEFAULT_AGENT_CONFIG_IDENTIFIER, OpampRemoteConfig,
588+
};
587589
use crate::sub_agent::collection::StartedSubAgents;
588590
use crate::sub_agent::error::SubAgentBuilderError;
589591
use crate::sub_agent::identity::AgentIdentity;
@@ -705,10 +707,10 @@ agents:
705707
AgentID::AgentControl,
706708
hash,
707709
ConfigState::Applying,
708-
Some(ConfigurationMap::new(HashMap::from([(
709-
"".to_string(),
710+
ConfigurationMap::new(HashMap::from([(
711+
DEFAULT_AGENT_CONFIG_IDENTIFIER.to_string(),
710712
s.to_string(),
711-
)]))),
713+
)])),
712714
)
713715
}
714716

agent-control/src/opamp/callbacks.rs

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,20 @@ where
9191
}
9292
};
9393

94-
let config_map: Option<ConfigurationMap> = match msg_remote_config.config {
94+
let config_map: ConfigurationMap = match msg_remote_config.config {
9595
Some(msg_config_map) => msg_config_map
9696
.try_into()
9797
.inspect_err(|err: &OpampRemoteConfigError| {
9898
state = ConfigState::Failed {
9999
error_message: format!("Invalid remote config format: {err}"),
100100
};
101101
})
102-
.ok(),
102+
.unwrap_or_default(),
103103
None => {
104104
state = ConfigState::Failed {
105105
error_message: "Config missing".into(),
106106
};
107-
None
107+
ConfigurationMap::default()
108108
}
109109
};
110110

@@ -264,7 +264,9 @@ pub(crate) mod tests {
264264
use crate::opamp::remote_config::signature::{
265265
ED25519, SIGNATURE_CUSTOM_CAPABILITY, SIGNATURE_CUSTOM_MESSAGE_TYPE,
266266
};
267-
use crate::opamp::remote_config::{ConfigurationMap, OpampRemoteConfig};
267+
use crate::opamp::remote_config::{
268+
ConfigurationMap, DEFAULT_AGENT_CONFIG_IDENTIFIER, OpampRemoteConfig,
269+
};
268270
use opamp_client::opamp::proto::{AgentConfigFile, AgentConfigMap, AgentRemoteConfig};
269271
use std::collections::HashMap;
270272
use std::time::Duration;
@@ -338,15 +340,15 @@ pub(crate) mod tests {
338340
let (valid_remote_config_map, expected_remote_config_map) = (
339341
AgentConfigMap {
340342
config_map: HashMap::from([(
341-
"my-config".to_string(),
343+
DEFAULT_AGENT_CONFIG_IDENTIFIER.to_string(),
342344
AgentConfigFile {
343345
body: "enable_proces_metrics: true".as_bytes().to_vec(),
344346
content_type: "".to_string(),
345347
},
346348
)]),
347349
},
348350
ConfigurationMap::new(HashMap::from([(
349-
"my-config".to_string(),
351+
DEFAULT_AGENT_CONFIG_IDENTIFIER.to_string(),
350352
"enable_proces_metrics: true".to_string(),
351353
)])),
352354
);
@@ -356,7 +358,7 @@ pub(crate) mod tests {
356358
opamp_msg: Option<MessageData>, // using option here to allow taking the ownership of the MessageData which cannot be cloned.
357359
expected_remote_config_hash: Hash,
358360
expected_remote_config_state: ConfigState,
359-
expected_remote_config_config_map: Option<ConfigurationMap>,
361+
expected_remote_config_config_map: ConfigurationMap,
360362
expected_signature: Option<Signatures>,
361363
}
362364
impl TestCase {
@@ -403,7 +405,7 @@ pub(crate) mod tests {
403405
}),
404406
..Default::default()
405407
}),
406-
expected_remote_config_config_map: Some(expected_remote_config_map.clone()),
408+
expected_remote_config_config_map: expected_remote_config_map.clone(),
407409
expected_remote_config_hash: Hash::from(valid_hash),
408410
expected_remote_config_state: ConfigState::Applying,
409411
expected_signature: None,
@@ -425,7 +427,7 @@ pub(crate) mod tests {
425427
}),
426428
..Default::default()
427429
}),
428-
expected_remote_config_config_map: None,
430+
expected_remote_config_config_map: ConfigurationMap::default(),
429431
expected_remote_config_hash: Hash::from(valid_hash),
430432
expected_remote_config_state: ConfigState::Failed {
431433
error_message: "Invalid remote config format: invalid UTF-8 sequence: `invalid utf-8 sequence of 1 bytes from index 0`".into(),
@@ -441,7 +443,7 @@ pub(crate) mod tests {
441443
}),
442444
..Default::default()
443445
}),
444-
expected_remote_config_config_map: None,
446+
expected_remote_config_config_map: ConfigurationMap::default(),
445447
expected_remote_config_hash: Hash::from(valid_hash),
446448
expected_remote_config_state: ConfigState::Failed {
447449
error_message: "Config missing".into(),
@@ -457,7 +459,7 @@ pub(crate) mod tests {
457459
}),
458460
..Default::default()
459461
}),
460-
expected_remote_config_config_map: None,
462+
expected_remote_config_config_map: ConfigurationMap::default(),
461463
expected_remote_config_hash: Hash::default(),
462464
expected_remote_config_state: ConfigState::Failed {
463465
error_message: "Config missing".into(),
@@ -473,7 +475,7 @@ pub(crate) mod tests {
473475
}),
474476
..Default::default()
475477
}),
476-
expected_remote_config_config_map: Some(expected_remote_config_map.clone()),
478+
expected_remote_config_config_map: expected_remote_config_map.clone(),
477479
expected_remote_config_hash: Hash::default(),
478480
expected_remote_config_state: ConfigState::Failed {
479481
error_message: "Invalid hash: invalid utf-8 sequence of 1 bytes from index 0".into(),
@@ -490,22 +492,23 @@ pub(crate) mod tests {
490492
custom_message: Some(CustomMessage {
491493
capability: SIGNATURE_CUSTOM_CAPABILITY.to_string(),
492494
r#type: SIGNATURE_CUSTOM_MESSAGE_TYPE.to_string(),
493-
data: r#"{
494-
"unique": [{
495+
data: serde_json::json!({
496+
DEFAULT_AGENT_CONFIG_IDENTIFIER: [{
495497
"signature": "fake config",
496498
"signingAlgorithm": "ED25519",
497499
"keyId": "fake keyid"
498500
}]
499-
}"#
501+
})
502+
.to_string()
500503
.as_bytes()
501504
.to_vec(),
502505
}),
503506
..Default::default()
504507
}),
505-
expected_remote_config_config_map: Some(expected_remote_config_map.clone()),
508+
expected_remote_config_config_map: expected_remote_config_map.clone(),
506509
expected_remote_config_hash: Hash::from(valid_hash),
507510
expected_remote_config_state: ConfigState::Applying,
508-
expected_signature: Some(Signatures::new_unique(
511+
expected_signature: Some(Signatures::new_default(
509512
"fake config",
510513
ED25519,
511514
"fake keyid",
@@ -533,7 +536,7 @@ pub(crate) mod tests {
533536
}),
534537
..Default::default()
535538
}),
536-
expected_remote_config_config_map: Some(expected_remote_config_map.clone()),
539+
expected_remote_config_config_map: expected_remote_config_map.clone(),
537540
expected_remote_config_hash: Hash::from(valid_hash),
538541
expected_remote_config_state: ConfigState::Applying,
539542
expected_signature: None,
@@ -560,7 +563,7 @@ pub(crate) mod tests {
560563
}),
561564
..Default::default()
562565
}),
563-
expected_remote_config_config_map: Some(expected_remote_config_map.clone()),
566+
expected_remote_config_config_map: expected_remote_config_map.clone(),
564567
expected_remote_config_hash: Hash::from(valid_hash),
565568
expected_remote_config_state: ConfigState::Applying,
566569
expected_signature: None,
@@ -579,7 +582,7 @@ pub(crate) mod tests {
579582
}),
580583
..Default::default()
581584
}),
582-
expected_remote_config_config_map: Some(expected_remote_config_map.clone()),
585+
expected_remote_config_config_map: expected_remote_config_map.clone(),
583586
expected_remote_config_hash: Hash::from(valid_hash),
584587
expected_remote_config_state: ConfigState::Failed {
585588
error_message: "Invalid remote config signature format: expected value at line 1 column 1".into(),

agent-control/src/opamp/remote_config.rs

Lines changed: 26 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ pub mod report;
1212
pub mod signature;
1313
pub mod validators;
1414

15+
/// Identifier key for the primary agent configuration within the OpAMP [opamp_client::opamp::proto::AgentConfigMap].
16+
pub const DEFAULT_AGENT_CONFIG_IDENTIFIER: &str = "configAgent";
17+
1518
/// This structure represents the remote configuration that we would retrieve from a server via OpAMP.
1619
/// Contains identifying metadata and the actual configuration values
1720
#[derive(Debug, PartialEq, Clone)]
@@ -20,7 +23,7 @@ pub struct OpampRemoteConfig {
2023
pub hash: Hash,
2124
pub state: ConfigState,
2225
signatures: Option<Signatures>,
23-
config_map: Option<ConfigurationMap>,
26+
config_map: ConfigurationMap,
2427
}
2528

2629
#[derive(Error, Debug, Clone, PartialEq)]
@@ -41,7 +44,7 @@ impl OpampRemoteConfig {
4144
agent_id: AgentID,
4245
hash: Hash,
4346
state: ConfigState,
44-
config_map: Option<ConfigurationMap>,
47+
config_map: ConfigurationMap,
4548
) -> Self {
4649
Self {
4750
agent_id,
@@ -57,57 +60,32 @@ impl OpampRemoteConfig {
5760
..self
5861
}
5962
}
60-
//TODO : This is temporal as when there is only one conf item we should receive an empty string as key
61-
pub fn get_unique(&self) -> Result<&str, OpampRemoteConfigError> {
62-
let config_map = self
63-
.config_map
64-
.as_ref()
65-
.ok_or(OpampRemoteConfigError::InvalidConfig(
66-
self.hash.to_string(),
67-
"missing config".to_string(),
68-
))?;
6963

70-
match config_map.0.len() {
71-
0 => Err(OpampRemoteConfigError::InvalidConfig(
72-
self.hash.to_string(),
73-
"empty config map".to_string(),
74-
)),
75-
1 => Ok(config_map
76-
.0
77-
.values()
78-
.next()
79-
.expect("at least one config has been provided")),
80-
_ => Err(OpampRemoteConfigError::InvalidConfig(
64+
/// Get configuration value at the
65+
pub fn get_default(&self) -> Result<&str, OpampRemoteConfigError> {
66+
self.config_map
67+
.0
68+
.get(DEFAULT_AGENT_CONFIG_IDENTIFIER)
69+
.map(|s| s.as_str())
70+
.ok_or(OpampRemoteConfigError::InvalidConfig(
8171
self.hash.to_string(),
82-
"too many config items".to_string(),
83-
)),
84-
}
72+
"missing default config".to_string(),
73+
))
8574
}
8675

87-
// gets the config signature if it exists. It fails if there are multiple signatures.
88-
pub fn get_unique_signature(&self) -> Result<Option<SignatureData>, OpampRemoteConfigError> {
76+
/// Get the signature data for the default config
77+
pub fn get_default_signature(&self) -> Result<Option<SignatureData>, OpampRemoteConfigError> {
8978
if let Some(signatures) = &self.signatures {
90-
match signatures.len() {
91-
0 => Err(OpampRemoteConfigError::InvalidConfig(
92-
self.hash.to_string(),
93-
"empty signature".to_string(),
94-
)),
95-
1 => Ok(Some(
96-
signatures
97-
.iter()
98-
.next()
99-
// assumes that the sigunature corresponds to the unique config item
100-
.map(|(_, signature)| signature.clone())
101-
.ok_or(OpampRemoteConfigError::InvalidConfig(
102-
self.hash.to_string(),
103-
"getting unique signature".to_string(),
104-
))?,
105-
)),
106-
_ => Err(OpampRemoteConfigError::InvalidConfig(
107-
self.hash.to_string(),
108-
"too many signature items".to_string(),
109-
)),
110-
}
79+
Ok(Some(
80+
signatures
81+
.signatures
82+
.get(DEFAULT_AGENT_CONFIG_IDENTIFIER)
83+
.cloned()
84+
.ok_or(OpampRemoteConfigError::InvalidConfig(
85+
self.hash.to_string(),
86+
"missing signature for default config".to_string(),
87+
))?,
88+
))
11189
} else {
11290
// Agent control config is not signed
11391
Ok(None)

agent-control/src/opamp/remote_config/signature.rs

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ fn parse_rsa_algorithm(algo: &str) -> Option<SigningAlgorithm> {
143143
/// capability: "com.newrelic.security.configSignature"
144144
/// type: "newrelicRemoteConfigSignature"
145145
/// data: {
146-
/// "3936250589": [{
146+
/// "agentConfig": [{
147147
/// "checksum": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
148148
/// "checksumAlgorithm": "SHA256",
149149
/// "signature": "nppw2CuZg+YO5MsEoNOsHlgHxF7qAwWPli37NGXAr5isfP1jUTSJcLi0l7k9lNlpbq31GF9DZ0JQBZhoGS0j+sDjvirKSb7yXdqj6JcZ8sxax7KWAnk5QPiwLHFA1kGmszVJ/ccbwtVozG46FvKedcc3X5RME/HGdJupKBe3UzmJawL0xs9jNY+9519CL+CpbkBl/WgCvrIUhTNZv5TUHK23hMD+kz1Brf60pW7MQVtsyClOllsb6WhAsSXdhkpSCJ+96ZGyYywUlvx3/vkBM5a7q4IWqiPM4U0LPZDMQJQCCpxWV3T7cnIR1Ye2yYUqJHs9vfKmTWeBKH2Tb5FgpQ==",
@@ -167,7 +167,7 @@ fn parse_rsa_algorithm(algo: &str) -> Option<SigningAlgorithm> {
167167
/// use crate::newrelic_agent_control::opamp::remote_config::signature::Signatures;
168168
///
169169
/// let data= r#"{
170-
/// "3936250589": [
170+
/// "agentConfig": [
171171
/// {
172172
/// "signature": "some signature",
173173
/// "signingAlgorithm": "UNSUPPORTED",
@@ -187,7 +187,7 @@ fn parse_rsa_algorithm(algo: &str) -> Option<SigningAlgorithm> {
187187
/// }"#.as_bytes().to_vec();
188188
///
189189
/// let signatures: Signatures = serde_json::from_slice(&data).unwrap();
190-
/// let (_, signature) = signatures.iter().next().unwrap();
190+
/// let (_, signature) = signatures.signatures.iter().next().unwrap();
191191
/// assert_eq!(signature.signing_algorithm.as_ref(), "ED25519");
192192
/// ```
193193
#[derive(Debug, Serialize, PartialEq, Clone)]
@@ -290,8 +290,8 @@ impl SignatureData {
290290
}
291291
}
292292

293-
/// CRC32 checksum of the config data. Currently this field is ignored since the current implementation of RemoteConfig
294-
/// doesn't support multiple config items.
293+
/// Configuration identifier that corresponds to a specific remote configuration.
294+
/// This key links signature data to its associated configuration in the remote config map.
295295
pub type ConfigID = String;
296296

297297
#[derive(Error, Debug, Clone, PartialEq)]
@@ -306,18 +306,6 @@ pub enum SignatureError {
306306
UnsupportedAlgorithm(String),
307307
}
308308

309-
impl Signatures {
310-
pub fn is_empty(&self) -> bool {
311-
self.signatures.is_empty()
312-
}
313-
pub fn len(&self) -> usize {
314-
self.signatures.len()
315-
}
316-
pub fn iter(&self) -> impl Iterator<Item = (&ConfigID, &SignatureData)> {
317-
self.signatures.iter()
318-
}
319-
}
320-
321309
impl TryFrom<&CustomMessage> for Signatures {
322310
type Error = SignatureError;
323311

@@ -340,6 +328,7 @@ impl TryFrom<&CustomMessage> for Signatures {
340328
mod tests {
341329
use super::SignatureData;
342330
use super::Signatures;
331+
use crate::opamp::remote_config::DEFAULT_AGENT_CONFIG_IDENTIFIER;
343332
use crate::opamp::remote_config::signature::ECDSA_P256_SHA256;
344333
use crate::opamp::remote_config::signature::ECDSA_P256_SHA384;
345334
use crate::opamp::remote_config::signature::ED25519;
@@ -348,10 +337,10 @@ mod tests {
348337
use std::collections::HashMap;
349338

350339
impl Signatures {
351-
pub fn new_unique(signature: &str, signing_algorithm: &str, key_id: &str) -> Self {
340+
pub fn new_default(signature: &str, signing_algorithm: &str, key_id: &str) -> Self {
352341
Self {
353342
signatures: HashMap::from([(
354-
"unique".to_string(),
343+
DEFAULT_AGENT_CONFIG_IDENTIFIER.to_string(),
355344
SignatureData::new(signature, signing_algorithm, key_id),
356345
)]),
357346
}
@@ -388,7 +377,7 @@ mod tests {
388377
fn run(self) {
389378
let signatures = Signatures::try_from(&self.custom_message)
390379
.unwrap_or_else(|err| panic!("case: {} - {}", self.name, err));
391-
let (_, signature) = signatures.iter().next().unwrap();
380+
let (_, signature) = signatures.signatures.iter().next().unwrap();
392381
assert_eq!(signature.signing_algorithm, self.algorithm);
393382
}
394383
}

0 commit comments

Comments
 (0)