diff --git a/package-lock.json b/package-lock.json index f0eb998412..23adc28244 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,8 @@ "@elastic/elasticsearch-v7.9.0": "npm:@elastic/elasticsearch@7.9.0", "@google-cloud/pubsub": "^4.3.3", "@google-cloud/storage": "^7.9.0", - "@grpc/grpc-js": "1.9.4", + "@grpc/grpc-js": "1.10.6", + "@grpc/grpc-js-v1": "npm:@grpc/grpc-js@1.9.4", "@grpc/proto-loader": "^0.7.10", "@hapi/hapi": "^21.3.7", "@ibm/tekton-lint": "^1.0.0-beta.9", @@ -8196,6 +8197,20 @@ } }, "node_modules/@grpc/grpc-js": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.6.tgz", + "integrity": "sha512-xP58G7wDQ4TCmN/cMUHh00DS7SRDv/+lC+xFLrTkMIN8h55X5NhZMLYbvy7dSELP15qlI6hPhNCRWVMtZMwqLA==", + "dev": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.10", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/grpc-js-v1": { + "name": "@grpc/grpc-js", "version": "1.9.4", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.4.tgz", "integrity": "sha512-oEnzYiDuEsBydZBtP84BkpduLsE1nSAO4KrhTLHRzNrIQE647fhchmosTQsJdCo8X9zBBt+l5+fNk+m/yCFJ/Q==", @@ -11679,19 +11694,6 @@ "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@grpc/grpc-js": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.3.tgz", - "integrity": "sha512-qiO9MNgYnwbvZ8MK0YLWbnGrNX3zTcj6/Ef7UHu5ZofER3e2nF3Y35GaPo9qNJJ/UJQKa4KL+z/F4Q8Q+uCdUQ==", - "dev": true, - "dependencies": { - "@grpc/proto-loader": "^0.7.10", - "@js-sdsl/ordered-map": "^4.4.2" - }, - "engines": { - "node": ">=12.10.0" - } - }, "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/resources": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.22.0.tgz", @@ -13609,19 +13611,6 @@ "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@grpc/grpc-js": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.3.tgz", - "integrity": "sha512-qiO9MNgYnwbvZ8MK0YLWbnGrNX3zTcj6/Ef7UHu5ZofER3e2nF3Y35GaPo9qNJJ/UJQKa4KL+z/F4Q8Q+uCdUQ==", - "dev": true, - "dependencies": { - "@grpc/proto-loader": "^0.7.10", - "@js-sdsl/ordered-map": "^4.4.2" - }, - "engines": { - "node": ">=12.10.0" - } - }, "node_modules/@opentelemetry/otlp-proto-exporter-base": { "version": "0.49.1", "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-proto-exporter-base/-/otlp-proto-exporter-base-0.49.1.tgz", @@ -23142,19 +23131,6 @@ "node": ">=14" } }, - "node_modules/google-gax/node_modules/@grpc/grpc-js": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.3.tgz", - "integrity": "sha512-qiO9MNgYnwbvZ8MK0YLWbnGrNX3zTcj6/Ef7UHu5ZofER3e2nF3Y35GaPo9qNJJ/UJQKa4KL+z/F4Q8Q+uCdUQ==", - "dev": true, - "dependencies": { - "@grpc/proto-loader": "^0.7.10", - "@js-sdsl/ordered-map": "^4.4.2" - }, - "engines": { - "node": ">=12.10.0" - } - }, "node_modules/google-protobuf": { "version": "3.21.2", "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz", diff --git a/package.json b/package.json index 499499e972..4c18e4c5d9 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,8 @@ "@elastic/elasticsearch-v7.9.0": "npm:@elastic/elasticsearch@7.9.0", "@google-cloud/pubsub": "^4.3.3", "@google-cloud/storage": "^7.9.0", - "@grpc/grpc-js": "1.9.4", + "@grpc/grpc-js": "1.10.6", + "@grpc/grpc-js-v1": "npm:@grpc/grpc-js@1.9.4", "@grpc/proto-loader": "^0.7.10", "@hapi/hapi": "^21.3.7", "@ibm/tekton-lint": "^1.0.0-beta.9", diff --git a/packages/collector/test/tracing/protocols/grpc-js/client.js b/packages/collector/test/tracing/protocols/grpc-js/client.js index f1ca21b3eb..560d84eee5 100644 --- a/packages/collector/test/tracing/protocols/grpc-js/client.js +++ b/packages/collector/test/tracing/protocols/grpc-js/client.js @@ -10,6 +10,7 @@ process.on('SIGTERM', () => { process.exit(0); }); +require('./mockVersion'); const port = require('../../../test_util/app-port')(); require('../../../..')(); diff --git a/packages/collector/test/tracing/protocols/grpc-js/maliServer.js b/packages/collector/test/tracing/protocols/grpc-js/maliServer.js index 1386b62e27..9385d34bbf 100644 --- a/packages/collector/test/tracing/protocols/grpc-js/maliServer.js +++ b/packages/collector/test/tracing/protocols/grpc-js/maliServer.js @@ -10,6 +10,7 @@ process.on('SIGTERM', () => { process.exit(0); }); +require('./mockVersion'); const port = require('../../../test_util/app-port')(); require('../../../..')(); diff --git a/packages/collector/test/tracing/protocols/grpc-js/mockVersion.js b/packages/collector/test/tracing/protocols/grpc-js/mockVersion.js new file mode 100644 index 0000000000..96acb7762d --- /dev/null +++ b/packages/collector/test/tracing/protocols/grpc-js/mockVersion.js @@ -0,0 +1,31 @@ +/* + * (c) Copyright IBM Corp. 2024 + */ + +'use strict'; + +const mock = require('mock-require'); +const requireHook = require('../../../../../core/src/util/requireHook'); + +const INSTANA_GRPC_VERSION = process.env.INSTANA_GRPC_VERSION; +const GRPC_REQUIRE = + process.env.INSTANA_GRPC_VERSION === 'latest' ? '@grpc/grpc-js' : `@grpc/grpc-js-${INSTANA_GRPC_VERSION}`; + +if (GRPC_REQUIRE !== '@grpc/grpc-js') { + mock('@grpc/grpc-js', GRPC_REQUIRE); +} + +const originalOnFileLoad = requireHook.onFileLoad; +requireHook.onFileLoad = function onFileLoad() { + if ( + arguments[0].source === '\\/@grpc\\/grpc-js\\/build\\/src\\/server\\.js' || + arguments[0].source === '\\/@grpc\\/grpc-js\\/build\\/src\\/client\\.js' + ) { + const str = arguments[0].source.replace('@grpc\\/grpc-js', GRPC_REQUIRE); + const reg = new RegExp(str, ''); + arguments[0] = reg; + return originalOnFileLoad.apply(this, arguments); + } + + return originalOnFileLoad.apply(this, arguments); +}; diff --git a/packages/collector/test/tracing/protocols/grpc-js/server.js b/packages/collector/test/tracing/protocols/grpc-js/server.js index a31e0be8df..d7ce6e7778 100644 --- a/packages/collector/test/tracing/protocols/grpc-js/server.js +++ b/packages/collector/test/tracing/protocols/grpc-js/server.js @@ -10,6 +10,7 @@ process.on('SIGTERM', () => { process.exit(0); }); +require('./mockVersion'); const port = require('../../../test_util/app-port')(); require('../../../..')(); diff --git a/packages/collector/test/tracing/protocols/grpc-js/test.js b/packages/collector/test/tracing/protocols/grpc-js/test.js index 1442e6c49f..d898f51de9 100644 --- a/packages/collector/test/tracing/protocols/grpc-js/test.js +++ b/packages/collector/test/tracing/protocols/grpc-js/test.js @@ -19,292 +19,328 @@ const agentControls = globalAgent.instance; const mochaSuiteFn = semver.satisfies(process.versions.node, '>=8.13.0') ? describe : describe.skip; -mochaSuiteFn('tracing/grpc-js', function () { - this.timeout(config.getTestTimeout()); - - globalAgent.setUpCleanUpHooks(); +/** + * @grpc/grpc-js 1.10 is no longer compatible with mali server + * - https://github.com/malijs/mali/issues/376 + * - mali seems unmaintained. no release since > 2y + */ +['latest', 'v1'].forEach(version => { + mochaSuiteFn(`tracing/grpc-js@${version}`, function () { + this.timeout(config.getTestTimeout()); + + globalAgent.setUpCleanUpHooks(); + + describe('success', function () { + let serverControls; + let clientControls; + + before(async function () { + serverControls = new ProcessControls({ + appPath: path.join(__dirname, 'server'), + useGlobalAgent: true, + env: { + INSTANA_GRPC_VERSION: version + } + }); - describe('success', function () { - let serverControls; - let clientControls; + clientControls = new ProcessControls({ + appPath: path.join(__dirname, 'client'), + useGlobalAgent: true, + env: { + INSTANA_GRPC_VERSION: version + } + }); - before(async function () { - serverControls = new ProcessControls({ - appPath: path.join(__dirname, 'server'), - useGlobalAgent: true + await serverControls.startAndWaitForAgentConnection(); + await clientControls.startAndWaitForAgentConnection(); }); - clientControls = new ProcessControls({ - appPath: path.join(__dirname, 'client'), - useGlobalAgent: true + beforeEach(async () => { + await agentControls.clearReceivedTraceData(); }); - await serverControls.startAndWaitForAgentConnection(); - await clientControls.startAndWaitForAgentConnection(); - }); + after(async () => { + await serverControls.stop(); + await clientControls.stop(); + }); - beforeEach(async () => { - await agentControls.clearReceivedTraceData(); - }); + afterEach(async () => { + await serverControls.clearIpcMessages(); + await clientControls.clearIpcMessages(); + }); - after(async () => { - await serverControls.stop(); - await clientControls.stop(); - }); + it('must trace an unary call', () => { + const expectedReply = 'received: request'; + return runTest('/unary-call', serverControls, clientControls, expectedReply); + }); - afterEach(async () => { - await serverControls.clearIpcMessages(); - await clientControls.clearIpcMessages(); - }); + it('must mark unary call as erroneous', () => + runTest('/unary-call', serverControls, clientControls, null, false, true)); - it('must trace an unary call', () => { - const expectedReply = 'received: request'; - return runTest('/unary-call', serverControls, clientControls, expectedReply); - }); + it('must cancel an unary call', () => runTest('/unary-call', serverControls, clientControls, null, true, false)); - it('must mark unary call as erroneous', () => - runTest('/unary-call', serverControls, clientControls, null, false, true)); + it('must trace server-side streaming', () => { + const expectedReply = ['received: request', 'streaming', 'more', 'data']; + return runTest('/server-stream', serverControls, clientControls, expectedReply); + }); - it('must cancel an unary call', () => runTest('/unary-call', serverControls, clientControls, null, true, false)); + it('must mark server-side streaming as erroneous', () => + runTest('/server-stream', serverControls, clientControls, null, false, true)); - it('must trace server-side streaming', () => { - const expectedReply = ['received: request', 'streaming', 'more', 'data']; - return runTest('/server-stream', serverControls, clientControls, expectedReply); - }); + it('must cancel server-side streaming', () => + runTest('/server-stream', serverControls, clientControls, null, true, false)); - it('must mark server-side streaming as erroneous', () => - runTest('/server-stream', serverControls, clientControls, null, false, true)); + it('must trace client-side streaming', () => { + const expectedReply = 'first; second; third'; + return runTest('/client-stream', serverControls, clientControls, expectedReply); + }); - it('must cancel server-side streaming', () => - runTest('/server-stream', serverControls, clientControls, null, true, false)); + it('must mark client-side streaming as erroneous', () => + runTest('/client-stream', serverControls, clientControls, null, false, true)); - it('must trace client-side streaming', () => { - const expectedReply = 'first; second; third'; - return runTest('/client-stream', serverControls, clientControls, expectedReply); - }); + it('must cancel client-side streaming', () => + runTest('/client-stream', serverControls, clientControls, null, true, false)); - it('must mark client-side streaming as erroneous', () => - runTest('/client-stream', serverControls, clientControls, null, false, true)); + it('must trace bidi streaming', () => { + const expectedReply = ['received: first', 'received: second', 'received: third', 'STOP']; + return runTest('/bidi-stream', serverControls, clientControls, expectedReply); + }); - it('must cancel client-side streaming', () => - runTest('/client-stream', serverControls, clientControls, null, true, false)); + it('must mark bidi streaming as erroneous', () => + runTest('/bidi-stream', serverControls, clientControls, null, false, true)); - it('must trace bidi streaming', () => { - const expectedReply = ['received: first', 'received: second', 'received: third', 'STOP']; - return runTest('/bidi-stream', serverControls, clientControls, expectedReply); + it('must cancel bidi streaming', () => + runTest('/bidi-stream', serverControls, clientControls, null, true, false)); }); - it('must mark bidi streaming as erroneous', () => - runTest('/bidi-stream', serverControls, clientControls, null, false, true)); + // See https://github.com/malijs/mali/issues/376 + const maliMochaSuiteFn = + semver.satisfies(process.versions.node, '>=14.0.0') && version !== 'latest' ? describe : describe.skip; - it('must cancel bidi streaming', () => runTest('/bidi-stream', serverControls, clientControls, null, true, false)); - }); + maliMochaSuiteFn('with mali server', function () { + let serverControls; + let clientControls; - const maliMochaSuiteFn = semver.satisfies(process.versions.node, '>=14.0.0') ? describe : describe.skip; + before(async function () { + serverControls = new ProcessControls({ + appPath: path.join(__dirname, 'maliServer'), + useGlobalAgent: true, + env: { + INSTANA_GRPC_VERSION: version + } + }); - maliMochaSuiteFn('with mali server', function () { - let serverControls; - let clientControls; + clientControls = new ProcessControls({ + appPath: path.join(__dirname, 'client'), + useGlobalAgent: true, + env: { + INSTANA_GRPC_VERSION: version + } + }); - before(async function () { - serverControls = new ProcessControls({ - appPath: path.join(__dirname, 'maliServer'), - useGlobalAgent: true + await serverControls.startAndWaitForAgentConnection(); + await clientControls.startAndWaitForAgentConnection(); }); - clientControls = new ProcessControls({ - appPath: path.join(__dirname, 'client'), - useGlobalAgent: true + beforeEach(async () => { + await agentControls.clearReceivedTraceData(); }); - await serverControls.startAndWaitForAgentConnection(); - await clientControls.startAndWaitForAgentConnection(); - }); + after(async () => { + await serverControls.stop(); + await clientControls.stop(); + }); - beforeEach(async () => { - await agentControls.clearReceivedTraceData(); - }); + afterEach(async () => { + await serverControls.clearIpcMessages(); + await clientControls.clearIpcMessages(); + }); - after(async () => { - await serverControls.stop(); - await clientControls.stop(); + it('must trace an unary call', () => { + const expectedReply = 'received: request'; + return runTest('/unary-call', serverControls, clientControls, expectedReply); + }); }); - afterEach(async () => { - await serverControls.clearIpcMessages(); - await clientControls.clearIpcMessages(); - }); + describe('suppressed', function () { + let serverControls; + let clientControls; - it('must trace an unary call', () => { - const expectedReply = 'received: request'; - return runTest('/unary-call', serverControls, clientControls, expectedReply); - }); - }); + before(async function () { + serverControls = new ProcessControls({ + appPath: path.join(__dirname, 'server'), + useGlobalAgent: true, + env: { + INSTANA_GRPC_VERSION: version + } + }); - describe('suppressed', function () { - let serverControls; - let clientControls; + clientControls = new ProcessControls({ + appPath: path.join(__dirname, 'client'), + useGlobalAgent: true, + env: { + INSTANA_GRPC_VERSION: version + } + }); - before(async function () { - serverControls = new ProcessControls({ - appPath: path.join(__dirname, 'server'), - useGlobalAgent: true + await serverControls.startAndWaitForAgentConnection(); + await clientControls.startAndWaitForAgentConnection(); }); - clientControls = new ProcessControls({ - appPath: path.join(__dirname, 'client'), - useGlobalAgent: true + beforeEach(async () => { + await agentControls.clearReceivedTraceData(); }); - await serverControls.startAndWaitForAgentConnection(); - await clientControls.startAndWaitForAgentConnection(); - }); - - beforeEach(async () => { - await agentControls.clearReceivedTraceData(); - }); + after(async () => { + await serverControls.stop(); + await clientControls.stop(); + }); - after(async () => { - await serverControls.stop(); - await clientControls.stop(); - }); + afterEach(async () => { + await serverControls.clearIpcMessages(); + await clientControls.clearIpcMessages(); + }); - afterEach(async () => { - await serverControls.clearIpcMessages(); - await clientControls.clearIpcMessages(); + it('[suppressed] should not trace', () => + clientControls + .sendRequest({ + method: 'POST', + path: '/unary-call', + headers: { + 'X-INSTANA-L': '0' + } + }) + .then(response => { + expect(response.reply).to.equal('received: request'); + return delay(1000); + }) + .then(() => + agentControls.getSpans().then(spans => { + expect(spans).to.have.lengthOf(0); + }) + )); }); - it('[suppressed] should not trace', () => - clientControls - .sendRequest({ - method: 'POST', - path: '/unary-call', - headers: { - 'X-INSTANA-L': '0' + describe('individually disabled', function () { + let serverControls; + let clientControls; + + before(async function () { + serverControls = new ProcessControls({ + appPath: path.join(__dirname, 'server'), + useGlobalAgent: true, + env: { + INSTANA_DISABLED_TRACERS: 'grpcjs', + INSTANA_GRPC_VERSION: version } - }) - .then(response => { - expect(response.reply).to.equal('received: request'); - return delay(1000); - }) - .then(() => - agentControls.getSpans().then(spans => { - expect(spans).to.have.lengthOf(0); - }) - )); - }); + }); + + clientControls = new ProcessControls({ + appPath: path.join(__dirname, 'client'), + useGlobalAgent: true, + env: { + INSTANA_DISABLED_TRACERS: 'grpcjs', + INSTANA_GRPC_VERSION: version + } + }); - describe('individually disabled', function () { - let serverControls; - let clientControls; - - before(async function () { - serverControls = new ProcessControls({ - appPath: path.join(__dirname, 'server'), - useGlobalAgent: true, - env: { - INSTANA_DISABLED_TRACERS: 'grpcjs' - } + await serverControls.startAndWaitForAgentConnection(); + await clientControls.startAndWaitForAgentConnection(); }); - clientControls = new ProcessControls({ - appPath: path.join(__dirname, 'client'), - useGlobalAgent: true, - env: { - INSTANA_DISABLED_TRACERS: 'grpcjs' - } + beforeEach(async () => { + await agentControls.clearReceivedTraceData(); }); - await serverControls.startAndWaitForAgentConnection(); - await clientControls.startAndWaitForAgentConnection(); - }); + after(async () => { + await serverControls.stop(); + await clientControls.stop(); + }); - beforeEach(async () => { - await agentControls.clearReceivedTraceData(); - }); + afterEach(async () => { + await serverControls.clearIpcMessages(); + await clientControls.clearIpcMessages(); + }); - after(async () => { - await serverControls.stop(); - await clientControls.stop(); + it('should not trace when GRPC tracing is individually disabled', () => + clientControls + .sendRequest({ + method: 'POST', + path: '/unary-call' + }) + .then(response => { + expect(response.reply).to.equal('received: request'); + return delay(500); + }) + .then(() => { + return retry(() => + agentControls.getSpans().then(spans => { + expectExactlyOneMatching(spans, checkHttpEntry({ url: '/unary-call' })); + expect(getSpansByName(spans, 'rpc-client')).to.be.empty; + expect(getSpansByName(spans, 'rpc-server')).to.be.empty; + }) + ); + })); }); - afterEach(async () => { - await serverControls.clearIpcMessages(); - await clientControls.clearIpcMessages(); - }); + describe('multiple hosts', function () { + let serverControls; + let clientControls; - it('should not trace when GRPC tracing is individually disabled', () => - clientControls - .sendRequest({ - method: 'POST', - path: '/unary-call' - }) - .then(response => { - expect(response.reply).to.equal('received: request'); - return delay(500); - }) - .then(() => { - return retry(() => - agentControls.getSpans().then(spans => { - expectExactlyOneMatching(spans, checkHttpEntry({ url: '/unary-call' })); - expect(getSpansByName(spans, 'rpc-client')).to.be.empty; - expect(getSpansByName(spans, 'rpc-server')).to.be.empty; - }) - ); - })); - }); + before(async function () { + serverControls = new ProcessControls({ + appPath: path.join(__dirname, 'server'), + useGlobalAgent: true, + env: { + INSTANA_GRPC_VERSION: version + } + }); - describe('multiple hosts', function () { - let serverControls; - let clientControls; + clientControls = new ProcessControls({ + appPath: path.join(__dirname, 'client'), + useGlobalAgent: true, + env: { + INSTANA_GRPC_VERSION: version + } + }); - before(async function () { - serverControls = new ProcessControls({ - appPath: path.join(__dirname, 'server'), - useGlobalAgent: true + await serverControls.startAndWaitForAgentConnection(); + await clientControls.startAndWaitForAgentConnection(); }); - clientControls = new ProcessControls({ - appPath: path.join(__dirname, 'client'), - useGlobalAgent: true + beforeEach(async () => { + await agentControls.clearReceivedTraceData(); }); - await serverControls.startAndWaitForAgentConnection(); - await clientControls.startAndWaitForAgentConnection(); - }); - - beforeEach(async () => { - await agentControls.clearReceivedTraceData(); - }); - - after(async () => { - await serverControls.stop(); - await clientControls.stop(); - }); - - afterEach(async () => { - await serverControls.clearIpcMessages(); - await clientControls.clearIpcMessages(); - }); - - it('call two different hosts', async () => { - const url = '/two-different-hosts'; - const response = await clientControls.sendRequest({ - method: 'POST', - path: url + after(async () => { + await serverControls.stop(); + await clientControls.stop(); }); - expect(response.reply1).to.equal('received: request'); - expect(response.reply2).to.equal('received: request'); + afterEach(async () => { + await serverControls.clearIpcMessages(); + await clientControls.clearIpcMessages(); + }); - let spans; - await retry(async () => { - spans = await agentControls.getSpans(); - expect(spans.length).to.eql(7); + it('call two different hosts', async () => { + const url = '/two-different-hosts'; + const response = await clientControls.sendRequest({ + method: 'POST', + path: url + }); + + expect(response.reply1).to.equal('received: request'); + expect(response.reply2).to.equal('received: request'); + + let spans; + await retry(async () => { + spans = await agentControls.getSpans(); + expect(spans.length).to.eql(7); + }); + const httpEntry = expectExactlyOneMatching(spans, checkHttpEntry({ url })); + expectExactlyOneMatching(spans, checkGrpcClientSpan({ httpEntry, clientControls, url, host: 'localhost' })); + expectExactlyOneMatching(spans, checkGrpcClientSpan({ httpEntry, clientControls, url, host: '127.0.0.1' })); }); - const httpEntry = expectExactlyOneMatching(spans, checkHttpEntry({ url })); - expectExactlyOneMatching(spans, checkGrpcClientSpan({ httpEntry, clientControls, url, host: 'localhost' })); - expectExactlyOneMatching(spans, checkGrpcClientSpan({ httpEntry, clientControls, url, host: '127.0.0.1' })); }); }); });