Skip to content

Commit d5fe64c

Browse files
authored
feat(opamp-client-node): support dynamically setting the heartbeat interval; release v0.4.0 (#1152)
This is part 1 of #1128 Once released, EDOT Node.js will use these methods to update its OpAMP client for central config that changes the polling interval. Refs: #1128
1 parent f6e84e5 commit d5fe64c

File tree

5 files changed

+118
-9
lines changed

5 files changed

+118
-9
lines changed

packages/opamp-client-node/CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# @elastic/opamp-client-node Changelog
22

3+
## v0.4.0
4+
5+
- BREAKING CHANGE: The `heartbeatIntervalSeconds` option to `createOpAMPClient`
6+
used to *clamp* the given value to `[100ms, 1d]`. Starting in this version,
7+
a value less than 100ms will **be ignored**, and the default value will be
8+
used. The reason for this is to ignore a possible accidental error case where
9+
*zero* or a negative number is provided, resulting in a too-fast 100ms
10+
interval. (The 100ms lower bound really only exists for faster testing. It
11+
is not a reasonable value for production.)
12+
13+
- Add `opampClient.setHeartbeatIntervalSeconds(num)` and
14+
`.resetHeartbeatIntervalSeconds()` methods for dynamically changing the
15+
heartbeat interval used by the OpAMP client. Values less than 100ms are
16+
ignored (with a log.warn) and values greater than 1d are clamped to 1d.
17+
[#1128](https://github.com/elastic/elastic-otel-node/issues/1128)
18+
319
## v0.3.0
420

521
- Add TLS and mTLS support. [#1044](https://github.com/elastic/elastic-otel-node/issues/1044)

packages/opamp-client-node/lib/opamp-client.js

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ function normalizeHeartbeatIntervalSeconds(input) {
127127
} else if (typeof input !== 'number' || isNaN(input)) {
128128
throw new Error(`invalid "heartbeatIntervalSeconds" value: ${input}`);
129129
} else if (input < MIN_HEARTBEAT_INTERVAL_SECONDS) {
130-
return MIN_HEARTBEAT_INTERVAL_SECONDS;
130+
throw new Error(
131+
`"heartbeatIntervalSeconds" value is too short (<100ms): ${input}`
132+
);
131133
} else if (input > MAX_HEARTBEAT_INTERVAL_SECONDS) {
132134
return MAX_HEARTBEAT_INTERVAL_SECONDS;
133135
} else {
@@ -174,8 +176,8 @@ function normalizeHeartbeatIntervalSeconds(input) {
174176
* remote config. Receiving remote config requires setting the
175177
* `AcceptsRemoteConfig` capability in `capabilities`.
176178
* @property {Number} [heartbeatIntervalSeconds] The approximate time between
177-
* heartbeat messages sent by the client. Default 30.
178-
* Clamped to [100ms, 1d].
179+
* heartbeat messages sent by the client. Default 30. Values less than
180+
* 100ms will be ignored. Values greater than 1d will be clamped to 1d.
179181
* @property {number} [headersTimeout] The timeout (in milliseconds) to wait
180182
* for the response headers on a request to the OpAMP server. Default 10s.
181183
* @property {number} [bodyTimeout] The timeout (in milliseconds) to wait for
@@ -222,11 +224,12 @@ class OpAMPClient {
222224
} catch (err) {
223225
this._log.warn(
224226
{err, heartbeatIntervalSeconds: opts.heartbeatIntervalSeconds},
225-
'invalid heartbeatIntervalSeconds'
227+
'invalid heartbeatIntervalSeconds, using default value'
226228
);
227229
this._heartbeatIntervalMs =
228230
DEFAULT_HEARTBEAT_INTERVAL_SECONDS * 1000;
229231
}
232+
this._initialHeartbeatIntervalMs = this._heartbeatIntervalMs;
230233
this._onMessage = opts.onMessage;
231234

232235
if (opts.diagEnabled) {
@@ -303,6 +306,31 @@ class OpAMPClient {
303306
}
304307
}
305308

309+
/**
310+
* Dynamically set the heartbeat interval of the client to a new value.
311+
*
312+
* @param {Number} n - A number of seconds.
313+
*/
314+
setHeartbeatIntervalSeconds(n) {
315+
try {
316+
this._heartbeatIntervalMs =
317+
normalizeHeartbeatIntervalSeconds(n) * 1000;
318+
} catch (err) {
319+
this._log.warn(
320+
{err, heartbeatIntervalSeconds: n},
321+
'invalid heartbeatIntervalSeconds, ignoring'
322+
);
323+
}
324+
}
325+
326+
/**
327+
* Reset the heartbeat interval used by the client back to its initial
328+
* value set at creation time.
329+
*/
330+
resetHeartbeatIntervalSeconds() {
331+
this._heartbeatIntervalMs = this._initialHeartbeatIntervalMs;
332+
}
333+
306334
/**
307335
* Dev Note: This client manages the `instanceUid`, so I'm not sure if this
308336
* API method is useful. The instanceUid *can* be changed by the OpAMP

packages/opamp-client-node/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@elastic/opamp-client-node",
3-
"version": "0.3.0",
3+
"version": "0.4.0",
44
"type": "commonjs",
55
"description": "an OpAMP client for Node.js",
66
"publishConfig": {

packages/opamp-client-node/test/client.test.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,59 @@ test('OpAMPClient', (suite) => {
232232
t.end();
233233
});
234234

235+
suite.test('client.{set,reset}HeartbeatIntervalSeconds', async (t) => {
236+
const server = new MockOpAMPServer({
237+
logLevel: 'warn', // use 'debug' for some debugging of the server
238+
hostname: '127.0.0.1',
239+
port: 0,
240+
testMode: true,
241+
});
242+
await server.start();
243+
244+
const client = createOpAMPClient({
245+
log,
246+
endpoint: server.endpoint,
247+
heartbeatIntervalSeconds: 0.5,
248+
diagEnabled: true,
249+
});
250+
client.setAgentDescription({identifyingAttributes: {foo: 'bar'}});
251+
client.start();
252+
253+
// Cheating a bit by testing a private attribute.
254+
t.equal(client._heartbeatIntervalMs, 500, 'initial value');
255+
256+
client.setHeartbeatIntervalSeconds(1);
257+
t.equal(client._heartbeatIntervalMs, 1000, 'valid value works');
258+
259+
client.setHeartbeatIntervalSeconds(0.01);
260+
t.equal(client._heartbeatIntervalMs, 1000, 'too low value ignored');
261+
262+
client.setHeartbeatIntervalSeconds(0.1);
263+
t.equal(client._heartbeatIntervalMs, 100, 'min value works');
264+
265+
client.setHeartbeatIntervalSeconds('bogus');
266+
t.equal(client._heartbeatIntervalMs, 100, 'invalid type is ignored');
267+
268+
const DAY_IN_S = 86400;
269+
client.setHeartbeatIntervalSeconds(DAY_IN_S * 2);
270+
t.equal(
271+
client._heartbeatIntervalMs,
272+
DAY_IN_S * 1000,
273+
'too large value is clamped to 1d'
274+
);
275+
276+
client.resetHeartbeatIntervalSeconds();
277+
t.equal(
278+
client._heartbeatIntervalMs,
279+
500, // the initial value
280+
'resetHeartbeatIntervalSeconds works'
281+
);
282+
283+
await client.shutdown();
284+
await server.close();
285+
t.end();
286+
});
287+
235288
suite.test('remote config', async (t) => {
236289
// Setup MockOpAMPServer to provide `config` as remote config.
237290
const config = {foo: 42};

packages/opamp-client-node/types/opamp-client.d.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ export type OpAMPClientOptions = {
5656
onMessage?: OnMessageCallback;
5757
/**
5858
* The approximate time between
59-
* heartbeat messages sent by the client. Default 30.
60-
* Clamped to [100ms, 1d].
59+
* heartbeat messages sent by the client. Default 30. Values less than
60+
* 100ms will be ignored. Values greater than 1d will be clamped to 1d.
6161
*/
6262
heartbeatIntervalSeconds?: number;
6363
/**
@@ -138,8 +138,8 @@ export function createOpAMPClient(opts: OpAMPClientOptions): OpAMPClient;
138138
* remote config. Receiving remote config requires setting the
139139
* `AcceptsRemoteConfig` capability in `capabilities`.
140140
* @property {Number} [heartbeatIntervalSeconds] The approximate time between
141-
* heartbeat messages sent by the client. Default 30.
142-
* Clamped to [100ms, 1d].
141+
* heartbeat messages sent by the client. Default 30. Values less than
142+
* 100ms will be ignored. Values greater than 1d will be clamped to 1d.
143143
* @property {number} [headersTimeout] The timeout (in milliseconds) to wait
144144
* for the response headers on a request to the OpAMP server. Default 10s.
145145
* @property {number} [bodyTimeout] The timeout (in milliseconds) to wait for
@@ -175,6 +175,7 @@ declare class OpAMPClient {
175175
_instanceUidStr: string;
176176
_capabilities: bigint;
177177
_heartbeatIntervalMs: number;
178+
_initialHeartbeatIntervalMs: number;
178179
_onMessage: OnMessageCallback;
179180
_diagChs: {
180181
"opamp-client.send.success": import("diagnostics_channel").Channel<unknown, unknown>;
@@ -209,6 +210,17 @@ declare class OpAMPClient {
209210
nonIdentifyingAttributes?: object;
210211
}): void;
211212
_agentDescriptionSer: any;
213+
/**
214+
* Dynamically set the heartbeat interval of the client to a new value.
215+
*
216+
* @param {Number} n - A number of seconds.
217+
*/
218+
setHeartbeatIntervalSeconds(n: number): void;
219+
/**
220+
* Reset the heartbeat interval used by the client back to its initial
221+
* value set at creation time.
222+
*/
223+
resetHeartbeatIntervalSeconds(): void;
212224
/**
213225
* Dev Note: This client manages the `instanceUid`, so I'm not sure if this
214226
* API method is useful. The instanceUid *can* be changed by the OpAMP

0 commit comments

Comments
 (0)