diff --git a/agent-control/tests/common/custom_agent_type.rs b/agent-control/tests/common/custom_agent_type.rs new file mode 100644 index 0000000000..2c5ab57133 --- /dev/null +++ b/agent-control/tests/common/custom_agent_type.rs @@ -0,0 +1,62 @@ +use fs::file::LocalFile; +use fs::file::writer::FileWriter; +use newrelic_agent_control::agent_control::defaults::DYNAMIC_AGENT_TYPES_DIR; +use newrelic_agent_control::agent_type::agent_type_id::AgentTypeID; +use newrelic_agent_control::agent_type::definition::AgentTypeDefinition; +use std::path::Path; + +/// Fields and builder methods shared by [`crate::k8s::tools::custom_agent_type::K8sCustomAgentType`] +/// and [`crate::on_host::tools::custom_agent_type::OnHostCustomAgentType`]. +pub struct CommonCustomAgentType { + pub agent_type_id: AgentTypeID, + pub variables: Option, +} + +impl CommonCustomAgentType { + pub fn new(agent_type_id: AgentTypeID) -> Self { + Self { + agent_type_id, + variables: None, + } + } + + pub fn with_variables(self, variables: &str) -> Self { + Self { + variables: Some(serde_saphyr::from_str(variables).unwrap()), + ..self + } + } + + pub fn with_agent_type_id(self, agent_type_id: &str) -> Self { + Self { + agent_type_id: AgentTypeID::try_from(agent_type_id).unwrap(), + ..self + } + } + + /// Validates `content` (the caller's rendered YAML) against [`AgentTypeDefinition`] and writes + /// it under `local_dir`'s dynamic agent types directory. The file name is derived from the full + /// agent type id (namespace, name and version) so several distinct types can coexist in the + /// dynamic agent types dir (the loader reads every file there); two ids sharing only a name + /// would otherwise clobber each other. Returns the agent type id as a string. + pub fn build(&self, local_dir: &Path, content: &str) -> String { + // The id (`namespace/name:version`) has `/` and `:`, which are not portable in file names. + let file_stem = self.agent_type_id.to_string().replace(['/', ':'], "_"); + let agent_type_file_path = local_dir + .join(DYNAMIC_AGENT_TYPES_DIR) + .join(format!("{file_stem}.yaml")); + + let parsed_agent_type = AgentTypeDefinition::from_slice(content.as_bytes()); + assert!( + parsed_agent_type.is_ok(), + "CustomAgentType did not produce valid AgentTypeDefinition: {}\n{content}", + parsed_agent_type.err().unwrap(), + ); + + std::fs::create_dir_all(agent_type_file_path.parent().unwrap()).unwrap(); + LocalFile + .write(&agent_type_file_path, content.to_string()) + .expect("failed to write custom agent type"); + self.agent_type_id.to_string() + } +} diff --git a/agent-control/tests/common/mod.rs b/agent-control/tests/common/mod.rs index 8d9e4b19f3..554b325a74 100644 --- a/agent-control/tests/common/mod.rs +++ b/agent-control/tests/common/mod.rs @@ -2,6 +2,7 @@ pub(super) mod agent_control; pub(crate) mod attributes; pub(super) mod base_paths; pub(crate) mod config; +pub(crate) mod custom_agent_type; /// Includes a OpAMP mock server to test scenarios involving OpAMP. pub(super) mod effective_config; pub(super) mod global_logger; diff --git a/agent-control/tests/k8s/data/bar_cr_agent_type.yml b/agent-control/tests/k8s/data/bar_cr_agent_type.yml deleted file mode 100644 index 77d55f061a..0000000000 --- a/agent-control/tests/k8s/data/bar_cr_agent_type.yml +++ /dev/null @@ -1,25 +0,0 @@ -namespace: newrelic -name: com.newrelic.bar_cr_agent -version: 0.0.1 -platform: kubernetes -protocol_version: "1.0" -variables: - data: - description: "bar data" - type: string - required: false - default: "default" -deployment: - health: - interval: 5s - objects: - bar_cr: - # This CRD is created in the cluster when initializing the test environment. - apiVersion: newrelic.com/v1 - kind: Bar - metadata: - name: ${nr-sub:agent_id} - namespace: ${nr-ac:namespace} - spec: - data: ${nr-var:data} - diff --git a/agent-control/tests/k8s/data/config_map_type.yml b/agent-control/tests/k8s/data/config_map_type.yml deleted file mode 100644 index 280a81b723..0000000000 --- a/agent-control/tests/k8s/data/config_map_type.yml +++ /dev/null @@ -1,26 +0,0 @@ -namespace: newrelic -name: com.newrelic.test_config_map -version: 0.1.0 -platform: kubernetes -protocol_version: "1.0" -variables: - chart_values: - cm_content: - description: "configmap content" - type: yaml - required: false - default: { } -deployment: - health: - interval: 30s - initial_delay: 30s - objects: - values: - apiVersion: v1 - kind: ConfigMap - metadata: - name: test-config-map - namespace: ${nr-ac:namespace} - data: - deployment-config.yaml: | - ${nr-var:chart_values.cm_content} diff --git a/agent-control/tests/k8s/data/custom_agent_type.yml b/agent-control/tests/k8s/data/custom_agent_type.yml deleted file mode 100644 index ed8964c29b..0000000000 --- a/agent-control/tests/k8s/data/custom_agent_type.yml +++ /dev/null @@ -1,63 +0,0 @@ -namespace: newrelic -name: com.newrelic.custom_agent -version: 0.0.1 -platform: kubernetes -protocol_version: "1.0" -variables: - chart_values: - description: "chart_values" - type: yaml - required: false - default: { } -deployment: - health: - interval: 5s - initial_delay: 2s - checks: - - namespace: ${nr-ac:namespace} - name: ${nr-sub:agent_id} - kind: HelmReleaseWorkload - target_namespace: ${nr-ac:namespace_agents} - objects: - repository: - apiVersion: source.toolkit.fluxcd.io/v1 - kind: HelmRepository - metadata: - name: ${nr-sub:agent_id} - namespace: ${nr-ac:namespace} - spec: - # we don't want to trigger this in the test to avoid extra load in the cluster - interval: 99m - url: https://helm.github.io/examples - release: - apiVersion: helm.toolkit.fluxcd.io/v2 - kind: HelmRelease - metadata: - name: ${nr-sub:agent_id} - namespace: ${nr-ac:namespace} - spec: - # we don't want to trigger this in the test to avoid extra load in the cluster - interval: 10s - releaseName: ${nr-sub:agent_id} - targetNamespace: ${nr-ac:namespace_agents} - chart: - spec: - chart: hello-world - version: 0.1.0 - sourceRef: - kind: HelmRepository - name: ${nr-sub:agent_id} - namespace: ${nr-ac:namespace} - install: - disableWait: true - disableWaitForJobs: true - disableTakeOwnership: true - # namespaces lifecycle is handled by the test. - # createNamespace: true - upgrade: - disableWait: true - disableWaitForJobs: true - disableTakeOwnership: true - cleanupOnFail: true - values: - ${nr-var:chart_values} diff --git a/agent-control/tests/k8s/data/custom_agent_type_direct_checks.yml b/agent-control/tests/k8s/data/custom_agent_type_direct_checks.yml deleted file mode 100644 index dbebee99ed..0000000000 --- a/agent-control/tests/k8s/data/custom_agent_type_direct_checks.yml +++ /dev/null @@ -1,24 +0,0 @@ -namespace: newrelic -name: com.newrelic.custom_agent -version: 0.0.1 -platform: kubernetes -protocol_version: "1.0" -variables: {} -deployment: - health: - interval: 5s - initial_delay: 2s - # The integration test defines a Deployment. StatefulSet and DaemonSet are also defined to check that the - # health-checker is healthy when one of them is found and healthy, allowing the definition of health-checks - # for Agent-Type whose workload definition is configurable. - checks: - - namespace: ${nr-ac:namespace_agents} - name: ${nr-sub:agent_id} - kind: Deployment - - namespace: ${nr-ac:namespace_agents} - name: ${nr-sub:agent_id} - kind: StatefulSet - - namespace: ${nr-ac:namespace_agents} - name: ${nr-sub:agent_id} - kind: DaemonSet - objects: {} # No objects, the workload is defined externally diff --git a/agent-control/tests/k8s/data/custom_agent_type_secrets.yml b/agent-control/tests/k8s/data/custom_agent_type_secrets.yml deleted file mode 100644 index b034f981a5..0000000000 --- a/agent-control/tests/k8s/data/custom_agent_type_secrets.yml +++ /dev/null @@ -1,36 +0,0 @@ -namespace: newrelic -name: com.newrelic.custom_agent -version: 0.0.1 -platform: kubernetes -protocol_version: "1.0" -variables: - chart_values: - description: "chart_values" - type: yaml - required: false - default: { } -deployment: - objects: - release: - apiVersion: helm.toolkit.fluxcd.io/v2 - kind: HelmRelease - metadata: - name: ${nr-sub:agent_id} - namespace: ${nr-ac:namespace} - labels: - agentTypeEnvVarKey: ${nr-env:k8s_template_secrets_zip4} - spec: - # we don't want to trigger this in the test to avoid extra load in the cluster - interval: 10s - releaseName: ${nr-sub:agent_id} - targetNamespace: ${nr-ac:namespace_agents} - chart: - spec: - chart: hello-world - version: "0.1.0" - sourceRef: - kind: HelmRepository - name: ${nr-sub:agent_id} - namespace: ${nr-ac:namespace} - values: - ${nr-var:chart_values} diff --git a/agent-control/tests/k8s/data/custom_agent_type_split_ns.yml b/agent-control/tests/k8s/data/custom_agent_type_split_ns.yml deleted file mode 100644 index c2137afce2..0000000000 --- a/agent-control/tests/k8s/data/custom_agent_type_split_ns.yml +++ /dev/null @@ -1,73 +0,0 @@ -namespace: newrelic -name: com.newrelic.custom_agent -version: 0.0.1 -platform: kubernetes -protocol_version: "1.0" -variables: - chart_values: - description: "chart_values" - type: yaml - required: false - default: { } -deployment: - health: - interval: 5s - initial_delay: 2s - checks: - - namespace: ${nr-ac:namespace} - name: ${nr-sub:agent_id} - kind: HelmReleaseWorkload - target_namespace: ${nr-ac:namespace_agents} - objects: - repository: - apiVersion: source.toolkit.fluxcd.io/v1 - kind: HelmRepository - metadata: - name: ${nr-sub:agent_id} - namespace: ${nr-ac:namespace} - spec: - # we don't want to trigger this in the test to avoid extra load in the cluster - interval: 99m - url: https://helm.github.io/examples - default-values: - apiVersion: v1 - kind: Secret - metadata: - name: default-values-${nr-sub:agent_id} - namespace: ${nr-ac:namespace} - stringData: - values.yaml: | - ${nr-var:chart_values} - release: - apiVersion: helm.toolkit.fluxcd.io/v2 - kind: HelmRelease - metadata: - name: ${nr-sub:agent_id} - namespace: ${nr-ac:namespace} - spec: - # until we address https://new-relic.atlassian.net/browse/NR-435351 - interval: 10s - targetNamespace: ${nr-ac:namespace_agents} - releaseName: ${nr-sub:agent_id} - chart: - spec: - chart: hello-world - version: 0.1.0 - sourceRef: - kind: HelmRepository - name: ${nr-sub:agent_id} - install: - disableWait: true - disableWaitForJobs: true - disableTakeOwnership: true - # namespaces lifecycle is handled by the test. - # createNamespace: true - upgrade: - disableWait: true - disableWaitForJobs: true - disableTakeOwnership: true - cleanupOnFail: true - valuesFrom: - - kind: Secret - name: default-values-${nr-sub:agent_id} - valuesKey: values.yaml diff --git a/agent-control/tests/k8s/data/foo_cr_agent_type.yml b/agent-control/tests/k8s/data/foo_cr_agent_type.yml deleted file mode 100644 index 30eb3fd277..0000000000 --- a/agent-control/tests/k8s/data/foo_cr_agent_type.yml +++ /dev/null @@ -1,25 +0,0 @@ -namespace: newrelic -name: com.newrelic.foo_cr_agent -version: 0.0.1 -platform: kubernetes -protocol_version: "1.0" -variables: - data: - description: "foo data" - type: string - required: false - default: "default" -deployment: - health: - interval: 5s - objects: - foo_cr: - # This CRD is created in the cluster when initializing the test environment. - apiVersion: newrelic.com/v1 - kind: Foo - metadata: - name: ${nr-sub:agent_id} - namespace: ${nr-ac:namespace} - spec: - data: ${nr-var:data} - diff --git a/agent-control/tests/k8s/data/k8s_fail_remote_config_missing_required_values/custom_agent_type.yml b/agent-control/tests/k8s/data/k8s_fail_remote_config_missing_required_values/custom_agent_type.yml deleted file mode 100644 index 2d90fca1ac..0000000000 --- a/agent-control/tests/k8s/data/k8s_fail_remote_config_missing_required_values/custom_agent_type.yml +++ /dev/null @@ -1,26 +0,0 @@ -namespace: newrelic -name: com.newrelic.test -version: 0.0.1 -platform: kubernetes -protocol_version: "1.0" -variables: - required_var: - description: "required_var" - type: yaml - required: true - non_required_var: - description: "non_required_var" - type: yaml - required: false - default: { } -deployment: - objects: - some-resource: - apiVersion: v1 - kind: Secret - metadata: - name: some-resource - namespace: ${nr-ac:namespace} - StringData: - non_required_var: ${nr-var:non_required_var} - required_var: ${nr-var:required_var} diff --git a/agent-control/tests/k8s/flux_self_update.rs b/agent-control/tests/k8s/flux_self_update.rs index 2ebca7face..0eec4b485d 100644 --- a/agent-control/tests/k8s/flux_self_update.rs +++ b/agent-control/tests/k8s/flux_self_update.rs @@ -11,10 +11,10 @@ use crate::common::health::{check_latest_health_status, check_latest_health_stat use crate::common::remote_config_status::check_latest_remote_config_status_is_expected; use crate::common::retry::{DeferredCommand, retry}; use crate::common::runtime::{block_on, tokio_runtime}; -use crate::k8s::tools::agent_control::CUSTOM_AGENT_TYPE_SPLIT_NS_PATH; use crate::k8s::tools::agent_control::start_agent_control; use crate::k8s::tools::cmd::print_cli_output; use crate::k8s::tools::config::K8sAgentControlConfigBuilder; +use crate::k8s::tools::custom_agent_type::K8sCustomAgentType; use crate::k8s::tools::instance_id::get_instance_id; use crate::k8s::tools::k8s_api::{check_helmrelease_chart_version, create_values_secret}; use crate::k8s::tools::k8s_env::K8sEnv; @@ -94,12 +94,8 @@ fn k8s_remote_flux_update() { .with_current_chart_version("0.0.1000") .write(k8s.client.clone(), tmp_dir.path()); - let _sa = start_agent_control( - CUSTOM_AGENT_TYPE_SPLIT_NS_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + K8sCustomAgentType::split_ns().build(tmp_dir.path()); + let _sa = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); let ac_instance_id = get_instance_id(k8s.client.clone(), &namespace, &AgentID::AgentControl); @@ -180,12 +176,8 @@ fn k8s_remote_flux_update_with_wrong_version_causes_unhealthy() { .with_current_chart_version("0.0.1000") .write(k8s.client.clone(), tmp_dir.path()); - let _sa = start_agent_control( - CUSTOM_AGENT_TYPE_SPLIT_NS_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + K8sCustomAgentType::split_ns().build(tmp_dir.path()); + let _sa = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); let ac_instance_id = get_instance_id(k8s.client.clone(), &namespace, &AgentID::AgentControl); diff --git a/agent-control/tests/k8s/scenarios/ac_restarts.rs b/agent-control/tests/k8s/scenarios/ac_restarts.rs index ac17a6eab2..a53b018ed3 100644 --- a/agent-control/tests/k8s/scenarios/ac_restarts.rs +++ b/agent-control/tests/k8s/scenarios/ac_restarts.rs @@ -3,10 +3,10 @@ use crate::common::health::check_latest_health_status_was_healthy; use crate::common::retry::retry; use crate::common::runtime::{block_on, tokio_runtime}; use crate::k8s::tools::agent_control::{ - CUSTOM_AGENT_TYPE_PATH, create_config_map, start_agent_control, - wait_until_agent_control_with_opamp_is_started, + create_config_map, start_agent_control, wait_until_agent_control_with_opamp_is_started, }; use crate::k8s::tools::config::K8sAgentControlConfigBuilder; +use crate::k8s::tools::custom_agent_type::K8sCustomAgentType; use crate::k8s::tools::instance_id; use crate::k8s::tools::k8s_api::check_helmrelease_spec_values; use crate::k8s::tools::k8s_env::K8sEnv; @@ -31,14 +31,17 @@ fn k8s_opamp_subagent_configuration_change_after_ac_restarts() { let namespace = block_on(k8s.test_namespace()); let tmp_dir = tempdir().expect("failed to create local temp dir"); - let agents = r#" + let agent_type_id = K8sCustomAgentType::default().build(tmp_dir.path()); + let agents = format!( + r#" hello-world: - agent_type: "newrelic/com.newrelic.custom_agent:0.0.1" -"#; + agent_type: "{agent_type_id}" +"# + ); K8sAgentControlConfigBuilder::new(&namespace) .with_fleet(server.endpoint(), server.jwks_endpoint()) - .with_agents(agents) + .with_agents(&agents) .write(k8s.client.clone(), tmp_dir.path()); // This config is intended to be empty @@ -49,12 +52,7 @@ fn k8s_opamp_subagent_configuration_change_after_ac_restarts() { "".to_string(), )); - let _sa = start_agent_control( - CUSTOM_AGENT_TYPE_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + let _sa = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); let instance_id = instance_id::get_instance_id( k8s.client.clone(), @@ -102,15 +100,10 @@ valid: true // start the agent-control with the same configuration K8sAgentControlConfigBuilder::new(&namespace) .with_fleet(server.endpoint(), server.jwks_endpoint()) - .with_agents(agents) + .with_agents(&agents) .write(k8s.client.clone(), tmp_dir.path()); - let _sa = start_agent_control( - CUSTOM_AGENT_TYPE_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + let _sa = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); wait_until_agent_control_with_opamp_is_started(k8s.client.clone(), namespace.as_str()); // Check that after restarting the sub-agent configuration remains as set remotely diff --git a/agent-control/tests/k8s/scenarios/attributes.rs b/agent-control/tests/k8s/scenarios/attributes.rs index 5ffef3d760..96607f9096 100644 --- a/agent-control/tests/k8s/scenarios/attributes.rs +++ b/agent-control/tests/k8s/scenarios/attributes.rs @@ -6,10 +6,9 @@ use crate::common::attributes::{ }; use crate::common::retry::retry; use crate::common::runtime::{block_on, tokio_runtime}; -use crate::k8s::tools::agent_control::{ - CUSTOM_AGENT_TYPE_PATH, create_config_map, start_agent_control, -}; +use crate::k8s::tools::agent_control::{create_config_map, start_agent_control}; use crate::k8s::tools::config::K8sAgentControlConfigBuilder; +use crate::k8s::tools::custom_agent_type::K8sCustomAgentType; use crate::k8s::tools::{instance_id, k8s_env::K8sEnv}; use fake_opamp_server::FakeServer; use newrelic_agent_control::agent_control::agent_id::AgentID; @@ -38,10 +37,13 @@ fn k8s_test_attributes_from_existing_agent_type() { let namespace = block_on(k8s.test_namespace()); let tmp_dir = tempdir().expect("failed to create local temp dir"); - let agents = r#" + let agent_type_id = K8sCustomAgentType::default().build(tmp_dir.path()); + let agents = format!( + r#" hello-world: - agent_type: "newrelic/com.newrelic.custom_agent:0.0.1" -"#; + agent_type: "{agent_type_id}" +"# + ); K8sAgentControlConfigBuilder::new(&namespace) .with_fleet(server.endpoint(), server.jwks_endpoint()) @@ -58,23 +60,20 @@ fn k8s_test_attributes_from_existing_agent_type() { "chart_values:\n cluster: minikube\n licenseKey: test\n".to_string(), )); - let _sa = start_agent_control( - CUSTOM_AGENT_TYPE_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + let _sa = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); let expected_chart_version = "1.2.3-beta".to_string(); let instance_id = instance_id::get_instance_id(k8s.client.clone(), &namespace, &AgentID::AgentControl); server.set_config_response( instance_id.clone(), - r#" + format!( + r#" agents: hello-world: - agent_type: "newrelic/com.newrelic.custom_agent:0.0.1" - "#, + agent_type: "{agent_type_id}" + "# + ), ); let ac_expected_identifying_attributes = convert_to_vec_key_value(Vec::from([ @@ -226,12 +225,8 @@ fn k8s_test_custom_capabilities_when_cd_disabled() { .with_cd_enabled(false) .write(k8s.client.clone(), tmp_dir.path()); - let _ac = start_agent_control( - CUSTOM_AGENT_TYPE_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + K8sCustomAgentType::default().build(tmp_dir.path()); + let _ac = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); let instance_id = instance_id::get_instance_id(k8s.client.clone(), &namespace, &AgentID::AgentControl); diff --git a/agent-control/tests/k8s/scenarios/config_signature.rs b/agent-control/tests/k8s/scenarios/config_signature.rs index 494bb63f19..c968c3316a 100644 --- a/agent-control/tests/k8s/scenarios/config_signature.rs +++ b/agent-control/tests/k8s/scenarios/config_signature.rs @@ -2,12 +2,12 @@ use crate::common::{ retry::retry, runtime::{block_on, tokio_runtime}, }; -use crate::k8s::tools::agent_control::CUSTOM_AGENT_TYPE_PATH; use fake_opamp_server::FakeServer; use crate::k8s::tools::{ agent_control::{create_config_map, start_agent_control}, config::K8sAgentControlConfigBuilder, + custom_agent_type::K8sCustomAgentType, instance_id, k8s_api::check_helmrelease_spec_values, k8s_env::K8sEnv, @@ -29,10 +29,13 @@ fn k8s_signature_disabled() { let namespace = block_on(k8s.test_namespace()); let tmp_dir = tempdir().expect("failed to create local temp dir"); - let agents = r#" + let agent_type_id = K8sCustomAgentType::default().build(tmp_dir.path()); + let agents = format!( + r#" hello-world: - agent_type: "newrelic/com.newrelic.custom_agent:0.0.1" -"#; + agent_type: "{agent_type_id}" +"# + ); K8sAgentControlConfigBuilder::new(&namespace) .with_fleet(server.endpoint(), server.jwks_endpoint()) @@ -48,12 +51,7 @@ fn k8s_signature_disabled() { "".to_string(), )); - let _sa = start_agent_control( - CUSTOM_AGENT_TYPE_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + let _sa = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); let instance_id = instance_id::get_instance_id( k8s.client.clone(), diff --git a/agent-control/tests/k8s/scenarios/cr_based_agents.rs b/agent-control/tests/k8s/scenarios/cr_based_agents.rs index a6f28f0bd9..0b5a9a5293 100644 --- a/agent-control/tests/k8s/scenarios/cr_based_agents.rs +++ b/agent-control/tests/k8s/scenarios/cr_based_agents.rs @@ -2,14 +2,12 @@ use crate::common::{ retry::retry, runtime::{block_on, tokio_runtime}, }; -use crate::k8s::tools::agent_control::BAR_CR_AGENT_TYPE_PATH; use crate::k8s::tools::k8s_api::check_config_map_exist; use crate::k8s::tools::test_crd::{Foo, create_crd, delete_crd}; use crate::k8s::tools::{ - agent_control::{ - FOO_CR_AGENT_TYPE_PATH, start_agent_control, wait_until_agent_control_with_opamp_is_started, - }, + agent_control::{start_agent_control, wait_until_agent_control_with_opamp_is_started}, config::K8sAgentControlConfigBuilder, + custom_agent_type::K8sCustomAgentType, instance_id, k8s_env::K8sEnv, }; @@ -40,23 +38,46 @@ fn k8s_opamp_foo_cr_subagent() { .with_cr_type_meta(cr_type_meta) .write(k8s.client.clone(), tmp_dir.path()); - let _sa = start_agent_control( - FOO_CR_AGENT_TYPE_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + let agent_type_id = K8sCustomAgentType::new() + .with_agent_type_id("newrelic/com.newrelic.foo_cr_agent:0.0.1") + .with_variables( + r#" +data: + description: "foo data" + type: string + required: false + default: "default" +"#, + ) + .with_health(Some("interval: 5s")) + .with_objects(Some( + r#" +foo_cr: + # This CRD is created in the cluster when initializing the test environment. + apiVersion: newrelic.com/v1 + kind: Foo + metadata: + name: ${nr-sub:agent_id} + namespace: ${nr-ac:namespace} + spec: + data: ${nr-var:data} +"#, + )) + .build(tmp_dir.path()); + let _sa = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); let instance_id = instance_id::get_instance_id(k8s.client.clone(), &namespace, &AgentID::AgentControl); server.set_config_response( instance_id.clone(), - r#" + format!( + r#" agents: foo-agent: - agent_type: "newrelic/com.newrelic.foo_cr_agent:0.0.1" - "#, + agent_type: "{agent_type_id}" + "# + ), ); // Set sub-agent remote config (there is no local config and the supervisor will not start otherwise) @@ -120,12 +141,33 @@ fn k8s_opamp_cr_subagent_installed_before_crd() { .with_cr_type_meta(cr_type_meta) .write(k8s.client.clone(), tmp_dir.path()); - let _sa = start_agent_control( - BAR_CR_AGENT_TYPE_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + let agent_type_id = K8sCustomAgentType::new() + .with_agent_type_id("newrelic/com.newrelic.bar_cr_agent:0.0.1") + .with_variables( + r#" +data: + description: "bar data" + type: string + required: false + default: "default" +"#, + ) + .with_health(Some("interval: 5s")) + .with_objects(Some( + r#" +bar_cr: + # This CRD is created in the cluster when initializing the test environment. + apiVersion: newrelic.com/v1 + kind: Bar + metadata: + name: ${nr-sub:agent_id} + namespace: ${nr-ac:namespace} + spec: + data: ${nr-var:data} +"#, + )) + .build(tmp_dir.path()); + let _sa = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); wait_until_agent_control_with_opamp_is_started(k8s.client.clone(), namespace.as_str()); let instance_id = @@ -134,11 +176,13 @@ fn k8s_opamp_cr_subagent_installed_before_crd() { // Set AC remote config server.set_config_response( instance_id.clone(), - r#" + format!( + r#" agents: bar-agent: - agent_type: "newrelic/com.newrelic.bar_cr_agent:0.0.1" - "#, + agent_type: "{agent_type_id}" + "# + ), ); let api: Api = Api::namespaced(k8s.client.clone(), &namespace); diff --git a/agent-control/tests/k8s/scenarios/fail_remote_config.rs b/agent-control/tests/k8s/scenarios/fail_remote_config.rs index 41a7dfbad8..6fcde12344 100644 --- a/agent-control/tests/k8s/scenarios/fail_remote_config.rs +++ b/agent-control/tests/k8s/scenarios/fail_remote_config.rs @@ -7,6 +7,7 @@ use crate::common::{ use crate::k8s::tools::{ agent_control::{create_config_map, start_agent_control}, config::K8sAgentControlConfigBuilder, + custom_agent_type::K8sCustomAgentType, instance_id, k8s_env::K8sEnv, }; @@ -16,9 +17,6 @@ use opamp_client::opamp::proto::RemoteConfigStatuses; use std::time::Duration; use tempfile::tempdir; -const CUSTOM_AGENT_TYPE_PATH: &str = - "tests/k8s/data/k8s_fail_remote_config_missing_required_values/custom_agent_type.yml"; - #[test] #[ignore = "needs k8s cluster"] fn k8s_fail_remote_config_missing_required_values() { @@ -28,10 +26,41 @@ fn k8s_fail_remote_config_missing_required_values() { let namespace = block_on(k8s.test_namespace()); let tmp_dir = tempdir().expect("failed to create local temp dir"); - let agents = r#" + let agent_type_id = K8sCustomAgentType::new() + .with_agent_type_id("newrelic/com.newrelic.test:0.0.1") + .with_variables( + r#" +required_var: + description: "required_var" + type: yaml + required: true +non_required_var: + description: "non_required_var" + type: yaml + required: false + default: { } +"#, + ) + .with_objects(Some( + r#" +some-resource: + apiVersion: v1 + kind: Secret + metadata: + name: some-resource + namespace: ${nr-ac:namespace} + StringData: + non_required_var: ${nr-var:non_required_var} + required_var: ${nr-var:required_var} +"#, + )) + .build(tmp_dir.path()); + let agents = format!( + r#" fake-agent: - agent_type: "newrelic/com.newrelic.test:0.0.1" -"#; + agent_type: "{agent_type_id}" +"# + ); K8sAgentControlConfigBuilder::new(&namespace) .with_fleet(server.endpoint(), server.jwks_endpoint()) @@ -45,12 +74,7 @@ fn k8s_fail_remote_config_missing_required_values() { "required_var: \"local\"\n".to_string(), )); - let _sa = start_agent_control( - CUSTOM_AGENT_TYPE_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + let _sa = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); let instance_id = instance_id::get_instance_id( k8s.client.clone(), diff --git a/agent-control/tests/k8s/scenarios/garbage_collector.rs b/agent-control/tests/k8s/scenarios/garbage_collector.rs index 0d915b8f29..ec1e3d32da 100644 --- a/agent-control/tests/k8s/scenarios/garbage_collector.rs +++ b/agent-control/tests/k8s/scenarios/garbage_collector.rs @@ -14,11 +14,9 @@ use crate::{ runtime::{block_on, tokio_runtime}, }, k8s::tools::{ - agent_control::{ - CUSTOM_AGENT_TYPE_PATH, start_agent_control, - wait_until_agent_control_with_opamp_is_started, - }, + agent_control::{start_agent_control, wait_until_agent_control_with_opamp_is_started}, config::K8sAgentControlConfigBuilder, + custom_agent_type::K8sCustomAgentType, k8s_env::K8sEnv, test_crd::{Foo, create_foo_cr}, }, @@ -56,12 +54,8 @@ fn k8s_garbage_collector_triggers_on_ac_startup() { .with_cr_type_meta(" - apiVersion: newrelic.com/v1\n kind: Foo") .write(k8s.client.clone(), tmp_dir.path()); - let _sa = start_agent_control( - CUSTOM_AGENT_TYPE_PATH, - k8s.client.clone(), - &test_ns, - tmp_dir.path(), - ); + K8sCustomAgentType::default().build(tmp_dir.path()); + let _sa = start_agent_control(k8s.client.clone(), &test_ns, tmp_dir.path()); wait_until_agent_control_with_opamp_is_started(k8s.client.clone(), test_ns.as_str()); let api: Api = Api::namespaced(k8s.client.clone(), &test_ns); diff --git a/agent-control/tests/k8s/scenarios/health_checks.rs b/agent-control/tests/k8s/scenarios/health_checks.rs index 52d6474035..65b7511d02 100644 --- a/agent-control/tests/k8s/scenarios/health_checks.rs +++ b/agent-control/tests/k8s/scenarios/health_checks.rs @@ -1,16 +1,14 @@ -use crate::{ - common::{ - health::{check_latest_health_status, check_latest_health_status_was_healthy}, - retry::retry, - runtime::{block_on, tokio_runtime}, - }, - k8s::tools::agent_control::CUSTOM_AGENT_TYPE_DIRECT_CHECKS_PATH, +use crate::common::{ + health::{check_latest_health_status, check_latest_health_status_was_healthy}, + retry::retry, + runtime::{block_on, tokio_runtime}, }; use fake_opamp_server::FakeServer; use crate::k8s::tools::{ agent_control::{create_config_map, start_agent_control}, config::K8sAgentControlConfigBuilder, + custom_agent_type::K8sCustomAgentType, instance_id, k8s_env::K8sEnv, }; @@ -48,10 +46,13 @@ fn k8s_direct_workload_health_checks() { 0, )); - let agents = r#" + let agent_type_id = direct_checks_agent_type().build(tmp_dir.path()); + let agents = format!( + r#" hello-world: - agent_type: "newrelic/com.newrelic.custom_agent:0.0.1" -"#; + agent_type: "{agent_type_id}" +"# + ); K8sAgentControlConfigBuilder::new(&ac_ns) .with_fleet(server.endpoint(), server.jwks_endpoint()) @@ -66,12 +67,7 @@ fn k8s_direct_workload_health_checks() { "{}".to_string(), )); - let _ac = start_agent_control( - CUSTOM_AGENT_TYPE_DIRECT_CHECKS_PATH, - k8s.client.clone(), - &ac_ns, - tmp_dir.path(), - ); + let _ac = start_agent_control(k8s.client.clone(), &ac_ns, tmp_dir.path()); let sub_agent_instance_id = instance_id::get_instance_id( k8s.client.clone(), @@ -108,10 +104,13 @@ fn k8s_direct_workload_health_checks_unhealthy() { 1, )); - let agents = r#" + let agent_type_id = direct_checks_agent_type().build(tmp_dir.path()); + let agents = format!( + r#" hello-world: - agent_type: "newrelic/com.newrelic.custom_agent:0.0.1" -"#; + agent_type: "{agent_type_id}" +"# + ); K8sAgentControlConfigBuilder::new(&ac_ns) .with_fleet(server.endpoint(), server.jwks_endpoint()) @@ -126,12 +125,7 @@ fn k8s_direct_workload_health_checks_unhealthy() { "{}".to_string(), )); - let _ac = start_agent_control( - CUSTOM_AGENT_TYPE_DIRECT_CHECKS_PATH, - k8s.client.clone(), - &ac_ns, - tmp_dir.path(), - ); + let _ac = start_agent_control(k8s.client.clone(), &ac_ns, tmp_dir.path()); let sub_agent_instance_id = instance_id::get_instance_id( k8s.client.clone(), @@ -144,6 +138,30 @@ fn k8s_direct_workload_health_checks_unhealthy() { }); } +/// The integration test defines a Deployment. StatefulSet and DaemonSet are also defined to check that the +/// health-checker is healthy when one of them is found and healthy, allowing the definition of health-checks +/// for Agent-Type whose workload definition is configurable. +/// +/// No objects, the workload is defined externally +fn direct_checks_agent_type() -> K8sCustomAgentType { + K8sCustomAgentType::new().with_health(Some( + r#" +interval: 5s +initial_delay: 2s +checks: + - namespace: ${nr-ac:namespace_agents} + name: ${nr-sub:agent_id} + kind: Deployment + - namespace: ${nr-ac:namespace_agents} + name: ${nr-sub:agent_id} + kind: StatefulSet + - namespace: ${nr-ac:namespace_agents} + name: ${nr-sub:agent_id} + kind: DaemonSet +"#, + )) +} + /// Creates a Deployment named `name` in `namespace` with the given replica count. /// /// `imagePullPolicy: Never` with a placeholder image is used throughout so no image pull diff --git a/agent-control/tests/k8s/scenarios/http_status_server.rs b/agent-control/tests/k8s/scenarios/http_status_server.rs index c4d764804e..98aa3c10d1 100644 --- a/agent-control/tests/k8s/scenarios/http_status_server.rs +++ b/agent-control/tests/k8s/scenarios/http_status_server.rs @@ -4,10 +4,10 @@ use crate::common::http_port::{available_port, status_server_url}; use crate::common::retry::retry; use crate::common::runtime::{block_on, tokio_runtime}; use crate::k8s::tools::agent_control::{ - CUSTOM_AGENT_TYPE_PATH, DUMMY_PRIVATE_KEY, DYNAMIC_AGENT_TYPE_FILENAME, K8S_KEY_SECRET, - K8S_PRIVATE_KEY_SECRET, TEST_CLUSTER_NAME, create_config_map, + DUMMY_PRIVATE_KEY, K8S_KEY_SECRET, K8S_PRIVATE_KEY_SECRET, TEST_CLUSTER_NAME, create_config_map, }; use crate::k8s::tools::config::K8sAgentControlConfigBuilder; +use crate::k8s::tools::custom_agent_type::K8sCustomAgentType; use crate::k8s::tools::k8s_api::create_values_secret; use crate::k8s::tools::k8s_env::K8sEnv; use fake_opamp_server::FakeServer; @@ -29,22 +29,18 @@ use std::time::Duration; #[ignore = "needs a k8s cluster"] fn test_k8s_http_status_endpoint_response() { const AGENT_ID: &str = "hello-world"; - const AGENT_TYPE: &str = "newrelic/com.newrelic.custom_agent:0.0.1"; let opamp_server = FakeServer::start(tokio_runtime().handle()); let mut k8s = block_on(K8sEnv::new()); let namespace = block_on(k8s.test_namespace()); let dirs = TempBasePaths::default(); - // Copy the k8s custom agent type into the dynamic agent types directory - let agent_type_file_path = dirs.local_dir().join(DYNAMIC_AGENT_TYPE_FILENAME); - std::fs::create_dir_all(agent_type_file_path.parent().unwrap()).unwrap(); - std::fs::copy(CUSTOM_AGENT_TYPE_PATH, &agent_type_file_path).unwrap(); + let agent_type_id = K8sCustomAgentType::default().build(&dirs.local_dir()); let agents = format!( r#" {AGENT_ID}: - agent_type: "{AGENT_TYPE}" + agent_type: "{agent_type_id}" "# ); diff --git a/agent-control/tests/k8s/scenarios/no_opamp.rs b/agent-control/tests/k8s/scenarios/no_opamp.rs index d8a37bbfbf..ddd96d9a44 100644 --- a/agent-control/tests/k8s/scenarios/no_opamp.rs +++ b/agent-control/tests/k8s/scenarios/no_opamp.rs @@ -1,8 +1,8 @@ use crate::common::{retry::retry, runtime::block_on}; -use crate::k8s::tools::agent_control::CUSTOM_AGENT_TYPE_SPLIT_NS_PATH; use crate::k8s::tools::{ agent_control::{create_config_map, start_agent_control}, config::K8sAgentControlConfigBuilder, + custom_agent_type::K8sCustomAgentType, k8s_api::check_deployments_exist, k8s_env::K8sEnv, }; @@ -18,10 +18,13 @@ fn k8s_sub_agent_started_with_no_opamp() { let namespace = block_on(k8s.test_namespace()); let tmp_dir = tempdir().expect("failed to create local temp dir"); - let agents = r#" + let agent_type_id = K8sCustomAgentType::split_ns().build(tmp_dir.path()); + let agents = format!( + r#" hello-world: - agent_type: "newrelic/com.newrelic.custom_agent:0.0.1" -"#; + agent_type: "{agent_type_id}" +"# + ); K8sAgentControlConfigBuilder::new(&namespace) .with_agents(agents) @@ -34,12 +37,7 @@ fn k8s_sub_agent_started_with_no_opamp() { "chart_values: \n nameOverride: from-local\n".to_string(), )); - let _child = start_agent_control( - CUSTOM_AGENT_TYPE_SPLIT_NS_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + let _child = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); // Check deployment for first Agent is created with retry, the name has the key // 'from-local' concatenated to the name because the secret created adds that diff --git a/agent-control/tests/k8s/scenarios/opamp.rs b/agent-control/tests/k8s/scenarios/opamp.rs index de5a8c8a9a..b111362c45 100644 --- a/agent-control/tests/k8s/scenarios/opamp.rs +++ b/agent-control/tests/k8s/scenarios/opamp.rs @@ -1,11 +1,8 @@ -use crate::{ - common::{ - effective_config::check_latest_effective_config_is_expected, - health::check_latest_health_status_was_healthy, - retry::retry, - runtime::{block_on, tokio_runtime}, - }, - k8s::tools::agent_control::CUSTOM_AGENT_TYPE_SPLIT_NS_PATH, +use crate::common::{ + effective_config::check_latest_effective_config_is_expected, + health::check_latest_health_status_was_healthy, + retry::retry, + runtime::{block_on, tokio_runtime}, }; use fake_opamp_server::FakeServer; @@ -13,6 +10,7 @@ use crate::k8s::tools::k8s_api::{check_helmrelease_exists, delete_helm_release}; use crate::k8s::tools::{ agent_control::{create_config_map, start_agent_control}, config::K8sAgentControlConfigBuilder, + custom_agent_type::K8sCustomAgentType, instance_id, k8s_api::check_deployments_exist, k8s_env::K8sEnv, @@ -37,10 +35,13 @@ fn k8s_opamp_remove_subagent() { let agents_ns = block_on(k8s.test_namespace()); let tmp_dir = tempdir().expect("failed to create local temp dir"); - let agents = r#" + let agent_type_id = K8sCustomAgentType::split_ns().build(tmp_dir.path()); + let agents = format!( + r#" hello-world: - agent_type: "newrelic/com.newrelic.custom_agent:0.0.1" -"#; + agent_type: "{agent_type_id}" +"# + ); K8sAgentControlConfigBuilder::new(&ac_ns) .with_fleet(server.endpoint(), server.jwks_endpoint()) @@ -55,12 +56,7 @@ fn k8s_opamp_remove_subagent() { "chart_values: \n nameOverride: from-local\n".to_string(), )); - let _sa = start_agent_control( - CUSTOM_AGENT_TYPE_SPLIT_NS_PATH, - k8s.client.clone(), - &ac_ns, - tmp_dir.path(), - ); + let _sa = start_agent_control(k8s.client.clone(), &ac_ns, tmp_dir.path()); let ac_instance_id = instance_id::get_instance_id(k8s.client.clone(), &ac_ns, &AgentID::AgentControl); @@ -77,16 +73,14 @@ fn k8s_opamp_remove_subagent() { agents_ns.as_str(), )?; - let expected_config = r#"agents: + let expected_config = format!( + r#"agents: hello-world: - agent_type: newrelic/com.newrelic.custom_agent:0.0.1 - "#; + agent_type: {agent_type_id} + "# + ); - check_latest_effective_config_is_expected( - &server, - &ac_instance_id, - expected_config.to_string(), - )?; + check_latest_effective_config_is_expected(&server, &ac_instance_id, expected_config)?; check_latest_health_status_was_healthy(&server, &sub_agent_instance_id.clone()) }); @@ -162,23 +156,21 @@ fn k8s_opamp_add_subagent() { "chart_values:\n cluster: minikube\n licenseKey: test\n".to_string(), )); - let _sa = start_agent_control( - CUSTOM_AGENT_TYPE_SPLIT_NS_PATH, - k8s.client.clone(), - &ac_ns, - tmp_dir.path(), - ); + let agent_type_id = K8sCustomAgentType::split_ns().build(tmp_dir.path()); + let _sa = start_agent_control(k8s.client.clone(), &ac_ns, tmp_dir.path()); let ac_instance_id = instance_id::get_instance_id(k8s.client.clone(), &ac_ns, &AgentID::AgentControl); server.set_config_response( ac_instance_id.clone(), - r#" + format!( + r#" agents: hello-world: - agent_type: "newrelic/com.newrelic.custom_agent:0.0.1" - "#, + agent_type: "{agent_type_id}" + "# + ), ); let sub_agent_instance_id = instance_id::get_instance_id( @@ -193,11 +185,12 @@ agents: check_latest_effective_config_is_expected( &server, &ac_instance_id, - r#"agents: + format!( + r#"agents: hello-world: - agent_type: newrelic/com.newrelic.custom_agent:0.0.1 + agent_type: {agent_type_id} "# - .to_string(), + ), )?; check_latest_health_status_was_healthy(&server, &sub_agent_instance_id) @@ -214,10 +207,13 @@ fn k8s_opamp_modify_subagent_config() { let namespace = block_on(k8s.test_namespace()); let tmp_dir = tempdir().expect("failed to create local temp dir"); - let agents = r#" + let agent_type_id = K8sCustomAgentType::split_ns().build(tmp_dir.path()); + let agents = format!( + r#" hello-world: - agent_type: "newrelic/com.newrelic.custom_agent:0.0.1" -"#; + agent_type: "{agent_type_id}" +"# + ); K8sAgentControlConfigBuilder::new(&namespace) .with_fleet(server.endpoint(), server.jwks_endpoint()) @@ -231,12 +227,7 @@ fn k8s_opamp_modify_subagent_config() { "chart_values: \n nameOverride: from-local\n".to_string(), )); - let _sa = start_agent_control( - CUSTOM_AGENT_TYPE_SPLIT_NS_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + let _sa = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); let instance_id = instance_id::get_instance_id( k8s.client.clone(), diff --git a/agent-control/tests/k8s/scenarios/secrets_providers.rs b/agent-control/tests/k8s/scenarios/secrets_providers.rs index 6ba5a7b7f6..6f178c4bb3 100644 --- a/agent-control/tests/k8s/scenarios/secrets_providers.rs +++ b/agent-control/tests/k8s/scenarios/secrets_providers.rs @@ -1,9 +1,8 @@ use crate::common::retry::retry; use crate::common::runtime::block_on; -use crate::k8s::tools::agent_control::{ - CUSTOM_AGENT_TYPE_SECRETS_PATH, create_config_map, start_agent_control, -}; +use crate::k8s::tools::agent_control::{create_config_map, start_agent_control}; use crate::k8s::tools::config::K8sAgentControlConfigBuilder; +use crate::k8s::tools::custom_agent_type::K8sCustomAgentType; use crate::k8s::tools::k8s_api::{ check_helmrelease_labels_contains, check_helmrelease_spec_values, create_values_secret, }; @@ -22,11 +21,6 @@ fn k8s_template_secrets() { let namespace = block_on(k8s.test_namespace()); let tmp_dir = tempdir().expect("failed to create local temp dir"); - let agents = r#" - hello-world: - agent_type: "newrelic/com.newrelic.custom_agent:0.0.1" -"#; - let secrets_providers = r#" vault: sources: sourceA: @@ -38,6 +32,51 @@ fn k8s_template_secrets() { token: root engine: kv2"#; + let agent_type_id = K8sCustomAgentType::new() + .with_variables( + r#" +chart_values: + description: "chart_values" + type: yaml + required: false + default: { } +"#, + ) + .with_objects(Some( + r#" +release: + apiVersion: helm.toolkit.fluxcd.io/v2 + kind: HelmRelease + metadata: + name: ${nr-sub:agent_id} + namespace: ${nr-ac:namespace} + labels: + agentTypeEnvVarKey: ${nr-env:k8s_template_secrets_zip4} + spec: + # we don't want to trigger this in the test to avoid extra load in the cluster + interval: 10s + releaseName: ${nr-sub:agent_id} + targetNamespace: ${nr-ac:namespace_agents} + chart: + spec: + chart: hello-world + version: "0.1.0" + sourceRef: + kind: HelmRepository + name: ${nr-sub:agent_id} + namespace: ${nr-ac:namespace} + values: + ${nr-var:chart_values} +"#, + )) + .build(tmp_dir.path()); + let agents = format!( + r#" + hello-world: + agent_type: "{agent_type_id}" +"# + ); + K8sAgentControlConfigBuilder::new(&namespace) .with_agents(agents) .with_secrets_providers(secrets_providers) @@ -56,12 +95,7 @@ fn k8s_template_secrets() { ), )); - let _sa = start_agent_control( - CUSTOM_AGENT_TYPE_SECRETS_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + let _sa = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); // Now, we create all the required secrets. // Hashicorp Vault secrets -> handled in the Tiltfile. diff --git a/agent-control/tests/k8s/scenarios/without_flux.rs b/agent-control/tests/k8s/scenarios/without_flux.rs index 4a5dddfd70..a4b52de5cd 100644 --- a/agent-control/tests/k8s/scenarios/without_flux.rs +++ b/agent-control/tests/k8s/scenarios/without_flux.rs @@ -6,6 +6,7 @@ use crate::common::retry::retry; use crate::common::runtime::{block_on, tokio_runtime}; use crate::k8s::tools::agent_control::start_agent_control; use crate::k8s::tools::config::K8sAgentControlConfigBuilder; +use crate::k8s::tools::custom_agent_type::K8sCustomAgentType; use crate::k8s::tools::k8s_api::{check_config_map_exist, check_config_map_has_annotation}; use crate::k8s::tools::k8s_env::K8sEnv; use crate::k8s::tools::{agent_control, instance_id}; @@ -13,14 +14,50 @@ use fake_opamp_server::FakeServer; use k8s_openapi::api::core::v1::ConfigMap; use kube::Api; use newrelic_agent_control::agent_control::agent_id::AgentID; +use std::path::Path; use std::time::Duration; use tempfile::tempdir; -const CONFIG_AGENT_TYPE_PATH: &str = "tests/k8s/data/config_map_type.yml"; - const CR_TYPE_META_CONFIG_MAP: &str = r#" - apiVersion: v1 kind: ConfigMap"#; +/// Agent type that deploys a plain ConfigMap object, used to test the config-map based +/// (non-Flux) deployment mechanism. Returns the agent type id. +fn write_config_map_agent_type(local_dir: &Path) -> String { + K8sCustomAgentType::new() + .with_agent_type_id("newrelic/com.newrelic.test_config_map:0.1.0") + .with_variables( + r#" +chart_values: + cm_content: + description: "configmap content" + type: yaml + required: false + default: { } +"#, + ) + .with_health(Some( + r#" +interval: 30s +initial_delay: 30s +"#, + )) + .with_objects(Some( + r#" +values: + apiVersion: v1 + kind: ConfigMap + metadata: + name: test-config-map + namespace: ${nr-ac:namespace} + data: + deployment-config.yaml: | + ${nr-var:chart_values.cm_content} +"#, + )) + .build(local_dir) +} + /// This test verifies that the config_map_type agent type creates /// a ConfigMap when configured through OpAMP. #[test] @@ -37,12 +74,8 @@ fn k8s_config_map_type_creates_configmap() { .with_cr_type_meta(CR_TYPE_META_CONFIG_MAP) .write(k8s.client.clone(), tmp_dir.path()); - let _ac = start_agent_control( - CONFIG_AGENT_TYPE_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + let agent_type_id = write_config_map_agent_type(tmp_dir.path()); + let _ac = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); agent_control::wait_until_agent_control_with_opamp_is_started( k8s.client.clone(), @@ -54,11 +87,13 @@ fn k8s_config_map_type_creates_configmap() { server.set_config_response( instance_id.clone(), - r#" + format!( + r#" agents: test-config-map: - agent_type: "newrelic/com.newrelic.test_config_map:0.1.0" - "#, + agent_type: "{agent_type_id}" + "# + ), ); println!("Waiting for fleet-data ConfigMap to be created..."); @@ -178,12 +213,8 @@ fn k8s_config_map_type_gc_does_not_fail_on_restart() { .with_cr_type_meta(CR_TYPE_META_CONFIG_MAP) .write(k8s.client.clone(), tmp_dir.path()); - let _ac = start_agent_control( - CONFIG_AGENT_TYPE_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + let agent_type_id = write_config_map_agent_type(tmp_dir.path()); + let _ac = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); agent_control::wait_until_agent_control_with_opamp_is_started( k8s.client.clone(), @@ -196,11 +227,13 @@ fn k8s_config_map_type_gc_does_not_fail_on_restart() { // Deploy the config-map-type agent via the fleet-level config. server.set_config_response( instance_id.clone(), - r#" + format!( + r#" agents: test-config-map: - agent_type: "newrelic/com.newrelic.test_config_map:0.1.0" - "#, + agent_type: "{agent_type_id}" + "# + ), ); // Wait for the fleet-data ConfigMap to be created (instance-ID written by the storer). @@ -245,20 +278,16 @@ chart_values: drop(_ac); // Restart AC with the same configuration. On startup, `retain` is called with the - // active agent ({test-config-map: newrelic/com.newrelic.test_config_map:0.1.0}) and - // cr_type_meta includes ConfigMap. GC finds the fleet-data ConfigMap, reads the - // agent-type-id annotation, and correctly retains it. + // active agent ({test-config-map: }) and cr_type_meta includes ConfigMap. + // GC finds the fleet-data ConfigMap, reads the agent-type-id annotation, and correctly + // retains it. K8sAgentControlConfigBuilder::new(&namespace) .with_fleet(server.endpoint(), server.jwks_endpoint()) .with_cr_type_meta(CR_TYPE_META_CONFIG_MAP) .write(k8s.client.clone(), tmp_dir.path()); - let _ac = start_agent_control( - CONFIG_AGENT_TYPE_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + write_config_map_agent_type(tmp_dir.path()); + let _ac = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); // If GC correctly handles the annotated fleet-data ConfigMap on restart, AC starts // successfully. If not, this will time out because AC crashes before creating @@ -284,12 +313,8 @@ fn k8s_agent_control_update_remote_config() { .with_cr_type_meta(CR_TYPE_META_CONFIG_MAP) .write(k8s.client.clone(), tmp_dir.path()); - let _ac = start_agent_control( - CONFIG_AGENT_TYPE_PATH, - k8s.client.clone(), - &namespace, - tmp_dir.path(), - ); + let agent_type_id = write_config_map_agent_type(tmp_dir.path()); + let _ac = start_agent_control(k8s.client.clone(), &namespace, tmp_dir.path()); agent_control::wait_until_agent_control_with_opamp_is_started( k8s.client.clone(), @@ -301,11 +326,13 @@ fn k8s_agent_control_update_remote_config() { server.set_config_response( instance_id.clone(), - r#" + format!( + r#" agents: test-config-map: - agent_type: "newrelic/com.newrelic.test_config_map:0.1.0" - "#, + agent_type: "{agent_type_id}" + "# + ), ); println!("Waiting for fleet-data ConfigMap to be created..."); diff --git a/agent-control/tests/k8s/tools/agent_control.rs b/agent-control/tests/k8s/tools/agent_control.rs index 04ca69862e..723f870a29 100644 --- a/agent-control/tests/k8s/tools/agent_control.rs +++ b/agent-control/tests/k8s/tools/agent_control.rs @@ -19,14 +19,6 @@ use std::time::Duration; pub const TEST_CLUSTER_NAME: &str = "minikube"; -pub const CUSTOM_AGENT_TYPE_PATH: &str = "tests/k8s/data/custom_agent_type.yml"; -pub const CUSTOM_AGENT_TYPE_SPLIT_NS_PATH: &str = "tests/k8s/data/custom_agent_type_split_ns.yml"; -pub const CUSTOM_AGENT_TYPE_SECRETS_PATH: &str = "tests/k8s/data/custom_agent_type_secrets.yml"; -pub const CUSTOM_AGENT_TYPE_DIRECT_CHECKS_PATH: &str = - "tests/k8s/data/custom_agent_type_direct_checks.yml"; -pub const FOO_CR_AGENT_TYPE_PATH: &str = "tests/k8s/data/foo_cr_agent_type.yml"; -pub const BAR_CR_AGENT_TYPE_PATH: &str = "tests/k8s/data/bar_cr_agent_type.yml"; - pub const DYNAMIC_AGENT_TYPE_FILENAME: &str = "dynamic-agent-types/type.yaml"; pub const K8S_PRIVATE_KEY_SECRET: &str = "agent-control-auth"; @@ -36,18 +28,10 @@ pub const DUMMY_PRIVATE_KEY: &str = r#"-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCt -----END PRIVATE KEY-----"#; -/// Starts agent-control after the config has already been written via [K8sAgentControlConfigBuilder]. -/// Copies the dynamic agent type file, creates the auth secret, and starts the process. -pub fn start_agent_control( - dynamic_agent_type_path: &str, - client: Client, - ac_ns: &str, - local_dir: &Path, -) -> StartedAgentControl { - let agent_type_file_path = local_dir.join(DYNAMIC_AGENT_TYPE_FILENAME); - std::fs::create_dir_all(agent_type_file_path.parent().unwrap()).unwrap(); - std::fs::copy(dynamic_agent_type_path, agent_type_file_path).unwrap(); - +/// Starts agent-control after the config has already been written via [K8sAgentControlConfigBuilder] +/// and the agent type has already been written via [super::custom_agent_type::K8sCustomAgentType]. +/// Creates the auth secret and starts the process. +pub fn start_agent_control(client: Client, ac_ns: &str, local_dir: &Path) -> StartedAgentControl { create_values_secret( client, ac_ns, diff --git a/agent-control/tests/k8s/tools/custom_agent_type.rs b/agent-control/tests/k8s/tools/custom_agent_type.rs new file mode 100644 index 0000000000..a899dfefd0 --- /dev/null +++ b/agent-control/tests/k8s/tools/custom_agent_type.rs @@ -0,0 +1,237 @@ +use crate::common::custom_agent_type::CommonCustomAgentType; +use newrelic_agent_control::agent_type::agent_type_id::AgentTypeID; +use std::fmt::Display; +use std::path::Path; + +/// Helper to build a Custom Agent type with defaults ready to use in k8s integration tests. +pub struct K8sCustomAgentType { + common: CommonCustomAgentType, + health: Option, + objects: Option, +} + +impl Default for K8sCustomAgentType { + fn default() -> Self { + Self::new() + .with_variables( + r#" +chart_values: + description: "chart_values" + type: yaml + required: false + default: { } +"#, + ) + .with_health(Some( + r#" +interval: 5s +initial_delay: 2s +checks: + - namespace: ${nr-ac:namespace} + name: ${nr-sub:agent_id} + kind: HelmReleaseWorkload + target_namespace: ${nr-ac:namespace_agents} +"#, + )) + .with_objects(Some( + r#" +repository: + apiVersion: source.toolkit.fluxcd.io/v1 + kind: HelmRepository + metadata: + name: ${nr-sub:agent_id} + namespace: ${nr-ac:namespace} + spec: + # we don't want to trigger this in the test to avoid extra load in the cluster + interval: 99m + url: https://helm.github.io/examples +release: + apiVersion: helm.toolkit.fluxcd.io/v2 + kind: HelmRelease + metadata: + name: ${nr-sub:agent_id} + namespace: ${nr-ac:namespace} + spec: + # we don't want to trigger this in the test to avoid extra load in the cluster + interval: 10s + releaseName: ${nr-sub:agent_id} + targetNamespace: ${nr-ac:namespace_agents} + chart: + spec: + chart: hello-world + version: 0.1.0 + sourceRef: + kind: HelmRepository + name: ${nr-sub:agent_id} + namespace: ${nr-ac:namespace} + install: + disableWait: true + disableWaitForJobs: true + disableTakeOwnership: true + upgrade: + disableWait: true + disableWaitForJobs: true + disableTakeOwnership: true + cleanupOnFail: true + values: + ${nr-var:chart_values} +"#, + )) + } +} + +impl Display for K8sCustomAgentType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let content = format!( + r#" + namespace: {} + name: {} + version: {} + platform: kubernetes + protocol_version: "1.0" + "#, + self.common.agent_type_id.namespace(), + self.common.agent_type_id.name(), + self.common.agent_type_id.version(), + ); + let mut content: serde_json::Map = + serde_saphyr::from_str(&content).unwrap(); + + let variables = self + .common + .variables + .clone() + .unwrap_or_else(|| serde_json::Map::::new().into()); + + let mut deployment = serde_json::Map::::new(); + if let Some(health) = self.health.as_ref() { + deployment.insert("health".into(), health.clone()); + } + deployment.insert( + "objects".into(), + self.objects + .clone() + .unwrap_or_else(|| serde_json::Map::::new().into()), + ); + + content.insert("variables".into(), variables); + content.insert("deployment".into(), deployment.into()); + let content = serde_json::Value::from(content); + + write!(f, "{}", serde_saphyr::to_string(&content).unwrap()) + } +} + +impl K8sCustomAgentType { + fn default_agent_type_id() -> AgentTypeID { + AgentTypeID::try_from("newrelic/com.newrelic.custom_agent:0.0.1").unwrap() + } + + pub fn new() -> Self { + Self { + common: CommonCustomAgentType::new(Self::default_agent_type_id()), + health: None, + objects: None, + } + } + + /// Like [`Self::default`], but the HelmRelease values are provided through a separate Secret + /// (`valuesFrom`) instead of being inlined in the release spec, exercising the split + /// namespace/values-secret path. + pub fn split_ns() -> Self { + Self { + objects: Some( + serde_saphyr::from_str( + r#" +repository: + apiVersion: source.toolkit.fluxcd.io/v1 + kind: HelmRepository + metadata: + name: ${nr-sub:agent_id} + namespace: ${nr-ac:namespace} + spec: + # we don't want to trigger this in the test to avoid extra load in the cluster + interval: 99m + url: https://helm.github.io/examples +default-values: + apiVersion: v1 + kind: Secret + metadata: + name: default-values-${nr-sub:agent_id} + namespace: ${nr-ac:namespace} + stringData: + values.yaml: | + ${nr-var:chart_values} +release: + apiVersion: helm.toolkit.fluxcd.io/v2 + kind: HelmRelease + metadata: + name: ${nr-sub:agent_id} + namespace: ${nr-ac:namespace} + spec: + # until we address https://new-relic.atlassian.net/browse/NR-435351 + interval: 10s + targetNamespace: ${nr-ac:namespace_agents} + releaseName: ${nr-sub:agent_id} + chart: + spec: + chart: hello-world + version: 0.1.0 + sourceRef: + kind: HelmRepository + name: ${nr-sub:agent_id} + install: + disableWait: true + disableWaitForJobs: true + disableTakeOwnership: true + upgrade: + disableWait: true + disableWaitForJobs: true + disableTakeOwnership: true + cleanupOnFail: true + valuesFrom: + - kind: Secret + name: default-values-${nr-sub:agent_id} + valuesKey: values.yaml +"#, + ) + .unwrap(), + ), + ..Self::default() + } + } + + pub fn with_variables(self, variables: &str) -> Self { + Self { + common: self.common.with_variables(variables), + ..self + } + } + + pub fn with_health(self, health: Option<&str>) -> Self { + Self { + health: health.map(|h| serde_saphyr::from_str(h).unwrap()), + ..self + } + } + + pub fn with_objects(self, objects: Option<&str>) -> Self { + Self { + objects: objects.map(|o| serde_saphyr::from_str(o).unwrap()), + ..self + } + } + + pub fn with_agent_type_id(self, agent_type_id: &str) -> Self { + Self { + common: self.common.with_agent_type_id(agent_type_id), + ..self + } + } + + /// Writes the custom agent type and returns its id as string. + pub fn build(self, local_dir: &Path) -> String { + let content = self.to_string(); + self.common.build(local_dir, &content) + } +} diff --git a/agent-control/tests/k8s/tools/mod.rs b/agent-control/tests/k8s/tools/mod.rs index d85e863408..5452f45bb3 100644 --- a/agent-control/tests/k8s/tools/mod.rs +++ b/agent-control/tests/k8s/tools/mod.rs @@ -3,6 +3,8 @@ pub mod agent_control; /// Helpers for assert_cmd. pub mod cmd; pub mod config; +/// Helper to build Custom Agent types ready to use in k8s integration tests. +pub mod custom_agent_type; pub mod instance_id; /// Provides tools to perform queries through the k8s API in order to perform assertions. pub mod k8s_api; diff --git a/agent-control/tests/on_host/scenarios/ac_self_update.rs b/agent-control/tests/on_host/scenarios/ac_self_update.rs index 32877fa330..f01f486be6 100644 --- a/agent-control/tests/on_host/scenarios/ac_self_update.rs +++ b/agent-control/tests/on_host/scenarios/ac_self_update.rs @@ -9,7 +9,7 @@ use crate::common::runtime::tokio_runtime; use crate::on_host::tools::config::OnHostAgentControlConfigBuilder; use crate::on_host::tools::config::create_local_config; use crate::on_host::tools::config::load_remote_config_content; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use crate::on_host::tools::fake_binary::assert_is_fake_binary; use crate::on_host::tools::fake_binary::build_fake_ac_binary; use crate::on_host::tools::fake_binary::build_invalid_fake_ac_binary; @@ -118,7 +118,7 @@ fn test_ac_self_update_defers_subagent_reconciliation_to_restart_with_oci_regist // A sub-agent type that renders a directory entry, making its reconciliation observable. let dir_entry = "reconciled"; - let agent_type = CustomAgentType::default() + let agent_type = OnHostCustomAgentType::default() .with_filesystem(Some(&format!("{dir_entry}:\n kind: dir\n"))) .build(dirs.local_dir()); diff --git a/agent-control/tests/on_host/scenarios/attributes.rs b/agent-control/tests/on_host/scenarios/attributes.rs index ddf2ceb4c4..9b5cce7dd1 100644 --- a/agent-control/tests/on_host/scenarios/attributes.rs +++ b/agent-control/tests/on_host/scenarios/attributes.rs @@ -7,7 +7,7 @@ use crate::common::base_paths::TempBasePaths; use crate::common::retry::retry; use crate::common::runtime::tokio_runtime; use crate::on_host::tools::config::{OnHostAgentControlConfigBuilder, create_local_config}; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use crate::on_host::tools::instance_id::get_instance_id; use crate::on_host::tools::oci_package_manager::push_test_package; use fake_opamp_server::FakeServer; @@ -177,7 +177,7 @@ fn test_attributes_from_an_existing_agent_type_with_oci_registry() { ) }; - let sleep_agent_type = CustomAgentType::default() + let sleep_agent_type = OnHostCustomAgentType::default() .with_variables( r#" package_version: diff --git a/agent-control/tests/on_host/scenarios/empty_config.rs b/agent-control/tests/on_host/scenarios/empty_config.rs index 5f01d512f4..5a0fcf5dde 100644 --- a/agent-control/tests/on_host/scenarios/empty_config.rs +++ b/agent-control/tests/on_host/scenarios/empty_config.rs @@ -8,7 +8,7 @@ use crate::{ }, on_host::tools::{ config::{OnHostAgentControlConfigBuilder, create_local_config}, - custom_agent_type::CustomAgentType, + custom_agent_type::OnHostCustomAgentType, instance_id::get_instance_id, }, }; @@ -28,7 +28,7 @@ fn onhost_opamp_sub_agent_set_empty_config_defaults_to_local() { let dirs = TempBasePaths::default(); - let sleep_agent_type = CustomAgentType::default().build(dirs.local_dir()); + let sleep_agent_type = OnHostCustomAgentType::default().build(dirs.local_dir()); let agents = format!( r#" @@ -97,7 +97,7 @@ fn onhost_opamp_sub_agent_with_no_local_config() { let dirs = TempBasePaths::default(); - let sleep_agent_type = CustomAgentType::default().build(dirs.local_dir()); + let sleep_agent_type = OnHostCustomAgentType::default().build(dirs.local_dir()); let agents = format!( r#" diff --git a/agent-control/tests/on_host/scenarios/health_check.rs b/agent-control/tests/on_host/scenarios/health_check.rs index 34e7271a93..47d0456ab8 100644 --- a/agent-control/tests/on_host/scenarios/health_check.rs +++ b/agent-control/tests/on_host/scenarios/health_check.rs @@ -6,7 +6,7 @@ use crate::on_host::consts::NO_CONFIG; use crate::on_host::tools::config::{ OnHostAgentControlConfigBuilder, create_file, create_local_config, }; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use crate::on_host::tools::instance_id::get_instance_id; use fake_opamp_server::FakeServer; use httpmock::Method::GET; @@ -37,7 +37,7 @@ checks: health_file_path.to_str().unwrap() ); - let agent_type = CustomAgentType::empty() + let agent_type = OnHostCustomAgentType::empty() .with_health(Some(&health_config)) .build(dirs.local_dir()); @@ -142,7 +142,7 @@ checks: health_server.port(), ); - let agent_type = CustomAgentType::empty() + let agent_type = OnHostCustomAgentType::empty() .with_health(Some(&health_config)) .build(dirs.local_dir()); diff --git a/agent-control/tests/on_host/scenarios/http_status_server.rs b/agent-control/tests/on_host/scenarios/http_status_server.rs index 7c358b629f..7b096ad5e5 100644 --- a/agent-control/tests/on_host/scenarios/http_status_server.rs +++ b/agent-control/tests/on_host/scenarios/http_status_server.rs @@ -5,7 +5,7 @@ use crate::common::retry::retry; use crate::common::runtime::tokio_runtime; use crate::on_host::consts::NO_CONFIG; use crate::on_host::tools::config::{OnHostAgentControlConfigBuilder, create_local_config}; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use fake_opamp_server::FakeServer; use newrelic_agent_control::agent_control::defaults::{ AGENT_CONTROL_ID, AGENT_CONTROL_NAMESPACE, AGENT_CONTROL_TYPE, AGENT_CONTROL_VERSION, @@ -28,7 +28,7 @@ fn test_http_status_endpoint_response() { let opamp_server = FakeServer::start(tokio_runtime().handle()); let dirs = TempBasePaths::default(); - let sleep_agent_type = CustomAgentType::default().build(dirs.local_dir()); + let sleep_agent_type = OnHostCustomAgentType::default().build(dirs.local_dir()); let agents = format!( r#" diff --git a/agent-control/tests/on_host/scenarios/install_remote_package.rs b/agent-control/tests/on_host/scenarios/install_remote_package.rs index 50accad829..c77f28ecff 100644 --- a/agent-control/tests/on_host/scenarios/install_remote_package.rs +++ b/agent-control/tests/on_host/scenarios/install_remote_package.rs @@ -8,7 +8,7 @@ use crate::common::remote_config_status::check_latest_remote_config_status; use crate::common::retry::retry; use crate::common::runtime::tokio_runtime; use crate::on_host::tools::config::{OnHostAgentControlConfigBuilder, create_local_config}; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use crate::on_host::tools::instance_id::get_instance_id; use crate::on_host::tools::oci_package_manager::TestDataHelper; use fake_opamp_server::FakeServer; @@ -308,7 +308,7 @@ fn create_agent_type( ]"# ); - CustomAgentType::default() + OnHostCustomAgentType::default() .with_executables(Some(&executables)) .with_packages(Some(&packages_config)) .build(local_dir) diff --git a/agent-control/tests/on_host/scenarios/invalid_remote_config.rs b/agent-control/tests/on_host/scenarios/invalid_remote_config.rs index 06a5745043..bf182b908f 100644 --- a/agent-control/tests/on_host/scenarios/invalid_remote_config.rs +++ b/agent-control/tests/on_host/scenarios/invalid_remote_config.rs @@ -5,7 +5,7 @@ use crate::common::remote_config_status::check_latest_remote_config_status_is_ex use crate::common::{retry::retry, runtime::tokio_runtime}; use crate::on_host::tools::config::load_remote_config_content; use crate::on_host::tools::config::{OnHostAgentControlConfigBuilder, create_local_config}; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use crate::on_host::tools::instance_id::get_instance_id; use fake_opamp_server::FakeServer; use newrelic_agent_control::agent_control::agent_id::AgentID; @@ -30,7 +30,7 @@ fn onhost_opamp_sub_agent_invalid_remote_config() { let sub_agent_id = AgentID::try_from("nr-sleep-agent").unwrap(); - let sleep_agent_type = CustomAgentType::default().build(dirs.local_dir()); + let sleep_agent_type = OnHostCustomAgentType::default().build(dirs.local_dir()); let agents = format!( r#" {sub_agent_id}: @@ -99,7 +99,7 @@ fn test_invalid_config_executable_less_supervisor() { let dirs = TempBasePaths::default(); let sub_agent_id = AgentID::try_from("test-agent").unwrap(); - let agent_type = CustomAgentType::default() + let agent_type = OnHostCustomAgentType::default() .without_deployment() .build(dirs.local_dir()); @@ -175,7 +175,7 @@ fn onhost_opamp_sub_agent_invalid_remote_config_rollback_previous_remote() { let sub_agent_id = AgentID::try_from("nr-sleep-agent").unwrap(); - let sleep_agent_type = CustomAgentType::default().build(dirs.local_dir()); + let sleep_agent_type = OnHostCustomAgentType::default().build(dirs.local_dir()); let agents = format!( r#" {sub_agent_id}: diff --git a/agent-control/tests/on_host/scenarios/memory_benchmark.rs b/agent-control/tests/on_host/scenarios/memory_benchmark.rs index e561073c3e..4f77d5de94 100644 --- a/agent-control/tests/on_host/scenarios/memory_benchmark.rs +++ b/agent-control/tests/on_host/scenarios/memory_benchmark.rs @@ -4,7 +4,7 @@ use crate::common::health::check_latest_health_status; use crate::common::retry::retry; use crate::common::runtime::tokio_runtime; use crate::on_host::tools::config::OnHostAgentControlConfigBuilder; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use crate::on_host::tools::instance_id::get_instance_id; use fake_opamp_server::FakeServer; use memory_stats::memory_stats; @@ -35,7 +35,7 @@ fn test_memory_on_agent_substitution_and_version_update() { ); // Add custom agent_type to registry - let sleep_agent_type = CustomAgentType::default() + let sleep_agent_type = OnHostCustomAgentType::default() .with_executables(Some( r#"[ {"id": "trap-term-sleep", "path": "sh", "args": ["tests/on_host/data/sleep_60.sh"]}, diff --git a/agent-control/tests/on_host/scenarios/multiple_executables.rs b/agent-control/tests/on_host/scenarios/multiple_executables.rs index 1cd7fbaec7..8f9eed334a 100644 --- a/agent-control/tests/on_host/scenarios/multiple_executables.rs +++ b/agent-control/tests/on_host/scenarios/multiple_executables.rs @@ -6,7 +6,7 @@ use crate::{ health::check_latest_health_status, retry::retry, runtime::tokio_runtime, }, on_host::tools::{ - config::OnHostAgentControlConfigBuilder, custom_agent_type::CustomAgentType, + config::OnHostAgentControlConfigBuilder, custom_agent_type::OnHostCustomAgentType, instance_id::get_instance_id, }, }; @@ -22,7 +22,7 @@ fn onhost_subagent_multiple_executables_some_failed_launching() { let dirs = TempBasePaths::default(); // Add custom agent_type to registry - let sleep_agent_type = CustomAgentType::default() + let sleep_agent_type = OnHostCustomAgentType::default() .with_executables(Some( r#"[ {"id": "trap-term-sleep", "path": "sh", "args": ["tests/on_host/data/sleep_60.sh"]}, @@ -70,7 +70,7 @@ fn onhost_subagent_multiple_executables_some_commands_failed_after_max_retries() let dirs = TempBasePaths::default(); // Add custom agent_type to registry - let sleep_agent_type = CustomAgentType::default() + let sleep_agent_type = OnHostCustomAgentType::default() .with_executables(Some( r#"[ {"id": "trap-term-sleep", "path": "sh", "args": ["tests/on_host/data/sleep_60.sh"]}, diff --git a/agent-control/tests/on_host/scenarios/multiple_remote_config.rs b/agent-control/tests/on_host/scenarios/multiple_remote_config.rs index 3cfc66ef50..a1c6afd69b 100644 --- a/agent-control/tests/on_host/scenarios/multiple_remote_config.rs +++ b/agent-control/tests/on_host/scenarios/multiple_remote_config.rs @@ -4,7 +4,7 @@ use crate::common::effective_config::check_latest_effective_config_is_expected; use crate::common::remote_config_status::check_latest_remote_config_status_is_expected; use crate::common::{retry::retry, runtime::tokio_runtime}; use crate::on_host::tools::config::OnHostAgentControlConfigBuilder; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use crate::on_host::tools::instance_id::get_instance_id; use fake_opamp_server::FakeServer; use newrelic_agent_control::agent_control::agent_id::AgentID; @@ -20,7 +20,7 @@ fn onhost_ac_multiconfig_agents_append() { let dirs = TempBasePaths::default(); - let sleep_agent_type = CustomAgentType::default().build(dirs.local_dir()); + let sleep_agent_type = OnHostCustomAgentType::default().build(dirs.local_dir()); OnHostAgentControlConfigBuilder::new(opamp_server.endpoint(), opamp_server.jwks_endpoint()) .write(dirs.local_dir()); @@ -86,7 +86,7 @@ fn onhost_ac_multiconfig_agents_append_fails() { let dirs = TempBasePaths::default(); - let sleep_agent_type = CustomAgentType::default().build(dirs.local_dir()); + let sleep_agent_type = OnHostCustomAgentType::default().build(dirs.local_dir()); OnHostAgentControlConfigBuilder::new(opamp_server.endpoint(), opamp_server.jwks_endpoint()) .write(dirs.local_dir()); @@ -130,7 +130,7 @@ fn onhost_sub_agent_multiconfig() { let dirs = TempBasePaths::default(); - let sleep_agent_type = CustomAgentType::default() + let sleep_agent_type = OnHostCustomAgentType::default() .with_variables( r#" var_a: diff --git a/agent-control/tests/on_host/scenarios/no_health_config.rs b/agent-control/tests/on_host/scenarios/no_health_config.rs index 6fc84dab6c..33f578ccda 100644 --- a/agent-control/tests/on_host/scenarios/no_health_config.rs +++ b/agent-control/tests/on_host/scenarios/no_health_config.rs @@ -6,7 +6,7 @@ use crate::common::runtime::tokio_runtime; use crate::on_host::consts::NO_CONFIG; use crate::on_host::tools::config::OnHostAgentControlConfigBuilder; use crate::on_host::tools::config::create_local_config; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use crate::on_host::tools::instance_id::get_instance_id; use fake_opamp_server::FakeServer; use newrelic_agent_control::agent_control::agent_id::AgentID; @@ -25,7 +25,7 @@ fn test_on_host_no_health_in_agent_type_reports_no_health_via_opamp() { let dirs = TempBasePaths::default(); let sub_agent_id = AgentID::try_from("test-agent-no-health").unwrap(); - let agent_type = CustomAgentType::default() + let agent_type = OnHostCustomAgentType::default() .with_health(None) .build(dirs.local_dir()); diff --git a/agent-control/tests/on_host/scenarios/no_orphan_processes.rs b/agent-control/tests/on_host/scenarios/no_orphan_processes.rs index b8c4b429c0..738781a832 100644 --- a/agent-control/tests/on_host/scenarios/no_orphan_processes.rs +++ b/agent-control/tests/on_host/scenarios/no_orphan_processes.rs @@ -6,7 +6,7 @@ use crate::common::base_paths::TempBasePaths; use crate::common::process_finder::find_processes_by_pattern; use crate::common::runtime::tokio_runtime; use crate::on_host::tools::config::{OnHostAgentControlConfigBuilder, create_local_config}; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use fake_opamp_server::FakeServer; use newrelic_agent_control::agent_control::run::on_host::AGENT_CONTROL_MODE_ON_HOST; use std::thread; @@ -22,7 +22,7 @@ fn test_no_orphan_processes_after_agent_control_stops() { // Create a custom agent type with a long-running sleep process #[cfg(target_family = "unix")] - let agent_type = CustomAgentType::empty() + let agent_type = OnHostCustomAgentType::empty() .with_executables(Some( r#"[ {"id": "long-sleep", "path": "sleep", "args": ["3600"]} @@ -31,7 +31,7 @@ fn test_no_orphan_processes_after_agent_control_stops() { .build(dirs.local_dir()); #[cfg(target_family = "windows")] - let agent_type = CustomAgentType::empty() + let agent_type = OnHostCustomAgentType::empty() .with_executables(Some( r#"[ {"id": "long-sleep", "path": "powershell", "args": ["-Command","Start-Sleep","-Seconds","3600"]} diff --git a/agent-control/tests/on_host/scenarios/oci_auth.rs b/agent-control/tests/on_host/scenarios/oci_auth.rs index 8903ef6219..df16d994c3 100644 --- a/agent-control/tests/on_host/scenarios/oci_auth.rs +++ b/agent-control/tests/on_host/scenarios/oci_auth.rs @@ -3,7 +3,7 @@ use crate::common::base_paths::TempBasePaths; use crate::common::retry::retry; use crate::common::runtime::tokio_runtime; use crate::on_host::tools::config::{OnHostAgentControlConfigBuilder, create_local_config}; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use crate::on_host::tools::instance_id::get_instance_id; use crate::on_host::tools::oci_package_manager::TestDataHelper; use fake_opamp_server::FakeServer; @@ -36,7 +36,7 @@ fn test_agent_remote_package_with_auth_oci_registry() { let dirs = TempBasePaths::default(); - let agent_type = CustomAgentType::default() + let agent_type = OnHostCustomAgentType::default() .with_packages(Some( format!( r#" diff --git a/agent-control/tests/on_host/scenarios/opamp.rs b/agent-control/tests/on_host/scenarios/opamp.rs index 894c7d6d5f..6c20195ab7 100644 --- a/agent-control/tests/on_host/scenarios/opamp.rs +++ b/agent-control/tests/on_host/scenarios/opamp.rs @@ -9,7 +9,7 @@ use crate::on_host::tools::config::{ OnHostAgentControlConfigBuilder, create_file, create_local_config, }; use crate::on_host::tools::config::{create_remote_config, load_remote_config_content}; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use crate::on_host::tools::instance_id::get_instance_id; use fake_opamp_server::FakeServer; use newrelic_agent_control::agent_control::agent_id::AgentID; @@ -74,7 +74,7 @@ fn onhost_opamp_agent_control_remote_effective_config() { .write(dirs.local_dir()); // Add custom agent_type to registry - let sleep_agent_type = CustomAgentType::default().build(dirs.local_dir()); + let sleep_agent_type = OnHostCustomAgentType::default().build(dirs.local_dir()); let _agent_control = start_agent_control_with_custom_config(dirs.base_paths(), AGENT_CONTROL_MODE_ON_HOST); @@ -200,7 +200,7 @@ fn onhost_opamp_sub_agent_local_effective_config_with_env_var() { let dirs = TempBasePaths::default(); - let sleep_agent_type = CustomAgentType::default().build(dirs.local_dir()); + let sleep_agent_type = OnHostCustomAgentType::default().build(dirs.local_dir()); let agents = format!( r#" @@ -264,7 +264,7 @@ fn onhost_opamp_sub_agent_remote_effective_config() { let dirs = TempBasePaths::default(); - let sleep_agent_type = CustomAgentType::default().build(dirs.local_dir()); + let sleep_agent_type = OnHostCustomAgentType::default().build(dirs.local_dir()); let agents = format!( r#" @@ -325,7 +325,7 @@ fn onhost_opamp_sub_agent_empty_local_effective_config() { let dirs = TempBasePaths::default(); - let sleep_agent_type = CustomAgentType::default().build(dirs.local_dir()); + let sleep_agent_type = OnHostCustomAgentType::default().build(dirs.local_dir()); let agents = format!( r#" @@ -398,7 +398,7 @@ checks: "#, health_file_path.to_string_lossy() ); - let agent_type_wo_exec = CustomAgentType::default() + let agent_type_wo_exec = OnHostCustomAgentType::default() .with_executables(None) .with_health(Some(&health_agent_type_config)) .build(dirs.local_dir()); diff --git a/agent-control/tests/on_host/scenarios/postdownload_hook.rs b/agent-control/tests/on_host/scenarios/postdownload_hook.rs index 247b639d8f..043cc821b7 100644 --- a/agent-control/tests/on_host/scenarios/postdownload_hook.rs +++ b/agent-control/tests/on_host/scenarios/postdownload_hook.rs @@ -4,7 +4,7 @@ use crate::common::health::{check_latest_health_status, check_latest_health_stat use crate::common::retry::retry; use crate::common::runtime::tokio_runtime; use crate::on_host::tools::config::{OnHostAgentControlConfigBuilder, create_local_config}; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use crate::on_host::tools::instance_id::get_instance_id; use fake_opamp_server::FakeServer; use newrelic_agent_control::agent_control::agent_id::AgentID; @@ -28,7 +28,7 @@ package_version: // Builds the custom agent type used by these tests, declaring the `package_version` variable. fn build_agent_type(dirs: &TempBasePaths, packages: &str, executables: &str) -> String { - CustomAgentType::default() + OnHostCustomAgentType::default() .with_variables(PACKAGE_VERSION_VARIABLE) .with_executables(Some(executables)) .with_packages(Some(packages)) diff --git a/agent-control/tests/on_host/scenarios/remote_agent_removal.rs b/agent-control/tests/on_host/scenarios/remote_agent_removal.rs index ece29948e2..c56f78274e 100644 --- a/agent-control/tests/on_host/scenarios/remote_agent_removal.rs +++ b/agent-control/tests/on_host/scenarios/remote_agent_removal.rs @@ -4,7 +4,7 @@ use crate::common::effective_config::check_latest_effective_config_is_expected; use crate::common::remote_config_status::check_latest_remote_config_status_is_expected; use crate::common::{retry::retry, runtime::tokio_runtime}; use crate::on_host::tools::config::OnHostAgentControlConfigBuilder; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use crate::on_host::tools::instance_id::get_instance_id; use fake_opamp_server::FakeServer; use newrelic_agent_control::agent_control::agent_id::AgentID; @@ -36,7 +36,7 @@ fn onhost_opamp_agent_control_remote_config_add_remove_add_agent() { let second_templated_content = "second"; // Add custom agent_type to registry with filesystem operations - let sleep_agent_type = CustomAgentType::default() + let sleep_agent_type = OnHostCustomAgentType::default() .with_filesystem(Some(&format!( r#" {dir_entry}: diff --git a/agent-control/tests/on_host/scenarios/restarting_processes.rs b/agent-control/tests/on_host/scenarios/restarting_processes.rs index 4be854c4b9..c223956b71 100644 --- a/agent-control/tests/on_host/scenarios/restarting_processes.rs +++ b/agent-control/tests/on_host/scenarios/restarting_processes.rs @@ -4,7 +4,7 @@ use crate::common::process_finder::find_processes_by_pattern; use crate::common::retry::retry; use crate::common::runtime::tokio_runtime; use crate::on_host::tools::config::{OnHostAgentControlConfigBuilder, create_local_config}; -use crate::on_host::tools::custom_agent_type::CustomAgentType; +use crate::on_host::tools::custom_agent_type::OnHostCustomAgentType; use fake_opamp_server::FakeServer; use newrelic_agent_control::agent_control::run::on_host::AGENT_CONTROL_MODE_ON_HOST; use std::thread; @@ -24,7 +24,7 @@ fn killing_subprocess_with_signal_restarts() -> Result<(), Box CustomAgentType { - CustomAgentType::default() +fn ohi_config_agent_type(type_name: &str, file: &str) -> OnHostCustomAgentType { + OnHostCustomAgentType::default() .with_agent_type_id(&format!("test/{type_name}:0.1.0")) .with_health(None) .with_shared_filesystem(Some(&format!( @@ -318,7 +318,7 @@ fn conflicting_shared_paths_are_rejected() { let redis_agent = "redis-agent"; let redis_agent_2 = "redis-agent-2"; - let ohi_type = CustomAgentType::default() + let ohi_type = OnHostCustomAgentType::default() .with_agent_type_id("test/redis:0.1.0") .with_health(None) .with_shared_filesystem(Some(&format!( @@ -405,9 +405,9 @@ agents: /// A minimal OHI-style agent type that renders a "binary" into its own per-agent filesystem /// (`bin/`) and then copies it into the shared binaries dir with `copy_from_file`. -fn ohi_binary_agent_type(type_name: &str, binary: &str) -> CustomAgentType { +fn ohi_binary_agent_type(type_name: &str, binary: &str) -> OnHostCustomAgentType { let payload = binary_payload(binary); - CustomAgentType::default() + OnHostCustomAgentType::default() .with_agent_type_id(&format!("test/{type_name}:0.1.0")) .with_health(None) .with_filesystem(Some(&format!( diff --git a/agent-control/tests/on_host/tools/custom_agent_type.rs b/agent-control/tests/on_host/tools/custom_agent_type.rs index 556ea8976e..689b5b4956 100644 --- a/agent-control/tests/on_host/tools/custom_agent_type.rs +++ b/agent-control/tests/on_host/tools/custom_agent_type.rs @@ -1,18 +1,14 @@ -use fs::file::LocalFile; -use fs::file::writer::FileWriter; -use newrelic_agent_control::agent_control::defaults::DYNAMIC_AGENT_TYPES_DIR; +use crate::common::custom_agent_type::CommonCustomAgentType; use newrelic_agent_control::agent_control::run::on_host::AGENT_CONTROL_MODE_ON_HOST; use newrelic_agent_control::agent_type::agent_type_id::AgentTypeID; -use newrelic_agent_control::agent_type::definition::AgentTypeDefinition; use std::fmt::Display; use std::path::PathBuf; pub const DYNAMIC_AGENT_TYPE_FILENAME: &str = "dynamic-agent-types/type.yaml"; /// Helper to build a Custom Agent type with defaults ready to use in integration tests -pub struct CustomAgentType { - agent_type_id: AgentTypeID, - variables: Option, +pub struct OnHostCustomAgentType { + common: CommonCustomAgentType, executables: Option, filesystem: Option, shared_filesystem: Option, @@ -20,21 +16,17 @@ pub struct CustomAgentType { health: Option, } -impl Default for CustomAgentType { +impl Default for OnHostCustomAgentType { fn default() -> Self { Self { - agent_type_id: Self::default_agent_type_id(), - variables: Some( - serde_saphyr::from_str( - r#" + common: CommonCustomAgentType::new(Self::default_agent_type_id()).with_variables( + r#" fake_variable: description: "fake variable to verify remote config" type: "string" required: false default: "default" "#, - ) - .unwrap(), ), executables: Some(Self::default_executables()), filesystem: None, @@ -56,7 +48,7 @@ checks: } } -impl Display for CustomAgentType { +impl Display for OnHostCustomAgentType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let content = format!( r#" @@ -67,15 +59,16 @@ impl Display for CustomAgentType { operating_system: {} protocol_version: "1.0" "#, - self.agent_type_id.namespace(), - self.agent_type_id.name(), - self.agent_type_id.version(), + self.common.agent_type_id.namespace(), + self.common.agent_type_id.name(), + self.common.agent_type_id.version(), AGENT_CONTROL_MODE_ON_HOST, ); let mut content: serde_json::Map = serde_saphyr::from_str(&content).unwrap(); let variables = self + .common .variables .clone() .unwrap_or_else(|| serde_json::Map::::new().into()); @@ -105,7 +98,7 @@ impl Display for CustomAgentType { } } -impl CustomAgentType { +impl OnHostCustomAgentType { fn default_agent_type_id() -> AgentTypeID { AgentTypeID::try_from("newrelic/com.newrelic.custom_agent:0.1.0").unwrap() } @@ -142,8 +135,7 @@ impl CustomAgentType { pub fn empty() -> Self { Self { - agent_type_id: Self::default_agent_type_id(), - variables: None, + common: CommonCustomAgentType::new(Self::default_agent_type_id()), executables: None, health: None, filesystem: None, @@ -189,14 +181,14 @@ impl CustomAgentType { pub fn with_variables(self, variables: &str) -> Self { Self { - variables: Some(serde_saphyr::from_str(variables).unwrap()), + common: self.common.with_variables(variables), ..self } } pub fn with_agent_type_id(self, agent_type_id: &str) -> Self { Self { - agent_type_id: AgentTypeID::try_from(agent_type_id).unwrap(), + common: self.common.with_agent_type_id(agent_type_id), ..self } } @@ -211,29 +203,9 @@ impl CustomAgentType { } } - /// Writes the custom agent type and returns its id as string. The file name is derived from the - /// full agent type id (namespace, name and version) so several distinct types can coexist in - /// the dynamic agent types dir (the loader reads every file there); two ids sharing only a name - /// would otherwise clobber each other. + /// Writes the custom agent type and returns its id as string. pub fn build(self, local_dir: PathBuf) -> String { - // The id (`namespace/name:version`) has `/` and `:`, which are not portable in file names. - let file_stem = self.agent_type_id.to_string().replace(['/', ':'], "_"); - let agent_type_file_path = local_dir - .join(DYNAMIC_AGENT_TYPES_DIR) - .join(format!("{file_stem}.yaml")); - - let parsed_agent_type = AgentTypeDefinition::from_slice(self.to_string().as_bytes()); - assert!( - parsed_agent_type.is_ok(), - "CustomAgentType did not produce valid AgentTypeDefinition: {}\n{}", - parsed_agent_type.err().unwrap(), - self - ); - - std::fs::create_dir_all(agent_type_file_path.parent().unwrap()).unwrap(); - LocalFile - .write(&agent_type_file_path, self.to_string()) - .expect("failed to write custom agent type"); - self.agent_type_id.to_string() + let content = self.to_string(); + self.common.build(&local_dir, &content) } }