Skip to content

Commit

Permalink
refactor(detector-alibaba): change implementation to DetectorSync int…
Browse files Browse the repository at this point in the history
…erface
  • Loading branch information
david-luna committed Jul 12, 2024
1 parent 5f2c160 commit 3389dcc
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
*/

import {
Detector,
DetectorSync,
Resource,
ResourceAttributes,
ResourceDetectionConfig,
} from '@opentelemetry/resources';
import {
Expand All @@ -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.
Expand All @@ -57,26 +58,40 @@ class AlibabaCloudEcsDetector implements Detector {
*
* @param config (unused) The resource detection config
*/
async detect(_config?: ResourceDetectionConfig): Promise<Resource> {
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<ResourceAttributes> {
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;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ describe('alibabaCloudEcsDetector', () => {
.reply(200, () => mockedHostResponse);

const resource: Resource = await alibabaCloudEcsDetector.detect();
await resource.waitForAsyncAttributes?.();

scope.done();

Expand All @@ -84,56 +85,47 @@ 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)
.reply(200, () => mockedIdentityResponse)
.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)
.get(ALIYUN_HOST_PATH)
.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();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
});

0 comments on commit 3389dcc

Please sign in to comment.