@elastic/mockopampserver
v0.5.0
Published
A mock OpAMP server, useful for dev and testing
Readme
@elastic/mockopampserver
A mock Open Agent Management Protocol (OpAMP, https://github.com/open-telemetry/opamp-spec)
server for development and testing. The intent is that this is something useful
to maintainers of OpAMP clients, especially for Elastic's Node.js OpAMP client
(package @elastic/opamp-client-node).
Features:
- It is a Node.js server (this may or may not be a feature to you :)
- It supports the OpAMP HTTP transport.
- Remote config (i.e. the
OffersRemoteConfigserver capability). - It logs the received
AgentToServerand sentServerToAgentprotobuf messages in a somewhat readable format. - A way to use this in Node.js testing (see
testMode: trueandtest*methods). See example usage in "packages/opamp-client-node/test/...". - "Bad mode": the
MockOpAMPServercan be configured to be in one of a number of "bad modes" where it responds in an atypical way, to support testing error cases / failure modes. SeebadModeusages in "packages/opamp-client-node/test/client.test.js".
Limitations:
- It only supports the HTTP transport of OpAMP, not the WebSocket Transport. (The spec says "Server implementations SHOULD accept both plain HTTP connections and WebSocket connections.")
- Most of the optional server capabilities are not implemented: effective config, packages, connection settings, command, custom capabilities.
Usage
CLI usage with Docker
Releases of mockopampserver include published Docker images. You can start a server via:
docker run --rm -it -p 4320:4320 --name mockopampserver \
ghcr.io/elastic/elastic-otel-node/mockopampserver:latestCLI usage with npx
If you use npx, you can start a server via:
npx @elastic/mockopampserverCLI usage from the repository
To build and use the server from this repository:
cd packages/mockopampserver
npm ci
npm startOnce the server is started, you can use it with an OpAMP client. For example:
cd ../packages/opamp-client-node
npm ci
npm run exampleOr, if you don't have a particular OpAMP client to use, you can try sending a request via curl using the included simple AgentToServer protobuf file:
curl -si http://localhost:4320/v1/opamp -X POST \
-H content-type:application/x-protobuf \
--data-binary @./test/fixtures/AgentToServer.simple.bin \
| ./scripts/ServerToAgent(The ServerToAgent script will deserialize opamp.proto.ServerToAgent` binary content on stdin and dump a representation to stdout.)
Module usage
See the block comment for class MockOpAMPServer for docs on all config options.
Here is an example showing how MockOpAMPServer could be used in a JS test case:
const {MockOpAMPServer} = require('@elastic/mockopampserver');
test('some OpAMP client test', async (t) => {
opampServer = new MockOpAMPServer({
logLevel: 'warn', // use 'debug' for some debugging of the server
hostname: '127.0.0.1',
port: 0,
testMode: true,
});
await opampServer.start();
t.comment(`MockOpAMPServer started: ${opampServer.endpoint}`);
// Reset test data in the OpAMP server for each test, if re-using the same
// mock server for multiple tests.
opampServer.testReset();
// Use an OpAMP client with `opampServer.endpoint`.
// ...
// Get details of every request/response from the server's point of view.
// Each entry can include the following fields:
// - `req`: some fields from the incoming HTTP request
// - `res`: some fields from the outgoing HTTP response
// - `a2s`: the parsed AgentToServer protobuf object sent by the client
// - `s2a`: the parsed ServerToAgent protobuf object sent by the server
// - `err`: an Error instance, in some failure cases
const reqs = opampServer.testGetRequests();
// console.dir(reqs, {depth: 50});
});An example "test request" object:
[
{
req: {
method: 'POST',
headers: {
host: '127.0.0.1:51204',
connection: 'keep-alive',
'user-agent': '@elastic/opamp-client-node/0.1.0',
'content-type': 'application/x-protobuf',
'content-length': '39'
}
},
res: {
statusCode: 200,
_header: 'HTTP/1.1 200 OK\r\n' +
'Content-Type: application/x-protobuf\r\n' +
'Content-Length: 20\r\n' +
'Date: Tue, 27 May 2025 21:49:36 GMT\r\n' +
'Connection: keep-alive\r\n' +
'Keep-Alive: timeout=5\r\n' +
'\r\n'
},
a2s: {
'$typeName': 'opamp.proto.AgentToServer',
instanceUid: Buffer(16) [Uint8Array] [
1, 151, 19, 184, 240, 118,
118, 159, 172, 57, 91, 52,
29, 167, 17, 41
],
sequenceNum: 1n,
capabilities: 8193n,
flags: 0n,
agentDescription: {
'$typeName': 'opamp.proto.AgentDescription',
identifyingAttributes: [
{
'$typeName': 'opamp.proto.KeyValue',
key: 'foo',
value: {
'$typeName': 'opamp.proto.AnyValue',
value: { case: 'stringValue', value: 'bar' }
}
}
],
nonIdentifyingAttributes: []
}
},
s2a: {
'$typeName': 'opamp.proto.ServerToAgent',
instanceUid: Buffer(16) [Uint8Array] [
1, 151, 19, 184, 240, 118,
118, 159, 172, 57, 91, 52,
29, 167, 17, 41
],
flags: 0,
capabilities: 3
},
err: undefined
}
]Remote config
Remote config is an important use case for OpAMP. Here is how it can be used with MockOpAMPServer.
The CLI supports a -F key=./some-file.json option. This will
setup the mock server to offer that file as remote config to requesting
clients/agents, resulting in a server response with:
...
remoteConfig: {
'$typeName': 'opamp.proto.AgentRemoteConfig',
configHash: Uint8Array(32) [ ... ],
config: {
'$typeName': 'opamp.proto.AgentConfigMap',
configMap: {
'key': { // <--- given "key" is here
'$typeName': 'opamp.proto.AgentConfigFile',
body: Uint8Array(...) [ ... ], // <--- content of ./some-file.json is here
contentType: 'application/json'
}
}
}
}To see an example:
# Run the server with config.
cd packages/mockopampserver
npm run example:remote-config
# Run the client.
cd packages/opamp-client-node
npm run exampleThe equivalent setup of the server in code is:
const config = {foo: 42};
const opampServer = new MockOpAMPServer({
agentConfigMap: {
configMap: {
'': {
contentType: 'application/json',
body: Buffer.from(JSON.stringify(config), 'utf8'),
},
},
},
// ... other config options.
});
await opampServer.start();Test mode API to live-update agent config
In addition to the agentConfigMap constructor option, the MockOpAMPServer
supports a POST /api/agentConfigMap HTTP endpoint to update the Agent Config
that the server will use. This endpoint is not part of the OpAMP spec. The
endpoint is only enabled when testMode: true.
Here are some curl examples showing how to live-update the Agent Config
offered by a running MockOpAMPServer
# Set `logging_level` for the "elastic" configMap key.
curl -i http://127.0.0.1:4320/api/agentConfigMap -F 'elastic={"logging_level":"debug"}'
# Set empty config for the "elastic" key.
curl -i http://127.0.0.1:4320/api/agentConfigMap -F 'elastic={}'
# Set the config to the contents of a local JSON file.
curl -i http://127.0.0.1:4320/api/agentConfigMap -F 'elastic=@./my-agent-config.json'See SetAgentConfigMap in "lib/mockopampserver.js" for more examples.
mTLS example
This section shows how to use the mockopampserver and opamp-client-node for mTLS, where both client and server must have valid certs. In these examples we'll be using certificate data generated for the packages/opamp-client-node test suite.
Let's start the mockopampserver using CLI options to set TLS server options:
- Custom CA certs (
--cacert), a server certificate (--cert) and private key (--key). These option names match those fromcurl - Configure the TLS server to request client certificates from connecting clients (
--request-client-cert).
cd packages/mockopampserver
npm ci
node lib/cli.js --cacert ../opamp-client-node/test/certs/ca.crt --cert ../opamp-client-node/test/certs/server.crt --key ../opamp-client-node/test/certs/server.key --request-client-certFirst, let's use curl as the client, as we did in a section above. The only differences are:
- the server is now using HTTPS, and
- we need to add the
--cacert,--cert, and--keyTLS options.
curl -v https://localhost:4320/v1/opamp -X POST \
--cacert ../opamp-client-node/test/certs/ca.crt --cert ../opamp-client-node/test/certs/client.crt --key ../opamp-client-node/test/certs/client.key \
-H content-type:application/x-protobuf \
--data-binary @./test/fixtures/AgentToServer.simple.bin \
| ./scripts/ServerToAgentIf that works you should see something like:
% curl -sv https://localhost:4320/v1/opamp -X POST \
--cacert ../opamp-client-node/test/certs/ca.crt --cert ../opamp-client-node/test/certs/client.crt --key ../opamp-client-node/test/certs/client.key \
-H content-type:application/x-protobuf \
--data-binary @./test/fixtures/AgentToServer.simple.bin \
| ./scripts/ServerToAgent
* Host localhost:4320 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:4320...
* Connected to localhost (::1) port 4320
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
} [314 bytes data]
* CAfile: ../opamp-client-node/test/certs/ca.crt
* CApath: none
* (304) (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* (304) (IN), TLS handshake, Unknown (8):
{ [21 bytes data]
* (304) (IN), TLS handshake, Request CERT (13): <-- This is the TLS server requesting the client provide a TLS cert.
{ [143 bytes data]
* (304) (IN), TLS handshake, Certificate (11):
{ [2849 bytes data]
* (304) (IN), TLS handshake, CERT verify (15):
{ [520 bytes data]
* (304) (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* (304) (OUT), TLS handshake, Certificate (11):
} [1415 bytes data]
* (304) (OUT), TLS handshake, CERT verify (15):
} [520 bytes data]
* (304) (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384 / [blank] / UNDEF
* ALPN: server accepted http/1.1
* Server certificate:
* subject: C=CL; ST=RM; L=EDOTTest; O=Test; OU=Server; CN=localhost
* start date: Nov 3 23:18:29 2025 GMT
* expire date: Nov 3 23:18:29 2026 GMT
* common name: localhost (matched)
* issuer: C=CL; ST=RM; L=EDOTTest; O=Root; OU=Test; CN=ca
* SSL certificate verify ok.
* using HTTP/1.x <-- The TLS connection has successfully been established.
> POST /v1/opamp HTTP/1.1
> Host: localhost:4320
> User-Agent: curl/8.7.1
> Accept: */*
> content-type:application/x-protobuf
> Content-Length: 18
>
} [18 bytes data]
* upload completely sent off: 18 bytes
< HTTP/1.1 200 OK
< Content-Type: application/x-protobuf
< Content-Length: 22
< Date: Tue, 04 Nov 2025 00:46:07 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
{ [22 bytes data]
* Connection #0 to host localhost left intact
{ <-- The decoded ServerToAgent message starts here.
'$typeName': 'opamp.proto.ServerToAgent',
instanceUid: Buffer(16) [Uint8Array] [
84, 108, 42, 240, 68,
144, 68, 201, 175, 144,
55, 194, 38, 98, 130,
102
],
flags: 1n,
capabilities: 3n
}Second, we can use @elastic/opamp-client-node as the client.
See the opamp-client-node example using mTLS.
cd packages/opamp-client-node
npm run examples:mTLS