From 1b6487eb19448b2bd523d5f1999ad10b5db5018f Mon Sep 17 00:00:00 2001 From: Michele Mancioppi Date: Sat, 29 Oct 2022 17:21:38 +0200 Subject: [PATCH 1/3] feat: implement cloud.provider, cloud.platform, aws.ecs, aws.log in ECS Detector --- src/Aws/src/Ecs/Detector.php | 126 ++++++++-- src/Aws/tests/Unit/Ecs/DetectorTest.php | 230 ++++++++++++++++-- .../metadatav4-response-container-ec2.json | 44 ++++ ...sponse-container-fargate-logsfirelens.json | 44 ++++ ...metadatav4-response-container-fargate.json | 50 ++++ .../Ecs/metadatav4-response-task-ec2.json | 94 +++++++ ...v4-response-task-fargate-logsfirelens.json | 71 ++++++ .../Ecs/metadatav4-response-task-fargate.json | 77 ++++++ 8 files changed, 697 insertions(+), 39 deletions(-) create mode 100644 src/Aws/tests/Unit/Ecs/metadatav4-response-container-ec2.json create mode 100644 src/Aws/tests/Unit/Ecs/metadatav4-response-container-fargate-logsfirelens.json create mode 100644 src/Aws/tests/Unit/Ecs/metadatav4-response-container-fargate.json create mode 100644 src/Aws/tests/Unit/Ecs/metadatav4-response-task-ec2.json create mode 100644 src/Aws/tests/Unit/Ecs/metadatav4-response-task-fargate-logsfirelens.json create mode 100644 src/Aws/tests/Unit/Ecs/metadatav4-response-task-fargate.json diff --git a/src/Aws/src/Ecs/Detector.php b/src/Aws/src/Ecs/Detector.php index d4933f6f..29da2a5e 100644 --- a/src/Aws/src/Ecs/Detector.php +++ b/src/Aws/src/Ecs/Detector.php @@ -24,6 +24,9 @@ use OpenTelemetry\SDK\Resource\ResourceInfo; use OpenTelemetry\SDK\Resource\ResourceInfoFactory; use OpenTelemetry\SemConv\ResourceAttributes; +use OpenTelemetry\SemConv\ResourceAttributeValues; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; use Throwable; /** @@ -39,34 +42,48 @@ class Detector implements ResourceDetectorInterface private const CONTAINER_ID_LENGTH = 64; private DataProvider $processData; + private ClientInterface $client; + private RequestFactoryInterface $requestFactory; - public function __construct(DataProvider $processData) - { + public function __construct( + DataProvider $processData, + ClientInterface $client, + RequestFactoryInterface $requestFactory + ) { $this->processData = $processData; + $this->client = $client; + $this->requestFactory = $requestFactory; } /** - * If running on ECS, runs getContainerId(), getClusterName(), and - * returns resource with valid extracted values - * If not running on ECS, returns empty rsource + * If running on ECS with an ECS agent v1.3, returns a resource with the following attributes set: + * + * - + * + * If not running on ECS, returns empty resource. */ public function getResource(): ResourceInfo { + $metadataEndpointV4 = getenv(self::ECS_METADATA_KEY_V4); // Check if running on ECS by looking for below environment variables - if (!getenv(self::ECS_METADATA_KEY_V4) && !getenv(self::ECS_METADATA_KEY_V3)) { + if (!$metadataEndpointV4 && !getenv(self::ECS_METADATA_KEY_V3)) { // TODO: add 'Process is not running on ECS' when logs are added return ResourceInfoFactory::emptyResource(); } - $hostName = $this->processData->getHostname(); - $containerId = $this->getContainerId(); + $basicEcsResource = ResourceInfo::create(Attributes::create([ + ResourceAttributes::CLOUD_PROVIDER => ResourceAttributeValues::CLOUD_PROVIDER_AWS, + ResourceAttributes::CLOUD_PLATFORM => ResourceAttributeValues::CLOUD_PLATFORM_AWS_ECS, + ])); - return !$hostName && !$containerId - ? ResourceInfoFactory::emptyResource() - : ResourceInfo::create(Attributes::create([ - ResourceAttributes::CONTAINER_NAME => $hostName, - ResourceAttributes::CONTAINER_ID => $containerId, - ])); + $metadataV4Resource = $this->getMetadataEndpointV4Resource(); + + $hostNameAndContainerIdResource = ResourceInfo::create(Attributes::create([ + ResourceAttributes::CONTAINER_NAME => $this->processData->getHostname(), + ResourceAttributes::CONTAINER_ID => $this->getContainerId(), + ])); + + return ResourceInfoFactory::merge($basicEcsResource, $hostNameAndContainerIdResource, $metadataV4Resource); } /** @@ -88,9 +105,88 @@ private function getContainerId(): ?string } } } catch (Throwable $e) { - //TODO: add 'Failed to read container ID' when logging is added + // TODO: add 'Failed to read container ID' when logging is added } return null; } + + private function getMetadataEndpointV4Resource(): ResourceInfo + { + $metadataEndpointV4 = getenv(self::ECS_METADATA_KEY_V4); + if (!$metadataEndpointV4) { + return ResourceInfoFactory::emptyResource(); + } + + $containerRequest = $this->requestFactory + ->createRequest('GET', $metadataEndpointV4); + $containerResponse = $this->client->sendRequest($containerRequest); + if ($containerResponse->getStatusCode() > 299) { + // TODO: Log error + return ResourceInfoFactory::emptyResource(); + } + + $taskRequest = $this->requestFactory + ->createRequest('GET', $metadataEndpointV4 . '/task'); + $taskResponse = $this->client->sendRequest($taskRequest); + if ($taskResponse->getStatusCode() > 299) { + // TODO: Log error + return ResourceInfoFactory::emptyResource(); + } + + $containerMetadata = json_decode($containerResponse->getBody()->getContents(), true); + $taskMetadata = json_decode($taskResponse->getBody()->getContents(), true); + + $launchType = isset($taskMetadata['LaunchType']) ? strtolower($taskMetadata['LaunchType']) : null; + $taskFamily = isset($taskMetadata['Family']) ? $taskMetadata['Family'] : null; + $taskRevision = isset($taskMetadata['Revision']) ? $taskMetadata['Revision'] : null; + + $clusterArn = null; + $taskArn = null; + if (isset($taskMetadata['Cluster']) && isset($taskMetadata['TaskARN'])) { + $taskArn = $taskMetadata['TaskARN']; + $lastIndexOfColon = strrpos($taskArn, ':'); + if ($lastIndexOfColon) { + $baseArn = substr($taskArn, 0, $lastIndexOfColon); + $cluster = $taskMetadata['Cluster']; + $clusterArn = strpos($cluster, 'arn:') === 0 ? $cluster : $baseArn . ':cluster/' . $cluster; + } + } + + $containerArn = isset($containerMetadata['ContainerARN']) ? $containerMetadata['ContainerARN'] : null; + + $logResource = ResourceInfoFactory::emptyResource(); + if (isset($containerMetadata['LogOptions']) && isset($containerMetadata['LogDriver']) && $containerMetadata['LogDriver'] === 'awslogs') { + $logOptions = $containerMetadata['LogOptions']; + $logsGroupName = $logOptions['awslogs-group']; + $logsStreamName = $logOptions['awslogs-stream']; + + $logsGroupArns = []; + $logsStreamArns = []; + if (isset($containerMetadata['ContainerARN']) && preg_match('/arn:aws:ecs:([^:]+):([^:]+):.*/', $containerMetadata['ContainerARN'], $matches)) { + [$arn, $awsRegion, $awsAccount] = $matches; + + $logsGroupArns = ['arn:aws:logs:' . $awsRegion . ':' . $awsAccount . ':log-group:' . $logsGroupName]; + $logsStreamArns = ['arn:aws:logs:' . $awsRegion . ':' . $awsAccount . ':log-group:' . $logsGroupName . ':log-stream:' . $logsStreamName]; + } + + $logResource = ResourceInfo::create(Attributes::create([ + ResourceAttributes::AWS_LOG_GROUP_NAMES => [$logsGroupName], + ResourceAttributes::AWS_LOG_GROUP_ARNS => $logsGroupArns, + ResourceAttributes::AWS_LOG_STREAM_NAMES => [$logsStreamName], + ResourceAttributes::AWS_LOG_STREAM_ARNS => $logsStreamArns, + ])); + } + + $ecsResource = ResourceInfo::create(Attributes::create([ + ResourceAttributes::AWS_ECS_CONTAINER_ARN => $containerArn, + ResourceAttributes::AWS_ECS_CLUSTER_ARN => $clusterArn, + ResourceAttributes::AWS_ECS_LAUNCHTYPE => $launchType, + ResourceAttributes::AWS_ECS_TASK_ARN => $taskArn, + ResourceAttributes::AWS_ECS_TASK_FAMILY => $taskFamily, + ResourceAttributes::AWS_ECS_TASK_REVISION => $taskRevision, + ])); + + return ResourceInfoFactory::merge($ecsResource, $logResource); + } } diff --git a/src/Aws/tests/Unit/Ecs/DetectorTest.php b/src/Aws/tests/Unit/Ecs/DetectorTest.php index 624c9394..87bb3fc1 100644 --- a/src/Aws/tests/Unit/Ecs/DetectorTest.php +++ b/src/Aws/tests/Unit/Ecs/DetectorTest.php @@ -4,12 +4,18 @@ namespace OpenTelemetry\Tests\Aws\Unit\Ecs; +use GuzzleHttp\Client; +use GuzzleHttp\Handler\MockHandler; +use GuzzleHttp\HandlerStack; +use GuzzleHttp\Psr7\HttpFactory; +use GuzzleHttp\Psr7\Response; use OpenTelemetry\Aws\Ecs\DataProvider; use OpenTelemetry\Aws\Ecs\Detector; use OpenTelemetry\SDK\Common\Attribute\Attributes; use OpenTelemetry\SDK\Resource\ResourceInfo; use OpenTelemetry\SDK\Resource\ResourceInfoFactory; use OpenTelemetry\SemConv\ResourceAttributes; +use OpenTelemetry\SemConv\ResourceAttributeValues; use PHPUnit\Framework\TestCase; class DetectorTest extends TestCase @@ -35,26 +41,32 @@ class DetectorTest extends TestCase */ public function TestValidCgroupData() { - putenv(self::ECS_ENV_VAR_V4_KEY . '=' . self::ECS_ENV_VAR_V4_VAL); + putenv(self::ECS_ENV_VAR_V3_KEY . '=' . self::ECS_ENV_VAR_V3_VAL); $mockData = $this->createMock(DataProvider::class); - $mockData->method('getCgroupData')->willReturn(self::VALID_CGROUP_DATA); $mockData->method('getHostName')->willReturn(self::HOST_NAME); - $detector = new Detector($mockData); + $mockGuzzle = new MockHandler([]); + $handlerStack = HandlerStack::create($mockGuzzle); + $client = new Client(['handler' => $handlerStack]); + $requestFactory = new HttpFactory(); + + $detector = new Detector($mockData, $client, $requestFactory); $this->assertEquals(ResourceInfo::create( Attributes::create( [ ResourceAttributes::CONTAINER_NAME => self::HOST_NAME, ResourceAttributes::CONTAINER_ID => self::EXTRACTED_CONTAINER_ID, + ResourceAttributes::CLOUD_PROVIDER => ResourceAttributeValues::CLOUD_PROVIDER_AWS, + ResourceAttributes::CLOUD_PLATFORM => ResourceAttributeValues::CLOUD_PLATFORM_AWS_ECS, ] ) ), $detector->getResource()); //unset environment variable - putenv(self::ECS_ENV_VAR_V4_KEY); + putenv(self::ECS_ENV_VAR_V3_KEY); } /** @@ -62,26 +74,32 @@ public function TestValidCgroupData() */ public function TestFirstValidCgroupData() { - putenv(self::ECS_ENV_VAR_V4_KEY . '=' . self::ECS_ENV_VAR_V4_VAL); + putenv(self::ECS_ENV_VAR_V3_KEY . '=' . self::ECS_ENV_VAR_V3_VAL); $mockData = $this->createMock(DataProvider::class); - $mockData->method('getCgroupData')->willReturn(self::MULTIVALID_CGROUP_DATA); $mockData->method('getHostName')->willReturn(self::HOST_NAME); - $detector = new Detector($mockData); + $mockGuzzle = new MockHandler([]); + $handlerStack = HandlerStack::create($mockGuzzle); + $client = new Client(['handler' => $handlerStack]); + $requestFactory = new HttpFactory(); + + $detector = new Detector($mockData, $client, $requestFactory); $this->assertEquals(ResourceInfo::create( Attributes::create( [ ResourceAttributes::CONTAINER_NAME => self::HOST_NAME, ResourceAttributes::CONTAINER_ID => self::EXTRACTED_CONTAINER_ID, + ResourceAttributes::CLOUD_PROVIDER => ResourceAttributeValues::CLOUD_PROVIDER_AWS, + ResourceAttributes::CLOUD_PLATFORM => ResourceAttributeValues::CLOUD_PLATFORM_AWS_ECS, ] ) ), $detector->getResource()); //unset environment variable - putenv(self::ECS_ENV_VAR_V4_KEY); + putenv(self::ECS_ENV_VAR_V3_KEY); } /** @@ -90,11 +108,15 @@ public function TestFirstValidCgroupData() public function TestIsRunningOnEcsReturnsEmpty() { $mockData = $this->createMock(DataProvider::class); - $mockData->method('getCgroupData')->willReturn(self::MULTIVALID_CGROUP_DATA); $mockData->method('getHostName')->willReturn(self::HOST_NAME); - $detector = new Detector($mockData); + $mockGuzzle = new MockHandler([]); + $handlerStack = HandlerStack::create($mockGuzzle); + $client = new Client(['handler' => $handlerStack]); + $requestFactory = new HttpFactory(); + + $detector = new Detector($mockData, $client, $requestFactory); $this->assertEquals(ResourceInfoFactory::emptyResource(), $detector->getResource()); } @@ -108,22 +130,28 @@ public function TestReturnOnlyHostnameWithoutCgroupFile() putenv(self::ECS_ENV_VAR_V3_KEY . '=' . self::ECS_ENV_VAR_V3_VAL); $mockData = $this->createMock(DataProvider::class); - $mockData->method('getCgroupData')->willReturn(false); $mockData->method('getHostName')->willReturn(self::HOST_NAME); - $detector = new Detector($mockData); + $mockGuzzle = new MockHandler([]); + $handlerStack = HandlerStack::create($mockGuzzle); + $client = new Client(['handler' => $handlerStack]); + $requestFactory = new HttpFactory(); + + $detector = new Detector($mockData, $client, $requestFactory); $this->assertEquals(ResourceInfo::create( Attributes::create( [ ResourceAttributes::CONTAINER_NAME => self::HOST_NAME, + ResourceAttributes::CLOUD_PROVIDER => ResourceAttributeValues::CLOUD_PROVIDER_AWS, + ResourceAttributes::CLOUD_PLATFORM => ResourceAttributeValues::CLOUD_PLATFORM_AWS_ECS, ] ) ), $detector->getResource()); //unset environment variable - putenv(self::ECS_ENV_VAR_V4_KEY); + putenv(self::ECS_ENV_VAR_V3_KEY); } /** @@ -132,12 +160,17 @@ public function TestReturnOnlyHostnameWithoutCgroupFile() public function TestReturnOnlyHostnameWithInvalidCgroupFile() { // Test other version (v3) - putenv(self::ECS_ENV_VAR_V4_KEY . '=' . self::ECS_ENV_VAR_V4_VAL); + putenv(self::ECS_ENV_VAR_V3_KEY . '=' . self::ECS_ENV_VAR_V4_KEY); $mockData = $this->createMock(DataProvider::class); $mockData->method('getHostName')->willReturn(self::HOST_NAME); - $detector = new Detector($mockData); + $mockGuzzle = new MockHandler([]); + $handlerStack = HandlerStack::create($mockGuzzle); + $client = new Client(['handler' => $handlerStack]); + $requestFactory = new HttpFactory(); + + $detector = new Detector($mockData, $client, $requestFactory); $invalidCgroups = [self::INVALID_CGROUP_LENGTH, self::INVALID_CGROUP_EMPTY, self::INVALID_CGROUP_VALUES]; @@ -148,13 +181,15 @@ public function TestReturnOnlyHostnameWithInvalidCgroupFile() Attributes::create( [ ResourceAttributes::CONTAINER_NAME => self::HOST_NAME, - ] + ResourceAttributes::CLOUD_PROVIDER => ResourceAttributeValues::CLOUD_PROVIDER_AWS, + ResourceAttributes::CLOUD_PLATFORM => ResourceAttributeValues::CLOUD_PLATFORM_AWS_ECS, + ] ) ), $detector->getResource()); } //unset environment variable - putenv(self::ECS_ENV_VAR_V4_KEY); + putenv(self::ECS_ENV_VAR_V3_KEY); } /** @@ -162,25 +197,31 @@ public function TestReturnOnlyHostnameWithInvalidCgroupFile() */ public function TestReturnOnlyContainerIdWithoutHostname() { - putenv(self::ECS_ENV_VAR_V4_KEY . '=' . self::ECS_ENV_VAR_V4_VAL); + putenv(self::ECS_ENV_VAR_V3_KEY . '=' . self::ECS_ENV_VAR_V3_VAL); $mockData = $this->createMock(DataProvider::class); - $mockData->method('getCgroupData')->willReturn(self::VALID_CGROUP_DATA); $mockData->method('getHostName')->willReturn(null); - $detector = new Detector($mockData); + $mockGuzzle = new MockHandler([]); + $handlerStack = HandlerStack::create($mockGuzzle); + $client = new Client(['handler' => $handlerStack]); + $requestFactory = new HttpFactory(); + + $detector = new Detector($mockData, $client, $requestFactory); $this->assertEquals(ResourceInfo::create( Attributes::create( [ ResourceAttributes::CONTAINER_ID => self::EXTRACTED_CONTAINER_ID, + ResourceAttributes::CLOUD_PROVIDER => ResourceAttributeValues::CLOUD_PROVIDER_AWS, + ResourceAttributes::CLOUD_PLATFORM => ResourceAttributeValues::CLOUD_PLATFORM_AWS_ECS, ] ) ), $detector->getResource()); //unset environment variable - putenv(self::ECS_ENV_VAR_V4_KEY); + putenv(self::ECS_ENV_VAR_V3_KEY); } /** @@ -189,18 +230,159 @@ public function TestReturnOnlyContainerIdWithoutHostname() public function TestReturnEmptyResourceInvalidContainerIdAndHostname() { // Set environment variable + putenv(self::ECS_ENV_VAR_V3_KEY . '=' . self::ECS_ENV_VAR_V3_VAL); + + $mockData = $this->createMock(DataProvider::class); + $mockData->method('getCgroupData')->willReturn(self::INVALID_CGROUP_LENGTH); + $mockData->method('getHostName')->willReturn(null); + + $mockGuzzle = new MockHandler([]); + $handlerStack = HandlerStack::create($mockGuzzle); + $client = new Client(['handler' => $handlerStack]); + $requestFactory = new HttpFactory(); + + $detector = new Detector($mockData, $client, $requestFactory); + + $this->assertEquals(ResourceInfo::create( + Attributes::create( + [ + ResourceAttributes::CLOUD_PROVIDER => ResourceAttributeValues::CLOUD_PROVIDER_AWS, + ResourceAttributes::CLOUD_PLATFORM => ResourceAttributeValues::CLOUD_PLATFORM_AWS_ECS, + ] + ) + ), $detector->getResource()); + + // Unset environment variable + putenv(self::ECS_ENV_VAR_V3_KEY); + } + + /** + * @test + */ + public function TestV4ResourceLaunchTypeEc2() + { + putenv(self::ECS_ENV_VAR_V4_KEY . '=' . self::ECS_ENV_VAR_V4_VAL); + + $mockData = $this->createMock(DataProvider::class); + $mockData->method('getCgroupData')->willReturn(self::INVALID_CGROUP_LENGTH); + $mockData->method('getHostName')->willReturn(null); + + $mockGuzzle = new MockHandler([ + new Response(200, ['Content-Type' => 'application/json'], $this->getResponseBodyFor('metadatav4-response-container-ec2.json')), + new Response(200, ['Content-Type' => 'application/json'], $this->getResponseBodyFor('metadatav4-response-task-ec2.json')), + ]); + $handlerStack = HandlerStack::create($mockGuzzle); + $client = new Client(['handler' => $handlerStack]); + $requestFactory = new HttpFactory(); + + $detector = new Detector($mockData, $client, $requestFactory); + + $this->assertEquals(ResourceInfo::create( + Attributes::create( + [ + ResourceAttributes::CLOUD_PROVIDER => ResourceAttributeValues::CLOUD_PROVIDER_AWS, + ResourceAttributes::CLOUD_PLATFORM => ResourceAttributeValues::CLOUD_PLATFORM_AWS_ECS, + ResourceAttributes::AWS_ECS_CONTAINER_ARN => 'arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9', + ResourceAttributes::AWS_ECS_CLUSTER_ARN => 'arn:aws:ecs:us-west-2:111122223333:cluster/default', + ResourceAttributes::AWS_ECS_LAUNCHTYPE => 'ec2', + ResourceAttributes::AWS_ECS_TASK_ARN => 'arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c', + ResourceAttributes::AWS_ECS_TASK_FAMILY => 'curltest', + ResourceAttributes::AWS_ECS_TASK_REVISION => '26', + ResourceAttributes::AWS_LOG_GROUP_NAMES => ['/ecs/metadata'], + ResourceAttributes::AWS_LOG_GROUP_ARNS => ['arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata'], + ResourceAttributes::AWS_LOG_STREAM_NAMES => ['ecs/curl/8f03e41243824aea923aca126495f665'], + ResourceAttributes::AWS_LOG_STREAM_ARNS => ['arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata:log-stream:ecs/curl/8f03e41243824aea923aca126495f665'], + ] + ) + ), $detector->getResource()); + + putenv(self::ECS_ENV_VAR_V4_KEY); + } + + /** + * @test + */ + public function TestV4ResourceLaunchTypeFargate() + { putenv(self::ECS_ENV_VAR_V4_KEY . '=' . self::ECS_ENV_VAR_V4_VAL); $mockData = $this->createMock(DataProvider::class); + $mockData->method('getCgroupData')->willReturn(self::INVALID_CGROUP_LENGTH); + $mockData->method('getHostName')->willReturn(null); + + $mockGuzzle = new MockHandler([ + new Response(200, ['Content-Type' => 'application/json'], $this->getResponseBodyFor('metadatav4-response-container-fargate.json')), + new Response(200, ['Content-Type' => 'application/json'], $this->getResponseBodyFor('metadatav4-response-task-fargate.json')), + ]); + $handlerStack = HandlerStack::create($mockGuzzle); + $client = new Client(['handler' => $handlerStack]); + $requestFactory = new HttpFactory(); + + $detector = new Detector($mockData, $client, $requestFactory); + + $this->assertEquals(ResourceInfo::create( + Attributes::create( + [ + ResourceAttributes::CLOUD_PROVIDER => ResourceAttributeValues::CLOUD_PROVIDER_AWS, + ResourceAttributes::CLOUD_PLATFORM => ResourceAttributeValues::CLOUD_PLATFORM_AWS_ECS, + ResourceAttributes::AWS_ECS_CONTAINER_ARN => 'arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1', + ResourceAttributes::AWS_ECS_CLUSTER_ARN => 'arn:aws:ecs:us-west-2:111122223333:cluster/default', + ResourceAttributes::AWS_ECS_LAUNCHTYPE => 'fargate', + ResourceAttributes::AWS_ECS_TASK_ARN => 'arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3', + ResourceAttributes::AWS_ECS_TASK_FAMILY => 'curltest', + ResourceAttributes::AWS_ECS_TASK_REVISION => '3', + ResourceAttributes::AWS_LOG_GROUP_NAMES => ['/ecs/containerlogs'], + ResourceAttributes::AWS_LOG_GROUP_ARNS => ['arn:aws:logs:us-west-2:111122223333:log-group:/ecs/containerlogs'], + ResourceAttributes::AWS_LOG_STREAM_NAMES => ['ecs/curl/cd189a933e5849daa93386466019ab50'], + ResourceAttributes::AWS_LOG_STREAM_ARNS => ['arn:aws:logs:us-west-2:111122223333:log-group:/ecs/containerlogs:log-stream:ecs/curl/cd189a933e5849daa93386466019ab50'], + ] + ) + ), $detector->getResource()); + putenv(self::ECS_ENV_VAR_V4_KEY); + } + + /** + * @test + */ + public function TestV4ResourceLogDriverFireLens() + { + putenv(self::ECS_ENV_VAR_V4_KEY . '=' . self::ECS_ENV_VAR_V4_VAL); + + $mockData = $this->createMock(DataProvider::class); $mockData->method('getCgroupData')->willReturn(self::INVALID_CGROUP_LENGTH); $mockData->method('getHostName')->willReturn(null); - $detector = new Detector($mockData); + $mockGuzzle = new MockHandler([ + new Response(200, ['Content-Type' => 'application/json'], $this->getResponseBodyFor('metadatav4-response-container-fargate-logsfirelens.json')), + new Response(200, ['Content-Type' => 'application/json'], $this->getResponseBodyFor('metadatav4-response-task-fargate-logsfirelens.json')), + ]); + $handlerStack = HandlerStack::create($mockGuzzle); + $client = new Client(['handler' => $handlerStack]); + $requestFactory = new HttpFactory(); - $this->assertEquals(ResourceInfoFactory::emptyResource(), $detector->getResource()); + $detector = new Detector($mockData, $client, $requestFactory); + + $this->assertEquals(ResourceInfo::create( + Attributes::create( + [ + ResourceAttributes::CLOUD_PROVIDER => ResourceAttributeValues::CLOUD_PROVIDER_AWS, + ResourceAttributes::CLOUD_PLATFORM => ResourceAttributeValues::CLOUD_PLATFORM_AWS_ECS, + ResourceAttributes::AWS_ECS_CONTAINER_ARN => 'arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1', + ResourceAttributes::AWS_ECS_CLUSTER_ARN => 'arn:aws:ecs:us-west-2:111122223333:cluster/default', + ResourceAttributes::AWS_ECS_LAUNCHTYPE => 'fargate', + ResourceAttributes::AWS_ECS_TASK_ARN => 'arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3', + ResourceAttributes::AWS_ECS_TASK_FAMILY => 'curltest', + ResourceAttributes::AWS_ECS_TASK_REVISION => '3', + ] + ) + ), $detector->getResource()); - // Unset environment variable putenv(self::ECS_ENV_VAR_V4_KEY); } + + private function getResponseBodyFor($filename) + { + return file_get_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . $filename); + } } diff --git a/src/Aws/tests/Unit/Ecs/metadatav4-response-container-ec2.json b/src/Aws/tests/Unit/Ecs/metadatav4-response-container-ec2.json new file mode 100644 index 00000000..9354e74f --- /dev/null +++ b/src/Aws/tests/Unit/Ecs/metadatav4-response-container-ec2.json @@ -0,0 +1,44 @@ +{ + "DockerId": "ea32192c8553fbff06c9340478a2ff089b2bb5646fb718b4ee206641c9086d66", + "Name": "curl", + "DockerName": "ecs-curltest-24-curl-cca48e8dcadd97805600", + "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", + "ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553", + "Labels": { + "com.amazonaws.ecs.cluster": "default", + "com.amazonaws.ecs.container-name": "curl", + "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/8f03e41243824aea923aca126495f665", + "com.amazonaws.ecs.task-definition-family": "curltest", + "com.amazonaws.ecs.task-definition-version": "24" + }, + "DesiredStatus": "RUNNING", + "KnownStatus": "RUNNING", + "Limits": { + "CPU": 10, + "Memory": 128 + }, + "CreatedAt": "2020-10-02T00:15:07.620912337Z", + "StartedAt": "2020-10-02T00:15:08.062559351Z", + "Type": "NORMAL", + "LogDriver": "awslogs", + "LogOptions": { + "awslogs-create-group": "true", + "awslogs-group": "/ecs/metadata", + "awslogs-region": "us-west-2", + "awslogs-stream": "ecs/curl/8f03e41243824aea923aca126495f665" + }, + "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9", + "Networks": [ + { + "NetworkMode": "awsvpc", + "IPv4Addresses": [ + "10.0.2.100" + ], + "AttachmentIndex": 0, + "MACAddress": "0e:9e:32:c7:48:85", + "IPv4SubnetCIDRBlock": "10.0.2.0/24", + "PrivateDNSName": "ip-10-0-2-100.us-west-2.compute.internal", + "SubnetGatewayIpv4Address": "10.0.2.1/24" + } + ] +} \ No newline at end of file diff --git a/src/Aws/tests/Unit/Ecs/metadatav4-response-container-fargate-logsfirelens.json b/src/Aws/tests/Unit/Ecs/metadatav4-response-container-fargate-logsfirelens.json new file mode 100644 index 00000000..b87f1d72 --- /dev/null +++ b/src/Aws/tests/Unit/Ecs/metadatav4-response-container-fargate-logsfirelens.json @@ -0,0 +1,44 @@ +{ + "DockerId": "cd189a933e5849daa93386466019ab50-2495160603", + "Name": "curl", + "DockerName": "curl", + "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", + "ImageID": "sha256:25f3695bedfb454a50f12d127839a68ad3caf91e451c1da073db34c542c4d2cb", + "Labels": { + "com.amazonaws.ecs.cluster": "arn:aws:ecs:us-west-2:111122223333:cluster/default", + "com.amazonaws.ecs.container-name": "curl", + "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/cd189a933e5849daa93386466019ab50", + "com.amazonaws.ecs.task-definition-family": "curltest", + "com.amazonaws.ecs.task-definition-version": "2" + }, + "DesiredStatus": "RUNNING", + "KnownStatus": "RUNNING", + "Limits": { + "CPU": 10, + "Memory": 128 + }, + "CreatedAt": "2020-10-08T20:09:11.44527186Z", + "StartedAt": "2020-10-08T20:09:11.44527186Z", + "Type": "NORMAL", + "Networks": [ + { + "NetworkMode": "awsvpc", + "IPv4Addresses": [ + "192.0.2.3" + ], + "AttachmentIndex": 0, + "MACAddress": "0a:de:f6:10:51:e5", + "IPv4SubnetCIDRBlock": "192.0.2.0/24", + "DomainNameServers": [ + "192.0.2.2" + ], + "DomainNameSearchList": [ + "us-west-2.compute.internal" + ], + "PrivateDNSName": "ip-10-0-0-222.us-west-2.compute.internal", + "SubnetGatewayIpv4Address": "192.0.2.0/24" + } + ], + "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1", + "LogDriver": "awsfirelens" +} \ No newline at end of file diff --git a/src/Aws/tests/Unit/Ecs/metadatav4-response-container-fargate.json b/src/Aws/tests/Unit/Ecs/metadatav4-response-container-fargate.json new file mode 100644 index 00000000..ccbe70bc --- /dev/null +++ b/src/Aws/tests/Unit/Ecs/metadatav4-response-container-fargate.json @@ -0,0 +1,50 @@ +{ + "DockerId": "cd189a933e5849daa93386466019ab50-2495160603", + "Name": "curl", + "DockerName": "curl", + "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", + "ImageID": "sha256:25f3695bedfb454a50f12d127839a68ad3caf91e451c1da073db34c542c4d2cb", + "Labels": { + "com.amazonaws.ecs.cluster": "arn:aws:ecs:us-west-2:111122223333:cluster/default", + "com.amazonaws.ecs.container-name": "curl", + "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/cd189a933e5849daa93386466019ab50", + "com.amazonaws.ecs.task-definition-family": "curltest", + "com.amazonaws.ecs.task-definition-version": "2" + }, + "DesiredStatus": "RUNNING", + "KnownStatus": "RUNNING", + "Limits": { + "CPU": 10, + "Memory": 128 + }, + "CreatedAt": "2020-10-08T20:09:11.44527186Z", + "StartedAt": "2020-10-08T20:09:11.44527186Z", + "Type": "NORMAL", + "Networks": [ + { + "NetworkMode": "awsvpc", + "IPv4Addresses": [ + "192.0.2.3" + ], + "AttachmentIndex": 0, + "MACAddress": "0a:de:f6:10:51:e5", + "IPv4SubnetCIDRBlock": "192.0.2.0/24", + "DomainNameServers": [ + "192.0.2.2" + ], + "DomainNameSearchList": [ + "us-west-2.compute.internal" + ], + "PrivateDNSName": "ip-10-0-0-222.us-west-2.compute.internal", + "SubnetGatewayIpv4Address": "192.0.2.0/24" + } + ], + "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1", + "LogOptions": { + "awslogs-create-group": "true", + "awslogs-group": "/ecs/containerlogs", + "awslogs-region": "us-west-2", + "awslogs-stream": "ecs/curl/cd189a933e5849daa93386466019ab50" + }, + "LogDriver": "awslogs" +} \ No newline at end of file diff --git a/src/Aws/tests/Unit/Ecs/metadatav4-response-task-ec2.json b/src/Aws/tests/Unit/Ecs/metadatav4-response-task-ec2.json new file mode 100644 index 00000000..bb9bd6f0 --- /dev/null +++ b/src/Aws/tests/Unit/Ecs/metadatav4-response-task-ec2.json @@ -0,0 +1,94 @@ +{ + "Cluster": "default", + "TaskARN": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c", + "Family": "curltest", + "Revision": "26", + "DesiredStatus": "RUNNING", + "KnownStatus": "RUNNING", + "PullStartedAt": "2020-10-02T00:43:06.202617438Z", + "PullStoppedAt": "2020-10-02T00:43:06.31288465Z", + "AvailabilityZone": "us-west-2d", + "LaunchType": "EC2", + "Containers": [ + { + "DockerId": "598cba581fe3f939459eaba1e071d5c93bb2c49b7d1ba7db6bb19deeb70d8e38", + "Name": "~internal~ecs~pause", + "DockerName": "ecs-curltest-26-internalecspause-e292d586b6f9dade4a00", + "Image": "amazon/amazon-ecs-pause:0.1.0", + "ImageID": "", + "Labels": { + "com.amazonaws.ecs.cluster": "default", + "com.amazonaws.ecs.container-name": "~internal~ecs~pause", + "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c", + "com.amazonaws.ecs.task-definition-family": "curltest", + "com.amazonaws.ecs.task-definition-version": "26" + }, + "DesiredStatus": "RESOURCES_PROVISIONED", + "KnownStatus": "RESOURCES_PROVISIONED", + "Limits": { + "CPU": 0, + "Memory": 0 + }, + "CreatedAt": "2020-10-02T00:43:05.602352471Z", + "StartedAt": "2020-10-02T00:43:06.076707576Z", + "Type": "CNI_PAUSE", + "Networks": [ + { + "NetworkMode": "awsvpc", + "IPv4Addresses": [ + "10.0.2.61" + ], + "AttachmentIndex": 0, + "MACAddress": "0e:10:e2:01:bd:91", + "IPv4SubnetCIDRBlock": "10.0.2.0/24", + "PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal", + "SubnetGatewayIpv4Address": "10.0.2.1/24" + } + ] + }, + { + "DockerId": "ee08638adaaf009d78c248913f629e38299471d45fe7dc944d1039077e3424ca", + "Name": "curl", + "DockerName": "ecs-curltest-26-curl-a0e7dba5aca6d8cb2e00", + "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", + "ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553", + "Labels": { + "com.amazonaws.ecs.cluster": "default", + "com.amazonaws.ecs.container-name": "curl", + "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c", + "com.amazonaws.ecs.task-definition-family": "curltest", + "com.amazonaws.ecs.task-definition-version": "26" + }, + "DesiredStatus": "RUNNING", + "KnownStatus": "RUNNING", + "Limits": { + "CPU": 10, + "Memory": 128 + }, + "CreatedAt": "2020-10-02T00:43:06.326590752Z", + "StartedAt": "2020-10-02T00:43:06.767535449Z", + "Type": "NORMAL", + "LogDriver": "awslogs", + "LogOptions": { + "awslogs-create-group": "true", + "awslogs-group": "/ecs/metadata", + "awslogs-region": "us-west-2", + "awslogs-stream": "ecs/curl/158d1c8083dd49d6b527399fd6414f5c" + }, + "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/abb51bdd-11b4-467f-8f6c-adcfe1fe059d", + "Networks": [ + { + "NetworkMode": "awsvpc", + "IPv4Addresses": [ + "10.0.2.61" + ], + "AttachmentIndex": 0, + "MACAddress": "0e:10:e2:01:bd:91", + "IPv4SubnetCIDRBlock": "10.0.2.0/24", + "PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal", + "SubnetGatewayIpv4Address": "10.0.2.1/24" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/Aws/tests/Unit/Ecs/metadatav4-response-task-fargate-logsfirelens.json b/src/Aws/tests/Unit/Ecs/metadatav4-response-task-fargate-logsfirelens.json new file mode 100644 index 00000000..2f6c3a77 --- /dev/null +++ b/src/Aws/tests/Unit/Ecs/metadatav4-response-task-fargate-logsfirelens.json @@ -0,0 +1,71 @@ +{ + "Cluster": "arn:aws:ecs:us-west-2:111122223333:cluster/default", + "TaskARN": "arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3", + "Family": "curltest", + "Revision": "3", + "DesiredStatus": "RUNNING", + "KnownStatus": "RUNNING", + "Limits": { + "CPU": 0.25, + "Memory": 512 + }, + "PullStartedAt": "2020-10-08T20:47:16.053330955Z", + "PullStoppedAt": "2020-10-08T20:47:19.592684631Z", + "AvailabilityZone": "us-west-2a", + "Containers": [ + { + "DockerId": "e9028f8d5d8e4f258373e7b93ce9a3c3-2495160603", + "Name": "curl", + "DockerName": "curl", + "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", + "ImageID": "sha256:25f3695bedfb454a50f12d127839a68ad3caf91e451c1da073db34c542c4d2cb", + "Labels": { + "com.amazonaws.ecs.cluster": "arn:aws:ecs:us-west-2:111122223333:cluster/default", + "com.amazonaws.ecs.container-name": "curl", + "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3", + "com.amazonaws.ecs.task-definition-family": "curltest", + "com.amazonaws.ecs.task-definition-version": "3" + }, + "DesiredStatus": "RUNNING", + "KnownStatus": "RUNNING", + "Limits": { + "CPU": 10, + "Memory": 128 + }, + "CreatedAt": "2020-10-08T20:47:20.567813946Z", + "StartedAt": "2020-10-08T20:47:20.567813946Z", + "Type": "NORMAL", + "Networks": [ + { + "NetworkMode": "awsvpc", + "IPv4Addresses": [ + "192.0.2.3" + ], + "IPv6Addresses": [ + "2001:dB8:10b:1a00:32bf:a372:d80f:e958" + ], + "AttachmentIndex": 0, + "MACAddress": "02:b7:20:19:72:39", + "IPv4SubnetCIDRBlock": "192.0.2.0/24", + "IPv6SubnetCIDRBlock": "2600:1f13:10b:1a00::/64", + "DomainNameServers": [ + "192.0.2.2" + ], + "DomainNameSearchList": [ + "us-west-2.compute.internal" + ], + "PrivateDNSName": "ip-172-31-30-173.us-west-2.compute.internal", + "SubnetGatewayIpv4Address": "192.0.2.0/24" + } + ], + "ClockDrift": { + "ClockErrorBound": 0.5458234999999999, + "ReferenceTimestamp": "2021-09-07T16:57:44Z", + "ClockSynchronizationStatus": "SYNCHRONIZED" + }, + "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/1bdcca8b-f905-4ee6-885c-4064cb70f6e6", + "LogDriver": "awsfirelens" + } + ], + "LaunchType": "FARGATE" +} \ No newline at end of file diff --git a/src/Aws/tests/Unit/Ecs/metadatav4-response-task-fargate.json b/src/Aws/tests/Unit/Ecs/metadatav4-response-task-fargate.json new file mode 100644 index 00000000..7979db70 --- /dev/null +++ b/src/Aws/tests/Unit/Ecs/metadatav4-response-task-fargate.json @@ -0,0 +1,77 @@ +{ + "Cluster": "arn:aws:ecs:us-west-2:111122223333:cluster/default", + "TaskARN": "arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3", + "Family": "curltest", + "Revision": "3", + "DesiredStatus": "RUNNING", + "KnownStatus": "RUNNING", + "Limits": { + "CPU": 0.25, + "Memory": 512 + }, + "PullStartedAt": "2020-10-08T20:47:16.053330955Z", + "PullStoppedAt": "2020-10-08T20:47:19.592684631Z", + "AvailabilityZone": "us-west-2a", + "Containers": [ + { + "DockerId": "e9028f8d5d8e4f258373e7b93ce9a3c3-2495160603", + "Name": "curl", + "DockerName": "curl", + "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", + "ImageID": "sha256:25f3695bedfb454a50f12d127839a68ad3caf91e451c1da073db34c542c4d2cb", + "Labels": { + "com.amazonaws.ecs.cluster": "arn:aws:ecs:us-west-2:111122223333:cluster/default", + "com.amazonaws.ecs.container-name": "curl", + "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3", + "com.amazonaws.ecs.task-definition-family": "curltest", + "com.amazonaws.ecs.task-definition-version": "3" + }, + "DesiredStatus": "RUNNING", + "KnownStatus": "RUNNING", + "Limits": { + "CPU": 10, + "Memory": 128 + }, + "CreatedAt": "2020-10-08T20:47:20.567813946Z", + "StartedAt": "2020-10-08T20:47:20.567813946Z", + "Type": "NORMAL", + "Networks": [ + { + "NetworkMode": "awsvpc", + "IPv4Addresses": [ + "192.0.2.3" + ], + "IPv6Addresses": [ + "2001:dB8:10b:1a00:32bf:a372:d80f:e958" + ], + "AttachmentIndex": 0, + "MACAddress": "02:b7:20:19:72:39", + "IPv4SubnetCIDRBlock": "192.0.2.0/24", + "IPv6SubnetCIDRBlock": "2600:1f13:10b:1a00::/64", + "DomainNameServers": [ + "192.0.2.2" + ], + "DomainNameSearchList": [ + "us-west-2.compute.internal" + ], + "PrivateDNSName": "ip-172-31-30-173.us-west-2.compute.internal", + "SubnetGatewayIpv4Address": "192.0.2.0/24" + } + ], + "ClockDrift": { + "ClockErrorBound": 0.5458234999999999, + "ReferenceTimestamp": "2021-09-07T16:57:44Z", + "ClockSynchronizationStatus": "SYNCHRONIZED" + }, + "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/1bdcca8b-f905-4ee6-885c-4064cb70f6e6", + "LogOptions": { + "awslogs-create-group": "true", + "awslogs-group": "/ecs/containerlogs", + "awslogs-region": "us-west-2", + "awslogs-stream": "ecs/curl/e9028f8d5d8e4f258373e7b93ce9a3c3" + }, + "LogDriver": "awslogs" + } + ], + "LaunchType": "FARGATE" +} \ No newline at end of file From 75ada3a4244e7cbfe58672fc571bc44052e4d26d Mon Sep 17 00:00:00 2001 From: Michele Mancioppi Date: Mon, 31 Oct 2022 09:25:45 +0100 Subject: [PATCH 2/3] Document in comments what the detector does --- src/Aws/src/Ecs/Detector.php | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Aws/src/Ecs/Detector.php b/src/Aws/src/Ecs/Detector.php index 29da2a5e..f920329f 100644 --- a/src/Aws/src/Ecs/Detector.php +++ b/src/Aws/src/Ecs/Detector.php @@ -56,11 +56,32 @@ public function __construct( } /** + * If not running on ECS, returns empty resource. + * * If running on ECS with an ECS agent v1.3, returns a resource with the following attributes set: + * - cloud.provider => aws + * - cloud.platform => aws_ecs + * - container.name => , which is usually the container name in the ECS task definition + * - container.id => * - * - + * If running on ECS with an ECS agent v1.4, the returned resource has additionally the following + * attributes as specified in https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/cloud_provider/aws/ecs/: * - * If not running on ECS, returns empty resource. + * - aws.ecs.container.arn + * - aws.ecs.cluster.arn + * - aws.ecs.launchtype + * - aws.ecs.task.arn + * - aws.ecs.task.family + * - aws.ecs.task.revision + * + * If running on ECS with an ECS agent v1.4 and the task definition is configured to report + * logs in AWS CloudWatch, the returned resource has additionally the following attributes as specified + * in https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/cloud_provider/aws/logs: + * + * - aws.log.group.names + * - aws.log.group.arns + * - aws.log.stream.names + * - aws.log.stream.arns */ public function getResource(): ResourceInfo { From 94c7d9a59131319f915b18fe9db92654648169ea Mon Sep 17 00:00:00 2001 From: Michele Mancioppi Date: Mon, 31 Oct 2022 09:41:26 +0100 Subject: [PATCH 3/3] Implement logging and add a test for metadata endpoint failure --- src/Aws/src/Ecs/Detector.php | 20 ++++++++++++---- src/Aws/tests/Unit/Ecs/DetectorTest.php | 32 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/Aws/src/Ecs/Detector.php b/src/Aws/src/Ecs/Detector.php index f920329f..3304f236 100644 --- a/src/Aws/src/Ecs/Detector.php +++ b/src/Aws/src/Ecs/Detector.php @@ -19,6 +19,7 @@ namespace OpenTelemetry\Aws\Ecs; +use OpenTelemetry\SDK\Behavior\LogsMessagesTrait; use OpenTelemetry\SDK\Common\Attribute\Attributes; use OpenTelemetry\SDK\Resource\ResourceDetectorInterface; use OpenTelemetry\SDK\Resource\ResourceInfo; @@ -36,6 +37,8 @@ */ class Detector implements ResourceDetectorInterface { + use LogsMessagesTrait; + private const ECS_METADATA_KEY_V4 = 'ECS_CONTAINER_METADATA_URI_V4'; private const ECS_METADATA_KEY_V3 = 'ECS_CONTAINER_METADATA_URI'; @@ -86,9 +89,8 @@ public function __construct( public function getResource(): ResourceInfo { $metadataEndpointV4 = getenv(self::ECS_METADATA_KEY_V4); - // Check if running on ECS by looking for below environment variables + if (!$metadataEndpointV4 && !getenv(self::ECS_METADATA_KEY_V3)) { - // TODO: add 'Process is not running on ECS' when logs are added return ResourceInfoFactory::emptyResource(); } @@ -126,7 +128,7 @@ private function getContainerId(): ?string } } } catch (Throwable $e) { - // TODO: add 'Failed to read container ID' when logging is added + self::logDebug('Failed to read container ID', ['exception' => $e]); } return null; @@ -143,7 +145,11 @@ private function getMetadataEndpointV4Resource(): ResourceInfo ->createRequest('GET', $metadataEndpointV4); $containerResponse = $this->client->sendRequest($containerRequest); if ($containerResponse->getStatusCode() > 299) { - // TODO: Log error + self::logError(sprintf('Cannot retrieve container metadata from %s endpoint', $metadataEndpointV4), [ + 'status_code' => $containerResponse->getStatusCode(), + 'response_body' => $containerResponse->getBody()->getContents(), + ]); + return ResourceInfoFactory::emptyResource(); } @@ -151,7 +157,11 @@ private function getMetadataEndpointV4Resource(): ResourceInfo ->createRequest('GET', $metadataEndpointV4 . '/task'); $taskResponse = $this->client->sendRequest($taskRequest); if ($taskResponse->getStatusCode() > 299) { - // TODO: Log error + self::logError(sprintf('Cannot retrieve task metadata from %s endpoint', $metadataEndpointV4 . '/task'), [ + 'status_code' => $taskResponse->getStatusCode(), + 'response_body' => $taskResponse->getBody()->getContents(), + ]); + return ResourceInfoFactory::emptyResource(); } diff --git a/src/Aws/tests/Unit/Ecs/DetectorTest.php b/src/Aws/tests/Unit/Ecs/DetectorTest.php index 87bb3fc1..772130bb 100644 --- a/src/Aws/tests/Unit/Ecs/DetectorTest.php +++ b/src/Aws/tests/Unit/Ecs/DetectorTest.php @@ -256,6 +256,38 @@ public function TestReturnEmptyResourceInvalidContainerIdAndHostname() putenv(self::ECS_ENV_VAR_V3_KEY); } + /** + * @test + */ + public function TestV4EndpointFails() + { + putenv(self::ECS_ENV_VAR_V4_KEY . '=' . self::ECS_ENV_VAR_V4_VAL); + + $mockData = $this->createMock(DataProvider::class); + $mockData->method('getCgroupData')->willReturn(self::INVALID_CGROUP_LENGTH); + $mockData->method('getHostName')->willReturn(null); + + $mockGuzzle = new MockHandler([ + new Response(500, ['Content-Type' => 'application/json'], '{"message":"cuz I have a baad daaay"}'), + ]); + $handlerStack = HandlerStack::create($mockGuzzle); + $client = new Client(['handler' => $handlerStack]); + $requestFactory = new HttpFactory(); + + $detector = new Detector($mockData, $client, $requestFactory); + + $this->assertEquals(ResourceInfo::create( + Attributes::create( + [ + ResourceAttributes::CLOUD_PROVIDER => ResourceAttributeValues::CLOUD_PROVIDER_AWS, + ResourceAttributes::CLOUD_PLATFORM => ResourceAttributeValues::CLOUD_PLATFORM_AWS_ECS, + ] + ) + ), $detector->getResource()); + + putenv(self::ECS_ENV_VAR_V4_KEY); + } + /** * @test */