Skip to content

Commit

Permalink
fix(collector): work around Bazel's node-patches module
Browse files Browse the repository at this point in the history
This fixes the annouce procedure for Node.js apps that have been built
with Bazel and include its node-patches module. Specifically, the
patches to `fs.readlinkSync` break the announcement of
@instana/collector to the Instana agent, by prefixing the result of
readlinkSync with an absolute path.

See https://github.com/bazelbuild/rules_nodejs/blob/5.3.0/packages/node-patches/src/fs.ts,
line 226.

refs 89102
  • Loading branch information
basti1302 committed Mar 24, 2022
1 parent 479a3f6 commit d06e9be
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 1 deletion.
12 changes: 11 additions & 1 deletion packages/collector/src/agentConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,17 @@ exports.announceNodeCollector = function announceNodeCollector(cb) {
payload.fd = String(socket._handle.fd);

try {
payload.inode = fs.readlinkSync(pathUtil.join('/proc', String(process.pid), 'fd', payload.fd));
const linkPathPrefix = `${pathUtil.join('/proc', String(process.pid), 'fd')}/`;
const linkPath = pathUtil.join(linkPathPrefix, payload.fd);
payload.inode = fs.readlinkSync(linkPath);
if (typeof payload.inode === 'string' && payload.inode.indexOf(linkPathPrefix) === 0) {
// Node.js apps built with Bazel need special handling here, since Bazel's node-patches turn the result of
// readlinkSync into an absolute path. See
// https://github.com/bazelbuild/rules_nodejs/blob/5.3.0/packages/node-patches/src/fs.ts#L226
// We work around that by removing those bogus leading path segments. The Instana agent will try to match on
// the inode value without any path prefix, so sending a fully qualified path would break the announcement.
payload.inode = payload.inode.substring(linkPathPrefix.length);
}
} catch (e) {
logger.debug('Failed to retrieve inode for file descriptor %s: %s', payload.fd, e.message);
}
Expand Down
113 changes: 113 additions & 0 deletions packages/collector/test/bazel_inode_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* (c) Copyright IBM Corp. 2022
*/

'use strict';

const { expect } = require('chai');
const proxyquire = require('proxyquire');
const EventEmitter = require('events');

class MockRequestEmitter extends EventEmitter {
setTimeout() {}

write(payload) {
this.payload = payload;
}

end() {}
}

class MockResponseEmitter extends EventEmitter {
setEncoding() {}
}

describe('agent connection/bazel', function () {
let agentConnection;
let lastRequest;

describe("Bazel's node-patches are present", () => {
before(() => {
agentConnection = proxyquire('../src/agentConnection', {
// Stub out the fs part part of the fd/inode lookup (the readlinkSync call), and act as if node-patches from
// Bazel were active, that is, return an absolute path from readlinkSync.
fs: mockFs(`/proc/${process.pid}/fd/socket:[12345]`),

// stub out the http communication part of the announce request
'@instana/core': mockInstanaCoreHttp()
});
});

it('should remove the leading path segmentes which node-patches prepends', done => {
agentConnection.announceNodeCollector(() => {
const announcePayload = JSON.parse(lastRequest.payload.toString());
expect(announcePayload.fd).to.equal('13');
expect(announcePayload.inode).to.equal('socket:[12345]');
done();
});
});
});

describe("Bazel's node-patches are not present", () => {
before(() => {
agentConnection = proxyquire('../src/agentConnection', {
// Stub out the fs part part of the fd/inode lookup (the readlinkSync call), and act as if node-patches from
// Bazel were not active, that is, act like an unpatched fs modules would work on Linux and return an
// unqualified file name (no absolute path) from readlinkSync.
fs: mockFs('socket:[12345]'),

// stub out the http communication part of the announce request
'@instana/core': mockInstanaCoreHttp()
});
});

it('should not modify the readlinkSync result', done => {
agentConnection.announceNodeCollector(() => {
const announcePayload = JSON.parse(lastRequest.payload.toString());
expect(announcePayload.fd).to.equal('13');
expect(announcePayload.inode).to.equal('socket:[12345]');
done();
});
});
});

function mockFs(readlinkSyncResult) {
return {
readlinkSync: () => {
return readlinkSyncResult;
}
};
}

function mockInstanaCoreHttp() {
return {
uninstrumentedHttp: {
http: {
request: function (options, responseCallback) {
const req = new MockRequestEmitter();
lastRequest = req;

setImmediate(() => {
req.emit('socket', {
_handle: {
fd: '13'
}
});

setImmediate(() => {
const res = new MockResponseEmitter();
res.statusCode = 200;
responseCallback(res);

setImmediate(() => {
res.emit('end');
});
});
});
return req;
}
}
}
};
}
});

0 comments on commit d06e9be

Please sign in to comment.