Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/component_onhost_e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ permissions:
# versions of the packages in our E2E tests. If you change the names or move these values outside
# the GitHub workflows please update `renovate.json` accordingly to avoid breaking the automation!
env:

HOST_E2E_INFRA_AGENT_VERSION: "1.76.1"
HOST_E2E_NRDOT_VERSION: "1.15.1"
HOST_E2E_PRELOAD_VERSION: "0.1.0"

jobs:
build-packages:
Expand All @@ -53,6 +55,7 @@ jobs:
- infra-agent
- nrdot-agent
- proxy
- preload-agent
- self-update-latest-to-current
- self-update-current-to-latest
steps:
Expand Down Expand Up @@ -81,7 +84,8 @@ jobs:
--agent-control-version "0.900.${{ github.run_id }}" \
--recipes-repo-branch "main" \
--infra-agent-version "${{ env.HOST_E2E_INFRA_AGENT_VERSION }}" \
--nrdot-version "${{ env.HOST_E2E_NRDOT_VERSION }}"
--nrdot-version "${{ env.HOST_E2E_NRDOT_VERSION }}" \
--preload-version "${{ env.HOST_E2E_PRELOAD_VERSION }}"

- name: Report test result to New Relic
if: always()
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ coverage/

# zig compiler
.zig-cache/

# vscode config dir
.vscode/
4 changes: 4 additions & 0 deletions test/e2e-runner/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ pub struct InstallationArgs {
/// Version of the NRDot OCI image to use in tests
#[arg(long)]
pub nrdot_version: Option<String>,

/// Version of the preload OCI image to use in tests
#[arg(long)]
pub preload_version: Option<String>,
}

