diff --git a/detectors/node/opentelemetry-resource-detector-alibaba-cloud/src/detectors/AlibabaCloudEcsDetector.ts b/detectors/node/opentelemetry-resource-detector-alibaba-cloud/src/detectors/AlibabaCloudEcsDetector.ts index 776e5dbccd..1fae739467 100644 --- a/detectors/node/opentelemetry-resource-detector-alibaba-cloud/src/detectors/AlibabaCloudEcsDetector.ts +++ b/detectors/node/opentelemetry-resource-detector-alibaba-cloud/src/detectors/AlibabaCloudEcsDetector.ts @@ -15,8 +15,9 @@ */ import { - Detector, + DetectorSync, Resource, + ResourceAttributes, ResourceDetectionConfig, } from '@opentelemetry/resources'; import { @@ -38,7 +39,7 @@ import * as http from 'http'; * AlibabaCloud ECS and return a {@link Resource} populated with metadata about * the ECS instance. Returns an empty Resource if detection fails. */ -class AlibabaCloudEcsDetector implements Detector { +class AlibabaCloudEcsDetector implements DetectorSync { /** * See https://www.alibabacloud.com/help/doc-detail/67254.htm for * documentation about the AlibabaCloud instance identity document. @@ -57,26 +58,40 @@ class AlibabaCloudEcsDetector implements Detector { * * @param config (unused) The resource detection config */ - async detect(_config?: ResourceDetectionConfig): Promise { - const { - 'owner-account-id': accountId, - 'instance-id': instanceId, - 'instance-type': instanceType, - 'region-id': region, - 'zone-id': availabilityZone, - } = await this._fetchIdentity(); - const hostname = await this._fetchHost(); + detect(_config?: ResourceDetectionConfig): Resource { + return new Resource({}, this._getAttributes()); + } - return new Resource({ - [SEMRESATTRS_CLOUD_PROVIDER]: CLOUDPROVIDERVALUES_ALIBABA_CLOUD, - [SEMRESATTRS_CLOUD_PLATFORM]: CLOUDPLATFORMVALUES_ALIBABA_CLOUD_ECS, - [SEMRESATTRS_CLOUD_ACCOUNT_ID]: accountId, - [SEMRESATTRS_CLOUD_REGION]: region, - [SEMRESATTRS_CLOUD_AVAILABILITY_ZONE]: availabilityZone, - [SEMRESATTRS_HOST_ID]: instanceId, - [SEMRESATTRS_HOST_TYPE]: instanceType, - [SEMRESATTRS_HOST_NAME]: hostname, - }); + /** Gets identity and host info and returns them as attribs. Empty object if fails */ + async _getAttributes( + _config?: ResourceDetectionConfig + ): Promise { + let attribs: ResourceAttributes; + try { + const { + 'owner-account-id': accountId, + 'instance-id': instanceId, + 'instance-type': instanceType, + 'region-id': region, + 'zone-id': availabilityZone, + } = await this._fetchIdentity(); + const hostname = await this._fetchHost(); + + attribs = { + [SEMRESATTRS_CLOUD_PROVIDER]: CLOUDPROVIDERVALUES_ALIBABA_CLOUD, + [SEMRESATTRS_CLOUD_PLATFORM]: CLOUDPLATFORMVALUES_ALIBABA_CLOUD_ECS, + [SEMRESATTRS_CLOUD_ACCOUNT_ID]: accountId, + [SEMRESATTRS_CLOUD_REGION]: region, + [SEMRESATTRS_CLOUD_AVAILABILITY_ZONE]: availabilityZone, + [SEMRESATTRS_HOST_ID]: instanceId, + [SEMRESATTRS_HOST_TYPE]: instanceType, + [SEMRESATTRS_HOST_NAME]: hostname, + }; + } catch { + attribs = {}; + } + + return attribs; } /** diff --git a/detectors/node/opentelemetry-resource-detector-alibaba-cloud/test/detectors/AlibabaCloudEcsDetector.test.ts b/detectors/node/opentelemetry-resource-detector-alibaba-cloud/test/detectors/AlibabaCloudEcsDetector.test.ts index c4a2c41d36..b09d0f8ae2 100644 --- a/detectors/node/opentelemetry-resource-detector-alibaba-cloud/test/detectors/AlibabaCloudEcsDetector.test.ts +++ b/detectors/node/opentelemetry-resource-detector-alibaba-cloud/test/detectors/AlibabaCloudEcsDetector.test.ts @@ -64,6 +64,7 @@ describe('alibabaCloudEcsDetector', () => { .reply(200, () => mockedHostResponse); const resource: Resource = await alibabaCloudEcsDetector.detect(); + await resource.waitForAsyncAttributes?.(); scope.done(); @@ -84,8 +85,7 @@ describe('alibabaCloudEcsDetector', () => { }); describe('with unsuccessful request', () => { - it('should throw when receiving error response code', async () => { - const expectedError = new Error('Failed to load page, status code: 404'); + it('should return empty resource when receiving error response code', async () => { const scope = nock(ALIYUN_HOST) .persist() .get(ALIYUN_IDENTITY_PATH) @@ -93,18 +93,15 @@ describe('alibabaCloudEcsDetector', () => { .get(ALIYUN_HOST_PATH) .reply(404, () => new Error()); - try { - await alibabaCloudEcsDetector.detect(); - assert.ok(false, 'Expected to throw'); - } catch (err) { - assert.deepStrictEqual(err, expectedError); - } + const resource = await alibabaCloudEcsDetector.detect(); + await resource.waitForAsyncAttributes?.(); + + assert.deepStrictEqual(resource.attributes, {}); scope.done(); }); - it('should throw when timed out', async () => { - const expectedError = new Error('ECS metadata api request timed out.'); + it('should return empty resource when timed out', async () => { const scope = nock(ALIYUN_HOST) .get(ALIYUN_IDENTITY_PATH) .reply(200, () => mockedIdentityResponse) @@ -112,28 +109,23 @@ describe('alibabaCloudEcsDetector', () => { .delayConnection(2000) .reply(200, () => mockedHostResponse); - try { - await alibabaCloudEcsDetector.detect(); - assert.ok(false, 'Expected to throw'); - } catch (err) { - assert.deepStrictEqual(err, expectedError); - } + const resource = await alibabaCloudEcsDetector.detect(); + await resource.waitForAsyncAttributes?.(); + + assert.deepStrictEqual(resource.attributes, {}); scope.done(); }); - it('should throw when replied with an Error', async () => { - const expectedError = new Error('NOT FOUND'); + it('should return empty resource when replied with an Error', async () => { const scope = nock(ALIYUN_HOST) .get(ALIYUN_IDENTITY_PATH) - .replyWithError(expectedError.message); - - try { - await alibabaCloudEcsDetector.detect(); - assert.ok(false, 'Expected to throw'); - } catch (err) { - assert.deepStrictEqual(err, expectedError); - } + .replyWithError('NOT FOUND'); + + const resource = await alibabaCloudEcsDetector.detect(); + await resource.waitForAsyncAttributes?.(); + + assert.deepStrictEqual(resource.attributes, {}); scope.done(); }); diff --git a/detectors/node/opentelemetry-resource-detector-alibaba-cloud/test/fixtures/use-alibaba-cloud-ecs-detector.js b/detectors/node/opentelemetry-resource-detector-alibaba-cloud/test/fixtures/use-alibaba-cloud-ecs-detector.js new file mode 100644 index 0000000000..3074ef6003 --- /dev/null +++ b/detectors/node/opentelemetry-resource-detector-alibaba-cloud/test/fixtures/use-alibaba-cloud-ecs-detector.js @@ -0,0 +1,86 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Usage: +// node use-gcp-detector.js + +const { createTestNodeSdk } = require('@opentelemetry/contrib-test-utils'); +const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); +const { alibabaCloudEcsDetector } = require('../../build/src/index.js'); + + +const sdk = createTestNodeSdk({ + serviceName: 'use-detector-alibaba-cloud-ecs', + instrumentations: [ + new HttpInstrumentation(), + ], + resourceDetectors: [alibabaCloudEcsDetector], +}); + +sdk.start(); + +const http = require('http'); + +const server = http.createServer((req, res) => { + console.log('incoming request: %s %s %s', req.method, req.url, req.headers); + + req.resume(); + req.on('end', function () { + const body = 'pong'; + res.writeHead(200, { + 'content-type': 'text/plain', + 'content-length': body.length, + }); + res.end(body); + }); +}); + +server.listen(0, '127.0.0.1', async function () { + const port = server.address().port; + + // First request to show a client error. + const startTime = Date.now(); + await new Promise((resolve) => { + const clientReq = http.request( + `http://127.0.0.1:${port}/ping`, + function (cres) { + console.log( + 'client response: %s %s', + cres.statusCode, + cres.headers + ); + const chunks = []; + cres.on('data', function (chunk) { + chunks.push(chunk); + }); + cres.on('end', function () { + const body = chunks.join(''); + console.log('client response body: %j', body); + resolve(); + }); + } + ); + clientReq.write('ping'); + clientReq.end(); + }); + + // flush any left spans + // NOTE: this adds extra requests but its necessary to make sure + // spans have the resouce and are queued in the exporter + await alibabaCloudEcsDetector.detect().waitForAsyncAttributes(); + await sdk.shutdown(); + await new Promise(resolve => server.close(resolve)); +});