diff --git a/lib/config/default.js b/lib/config/default.js index 08896ac5cf..228a8e41ee 100644 --- a/lib/config/default.js +++ b/lib/config/default.js @@ -147,6 +147,47 @@ defaultConfig.definition = () => ({ formatter: boolean, default: true }, + + /** + * Collects configuration related to New Relic Agent Control, i.e. centralized + * agent management in container based environments. + */ + agent_control: { + /** + * If set, must be a GUID string. Indicates that the agent is being managed + * by Agent Control. Must be set to enable health monitoring. + */ + fleet_id: { + env: 'NEW_RELIC_AGENT_CONTROL_FLEET_ID', + default: null + }, + + /** + * Settings specific to the health monitoring aspect of Agent Control. + */ + health: { + /** + * A string file path to a directory that the agent is expected to write + * health status files to. Must be set for health monitoring to be + * enabled. + */ + delivery_location: { + env: 'NEW_RELIC_AGENT_CONTROL_HEALTH_DELIVERY_LOCATION', + default: null + }, + + /** + * The time, in seconds, that the agent should wait between writing + * updates to its health status. The default interval is 5 seconds. + */ + frequency: { + env: 'NEW_RELIC_AGENT_CONTROL_HEALTH_FREQUENCY', + formatter: int, + default: 5 + } + } + }, + /** * The default Apdex tolerating / threshold value for applications, in * seconds. The default for Node is apdexT to 100 milliseconds, which is diff --git a/lib/health-reporter.js b/lib/health-reporter.js index 918903ff1e..ea6fd557b3 100644 --- a/lib/health-reporter.js +++ b/lib/health-reporter.js @@ -70,10 +70,14 @@ class HealthReporter { // STATUS_INTERNAL errors are the Node.js Agent specific error codes. static STATUS_INTERNAL_UNEXPECTED_ERROR = 'NR-APM-300' - constructor({ logger = defaultLogger, setInterval = global.setInterval } = {}) { - const fleetId = process.env.NEW_RELIC_SUPERAGENT_FLEET_ID - const outDir = process.env.NEW_RELIC_SUPERAGENT_HEALTH_DELIVERY_LOCATION - let checkInterval = process.env.NEW_RELIC_SUPERAGENT_HEALTH_FREQUENCY + constructor({ + agentConfig = { agent_control: { health: {} } }, + logger = defaultLogger, + setInterval = global.setInterval + } = {}) { + const fleetId = agentConfig.agent_control?.fleet_id + const outDir = agentConfig.agent_control?.health?.delivery_location + let checkInterval = agentConfig.agent_control?.health?.frequency this.#logger = logger @@ -168,7 +172,8 @@ class HealthReporter { */ stop(done) { if (this.#enabled === false) { - return done() + done && done() + return } clearInterval(this.#interval) diff --git a/package.json b/package.json index 3082cd5af4..82d14aaa5f 100644 --- a/package.json +++ b/package.json @@ -195,7 +195,8 @@ "newrelic-naming-rules": "./bin/test-naming-rules.js" }, "imports": { - "#agentlib/*.js": "./lib/*.js" + "#agentlib/*.js": "./lib/*.js", + "#test/assert": "./test/lib/custom-assertions/index.js" }, "dependencies": { "@grpc/grpc-js": "^1.12.2", diff --git a/test/unit/config/config.test.js b/test/unit/config/config.test.js index a34c59facf..fa5bfa15dc 100644 --- a/test/unit/config/config.test.js +++ b/test/unit/config/config.test.js @@ -85,6 +85,55 @@ test('when loading options via constructor', async (t) => { }) }) +test('agent control', async t => { + await t.test('loads defaults', () => { + const config = Config.initialize({}) + assert.deepStrictEqual(config.agent_control, { + fleet_id: null, + health: { + delivery_location: null, + frequency: 5 + } + }) + }) + + await t.test('loads from env', () => { + process.env.NEW_RELIC_AGENT_CONTROL_FLEET_ID = 42 + process.env.NEW_RELIC_AGENT_CONTROL_HEALTH_DELIVERY_LOCATION = 'file://find/me' + process.env.NEW_RELIC_AGENT_CONTROL_HEALTH_FREQUENCY = 1 + const config = Config.initialize({}) + delete process.env.NEW_RELIC_AGENT_CONTROL_FLEET_ID + delete process.env.NEW_RELIC_AGENT_CONTROL_HEALTH_DELIVERY_LOCATION + delete process.env.NEW_RELIC_AGENT_CONTROL_HEALTH_FREQUENCY + assert.deepStrictEqual(config.agent_control, { + fleet_id: '42', + health: { + delivery_location: 'file://find/me', + frequency: 1 + } + }) + }) + + await t.test('loads from provided config', () => { + const config = Config.initialize({ + agent_control: { + fleet_id: 'from-config', + health: { + delivery_location: 'file://find/me', + frequency: 10 + } + } + }) + assert.deepStrictEqual(config.agent_control, { + fleet_id: 'from-config', + health: { + delivery_location: 'file://find/me', + frequency: 10 + } + }) + }) +}) + test('#publicSettings', async (t) => { let configuration diff --git a/test/unit/lib/health-reporter.test.js b/test/unit/lib/health-reporter.test.js index 36c0364779..c2160b0dd9 100644 --- a/test/unit/lib/health-reporter.test.js +++ b/test/unit/lib/health-reporter.test.js @@ -11,7 +11,8 @@ const os = require('node:os') const fs = require('node:fs') const tspl = require('@matteo.collina/tspl') -const match = require('../../lib/custom-assertions/match') +const { match } = require('#test/assert') +const Config = require('#agentlib/config/index.js') const HealthReporter = require('#agentlib/health-reporter.js') function simpleInterval(method) { @@ -54,21 +55,24 @@ test.beforeEach((ctx) => { } } - process.env.NEW_RELIC_SUPERAGENT_FLEET_ID = 42 - process.env.NEW_RELIC_SUPERAGENT_HEALTH_DELIVERY_LOCATION = os.tmpdir() - process.env.NEW_RELIC_SUPERAGENT_HEALTH_FREQUENCY = 1 + ctx.nr.agentConfig = Config.initialize({ + agent_control: { + fleet_id: 42, + health: { + delivery_location: os.tmpdir(), + frequency: 1 + } + } + }) }) test.afterEach((ctx) => { fs.writeFile = ctx.nr.writeFileOrig process.hrtime.bigint = ctx.nr.bigintOrig - delete process.env.NEW_RELIC_SUPERAGENT_FLEET_ID - delete process.env.NEW_RELIC_SUPERAGENT_HEALTH_DELIVERY_LOCATION - delete process.env.NEW_RELIC_SUPERAGENT_HEALTH_FREQUENCY }) test('requires fleet id to be set', (t) => { - delete process.env.NEW_RELIC_SUPERAGENT_FLEET_ID + delete t.nr.agentConfig.agent_control.fleet_id const reporter = new HealthReporter(t.nr) assert.ok(reporter) @@ -80,7 +84,7 @@ test('requires fleet id to be set', (t) => { }) test('requires output directory to be set', (t) => { - delete process.env.NEW_RELIC_SUPERAGENT_HEALTH_DELIVERY_LOCATION + delete t.nr.agentConfig.agent_control.health.delivery_location const reporter = new HealthReporter(t.nr) assert.ok(reporter) @@ -95,7 +99,7 @@ test('requires output directory to be set', (t) => { }) test('sets default interval', (t) => { - delete process.env.NEW_RELIC_SUPERAGENT_HEALTH_FREQUENCY + delete t.nr.agentConfig.agent_control.health.frequency const reporter = new HealthReporter(t.nr) assert.ok(reporter) @@ -170,7 +174,7 @@ test('logs error if writing failed', async (t) => { }) test('setStatus and stop do nothing if reporter disabled', (t, end) => { - delete process.env.NEW_RELIC_SUPERAGENT_FLEET_ID + delete t.nr.agentConfig.agent_control.fleet_id fs.writeFile = () => { assert.fail('should not be invoked') }