/// Arguments for Fleet Control scenarios that also install Agent Control
Expand Down
5 changes: 5 additions & 0 deletions test/e2e-runner/src/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub fn local_config_path(agent_id: &str) -> String {

const DEFAULT_LOG_PATH: &str = "/var/log/newrelic-agent-control/agent-control/";

pub const AGENT_CONTROL_DATA_DIR: &str = "/var/lib/newrelic-agent-control";

const SERVICE_NAME: &str = "newrelic-agent-control";

/// Run Linux e2e corresponding scenario which will panic on failure
Expand All @@ -35,6 +37,9 @@ pub fn run_linux_e2e() {
LinuxScenarios::NrdotAgent(args) => {
scenarios::nrdot_agent::test_nrdot_agent(args);
}
LinuxScenarios::PreloadAgent(args) => {
scenarios::preload_agent::test_installation_with_preload_agent(args);
}
LinuxScenarios::RemoteConfig(args) => {
scenarios::remote_config::test_remote_config_is_applied(args);
}
Expand Down
1 change: 1 addition & 0 deletions test/e2e-runner/src/linux/scenarios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod ebpf_agent;
pub mod fleet_control;
pub mod infra_agent;
pub mod nrdot_agent;
pub mod preload_agent;
pub mod proxy;
pub mod remote_config;
pub mod self_update;
169 changes: 169 additions & 0 deletions test/e2e-runner/src/linux/scenarios/preload_agent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
use crate::common::config::{DEBUG_LOGGING_CONFIG, update_config, write_agent_local_config};
use crate::common::file::write;
use crate::common::on_drop::CleanUp;
use crate::common::test::{TestResult, retry_panic};
use crate::common::{InstallationArgs, RecipeData};
use crate::linux::{
self,
bash::exec_bash_command,
install::{install_agent_control_from_recipe, tear_down_test},
};
use glob::glob;
use std::path::Path;
use std::time::Duration;
use tracing::{debug, info};

/// Directory where Agent Control loads dynamic (custom) agent type definitions.
const DYNAMIC_AGENT_TYPES_DIR: &str = "/etc/newrelic-agent-control/dynamic-agent-types";

/// World-readable path the preload `.so` is staged to. /var/lib/newrelic-agent-control
/// isn't traversable by non-root, so referencing the OCI-managed copy directly from
/// /etc/ld.so.preload breaks every dynamically linked binary for non-root users.
const STAGED_PRELOAD_SO: &str = "/usr/local/lib/newrelic-preload-agent.so";

/// Expected package installation directory for the preload agent.
fn preload_package_dir() -> String {
format!(
"{}/packages/nr-preload/stored_packages/preload-agent",
linux::AGENT_CONTROL_DATA_DIR
)
}

pub fn test_installation_with_preload_agent(args: InstallationArgs) {
let preload_version = args
.preload_version
.clone()
.expect("--preload-version is required for this scenario");

let recipe_data = RecipeData {
args,
monitoring_source: "preload-agent".to_string(),
..Default::default()
};

let _clean_up = CleanUp::new(tear_down_test);

install_agent_control_from_recipe(&recipe_data);

// after the above is done inspect the directory structures to make sure everything is in there
// particularly the AC local configuration
let ac_local_config_path = Path::new(&linux::DEFAULT_AC_CONFIG_PATH);
assert!(
ac_local_config_path.exists(),
"AC local config should exist"
);
debug!(
"AC config file contents: {}",
std::fs::read_to_string(ac_local_config_path).expect("failed to read AC config file")
);

let test_id = format!(
"onhost-e2e-preload-agent_{}",
chrono::Local::now().format("%Y-%m-%d_%H-%M-%S%.3f")
);

let preload_agent_id = "nr-preload";

info!("Writing custom preload agent type definition");
let custom_agent_type_path = format!("{DYNAMIC_AGENT_TYPES_DIR}/preload.yaml");
let custom_agent_type = r#"namespace: newrelic
name: com.newrelic.preload
version: 0.1.0
variables:
linux:
oci:
repository:
description: "Package repository name"
type: string
required: false
default: newrelic/preload-agent-artifacts
variants:
ac_config_field: "oci_repository_urls"
values: ["newrelic/preload-agent-artifacts"]
version:
description: "Agent version"
type: string
required: true
deployment:
linux:
packages:
preload-agent:
download:
oci:
repository: ${nr-var:oci.repository}
version: ${nr-var:version}
public_key_url: https://publickeys.newrelic.com/g/agent-control-oci/global/nrpreloadagent/jwks.json
"#;
exec_bash_command(&format!("mkdir -p {DYNAMIC_AGENT_TYPES_DIR}"))
.unwrap_or_else(|err| panic!("Failed to create dynamic agent types directory: {err}"));
write(&custom_agent_type_path, custom_agent_type);

info!("Setup Agent Control config");
update_config(
linux::DEFAULT_AC_CONFIG_PATH,
format!(
r#"
host_id: {test_id}
agents:
{preload_agent_id}:
agent_type: "newrelic/com.newrelic.preload:0.1.0"
{DEBUG_LOGGING_CONFIG}
"#
),
);

write_agent_local_config(
&linux::local_config_path(preload_agent_id),
format! {r#"
version: {preload_version}"#},
);

linux::service::restart_service(linux::SERVICE_NAME);

info!("Waiting for preload OCI package to be downloaded and extracted");
let package_dir = preload_package_dir();
let retries = 60;
retry_panic(
retries,
Duration::from_secs(10),
"preload package download assertion",
|| assert_preload_package_downloaded(&package_dir),
);

info!("Searching for shared library inside extracted package");
let so_path = glob(&format!("{package_dir}/**/*.so"))
.expect("Failed to read glob pattern")
.find_map(|entry| entry.ok())
.map(|path| path.to_string_lossy().into_owned())
.unwrap_or_default();
if so_path.is_empty() {
panic!("No .so file found in extracted preload package at {package_dir}");
}
info!("Found shared library: {so_path}");

// Stage to a world-readable path so non-root processes can load it; see the
// STAGED_PRELOAD_SO doc comment for why we don't reference so_path directly.
info!("Staging shared library to {STAGED_PRELOAD_SO}");
exec_bash_command(&format!(
"install -m 0644 -o root -g root {so_path} {STAGED_PRELOAD_SO}"
))
.unwrap_or_else(|err| panic!("Failed to stage preload .so to {STAGED_PRELOAD_SO}: {err}"));

info!("Installing shared library into /etc/ld.so.preload");
let install_command = format!(r#"echo "{STAGED_PRELOAD_SO}" >> /etc/ld.so.preload"#);
let output = exec_bash_command(&install_command)
.unwrap_or_else(|err| panic!("Editing /etc/ld.so.preload failed: {err}"));
debug!("Install output:\n{output}");

info!("Test completed successfully");
}

fn assert_preload_package_downloaded(package_dir: &str) -> TestResult<()> {
let output = exec_bash_command(&format!("ls -d {package_dir}"))?;
if output.contains("No such file") || output.contains("cannot access") {
return Err(format!("Preload package directory not found yet at {package_dir}").into());
}
let listing = exec_bash_command(&format!("ls -la {package_dir}"))?;
debug!("Package listing:\n{listing}");
Ok(())
}
3 changes: 3 additions & 0 deletions test/e2e-runner/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ enum LinuxScenarios {
EBPFAgent(InstallationArgs),
/// Local installation of Agent Control with NRDot. It checks that nr-dot eventually reports data.
NrdotAgent(InstallationArgs),
/// Local installation of Agent Control with a custom preload agent type. Verifies that the
/// OCI artifact is downloaded and present in the environment.
PreloadAgent(InstallationArgs),
/// Checks that remote configuration for a sub-agent has been applied.
RemoteConfig(InstallationArgs),
/// Checks that the Agent Control proxy support works as expected. It uses mitproxy as a docker service.
Expand Down