diff --git a/package-lock.json b/package-lock.json index 19547712cf..a878509b22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -186,7 +186,8 @@ "node": ">=18.0.0" }, "optionalDependencies": { - "couchbase": "^4.4.3", + "couchbase": "^4.4.4", + "couchbase-v443": "npm:couchbase@4.4.3", "ibm_db": "^3.2.5", "kafka-avro": "^3.2.0", "node-rdkafka": "^3.2.1", @@ -21871,13 +21872,12 @@ } }, "node_modules/@couchbase/couchbase-darwin-arm64-napi": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@couchbase/couchbase-darwin-arm64-napi/-/couchbase-darwin-arm64-napi-4.4.3.tgz", - "integrity": "sha512-nH5282Pql5400P1zCOh70FVX4Yoaah2HMfA7ka3ZWB6M1h1C8VJ97gfUD/lsU5Pja8PekrLbZip3OARuieSeiw==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-darwin-arm64-napi/-/couchbase-darwin-arm64-napi-4.4.4.tgz", + "integrity": "sha512-JVBLeckWZpZIPMJCoOCFgVxLNMDAsK1rZsQRBTN6/dp4NYDOaLpy+521ica9QLN0Uqk8UNI6oLPaABwolQFRaA==", "cpu": [ "arm64" ], - "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -21887,13 +21887,12 @@ } }, "node_modules/@couchbase/couchbase-darwin-x64-napi": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@couchbase/couchbase-darwin-x64-napi/-/couchbase-darwin-x64-napi-4.4.3.tgz", - "integrity": "sha512-W9+x1CYkHG2jIdEeHZXo6D2IK8jq4JKVfo53D9AnqETFnO1W2Tf5+k2fuDz/NCHMfn4S9wXvAquwbhftE4DLEA==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-darwin-x64-napi/-/couchbase-darwin-x64-napi-4.4.4.tgz", + "integrity": "sha512-XjM+WK8AdCnCdiOwM+zFeg4/TKCj3wyF9rcuM6MRcYxusNoyGpZjyWYU5fYT8iLyrFwoBts7p9M8BKUaUMB0EQ==", "cpu": [ "x64" ], - "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -21903,13 +21902,12 @@ } }, "node_modules/@couchbase/couchbase-linux-arm64-napi": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@couchbase/couchbase-linux-arm64-napi/-/couchbase-linux-arm64-napi-4.4.3.tgz", - "integrity": "sha512-meeLqhhT+/6tZk5wjp/6EitYbJ5yn7WniINvYV+dFy6lNas3ev/XVlT/w+toZ+Smo/+sBnR4EJhXFcvYj39+MQ==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-linux-arm64-napi/-/couchbase-linux-arm64-napi-4.4.4.tgz", + "integrity": "sha512-hNiK/yjWUB1oygAlRufg5bVccucHKZifEXbn0cYduUKYjsob7Q61IT8/k9X+hIcCQIShA2NvE60VYKHvzyd5Yg==", "cpu": [ "arm64" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -21919,13 +21917,12 @@ } }, "node_modules/@couchbase/couchbase-linux-x64-napi": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@couchbase/couchbase-linux-x64-napi/-/couchbase-linux-x64-napi-4.4.3.tgz", - "integrity": "sha512-++Q/dOrywzJ2Rb4hWFfHxAuZZU70EGf1OSkhhl+yiFRyg3/9eeuhTkDZNaVCpjEFr8mZKY2nz9JKs8f8yjd9AQ==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-linux-x64-napi/-/couchbase-linux-x64-napi-4.4.4.tgz", + "integrity": "sha512-xVo3ARZDxH9XHJpO8JIDv92i9yEuoMk6yvHxh9XNvSOHN4Ymju3KEFwPPFmfNezLGFWbDRZi6+z5JPdB1IPoGQ==", "cpu": [ "x64" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -21935,13 +21932,12 @@ } }, "node_modules/@couchbase/couchbase-linuxmusl-arm64-napi": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@couchbase/couchbase-linuxmusl-arm64-napi/-/couchbase-linuxmusl-arm64-napi-4.4.3.tgz", - "integrity": "sha512-H4Y6SADDHQMdUwJ5AXbQN3YCBnbhs4S7jAYSVBlS8blVNqrpNH+ZCLoyiwgLSjHEt5qs4U1ODd/S9zYUrYYxww==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-linuxmusl-arm64-napi/-/couchbase-linuxmusl-arm64-napi-4.4.4.tgz", + "integrity": "sha512-s2MDllkq0XubGqUKbXB9B1U9+flYTeBIRMwQYXhO40UEBRDxsYOtrtVMAfgogDE4y/oA14OQNIHePyIR78D32g==", "cpu": [ "arm64" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -21951,13 +21947,12 @@ } }, "node_modules/@couchbase/couchbase-linuxmusl-x64-napi": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@couchbase/couchbase-linuxmusl-x64-napi/-/couchbase-linuxmusl-x64-napi-4.4.3.tgz", - "integrity": "sha512-NP4+puEW1nakxfXBhAL6I+hcj9iSSDF5NQ+W9sacnGEUQsPqqgqH293piAPEYj0WQrmtqPv4a3cVgSP7bWfEXw==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-linuxmusl-x64-napi/-/couchbase-linuxmusl-x64-napi-4.4.4.tgz", + "integrity": "sha512-I/yzOHY5Yxq7rtsNPJlc/d3E2Y4Nj85BIK3LM1SRvBe57kJBOJDckDg9xeMf9rB4YOfprFXYCWu2R4fPmNLBcg==", "cpu": [ "x64" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -21967,13 +21962,12 @@ } }, "node_modules/@couchbase/couchbase-win32-x64-napi": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@couchbase/couchbase-win32-x64-napi/-/couchbase-win32-x64-napi-4.4.3.tgz", - "integrity": "sha512-Pt66jVf6nNyN6DvY7PvzDFDA0pLyOyh4b63CSHFDcLVruF2Z9xwPpjvCiGSfk3N1zdod66ndLXMBo36Xo5UT0Q==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-win32-x64-napi/-/couchbase-win32-x64-napi-4.4.4.tgz", + "integrity": "sha512-DkOeEQzCE/SaKkKqe+hfN8PLc8b/UzhPn7ORsG03Xxisyme6KTd/U9DYRkNiENwT9PPx00/HdPnSgZmpXVkb0g==", "cpu": [ "x64" ], - "license": "Apache-2.0", "optional": true, "os": [ "win32" @@ -35576,11 +35570,34 @@ } }, "node_modules/couchbase": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/couchbase/-/couchbase-4.4.4.tgz", + "integrity": "sha512-4xBMNOvrD62lJCatzM9zhpKBbJ07MsRXx7zGEMcQYVNWX4bLNxI1jOYaOJnAvqG6gpo6sqFESrZbKzvkPBoUow==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "cmake-js": "^7.3.0", + "node-addon-api": "^8.0.0" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@couchbase/couchbase-darwin-arm64-napi": "4.4.4", + "@couchbase/couchbase-darwin-x64-napi": "4.4.4", + "@couchbase/couchbase-linux-arm64-napi": "4.4.4", + "@couchbase/couchbase-linux-x64-napi": "4.4.4", + "@couchbase/couchbase-linuxmusl-arm64-napi": "4.4.4", + "@couchbase/couchbase-linuxmusl-x64-napi": "4.4.4", + "@couchbase/couchbase-win32-x64-napi": "4.4.4" + } + }, + "node_modules/couchbase-v443": { + "name": "couchbase", "version": "4.4.3", "resolved": "https://registry.npmjs.org/couchbase/-/couchbase-4.4.3.tgz", "integrity": "sha512-J8lhlueKPmQGY51znMDEOBdVkfbOjZpArP8qW2VjktbV4AQWpeH81KwQvQqbso6BA1RKnxxUeiMyEt7PN6x+Wg==", "hasInstallScript": true, - "license": "Apache-2.0", "optional": true, "dependencies": { "cmake-js": "^7.3.0", @@ -35599,6 +35616,111 @@ "@couchbase/couchbase-win32-x64-napi": "4.4.3" } }, + "node_modules/couchbase-v443/node_modules/@couchbase/couchbase-darwin-arm64-napi": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-darwin-arm64-napi/-/couchbase-darwin-arm64-napi-4.4.3.tgz", + "integrity": "sha512-nH5282Pql5400P1zCOh70FVX4Yoaah2HMfA7ka3ZWB6M1h1C8VJ97gfUD/lsU5Pja8PekrLbZip3OARuieSeiw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/couchbase-v443/node_modules/@couchbase/couchbase-darwin-x64-napi": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-darwin-x64-napi/-/couchbase-darwin-x64-napi-4.4.3.tgz", + "integrity": "sha512-W9+x1CYkHG2jIdEeHZXo6D2IK8jq4JKVfo53D9AnqETFnO1W2Tf5+k2fuDz/NCHMfn4S9wXvAquwbhftE4DLEA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/couchbase-v443/node_modules/@couchbase/couchbase-linux-arm64-napi": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-linux-arm64-napi/-/couchbase-linux-arm64-napi-4.4.3.tgz", + "integrity": "sha512-meeLqhhT+/6tZk5wjp/6EitYbJ5yn7WniINvYV+dFy6lNas3ev/XVlT/w+toZ+Smo/+sBnR4EJhXFcvYj39+MQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/couchbase-v443/node_modules/@couchbase/couchbase-linux-x64-napi": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-linux-x64-napi/-/couchbase-linux-x64-napi-4.4.3.tgz", + "integrity": "sha512-++Q/dOrywzJ2Rb4hWFfHxAuZZU70EGf1OSkhhl+yiFRyg3/9eeuhTkDZNaVCpjEFr8mZKY2nz9JKs8f8yjd9AQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/couchbase-v443/node_modules/@couchbase/couchbase-linuxmusl-arm64-napi": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-linuxmusl-arm64-napi/-/couchbase-linuxmusl-arm64-napi-4.4.3.tgz", + "integrity": "sha512-H4Y6SADDHQMdUwJ5AXbQN3YCBnbhs4S7jAYSVBlS8blVNqrpNH+ZCLoyiwgLSjHEt5qs4U1ODd/S9zYUrYYxww==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/couchbase-v443/node_modules/@couchbase/couchbase-linuxmusl-x64-napi": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-linuxmusl-x64-napi/-/couchbase-linuxmusl-x64-napi-4.4.3.tgz", + "integrity": "sha512-NP4+puEW1nakxfXBhAL6I+hcj9iSSDF5NQ+W9sacnGEUQsPqqgqH293piAPEYj0WQrmtqPv4a3cVgSP7bWfEXw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/couchbase-v443/node_modules/@couchbase/couchbase-win32-x64-napi": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@couchbase/couchbase-win32-x64-napi/-/couchbase-win32-x64-napi-4.4.3.tgz", + "integrity": "sha512-Pt66jVf6nNyN6DvY7PvzDFDA0pLyOyh4b63CSHFDcLVruF2Z9xwPpjvCiGSfk3N1zdod66ndLXMBo36Xo5UT0Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, "node_modules/create-ecdh": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", diff --git a/package.json b/package.json index 268695a613..13a097ecd7 100644 --- a/package.json +++ b/package.json @@ -245,7 +245,8 @@ "yargs": "^17.5.1" }, "optionalDependencies": { - "couchbase": "^4.4.3", + "couchbase": "^4.4.4", + "couchbase-v443": "npm:couchbase@4.4.3", "ibm_db": "^3.2.5", "kafka-avro": "^3.2.0", "node-rdkafka": "^3.2.1", diff --git a/packages/collector/test/tracing/database/couchbase/app.js b/packages/collector/test/tracing/database/couchbase/app.js index 8490909cb2..052f062bd9 100644 --- a/packages/collector/test/tracing/database/couchbase/app.js +++ b/packages/collector/test/tracing/database/couchbase/app.js @@ -10,6 +10,8 @@ process.on('SIGTERM', () => { process.exit(0); }); +require('./mockVersion'); + require('../../../..')(); const couchbase = require('couchbase'); @@ -17,7 +19,6 @@ const bodyParser = require('body-parser'); const express = require('express'); const uuid = require('uuid'); const morgan = require('morgan'); -const fetch = require('node-fetch-v2'); const port = require('../../../test_util/app-port')(); const { delay } = require('../../../../../core/test/test_util'); const agentPort = process.env.INSTANA_AGENT_PORT; @@ -685,23 +686,28 @@ app.post('/queryindexes-callback', (req, res) => { scope2.query(qs1, err3 => { if (err3) return res.status(500).json({ err: err3.message }); - scope2.query(qs2, () => { - // ignore this error because we expect it to fail - - cluster.queryIndexes().dropIndex(bucket2.name, idx2, err5 => { - if (err5) return res.status(500).json({ err: err5.message }); - - cluster.queryIndexes().dropPrimaryIndex(bucket1.name, { name: idx1 }, err6 => { - if (err6) return res.status(500).json({ err: err6.message }); - - cluster.queryIndexes().getAllIndexes(bucket2.name, err7 => { - if (err7) return res.status(500).json({ err: err7.message }); - - res.json({ success: true }); + // Added a temporary promise catch handler due to an issue in the package. + // see https://github.com/couchbase/couchnode/issues/123 + scope2 + .query(qs2) + .catch(() => { + // Ignore this error because we expect it to fail + }) + .finally(() => { + cluster.queryIndexes().dropIndex(bucket2.name, idx2, err5 => { + if (err5) return res.status(500).json({ err: err5.message }); + + cluster.queryIndexes().dropPrimaryIndex(bucket1.name, { name: idx1 }, err6 => { + if (err6) return res.status(500).json({ err: err6.message }); + + cluster.queryIndexes().getAllIndexes(bucket2.name, err7 => { + if (err7) return res.status(500).json({ err: err7.message }); + + res.json({ success: true }); + }); }); }); }); - }); }); }); }); diff --git a/packages/collector/test/tracing/database/couchbase/app.mjs b/packages/collector/test/tracing/database/couchbase/app.mjs index c2e5d6c943..2cd623fc90 100644 --- a/packages/collector/test/tracing/database/couchbase/app.mjs +++ b/packages/collector/test/tracing/database/couchbase/app.mjs @@ -15,7 +15,6 @@ import bodyParser from 'body-parser'; import express from 'express'; import { v1 } from 'uuid'; import morgan from 'morgan'; -import fetch from 'node-fetch'; import portFactory from '../../../test_util/app-port.js'; import testUtil from '../../../../../core/test/test_util/index.js'; @@ -567,15 +566,22 @@ app.post('/queryindexes-callback', (req, res) => { cluster.queryIndexes().createIndex(bucket2.name, idx2, ['name'], () => { cluster.query(qs, () => { scope2.query(qs1, () => { - scope2.query(qs2, () => { - cluster.queryIndexes().dropIndex(bucket1.name, idx1, () => { - cluster.queryIndexes().dropPrimaryIndex(bucket2.name, { name: idx2 }, () => { - cluster.queryIndexes().getAllIndexes(bucket2.name, () => { - res.json({ success: true }); + // Added a temporary promise catch handler due to an issue in the package. + // see https://github.com/couchbase/couchnode/issues/123 + scope2 + .query(qs2) + .catch(() => { + // Ignore this error because we expect it to fail + }) + .finally(() => { + cluster.queryIndexes().dropIndex(bucket1.name, idx1, () => { + cluster.queryIndexes().dropPrimaryIndex(bucket2.name, { name: idx2 }, () => { + cluster.queryIndexes().getAllIndexes(bucket2.name, () => { + res.json({ success: true }); + }); }); }); }); - }); }); }); }); diff --git a/packages/collector/test/tracing/database/couchbase/mockVersion.js b/packages/collector/test/tracing/database/couchbase/mockVersion.js new file mode 100644 index 0000000000..4a25ea5208 --- /dev/null +++ b/packages/collector/test/tracing/database/couchbase/mockVersion.js @@ -0,0 +1,13 @@ +/* + * (c) Copyright IBM Corp. 2024 + */ + +'use strict'; + +const mock = require('mock-require'); +const COUCHBASE_VERSION = process.env.COUCHBASE_VERSION; +const COUCHBASE_REQUIRE = process.env.COUCHBASE_VERSION === 'latest' ? 'couchbase' : `couchbase-${COUCHBASE_VERSION}`; + +if (COUCHBASE_REQUIRE !== 'couchbase') { + mock('couchbase', COUCHBASE_REQUIRE); +} diff --git a/packages/collector/test/tracing/database/couchbase/test.js b/packages/collector/test/tracing/database/couchbase/test.js index 8f6087916e..43862cfc00 100644 --- a/packages/collector/test/tracing/database/couchbase/test.js +++ b/packages/collector/test/tracing/database/couchbase/test.js @@ -149,425 +149,89 @@ async function configureCouchbase() { } } -// NOTE: it takes 1-2 minutes till the couchbase server can be reached via docker -mochaSuiteFn('tracing/couchbase', function () { - this.timeout(config.getTestTimeout() * 4); +const couchbaseVersions = ['latest', 'v443']; - globalAgent.setUpCleanUpHooks(); - const agentControls = globalAgent.instance; +couchbaseVersions.forEach(version => { + // NOTE: it takes 1-2 minutes till the couchbase server can be reached via docker + mochaSuiteFn(`tracing/couchbase@${version}`, function () { + this.timeout(config.getTestTimeout() * 4); - let controls; + globalAgent.setUpCleanUpHooks(); + const agentControls = globalAgent.instance; - before(async () => { - await configureCouchbase(); + let controls; - controls = new ProcessControls({ - dirname: __dirname, - useGlobalAgent: true, - env: { - COUCHBASE_CONN_STR_1: connStr1, - COUCHBASE_CONN_STR_2: connStr2 - } + before(async () => { + await configureCouchbase(); + + controls = new ProcessControls({ + dirname: __dirname, + useGlobalAgent: true, + env: { + COUCHBASE_CONN_STR_1: connStr1, + COUCHBASE_CONN_STR_2: connStr2, + COUCHBASE_VERSION: version + } + }); + + // The operations for bootstrapping & cleanup can take a while. + await controls.startAndWaitForAgentConnection(1000, Date.now() + 60 * 1000); }); - // The operations for bootstrapping & cleanup can take a while. - await controls.startAndWaitForAgentConnection(1000, Date.now() + 60 * 1000); - }); + beforeEach(async () => { + await delay(2000); + await agentControls.clearReceivedTraceData(); + }); - beforeEach(async () => { - await delay(2000); - await agentControls.clearReceivedTraceData(); - }); + after(async () => { + await controls.stop(); + }); - after(async () => { - await controls.stop(); - }); + ['promise', 'callback'].forEach(apiType => { + describe(apiType, function () { + it('[crud] must trace get', () => + controls + .sendRequest({ + method: 'get', + path: `/get-${apiType}` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } - ['promise', 'callback'].forEach(apiType => { - describe(apiType, function () { - it('[crud] must trace get', () => - controls - .sendRequest({ - method: 'get', - path: `/get-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.result).to.eql({ foo: 1, bar: 2 }); - - return retry(() => - verifySpans(agentControls, controls, { - spanLength: 3, - verifyCustom: (entrySpan, spans) => { - expectExactlyOneMatching(spans, verifyCouchbaseSpan(controls, entrySpan)); - - expectExactlyOneMatching(spans, [ - span => expect(span.t).to.equal(entrySpan.t), - span => expect(span.p).to.equal(entrySpan.s), - span => expect(span.n).to.equal('node.http.client'), - span => expect(span.k).to.equal(constants.EXIT), - span => expect(span.f.e).to.equal(String(controls.getPid())), - span => expect(span.f.h).to.equal('agent-stub-uuid'), - span => expect(span.async).to.not.exist, - span => expect(span.error).to.not.exist, - span => expect(span.ec).to.equal(0), - span => expect(span.data.http.method).to.equal('GET'), - span => expect(span.data.http.url).to.contain('/'), - span => expect(span.data.http.status).to.equal(200) - ]); - } - }) - ); - })); - - it('[crud] must trace two different buckets', () => - controls - .sendRequest({ - method: 'get', - path: `/get-buckets-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.success).to.eql(true); - - return retry(() => - verifySpans(agentControls, controls, { - spanLength: 3, - verifyCustom: (entrySpan, spans) => { - expectExactlyOneMatching(spans, verifyCouchbaseSpan(controls, entrySpan)); - - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { bucket: 'companies', type: 'ephemeral', sql: 'INSERT' }) - ); - } - }) - ); - })); - - it('[crud] must trace getAndTouch', () => - controls - .sendRequest({ - method: 'post', - path: `/getAndTouch-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.success).to.eql(true); - return retry(() => verifySpans(agentControls, controls, { sql: 'GETANDTOUCH' })); - })); - - it('[crud] must trace replace', () => - controls - .sendRequest({ - method: 'post', - path: `/replace-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.result).to.eql('replacedvalue'); - - return retry(() => - verifySpans(agentControls, controls, { - spanLength: 3, - verifyCustom: (entrySpan, spans) => { - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { bucket: 'projects', type: 'membase', sql: 'REPLACE' }) - ); - - expectExactlyOneMatching(spans, verifyCouchbaseSpan(controls, entrySpan)); - } - }) - ); - })); - - it('[crud] must trace insert', () => - controls - .sendRequest({ - method: 'post', - path: `/insert-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.success).to.eql(true); - return retry(() => verifySpans(agentControls, controls, { sql: 'INSERT' })); - })); - - it('[crud] must trace upsert', () => - controls - .sendRequest({ - method: 'post', - path: `/upsert-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.success).to.eql(true); - return retry(() => verifySpans(agentControls, controls, { sql: 'UPSERT' })); - })); - - it('[crud] must trace mutateIn', () => - controls - .sendRequest({ - method: 'post', - path: `/mutateIn-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.success).to.eql(true); - return retry(() => verifySpans(agentControls, controls, { sql: 'MUTATEIN' })); - })); - - it('[crud] must trace lookupIn', () => - controls - .sendRequest({ - method: 'get', - path: `/lookupIn-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.result).to.eql(2); - return retry(() => verifySpans(agentControls, controls, { sql: 'LOOKUPIN' })); - })); - - it('[crud] must trace exists', () => - controls - .sendRequest({ - method: 'get', - path: `/exists-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.result).to.eql(true); - return retry(() => verifySpans(agentControls, controls, { sql: 'EXISTS' })); - })); - - it('[crud] must trace remove', () => - controls - .sendRequest({ - method: 'post', - path: `/remove-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.success).to.eql(true); - return retry(() => verifySpans(agentControls, controls, { sql: 'REMOVE' })); - })); - - it('[searchIndexes] must trace', () => - controls - .sendRequest({ - method: 'post', - path: `/searchindexes-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.success).to.eql(true); - - return retry(() => - verifySpans(agentControls, controls, { - spanLength: 5, - verifyCustom: (entrySpan, spans) => { - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: 'projects', - type: 'membase', - sql: 'UPSERTINDEX' - }) - ); - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: 'projects', - type: 'membase', - sql: 'GETINDEX' - }) - ); - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: 'projects', - type: 'membase', - sql: 'GETALLINDEXES' - }) - ); - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: '', - type: '', - sql: 'DROPINDEX' - }) - ); - } - }) - ); - })); - - it('[analyticsindexes] must trace', () => - controls - .sendRequest({ - method: 'post', - path: `/analyticsindexes-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.success).to.eql(true); - - return retry(() => - verifySpans(agentControls, controls, { - spanLength: 9, - verifyCustom: (entrySpan, spans) => { - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: 'projects', - type: 'membase', - sql: 'SELECT ' - }) - ); - expectExactlyNMatching( - spans, - 1, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: '', - type: '', - sql: 'CREATE DATAVERSE ' - }) - ); - expectExactlyNMatching( - spans, - 1, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: '', - type: '', - sql: 'CREATE DATASET ' - }) - ); - expectExactlyNMatching( - spans, - 1, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: '', - type: '', - sql: 'CREATE INDEX ' - }) - ); - expectExactlyNMatching( - spans, - 1, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: '', - type: '', - sql: 'SELECT' - }) - ); - expectExactlyNMatching( - spans, - 1, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: '', - type: '', - sql: 'DROP INDEX ' - }) - ); - expectExactlyNMatching( - spans, - 1, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: '', - type: '', - sql: 'DROP DATASET ' - }) - ); - expectExactlyNMatching( - spans, - 1, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: '', - type: '', - sql: 'DROP DATAVERSE ' - }) - ); - } - }) - ); - })); - - it('[searchquery] must trace', () => - controls - .sendRequest({ - method: 'get', - path: `/searchquery-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.success).to.eql(true); - - return retry(() => - verifySpans(agentControls, controls, { - spanLength: 4, - verifyCustom: (entrySpan, spans) => { - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: '', - type: '', - sql: 'SEARCHQUERY' - }) - ); - } - }) - ); - })); + expect(resp.result).to.eql({ foo: 1, bar: 2 }); + + return retry(() => + verifySpans(agentControls, controls, { + spanLength: 3, + verifyCustom: (entrySpan, spans) => { + expectExactlyOneMatching(spans, verifyCouchbaseSpan(controls, entrySpan)); + + expectExactlyOneMatching(spans, [ + span => expect(span.t).to.equal(entrySpan.t), + span => expect(span.p).to.equal(entrySpan.s), + span => expect(span.n).to.equal('node.http.client'), + span => expect(span.k).to.equal(constants.EXIT), + span => expect(span.f.e).to.equal(String(controls.getPid())), + span => expect(span.f.h).to.equal('agent-stub-uuid'), + span => expect(span.async).to.not.exist, + span => expect(span.error).to.not.exist, + span => expect(span.ec).to.equal(0), + span => expect(span.data.http.method).to.equal('GET'), + span => expect(span.data.http.url).to.contain('/'), + span => expect(span.data.http.status).to.equal(200) + ]); + } + }) + ); + })); - // NOTE: callbacks for transactions are not supported. - if (apiType === 'promise') { - it('must trace transactions', () => + it('[crud] must trace two different buckets', () => controls .sendRequest({ method: 'get', - path: `/transactions-${apiType}` + path: `/get-buckets-${apiType}` }) .then(resp => { if (resp.err) { @@ -578,45 +242,161 @@ mochaSuiteFn('tracing/couchbase', function () { return retry(() => verifySpans(agentControls, controls, { - spanLength: 5, + spanLength: 3, verifyCustom: (entrySpan, spans) => { + expectExactlyOneMatching(spans, verifyCouchbaseSpan(controls, entrySpan)); + expectExactlyOneMatching( spans, verifyCouchbaseSpan(controls, entrySpan, { - sql: 'GET' - }) - ); - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { + bucket: 'companies', + type: 'ephemeral', sql: 'INSERT' }) ); - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { - sql: 'REMOVE' - }) - ); + } + }) + ); + })); + + it('[crud] must trace getAndTouch', () => + controls + .sendRequest({ + method: 'post', + path: `/getAndTouch-${apiType}` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.success).to.eql(true); + return retry(() => verifySpans(agentControls, controls, { sql: 'GET AND TOUCH' })); + })); + + it('[crud] must trace replace', () => + controls + .sendRequest({ + method: 'post', + path: `/replace-${apiType}` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.result).to.eql('replacedvalue'); + return retry(() => + verifySpans(agentControls, controls, { + spanLength: 3, + verifyCustom: (entrySpan, spans) => { expectExactlyOneMatching( spans, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: '', - type: '', - sql: 'COMMIT' - }) + verifyCouchbaseSpan(controls, entrySpan, { bucket: 'projects', type: 'membase', sql: 'REPLACE' }) ); + + expectExactlyOneMatching(spans, verifyCouchbaseSpan(controls, entrySpan)); } }) ); })); - it('must trace transactions on rollback', () => + it('[crud] must trace insert', () => + controls + .sendRequest({ + method: 'post', + path: `/insert-${apiType}` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.success).to.eql(true); + return retry(() => verifySpans(agentControls, controls, { sql: 'INSERT' })); + })); + + it('[crud] must trace upsert', () => + controls + .sendRequest({ + method: 'post', + path: `/upsert-${apiType}` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.success).to.eql(true); + return retry(() => verifySpans(agentControls, controls, { sql: 'UPSERT' })); + })); + + it('[crud] must trace mutateIn', () => + controls + .sendRequest({ + method: 'post', + path: `/mutateIn-${apiType}` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.success).to.eql(true); + return retry(() => verifySpans(agentControls, controls, { sql: 'MUTATE IN' })); + })); + + it('[crud] must trace lookupIn', () => + controls + .sendRequest({ + method: 'get', + path: `/lookupIn-${apiType}` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.result).to.eql(2); + return retry(() => verifySpans(agentControls, controls, { sql: 'LOOKUP IN' })); + })); + + it('[crud] must trace exists', () => controls .sendRequest({ method: 'get', - path: `/transactions-${apiType}?rollback=true` + path: `/exists-${apiType}` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.result).to.eql(true); + return retry(() => verifySpans(agentControls, controls, { sql: 'EXISTS' })); + })); + + it('[crud] must trace remove', () => + controls + .sendRequest({ + method: 'post', + path: `/remove-${apiType}` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.success).to.eql(true); + return retry(() => verifySpans(agentControls, controls, { sql: 'REMOVE' })); + })); + + it('[searchIndexes] must trace', () => + controls + .sendRequest({ + method: 'post', + path: `/searchindexes-${apiType}` }) .then(resp => { if (resp.err) { @@ -627,18 +407,30 @@ mochaSuiteFn('tracing/couchbase', function () { return retry(() => verifySpans(agentControls, controls, { - spanLength: 4, + spanLength: 5, verifyCustom: (entrySpan, spans) => { expectExactlyOneMatching( spans, verifyCouchbaseSpan(controls, entrySpan, { - sql: 'GET' + bucket: 'projects', + type: 'membase', + sql: 'UPSERT INDEX' }) ); expectExactlyOneMatching( spans, verifyCouchbaseSpan(controls, entrySpan, { - sql: 'INSERT' + bucket: 'projects', + type: 'membase', + sql: 'GET INDEX' + }) + ); + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: 'projects', + type: 'membase', + sql: 'GET ALL INDEXES' }) ); expectExactlyOneMatching( @@ -646,115 +438,19 @@ mochaSuiteFn('tracing/couchbase', function () { verifyCouchbaseSpan(controls, entrySpan, { bucket: '', type: '', - sql: 'ROLLBACK' + sql: 'DROP INDEX' }) ); } }) ); })); - } - - it('[queryindexes] must trace', () => - controls - .sendRequest({ - method: 'post', - path: `/queryindexes-${apiType}` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.success).to.eql(true); - - return retry(() => - verifySpans(agentControls, controls, { - spanLength: 9, - verifyCustom: (entrySpan, spans) => { - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { - sql: 'CREATEINDEX' - }) - ); - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: 'companies', - type: 'ephemeral', - sql: 'CREATEINDEX' - }) - ); - - // cluster query - expectExactlyNMatching( - spans, - 1, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: '', - type: '', - sql: 'SELECT * FROM projects WHERE name=' - }) - ); - - // bucket query success - expectExactlyNMatching( - spans, - 1, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: 'companies', - type: 'ephemeral', - sql: 'SELECT * FROM _default WHERE name=' - }) - ); - - // bucket query error - expectExactlyNMatching( - spans, - 1, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: 'companies', - // FYI: this error msg does not come from us. - error: 'bucket not found', - type: 'ephemeral', - sql: 'SELECT * FROM TABLE_DOES_NOT_EXIST WHERE name' - }) - ); - - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { - sql: 'DROPINDEX' - }) - ); - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: 'companies', - type: 'ephemeral', - sql: 'DROPINDEX' - }) - ); - expectExactlyOneMatching( - spans, - verifyCouchbaseSpan(controls, entrySpan, { - bucket: 'companies', - type: 'ephemeral', - sql: 'GETALLINDEXES' - }) - ); - } - }) - ); - })); - if (apiType === 'promise') { - it('[multiple connections] must trace', () => + it('[analyticsindexes] must trace', () => controls .sendRequest({ - method: 'get', - path: `/multiple-connections-${apiType}` + method: 'post', + path: `/analyticsindexes-${apiType}` }) .then(resp => { if (resp.err) { @@ -765,68 +461,111 @@ mochaSuiteFn('tracing/couchbase', function () { return retry(() => verifySpans(agentControls, controls, { - spanLength: 5, + spanLength: 9, verifyCustom: (entrySpan, spans) => { - expectExactlyOneMatching( + expectExactlyNMatching( spans, + 1, verifyCouchbaseSpan(controls, entrySpan, { bucket: '', type: '', - sql: 'SELECT * FROM' + sql: 'CREATE DATAVERSE ' }) ); - expectExactlyOneMatching( + expectExactlyNMatching( spans, + 1, verifyCouchbaseSpan(controls, entrySpan, { bucket: '', - hostname: connStr2, type: '', - sql: 'SELECT * FROM' + sql: 'CREATE DATASET ' + }) + ); + expectExactlyNMatching( + spans, + 1, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: '', + type: '', + sql: 'CREATE INDEX ' + }) + ); + expectExactlyNMatching( + spans, + 1, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: '', + type: '', + sql: 'DROP INDEX ' + }) + ); + expectExactlyNMatching( + spans, + 1, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: '', + type: '', + sql: 'DROP DATASET ' + }) + ); + expectExactlyNMatching( + spans, + 1, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: '', + type: '', + sql: 'DROP DATAVERSE ' + }) + ); + + // It is difficult to get exact query from couchbase after v4.4.4 release + // as they are not generating queries from JS anymore, so we use the function name instead + // for v443, we use partial query statement + expectExactlyNMatching( + spans, + 1, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: '', + type: '', + sql: version === 'v443' ? 'SELECT ' : 'GET ALL INDEXES' + }) + ); + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: 'projects', + type: 'membase', + sql: version === 'v443' ? 'SELECT ' : 'GET ALL DATASETS ' }) ); } }) ); })); - } - if (apiType === 'promise') { - it('[datastructures list] must trace', () => + it('[searchquery] must trace', () => controls .sendRequest({ method: 'get', - path: `/datastructures-list-${apiType}` + path: `/searchquery-${apiType}` }) .then(resp => { if (resp.err) { throw new Error(resp.err); } - expect(resp.iteratedItems).to.eql(['test1', 'test2']); + expect(resp.success).to.eql(true); return retry(() => verifySpans(agentControls, controls, { - spanLength: 9, + spanLength: 4, verifyCustom: (entrySpan, spans) => { - expectExactlyNMatching( - spans, - 3, - verifyCouchbaseSpan(controls, entrySpan, { - sql: 'MUTATEIN' - }) - ); - expectExactlyNMatching( - spans, - 2, - verifyCouchbaseSpan(controls, entrySpan, { - sql: 'LOOKUPIN' - }) - ); - expectExactlyNMatching( + expectExactlyOneMatching( spans, - 3, verifyCouchbaseSpan(controls, entrySpan, { - sql: 'GET' + bucket: '', + type: '', + sql: 'SEARCH QUERY' }) ); } @@ -834,80 +573,356 @@ mochaSuiteFn('tracing/couchbase', function () { ); })); - it('[datastructures map] must trace', () => + // NOTE: callbacks for transactions are not supported. + if (apiType === 'promise') { + it('must trace transactions', () => + controls + .sendRequest({ + method: 'get', + path: `/transactions-${apiType}` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.success).to.eql(true); + + return retry(() => + verifySpans(agentControls, controls, { + spanLength: 5, + verifyCustom: (entrySpan, spans) => { + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + sql: 'GET' + }) + ); + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + sql: 'INSERT' + }) + ); + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + sql: 'REMOVE' + }) + ); + + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: '', + type: '', + sql: 'COMMIT' + }) + ); + } + }) + ); + })); + + it('must trace transactions on rollback', () => + controls + .sendRequest({ + method: 'get', + path: `/transactions-${apiType}?rollback=true` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.success).to.eql(true); + + return retry(() => + verifySpans(agentControls, controls, { + spanLength: 4, + verifyCustom: (entrySpan, spans) => { + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + sql: 'GET' + }) + ); + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + sql: 'INSERT' + }) + ); + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: '', + type: '', + sql: 'ROLLBACK' + }) + ); + } + }) + ); + })); + } + + it('[queryindexes] must trace', () => controls .sendRequest({ - method: 'get', - path: `/datastructures-map-${apiType}` + method: 'post', + path: `/queryindexes-${apiType}` }) .then(resp => { if (resp.err) { throw new Error(resp.err); } - expect(resp.iteratedItems).to.eql(['test1', 'test2']); + expect(resp.success).to.eql(true); return retry(() => verifySpans(agentControls, controls, { spanLength: 9, verifyCustom: (entrySpan, spans) => { + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + sql: 'CREATE INDEX' + }) + ); + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: 'companies', + type: 'ephemeral', + sql: 'CREATE INDEX' + }) + ); + + // cluster query expectExactlyNMatching( spans, - 3, + 1, verifyCouchbaseSpan(controls, entrySpan, { - sql: 'MUTATEIN' + bucket: '', + type: '', + sql: 'SELECT * FROM projects WHERE name=' }) ); + + // bucket query success expectExactlyNMatching( spans, - 3, + 1, verifyCouchbaseSpan(controls, entrySpan, { - sql: 'LOOKUPIN' + bucket: 'companies', + type: 'ephemeral', + sql: 'SELECT * FROM _default WHERE name=' }) ); + + // bucket query error expectExactlyNMatching( spans, - 2, + 1, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: 'companies', + // FYI: this error msg does not come from us. + error: 'bucket not found', + type: 'ephemeral', + sql: 'SELECT * FROM TABLE_DOES_NOT_EXIST WHERE name' + }) + ); + + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + sql: 'DROP INDEX' + }) + ); + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: 'companies', + type: 'ephemeral', + sql: 'DROP INDEX' + }) + ); + expectExactlyOneMatching( + spans, verifyCouchbaseSpan(controls, entrySpan, { - sql: 'GET' + bucket: 'companies', + type: 'ephemeral', + sql: 'GET ALL INDEXES' }) ); } }) ); })); - } - - it('[error] must trace remove', () => - controls - .sendRequest({ - method: 'post', - path: `/remove-${apiType}?error=true` - }) - .then(resp => { - if (resp.err) { - throw new Error(resp.err); - } - - expect(resp.errMsg).to.eql(apiType === 'promise' ? 'invalid argument' : 'document not found'); - - return retry(() => - verifySpans(agentControls, controls, { - sql: 'REMOVE', - error: apiType === 'promise' ? 'invalid argument' : 'document not found' + + if (apiType === 'promise') { + it('[multiple connections] must trace', () => + controls + .sendRequest({ + method: 'get', + path: `/multiple-connections-${apiType}` }) - ); - })); - - it('[supressed] must not trace', () => - controls - .sendRequest({ - method: 'post', - path: `/upsert-${apiType}`, - suppressTracing: true - }) - .then(() => delay(DELAY_TIMEOUT_IN_MS)) - .then(() => retry(() => verifySpans(agentControls, controls, { expectSpans: false })))); + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.success).to.eql(true); + + return retry(() => + verifySpans(agentControls, controls, { + spanLength: 5, + verifyCustom: (entrySpan, spans) => { + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: '', + type: '', + sql: 'SELECT * FROM' + }) + ); + expectExactlyOneMatching( + spans, + verifyCouchbaseSpan(controls, entrySpan, { + bucket: '', + hostname: connStr2, + type: '', + sql: 'SELECT * FROM' + }) + ); + } + }) + ); + })); + } + + if (apiType === 'promise') { + it('[datastructures list] must trace', () => + controls + .sendRequest({ + method: 'get', + path: `/datastructures-list-${apiType}` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.iteratedItems).to.eql(['test1', 'test2']); + + return retry(() => + verifySpans(agentControls, controls, { + spanLength: 9, + verifyCustom: (entrySpan, spans) => { + expectExactlyNMatching( + spans, + 3, + verifyCouchbaseSpan(controls, entrySpan, { + sql: 'MUTATE IN' + }) + ); + expectExactlyNMatching( + spans, + 2, + verifyCouchbaseSpan(controls, entrySpan, { + sql: 'LOOKUP IN' + }) + ); + expectExactlyNMatching( + spans, + 3, + verifyCouchbaseSpan(controls, entrySpan, { + sql: 'GET' + }) + ); + } + }) + ); + })); + + it('[datastructures map] must trace', () => + controls + .sendRequest({ + method: 'get', + path: `/datastructures-map-${apiType}` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.iteratedItems).to.eql(['test1', 'test2']); + + return retry(() => + verifySpans(agentControls, controls, { + spanLength: 9, + verifyCustom: (entrySpan, spans) => { + expectExactlyNMatching( + spans, + 3, + verifyCouchbaseSpan(controls, entrySpan, { + sql: 'MUTATE IN' + }) + ); + expectExactlyNMatching( + spans, + 3, + verifyCouchbaseSpan(controls, entrySpan, { + sql: 'LOOKUP IN' + }) + ); + expectExactlyNMatching( + spans, + 2, + verifyCouchbaseSpan(controls, entrySpan, { + sql: 'GET' + }) + ); + } + }) + ); + })); + + // Error handling in callback is affected with v4.4.4, + // see issue here: https://github.com/couchbase/couchnode/issues/123 + it('[error] must trace remove', () => + controls + .sendRequest({ + method: 'post', + path: `/remove-${apiType}?error=true` + }) + .then(resp => { + if (resp.err) { + throw new Error(resp.err); + } + + expect(resp.errMsg).to.eql(apiType === 'promise' ? 'invalid argument' : 'document not found'); + + return retry(() => + verifySpans(agentControls, controls, { + sql: 'REMOVE', + error: apiType === 'promise' ? 'invalid argument' : 'document not found' + }) + ); + })); + } + + it('[supressed] must not trace', () => + controls + .sendRequest({ + method: 'post', + path: `/upsert-${apiType}`, + suppressTracing: true + }) + .then(() => delay(DELAY_TIMEOUT_IN_MS)) + .then(() => retry(() => verifySpans(agentControls, controls, { expectSpans: false })))); + }); }); }); }); diff --git a/packages/core/src/tracing/instrumentation/database/couchbase.js b/packages/core/src/tracing/instrumentation/database/couchbase.js index 8817a52962..fa8efff99e 100644 --- a/packages/core/src/tracing/instrumentation/database/couchbase.js +++ b/packages/core/src/tracing/instrumentation/database/couchbase.js @@ -23,6 +23,8 @@ exports.deactivate = function deactivate() { isActive = false; }; +let instrumentLatest = false; + exports.init = function init() { hook.onModuleLoad('couchbase', instrument); @@ -46,6 +48,10 @@ exports.init = function init() { // CRUD operations: // https://github.com/couchbase/couchnode/blob/e855b094cd1b0140ffefc40f32a828b9134d181c/src/connection_autogen.cpp#L210 function instrument(cb) { + // RawBinaryTranscoder function is added in v4.4.4, + // so inorder to check version, we can rely on this logic + instrumentLatest = typeof cb.RawBinaryTranscoder === 'function'; + // NOTE: we could instrument `cb.Collection.prototype` here, but we want to instrument each cluster connection. shimmer.wrap(cb, 'connect', instrumentConnect); } @@ -86,7 +92,11 @@ function instrumentCluster(cluster, connectionStr) { if (!cluster) return; // #### SEARCH QUERY - shimmer.wrap(cluster, 'searchQuery', instrumentOperation.bind(null, { connectionStr, sql: 'SEARCHQUERY' })); + shimmer.wrap( + cluster, + 'searchQuery', + instrumentOperation.bind(null, { connectionStr, sql: camelCaseToUpperWords('searchQuery') }) + ); // #### CRUD instrumentCollection(cluster, connectionStr); @@ -117,28 +127,39 @@ function instrumentCluster(cluster, connectionStr) { }; }); - // #### ANALYTICS SERVICE - shimmer.wrap(cluster, 'analyticsQuery', function instanaClusterAnalyticsQuery(original) { - return function instanaClusterAnalyticsQueryWrapped() { - const originalThis = this; - const originalArgs = arguments; - const sqlStatement = originalArgs[0] || ''; - - return instrumentOperation( - { - connectionStr, - sql: tracingUtil.shortenDatabaseStatement(sqlStatement), - resultHandler: (span, result) => { - if (result && result.rows && result.rows.length > 0 && result.rows[0].BucketName) { - span.data.couchbase.bucket = result.rows[0].BucketName; - span.data.couchbase.type = bucketLookup[span.data.couchbase.bucket]; + // v4.4.4 introduced a breaking code change. + // We are no longer able to extract the SQL statements for "analyticsindexes". + // see changes: https://github.com/couchbase/couchnode/commit/b8118b8dd05c710e0f0d898ba8e16372028ea294 + if (instrumentLatest) { + // #### ANALYTICS SERVICES (.analyticsIndexes().) v >= 4.4.4 + instrumentAnalyticsIndexes(cluster, connectionStr); + } else { + // #### ANALYTICS SERVICE (.analyticsIndexes().) v <= 4.4.3 + // TODO: Remove the logic for v4.4.3 upon v5 release + shimmer.wrap(cluster, 'analyticsQuery', function instanaClusterAnalyticsQuery(original) { + return function instanaClusterAnalyticsQueryWrapped() { + const originalThis = this; + const originalArgs = arguments; + const sqlStatement = originalArgs[0] || ''; + + return instrumentOperation( + { + connectionStr, + sql: tracingUtil.shortenDatabaseStatement(sqlStatement), + resultHandler: (span, result) => { + // response structure in v4.4.3, + // we need to check inside result.rows for the data here + if (result && result.rows && result.rows.length > 0 && result.rows[0].BucketName) { + span.data.couchbase.bucket = result.rows[0].BucketName; + span.data.couchbase.type = bucketLookup[span.data.couchbase.bucket]; + } } - } - }, - original - ).apply(originalThis, originalArgs); - }; - }); + }, + original + ).apply(originalThis, originalArgs); + }; + }); + } } function instrumentCollection(cluster, connectionStr) { @@ -173,7 +194,7 @@ function instrumentCollection(cluster, connectionStr) { connectionStr, bucketName, getBucketTypeFn, - sql: op.toUpperCase() + sql: camelCaseToUpperWords(op) }, original ).apply(this, arguments); @@ -238,7 +259,7 @@ function instrumentSearchIndexes(cluster, connectionStr) { return instrumentOperation( { connectionStr, - sql: fnName.toUpperCase(), + sql: camelCaseToUpperWords(fnName), bucketName, getBucketTypeFn, resultHandler: (span, result) => { @@ -283,7 +304,7 @@ function instrumentQueryIndexes(cluster, connectionStr) { return instrumentOperation( { connectionStr, - sql: fnName.toUpperCase(), + sql: camelCaseToUpperWords(fnName), bucketName, getBucketTypeFn }, @@ -297,6 +318,50 @@ function instrumentQueryIndexes(cluster, connectionStr) { }; } +function instrumentAnalyticsIndexes(cluster, connectionStr) { + const origAnalyticsIndex = cluster.analyticsIndexes; + + cluster.analyticsIndexes = function instanaAnalyticsIndexes() { + const analyticsIndexes = origAnalyticsIndex.apply(this, arguments); + + [ + 'createIndex', + 'getAllIndexes', + 'dropIndex', + 'createDataverse', + 'dropDataverse', + 'createDataset', + 'getAllDatasets', + 'dropDataset' + ].forEach(fnName => { + shimmer.wrap(analyticsIndexes, fnName, function instanaInstrumentOperationWrapped(original) { + return function instanaInstrumentOperationWrappedInner() { + const originalThis = this; + const originalArgs = arguments; + + return instrumentOperation( + { + connectionStr, + sql: camelCaseToUpperWords(fnName), + resultHandler: (span, result) => { + // response structure is different in v4.4.4, + // we can check for bucketName directly inside the result here + if (result && Array.isArray(result) && result.length > 0 && result[0].bucketName) { + span.data.couchbase.bucket = result[0].bucketName; + span.data.couchbase.type = bucketLookup[span.data.couchbase.bucket]; + } + } + }, + original + ).apply(originalThis, originalArgs); + }; + }); + }); + + return analyticsIndexes; + }; +} + function instrumentTransactions(cluster, connectionStr) { const origTransactions = cluster.transactions; cluster.transactions = function instanaTransactions() { @@ -333,7 +398,7 @@ function instrumentTransactions(cluster, connectionStr) { connectionStr, bucketName, getBucketTypeFn, - sql: op.toUpperCase() + sql: camelCaseToUpperWords(op) }, original ).apply(originalThis1, originalArgs1); @@ -493,3 +558,8 @@ function getBucketType(c, n) { return bucketType; }; } + +// converts the operation into query format in uppercase +function camelCaseToUpperWords(op) { + return `${op.replace(/([a-z])([A-Z])/g, '$1 $2').toUpperCase()} `; +}