From 7deb1f7f019229dfcc4caf3d9562303af99df125 Mon Sep 17 00:00:00 2001 From: EdZou <1018766866@qq.com> Date: Wed, 19 Aug 2020 12:55:00 -0500 Subject: [PATCH 1/9] refactor: rebase repo and move ECS detector --- .../src/detectors/AwsEcsDetector.ts | 92 ++++++++ .../src/detectors/index.ts | 1 + .../test/detectors/AwsEcsDetector.test.ts | 199 ++++++++++++++++++ .../opentelemetry-resources/src/constants.ts | 3 + .../test/resource-assertions.test.ts | 2 + .../test/util/resource-assertions.ts | 6 + 6 files changed, 303 insertions(+) create mode 100644 packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts create mode 100644 packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts diff --git a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts new file mode 100644 index 0000000000..103eff3c46 --- /dev/null +++ b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts @@ -0,0 +1,92 @@ +/* + * 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. + */ + +import { + Detector, + Resource, + ResourceDetectionConfigWithLogger, + CONTAINER_RESOURCE, +} from '@opentelemetry/resources'; +import * as util from 'util'; +import * as fs from 'fs'; +import * as os from 'os'; + +/** + * The AwsEcsDetector can be used to detect if a process is running in AWS + * ECS and return a {@link Resource} populated with data about the ECS + * plugins of AWS X-Ray. Returns an empty Resource if detection fails. + */ +class AwsEcsDetector implements Detector { + readonly CONTAINER_ID_LENGTH = 64; + readonly DEFAULT_CGROUP_PATH = '/proc/self/cgroup'; + readonly readFileAsync = util.promisify(fs.readFile); + + async detect(config: ResourceDetectionConfigWithLogger): Promise { + if ( + !process.env.ECS_CONTAINER_METADATA_URI_V4 && + !process.env.ECS_CONTAINER_METADATA_URI + ) { + config.logger.debug('AwsEcsDetector failed: Process is not on ECS'); + return Resource.empty(); + } + + let hostName, containerId; + try { + hostName = os.hostname(); + } catch (e) { + config.logger.warn( + `AwsEcsDetector failed to read host name: ${e.message}` + ); + } + try { + const rawData = await this.readFileAsync( + this.DEFAULT_CGROUP_PATH, + 'utf8' + ); + containerId = this._getContainerId(rawData); + } catch (e) { + config.logger.warn( + `AwsEcsDetector failed to read container ID: ${e.message}` + ); + } + + return !hostName && !containerId + ? Resource.empty() + : new Resource({ + [CONTAINER_RESOURCE.NAME]: hostName || '', + [CONTAINER_RESOURCE.ID]: containerId || '', + }); + } + /** + * Read container ID from cgroup file + * In ECS, even if we fail to find target file + * or target file does not contain container ID + * we do not throw an error but throw warning message + * and then return null string + */ + private _getContainerId(rawData: string): string { + const splitData = rawData.trim().split('\n'); + + for (const str of splitData) { + if (str.length > this.CONTAINER_ID_LENGTH) { + return str.substring(str.length - this.CONTAINER_ID_LENGTH); + } + } + return ''; + } +} + +export const awsEcsDetector = new AwsEcsDetector(); diff --git a/packages/opentelemetry-resource-detector-aws/src/detectors/index.ts b/packages/opentelemetry-resource-detector-aws/src/detectors/index.ts index 215622744f..ba2e6dd903 100644 --- a/packages/opentelemetry-resource-detector-aws/src/detectors/index.ts +++ b/packages/opentelemetry-resource-detector-aws/src/detectors/index.ts @@ -15,3 +15,4 @@ */ export * from './AwsEc2Detector'; +export * from './AwsEcsDetector'; diff --git a/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts new file mode 100644 index 0000000000..de4cee3f99 --- /dev/null +++ b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts @@ -0,0 +1,199 @@ +/* + * 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. + */ + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { awsEcsDetector } from '../../src/detectors/AwsEcsDetector'; +import { + assertEmptyResource, + assertContainerResource, +} from '@opentelemetry/resources/test/util/resource-assertions'; +import { NoopLogger } from '@opentelemetry/core'; +import * as os from 'os'; + +describe('BeanstalkResourceDetector', () => { + const errorMsg = { + fileNotFoundError: new Error('cannot find cgroup file'), + hostNameError: new Error('failed to obtain host name'), + }; + + const correctCgroupData = + 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm'; + const noisyCgroupData = `\n\n\n abcdefghijklmnopqrstuvwxyz \n ${correctCgroupData}`; + const multiValidCgroupData = `${correctCgroupData}\nabc${correctCgroupData}\nbcd${correctCgroupData}`; + const hostNameData = 'abcd.test.testing.com'; + + let readStub, hostStub; + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + process.env.ECS_CONTAINER_METADATA_URI_V4 = ''; + process.env.ECS_CONTAINER_METADATA_URI = ''; + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should successfully return resource data', async () => { + process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; + hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); + readStub = sandbox + .stub(awsEcsDetector, 'readFileAsync') + .resolves(correctCgroupData); + + const resource = await awsEcsDetector.detect({ + logger: new NoopLogger(), + }); + + sandbox.assert.calledOnce(hostStub); + sandbox.assert.calledOnce(readStub); + assert.ok(resource); + assertContainerResource(resource, { + name: 'abcd.test.testing.com', + id: 'bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm', + }); + }); + + it('should successfully return resource data with noisy cgroup file', async () => { + process.env.ECS_CONTAINER_METADATA_URI = 'ecs_metadata_v3_uri'; + hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); + readStub = sandbox + .stub(awsEcsDetector, 'readFileAsync') + .resolves(noisyCgroupData); + + const resource = await awsEcsDetector.detect({ + logger: new NoopLogger(), + }); + + sandbox.assert.calledOnce(hostStub); + sandbox.assert.calledOnce(readStub); + assert.ok(resource); + assertContainerResource(resource, { + name: 'abcd.test.testing.com', + id: 'bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm', + }); + }); + + it('should always return first valid line of data', async () => { + process.env.ECS_CONTAINER_METADATA_URI = 'ecs_metadata_v3_uri'; + hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); + readStub = sandbox + .stub(awsEcsDetector, 'readFileAsync') + .resolves(multiValidCgroupData); + + const resource = await awsEcsDetector.detect({ + logger: new NoopLogger(), + }); + + sandbox.assert.calledOnce(hostStub); + // sandbox.assert.calledOnce(readStub); + assert.ok(resource); + assertContainerResource(resource, { + name: 'abcd.test.testing.com', + id: 'bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm', + }); + }); + + it('should empty resource without environmental variable', async () => { + hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); + readStub = sandbox + .stub(awsEcsDetector, 'readFileAsync') + .resolves(correctCgroupData); + + const resource = await awsEcsDetector.detect({ + logger: new NoopLogger(), + }); + + sandbox.assert.notCalled(hostStub); + sandbox.assert.notCalled(readStub); + assert.ok(resource); + assertEmptyResource(resource); + }); + + it('should return resource only with hostname attribute without cgroup file', async () => { + process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; + hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); + readStub = sandbox + .stub(awsEcsDetector, 'readFileAsync') + .rejects(errorMsg.fileNotFoundError); + + const resource = await awsEcsDetector.detect({ + logger: new NoopLogger(), + }); + + sandbox.assert.calledOnce(hostStub); + sandbox.assert.calledOnce(readStub); + assert.ok(resource); + assertContainerResource(resource, { + name: 'abcd.test.testing.com', + }); + }); + + it('should return resource only with hostname attribute when cgroup file does not contain valid container ID', async () => { + process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; + hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); + readStub = sandbox.stub(awsEcsDetector, 'readFileAsync').resolves(''); + + const resource = await awsEcsDetector.detect({ + logger: new NoopLogger(), + }); + + sandbox.assert.calledOnce(hostStub); + // sandbox.assert.calledOnce(readStub); + assert.ok(resource); + assertContainerResource(resource, { + name: 'abcd.test.testing.com', + }); + }); + + it('should return resource only with container ID attribute without hostname', async () => { + process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; + hostStub = sandbox.stub(os, 'hostname').throws(errorMsg.hostNameError); + readStub = sandbox + .stub(awsEcsDetector, 'readFileAsync') + .resolves(correctCgroupData); + + const resource = await awsEcsDetector.detect({ + logger: new NoopLogger(), + }); + + sandbox.assert.calledOnce(hostStub); + sandbox.assert.calledOnce(readStub); + assert.ok(resource); + assertContainerResource(resource, { + id: 'bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm', + }); + }); + + it('should return empty resource when both hostname and container ID are invalid', async () => { + process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; + hostStub = sandbox.stub(os, 'hostname').throws(errorMsg.hostNameError); + readStub = sandbox + .stub(awsEcsDetector, 'readFileAsync') + .rejects(errorMsg.fileNotFoundError); + + const resource = await awsEcsDetector.detect({ + logger: new NoopLogger(), + }); + + sandbox.assert.calledOnce(hostStub); + sandbox.assert.calledOnce(readStub); + assert.ok(resource); + assertEmptyResource(resource); + }); +}); diff --git a/packages/opentelemetry-resources/src/constants.ts b/packages/opentelemetry-resources/src/constants.ts index d60b604522..c90315ecf4 100644 --- a/packages/opentelemetry-resources/src/constants.ts +++ b/packages/opentelemetry-resources/src/constants.ts @@ -35,6 +35,9 @@ export const CONTAINER_RESOURCE = { /** The container name. */ NAME: 'container.name', + /** The container id. */ + ID: 'container.id', + /** The name of the image the container was built on. */ IMAGE_NAME: 'container.image.name', diff --git a/packages/opentelemetry-resources/test/resource-assertions.test.ts b/packages/opentelemetry-resources/test/resource-assertions.test.ts index c3a99f5998..01952f3839 100644 --- a/packages/opentelemetry-resources/test/resource-assertions.test.ts +++ b/packages/opentelemetry-resources/test/resource-assertions.test.ts @@ -66,11 +66,13 @@ describe('assertContainerResource', () => { it('validates optional attributes', () => { const resource = new Resource({ [CONTAINER_RESOURCE.NAME]: 'opentelemetry-autoconf', + [CONTAINER_RESOURCE.ID]: 'abc', [CONTAINER_RESOURCE.IMAGE_NAME]: 'gcr.io/opentelemetry/operator', [CONTAINER_RESOURCE.IMAGE_TAG]: '0.1', }); assertContainerResource(resource, { name: 'opentelemetry-autoconf', + id: 'abc', imageName: 'gcr.io/opentelemetry/operator', imageTag: '0.1', }); diff --git a/packages/opentelemetry-resources/test/util/resource-assertions.ts b/packages/opentelemetry-resources/test/util/resource-assertions.ts index 422415537c..a252340294 100644 --- a/packages/opentelemetry-resources/test/util/resource-assertions.ts +++ b/packages/opentelemetry-resources/test/util/resource-assertions.ts @@ -74,6 +74,7 @@ export const assertContainerResource = ( resource: Resource, validations: { name?: string; + id?: string; imageName?: string; imageTag?: string; } @@ -84,6 +85,11 @@ export const assertContainerResource = ( resource.attributes[CONTAINER_RESOURCE.NAME], validations.name ); + if (validations.id) + assert.strictEqual( + resource.attributes[CONTAINER_RESOURCE.ID], + validations.id + ); if (validations.imageName) assert.strictEqual( resource.attributes[CONTAINER_RESOURCE.IMAGE_NAME], From 5e39613d8d318275b5d03a323aacf7d14bef0ac2 Mon Sep 17 00:00:00 2001 From: EdZou <1018766866@qq.com> Date: Wed, 19 Aug 2020 13:24:05 -0500 Subject: [PATCH 2/9] refactor: move Beanstalk detector to aws repo --- .../src/detectors/AwsBeanstalkDetector.ts | 76 +++++++++++ .../src/detectors/index.ts | 1 + .../detectors/AwsBeanstalkDetector.test.ts | 126 ++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 packages/opentelemetry-resource-detector-aws/src/detectors/AwsBeanstalkDetector.ts create mode 100644 packages/opentelemetry-resource-detector-aws/test/detectors/AwsBeanstalkDetector.test.ts diff --git a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsBeanstalkDetector.ts b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsBeanstalkDetector.ts new file mode 100644 index 0000000000..b6ab7cb629 --- /dev/null +++ b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsBeanstalkDetector.ts @@ -0,0 +1,76 @@ +/* + * 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. + */ + +import { + Detector, + Resource, + SERVICE_RESOURCE, + ResourceDetectionConfigWithLogger, +} from '@opentelemetry/resources'; +import * as fs from 'fs'; +import * as util from 'util'; + +/** + * The AwsBeanstalkDetector can be used to detect if a process is running in AWS Elastic + * Beanstalk and return a {@link Resource} populated with data about the beanstalk + * plugins of AWS X-Ray. Returns an empty Resource if detection fails. + * + * See https://docs.amazonaws.cn/en_us/xray/latest/devguide/xray-guide.pdf + * for more details about detecting information of Elastic Beanstalk plugins + */ + +const DEFAULT_BEANSTALK_CONF_PATH = + '/var/elasticbeanstalk/xray/environment.conf'; +const WIN_OS_BEANSTALK_CONF_PATH = + 'C:\\Program Files\\Amazon\\XRay\\environment.conf'; + +class AwsBeanstalkDetector implements Detector { + BEANSTALK_CONF_PATH: string; + readonly readFileAsync = util.promisify(fs.readFile); + readonly fileAccessAsync = util.promisify(fs.access); + + constructor() { + if (process.platform === 'win32') { + this.BEANSTALK_CONF_PATH = WIN_OS_BEANSTALK_CONF_PATH; + } else { + this.BEANSTALK_CONF_PATH = DEFAULT_BEANSTALK_CONF_PATH; + } + } + + async detect(config: ResourceDetectionConfigWithLogger): Promise { + try { + await this.fileAccessAsync(this.BEANSTALK_CONF_PATH, fs.constants.R_OK); + + const rawData = await this.readFileAsync( + this.BEANSTALK_CONF_PATH, + 'utf8' + ); + const parsedData = JSON.parse(rawData); + + return new Resource({ + [SERVICE_RESOURCE.NAME]: 'elastic_beanstalk', + [SERVICE_RESOURCE.NAMESPACE]: parsedData.environment_name, + [SERVICE_RESOURCE.VERSION]: parsedData.version_label, + [SERVICE_RESOURCE.INSTANCE_ID]: parsedData.deployment_id, + }); + } catch (e) { + config.logger.debug(`AwsBeanstalkDetector failed: ${e.message}`); + return Resource.empty(); + } + } +} + +export const awsBeanstalkDetector = new AwsBeanstalkDetector(); diff --git a/packages/opentelemetry-resource-detector-aws/src/detectors/index.ts b/packages/opentelemetry-resource-detector-aws/src/detectors/index.ts index 215622744f..4bd440dd06 100644 --- a/packages/opentelemetry-resource-detector-aws/src/detectors/index.ts +++ b/packages/opentelemetry-resource-detector-aws/src/detectors/index.ts @@ -15,3 +15,4 @@ */ export * from './AwsEc2Detector'; +export * from './AwsBeanstalkDetector'; diff --git a/packages/opentelemetry-resource-detector-aws/test/detectors/AwsBeanstalkDetector.test.ts b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsBeanstalkDetector.test.ts new file mode 100644 index 0000000000..934e98a1eb --- /dev/null +++ b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsBeanstalkDetector.test.ts @@ -0,0 +1,126 @@ +/* + * 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. + */ + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { awsBeanstalkDetector } from '../../src'; +import { + assertEmptyResource, + assertServiceResource, +} from '@opentelemetry/resources/test/util/resource-assertions'; +import { NoopLogger } from '@opentelemetry/core'; + +describe('BeanstalkResourceDetector', () => { + const err = new Error('failed to read config file'); + const data = { + version_label: 'app-5a56-170119_190650-stage-170119_190650', + deployment_id: '32', + environment_name: 'scorekeep', + }; + const noisyData = { + noise: 'noise', + version_label: 'app-5a56-170119_190650-stage-170119_190650', + deployment_id: '32', + environment_name: 'scorekeep', + }; + + let readStub, fileStub; + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should successfully return resource data', async () => { + fileStub = sandbox.stub(awsBeanstalkDetector, 'fileAccessAsync').resolves(); + readStub = sandbox + .stub(awsBeanstalkDetector, 'readFileAsync') + .resolves(JSON.stringify(data)); + sandbox.stub(JSON, 'parse').returns(data); + + const resource = await awsBeanstalkDetector.detect({ + logger: new NoopLogger(), + }); + + sandbox.assert.calledOnce(fileStub); + sandbox.assert.calledOnce(readStub); + assert.ok(resource); + assertServiceResource(resource, { + name: 'elastic_beanstalk', + namespace: 'scorekeep', + version: 'app-5a56-170119_190650-stage-170119_190650', + instanceId: '32', + }); + }); + + it('should successfully return resource data with noise', async () => { + fileStub = sandbox.stub(awsBeanstalkDetector, 'fileAccessAsync').resolves(); + readStub = sandbox + .stub(awsBeanstalkDetector, 'readFileAsync') + .resolves(JSON.stringify(noisyData)); + sandbox.stub(JSON, 'parse').returns(noisyData); + + const resource = await awsBeanstalkDetector.detect({ + logger: new NoopLogger(), + }); + + sandbox.assert.calledOnce(fileStub); + sandbox.assert.calledOnce(readStub); + assert.ok(resource); + assertServiceResource(resource, { + name: 'elastic_beanstalk', + namespace: 'scorekeep', + version: 'app-5a56-170119_190650-stage-170119_190650', + instanceId: '32', + }); + }); + + it('should return empty resource when failing to read file', async () => { + fileStub = sandbox.stub(awsBeanstalkDetector, 'fileAccessAsync').resolves(); + readStub = sandbox.stub(awsBeanstalkDetector, 'readFileAsync').rejects(err); + + const resource = await awsBeanstalkDetector.detect({ + logger: new NoopLogger(), + }); + + sandbox.assert.calledOnce(fileStub); + sandbox.assert.calledOnce(readStub); + assert.ok(resource); + assertEmptyResource(resource); + }); + + it('should return empty resource when config file does not exist', async () => { + fileStub = sandbox + .stub(awsBeanstalkDetector, 'fileAccessAsync') + .rejects(err); + readStub = sandbox + .stub(awsBeanstalkDetector, 'readFileAsync') + .resolves(JSON.stringify(data)); + + const resource = await awsBeanstalkDetector.detect({ + logger: new NoopLogger(), + }); + + sandbox.assert.calledOnce(fileStub); + sandbox.assert.notCalled(readStub); + assert.ok(resource); + assertEmptyResource(resource); + }); +}); From 9d1303bb980f1e7147ed3c5df1f578fe6ed7cdff Mon Sep 17 00:00:00 2001 From: EdZou <1018766866@qq.com> Date: Thu, 20 Aug 2020 14:33:00 -0500 Subject: [PATCH 3/9] refactor: make promisify private static member for testing --- .../src/detectors/AwsBeanstalkDetector.ts | 13 ++++++---- .../detectors/AwsBeanstalkDetector.test.ts | 26 ++++++++++++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsBeanstalkDetector.ts b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsBeanstalkDetector.ts index b6ab7cb629..f50225857b 100644 --- a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsBeanstalkDetector.ts +++ b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsBeanstalkDetector.ts @@ -37,10 +37,10 @@ const DEFAULT_BEANSTALK_CONF_PATH = const WIN_OS_BEANSTALK_CONF_PATH = 'C:\\Program Files\\Amazon\\XRay\\environment.conf'; -class AwsBeanstalkDetector implements Detector { +export class AwsBeanstalkDetector implements Detector { BEANSTALK_CONF_PATH: string; - readonly readFileAsync = util.promisify(fs.readFile); - readonly fileAccessAsync = util.promisify(fs.access); + private static readFileAsync = util.promisify(fs.readFile); + private static fileAccessAsync = util.promisify(fs.access); constructor() { if (process.platform === 'win32') { @@ -52,9 +52,12 @@ class AwsBeanstalkDetector implements Detector { async detect(config: ResourceDetectionConfigWithLogger): Promise { try { - await this.fileAccessAsync(this.BEANSTALK_CONF_PATH, fs.constants.R_OK); + await AwsBeanstalkDetector.fileAccessAsync( + this.BEANSTALK_CONF_PATH, + fs.constants.R_OK + ); - const rawData = await this.readFileAsync( + const rawData = await AwsBeanstalkDetector.readFileAsync( this.BEANSTALK_CONF_PATH, 'utf8' ); diff --git a/packages/opentelemetry-resource-detector-aws/test/detectors/AwsBeanstalkDetector.test.ts b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsBeanstalkDetector.test.ts index 934e98a1eb..9ccebd7066 100644 --- a/packages/opentelemetry-resource-detector-aws/test/detectors/AwsBeanstalkDetector.test.ts +++ b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsBeanstalkDetector.test.ts @@ -16,7 +16,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import { awsBeanstalkDetector } from '../../src'; +import { awsBeanstalkDetector, AwsBeanstalkDetector } from '../../src'; import { assertEmptyResource, assertServiceResource, @@ -49,9 +49,11 @@ describe('BeanstalkResourceDetector', () => { }); it('should successfully return resource data', async () => { - fileStub = sandbox.stub(awsBeanstalkDetector, 'fileAccessAsync').resolves(); + fileStub = sandbox + .stub(AwsBeanstalkDetector, 'fileAccessAsync' as any) + .resolves(); readStub = sandbox - .stub(awsBeanstalkDetector, 'readFileAsync') + .stub(AwsBeanstalkDetector, 'readFileAsync' as any) .resolves(JSON.stringify(data)); sandbox.stub(JSON, 'parse').returns(data); @@ -71,9 +73,11 @@ describe('BeanstalkResourceDetector', () => { }); it('should successfully return resource data with noise', async () => { - fileStub = sandbox.stub(awsBeanstalkDetector, 'fileAccessAsync').resolves(); + fileStub = sandbox + .stub(AwsBeanstalkDetector, 'fileAccessAsync' as any) + .resolves(); readStub = sandbox - .stub(awsBeanstalkDetector, 'readFileAsync') + .stub(AwsBeanstalkDetector, 'readFileAsync' as any) .resolves(JSON.stringify(noisyData)); sandbox.stub(JSON, 'parse').returns(noisyData); @@ -93,8 +97,12 @@ describe('BeanstalkResourceDetector', () => { }); it('should return empty resource when failing to read file', async () => { - fileStub = sandbox.stub(awsBeanstalkDetector, 'fileAccessAsync').resolves(); - readStub = sandbox.stub(awsBeanstalkDetector, 'readFileAsync').rejects(err); + fileStub = sandbox + .stub(AwsBeanstalkDetector, 'fileAccessAsync' as any) + .resolves(); + readStub = sandbox + .stub(AwsBeanstalkDetector, 'readFileAsync' as any) + .rejects(err); const resource = await awsBeanstalkDetector.detect({ logger: new NoopLogger(), @@ -108,10 +116,10 @@ describe('BeanstalkResourceDetector', () => { it('should return empty resource when config file does not exist', async () => { fileStub = sandbox - .stub(awsBeanstalkDetector, 'fileAccessAsync') + .stub(AwsBeanstalkDetector, 'fileAccessAsync' as any) .rejects(err); readStub = sandbox - .stub(awsBeanstalkDetector, 'readFileAsync') + .stub(AwsBeanstalkDetector, 'readFileAsync' as any) .resolves(JSON.stringify(data)); const resource = await awsBeanstalkDetector.detect({ From 794b3375aaa9c3ebf32dc2b7a26f687b58a6ceef Mon Sep 17 00:00:00 2001 From: EdZou <1018766866@qq.com> Date: Thu, 20 Aug 2020 14:42:37 -0500 Subject: [PATCH 4/9] refactor: make promisified method to private static member --- .../src/detectors/AwsEcsDetector.ts | 6 ++--- .../test/detectors/AwsEcsDetector.test.ts | 23 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts index 103eff3c46..049087b0f8 100644 --- a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts +++ b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts @@ -29,10 +29,10 @@ import * as os from 'os'; * ECS and return a {@link Resource} populated with data about the ECS * plugins of AWS X-Ray. Returns an empty Resource if detection fails. */ -class AwsEcsDetector implements Detector { +export class AwsEcsDetector implements Detector { readonly CONTAINER_ID_LENGTH = 64; readonly DEFAULT_CGROUP_PATH = '/proc/self/cgroup'; - readonly readFileAsync = util.promisify(fs.readFile); + private static readFileAsync = util.promisify(fs.readFile); async detect(config: ResourceDetectionConfigWithLogger): Promise { if ( @@ -52,7 +52,7 @@ class AwsEcsDetector implements Detector { ); } try { - const rawData = await this.readFileAsync( + const rawData = await AwsEcsDetector.readFileAsync( this.DEFAULT_CGROUP_PATH, 'utf8' ); diff --git a/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts index de4cee3f99..d3994721ef 100644 --- a/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts +++ b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts @@ -16,7 +16,10 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import { awsEcsDetector } from '../../src/detectors/AwsEcsDetector'; +import { + awsEcsDetector, + AwsEcsDetector, +} from '../../src/detectors/AwsEcsDetector'; import { assertEmptyResource, assertContainerResource, @@ -53,7 +56,7 @@ describe('BeanstalkResourceDetector', () => { process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); readStub = sandbox - .stub(awsEcsDetector, 'readFileAsync') + .stub(AwsEcsDetector, 'readFileAsync' as any) .resolves(correctCgroupData); const resource = await awsEcsDetector.detect({ @@ -73,7 +76,7 @@ describe('BeanstalkResourceDetector', () => { process.env.ECS_CONTAINER_METADATA_URI = 'ecs_metadata_v3_uri'; hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); readStub = sandbox - .stub(awsEcsDetector, 'readFileAsync') + .stub(AwsEcsDetector, 'readFileAsync' as any) .resolves(noisyCgroupData); const resource = await awsEcsDetector.detect({ @@ -93,7 +96,7 @@ describe('BeanstalkResourceDetector', () => { process.env.ECS_CONTAINER_METADATA_URI = 'ecs_metadata_v3_uri'; hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); readStub = sandbox - .stub(awsEcsDetector, 'readFileAsync') + .stub(AwsEcsDetector, 'readFileAsync' as any) .resolves(multiValidCgroupData); const resource = await awsEcsDetector.detect({ @@ -112,7 +115,7 @@ describe('BeanstalkResourceDetector', () => { it('should empty resource without environmental variable', async () => { hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); readStub = sandbox - .stub(awsEcsDetector, 'readFileAsync') + .stub(AwsEcsDetector, 'readFileAsync' as any) .resolves(correctCgroupData); const resource = await awsEcsDetector.detect({ @@ -129,7 +132,7 @@ describe('BeanstalkResourceDetector', () => { process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); readStub = sandbox - .stub(awsEcsDetector, 'readFileAsync') + .stub(AwsEcsDetector, 'readFileAsync' as any) .rejects(errorMsg.fileNotFoundError); const resource = await awsEcsDetector.detect({ @@ -147,7 +150,9 @@ describe('BeanstalkResourceDetector', () => { it('should return resource only with hostname attribute when cgroup file does not contain valid container ID', async () => { process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); - readStub = sandbox.stub(awsEcsDetector, 'readFileAsync').resolves(''); + readStub = sandbox + .stub(AwsEcsDetector, 'readFileAsync' as any) + .resolves(''); const resource = await awsEcsDetector.detect({ logger: new NoopLogger(), @@ -165,7 +170,7 @@ describe('BeanstalkResourceDetector', () => { process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; hostStub = sandbox.stub(os, 'hostname').throws(errorMsg.hostNameError); readStub = sandbox - .stub(awsEcsDetector, 'readFileAsync') + .stub(AwsEcsDetector, 'readFileAsync' as any) .resolves(correctCgroupData); const resource = await awsEcsDetector.detect({ @@ -184,7 +189,7 @@ describe('BeanstalkResourceDetector', () => { process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; hostStub = sandbox.stub(os, 'hostname').throws(errorMsg.hostNameError); readStub = sandbox - .stub(awsEcsDetector, 'readFileAsync') + .stub(AwsEcsDetector, 'readFileAsync' as any) .rejects(errorMsg.fileNotFoundError); const resource = await awsEcsDetector.detect({ From 1d28da3d0490000d6b3c50fac445ba8846894a2d Mon Sep 17 00:00:00 2001 From: EdZou <1018766866@qq.com> Date: Thu, 20 Aug 2020 16:37:52 -0500 Subject: [PATCH 5/9] refactor: re-write getContainerId and getHostName method --- .../src/detectors/AwsEcsDetector.ts | 69 +++++++++++-------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts index 049087b0f8..17a00eb962 100644 --- a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts +++ b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts @@ -43,25 +43,10 @@ export class AwsEcsDetector implements Detector { return Resource.empty(); } - let hostName, containerId; - try { - hostName = os.hostname(); - } catch (e) { - config.logger.warn( - `AwsEcsDetector failed to read host name: ${e.message}` - ); - } - try { - const rawData = await AwsEcsDetector.readFileAsync( - this.DEFAULT_CGROUP_PATH, - 'utf8' - ); - containerId = this._getContainerId(rawData); - } catch (e) { - config.logger.warn( - `AwsEcsDetector failed to read container ID: ${e.message}` - ); - } + const [hostName, containerId] = await Promise.all([ + this._getHostName(config), + this._getContainerId(config), + ]); return !hostName && !containerId ? Resource.empty() @@ -70,6 +55,23 @@ export class AwsEcsDetector implements Detector { [CONTAINER_RESOURCE.ID]: containerId || '', }); } + + private _getHostName( + config: ResourceDetectionConfigWithLogger + ): Promise { + return new Promise(resolve => { + try { + const hostName = os.hostname(); + resolve(hostName); + } catch (e) { + config.logger.warn( + `AwsEcsDetector failed to read container ID: ${e.message}` + ); + resolve(undefined); + } + }); + } + /** * Read container ID from cgroup file * In ECS, even if we fail to find target file @@ -77,15 +79,28 @@ export class AwsEcsDetector implements Detector { * we do not throw an error but throw warning message * and then return null string */ - private _getContainerId(rawData: string): string { - const splitData = rawData.trim().split('\n'); + private async _getContainerId( + config: ResourceDetectionConfigWithLogger + ): Promise { + return new Promise(resolve => { + AwsEcsDetector.readFileAsync(this.DEFAULT_CGROUP_PATH, 'utf8') + .then(rawData => { + const splitData = rawData.trim().split('\n'); - for (const str of splitData) { - if (str.length > this.CONTAINER_ID_LENGTH) { - return str.substring(str.length - this.CONTAINER_ID_LENGTH); - } - } - return ''; + for (const str of splitData) { + if (str.length > this.CONTAINER_ID_LENGTH) { + resolve(str.substring(str.length - this.CONTAINER_ID_LENGTH)); + } + } + resolve(undefined); + }) + .catch(e => { + config.logger.warn( + `AwsEcsDetector failed to read container ID: ${e.message}` + ); + resolve(undefined); + }); + }); } } From bbd792f46d012fd13a74778a2de5504b4245fdb6 Mon Sep 17 00:00:00 2001 From: EdZou <1018766866@qq.com> Date: Fri, 21 Aug 2020 13:45:21 -0500 Subject: [PATCH 6/9] refactor: refactor _getContainerId and _getHostName --- .../src/detectors/AwsEcsDetector.ts | 46 ++++++++----------- .../test/detectors/AwsEcsDetector.test.ts | 5 +- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts index 17a00eb962..cd57646380 100644 --- a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts +++ b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts @@ -60,15 +60,7 @@ export class AwsEcsDetector implements Detector { config: ResourceDetectionConfigWithLogger ): Promise { return new Promise(resolve => { - try { - const hostName = os.hostname(); - resolve(hostName); - } catch (e) { - config.logger.warn( - `AwsEcsDetector failed to read container ID: ${e.message}` - ); - resolve(undefined); - } + resolve(os.hostname()); }); } @@ -82,25 +74,23 @@ export class AwsEcsDetector implements Detector { private async _getContainerId( config: ResourceDetectionConfigWithLogger ): Promise { - return new Promise(resolve => { - AwsEcsDetector.readFileAsync(this.DEFAULT_CGROUP_PATH, 'utf8') - .then(rawData => { - const splitData = rawData.trim().split('\n'); - - for (const str of splitData) { - if (str.length > this.CONTAINER_ID_LENGTH) { - resolve(str.substring(str.length - this.CONTAINER_ID_LENGTH)); - } - } - resolve(undefined); - }) - .catch(e => { - config.logger.warn( - `AwsEcsDetector failed to read container ID: ${e.message}` - ); - resolve(undefined); - }); - }); + try { + const rawData = await AwsEcsDetector.readFileAsync( + this.DEFAULT_CGROUP_PATH, + 'utf8' + ); + const splitData = rawData.trim().split('\n'); + for (const str of splitData) { + if (str.length > this.CONTAINER_ID_LENGTH) { + return str.substring(str.length - this.CONTAINER_ID_LENGTH); + } + } + } catch (e) { + config.logger.warn( + `AwsEcsDetector failed to read container ID: ${e.message}` + ); + } + return undefined; } } diff --git a/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts index d3994721ef..b58721d371 100644 --- a/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts +++ b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts @@ -30,7 +30,6 @@ import * as os from 'os'; describe('BeanstalkResourceDetector', () => { const errorMsg = { fileNotFoundError: new Error('cannot find cgroup file'), - hostNameError: new Error('failed to obtain host name'), }; const correctCgroupData = @@ -168,7 +167,7 @@ describe('BeanstalkResourceDetector', () => { it('should return resource only with container ID attribute without hostname', async () => { process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; - hostStub = sandbox.stub(os, 'hostname').throws(errorMsg.hostNameError); + hostStub = sandbox.stub(os, 'hostname').returns(''); readStub = sandbox .stub(AwsEcsDetector, 'readFileAsync' as any) .resolves(correctCgroupData); @@ -187,7 +186,7 @@ describe('BeanstalkResourceDetector', () => { it('should return empty resource when both hostname and container ID are invalid', async () => { process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; - hostStub = sandbox.stub(os, 'hostname').throws(errorMsg.hostNameError); + hostStub = sandbox.stub(os, 'hostname').returns(''); readStub = sandbox .stub(AwsEcsDetector, 'readFileAsync' as any) .rejects(errorMsg.fileNotFoundError); From 5ab11baecb8e1cb629583923d615d7c6d6e160a3 Mon Sep 17 00:00:00 2001 From: EdZou <1018766866@qq.com> Date: Fri, 21 Aug 2020 14:37:07 -0500 Subject: [PATCH 7/9] refactor: refactor hostname method --- .../src/detectors/AwsEcsDetector.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts index cd57646380..5614f476dd 100644 --- a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts +++ b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts @@ -43,10 +43,8 @@ export class AwsEcsDetector implements Detector { return Resource.empty(); } - const [hostName, containerId] = await Promise.all([ - this._getHostName(config), - this._getContainerId(config), - ]); + const hostName = os.hostname(); + const containerId = await this._getContainerId(config); return !hostName && !containerId ? Resource.empty() @@ -56,14 +54,6 @@ export class AwsEcsDetector implements Detector { }); } - private _getHostName( - config: ResourceDetectionConfigWithLogger - ): Promise { - return new Promise(resolve => { - resolve(os.hostname()); - }); - } - /** * Read container ID from cgroup file * In ECS, even if we fail to find target file From 24619238e3ec4a04f8a08b3854126c97201d9f15 Mon Sep 17 00:00:00 2001 From: EdZou <1018766866@qq.com> Date: Thu, 27 Aug 2020 14:36:08 -0500 Subject: [PATCH 8/9] chore: modified test case --- .../src/detectors/index.ts | 1 + .../test/detectors/AwsEcsDetector.test.ts | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/opentelemetry-resource-detector-aws/src/detectors/index.ts b/packages/opentelemetry-resource-detector-aws/src/detectors/index.ts index 4bd440dd06..01986175c3 100644 --- a/packages/opentelemetry-resource-detector-aws/src/detectors/index.ts +++ b/packages/opentelemetry-resource-detector-aws/src/detectors/index.ts @@ -16,3 +16,4 @@ export * from './AwsEc2Detector'; export * from './AwsBeanstalkDetector'; +export * from './AwsEcsDetector'; diff --git a/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts index b58721d371..e8bed2b3e5 100644 --- a/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts +++ b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts @@ -34,8 +34,10 @@ describe('BeanstalkResourceDetector', () => { const correctCgroupData = 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm'; + const unexpectedCgroupdata = + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const noisyCgroupData = `\n\n\n abcdefghijklmnopqrstuvwxyz \n ${correctCgroupData}`; - const multiValidCgroupData = `${correctCgroupData}\nabc${correctCgroupData}\nbcd${correctCgroupData}`; + const multiValidCgroupData = `${unexpectedCgroupdata}\n${correctCgroupData}\nbcd${unexpectedCgroupdata}`; const hostNameData = 'abcd.test.testing.com'; let readStub, hostStub; @@ -103,7 +105,7 @@ describe('BeanstalkResourceDetector', () => { }); sandbox.assert.calledOnce(hostStub); - // sandbox.assert.calledOnce(readStub); + sandbox.assert.calledOnce(readStub); assert.ok(resource); assertContainerResource(resource, { name: 'abcd.test.testing.com', @@ -158,7 +160,7 @@ describe('BeanstalkResourceDetector', () => { }); sandbox.assert.calledOnce(hostStub); - // sandbox.assert.calledOnce(readStub); + sandbox.assert.calledOnce(readStub); assert.ok(resource); assertContainerResource(resource, { name: 'abcd.test.testing.com', From 20f219dace9045e17210012913c6cec459f772e6 Mon Sep 17 00:00:00 2001 From: EdZou <1018766866@qq.com> Date: Thu, 27 Aug 2020 14:57:23 -0500 Subject: [PATCH 9/9] fix: delete weird directory from nowhere --- .../opentelemetry-exporter-collector/src/platform/node/protos | 1 - 1 file changed, 1 deletion(-) delete mode 160000 packages/opentelemetry-exporter-collector/src/platform/node/protos diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/protos b/packages/opentelemetry-exporter-collector/src/platform/node/protos deleted file mode 160000 index 9ffeee0ec5..0000000000 --- a/packages/opentelemetry-exporter-collector/src/platform/node/protos +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9ffeee0ec532efe02285af84880deb2a53a3eab1