Skip to content

Commit

Permalink
feat(resource-util): add App Engine detector (#510)
Browse files Browse the repository at this point in the history
  • Loading branch information
aabmass authored Jan 18, 2023
1 parent 9593cb9 commit 6f3fa8b
Show file tree
Hide file tree
Showing 9 changed files with 359 additions and 0 deletions.
39 changes: 39 additions & 0 deletions cloudbuild-e2e-gae.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2023 Google LLC
#
# 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.

steps:
# Wait for the image to exist
- name: "docker"
id: wait-for-image
entrypoint: "sh"
timeout: 10m
env: ["_TEST_SERVER_IMAGE=${_TEST_SERVER_IMAGE}"]
args:
- e2e-test-server/wait-for-image.sh

# Run the test
- name: $_TEST_RUNNER_IMAGE
id: run-tests-gae
dir: /
env: ["PROJECT_ID=$PROJECT_ID"]
args:
- gae
- --image=$_TEST_SERVER_IMAGE
- --runtime=node

logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs
timeout: 20m
substitutions:
_TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.15.0
_TEST_SERVER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-js-e2e-test-server:${SHORT_SHA}
11 changes: 11 additions & 0 deletions e2e-test-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ function pubSubPush() {

app
.use(express.json())

// Health checks for GAE. See:
// https://cloud.google.com/appengine/docs/flexible/reference/app-yaml#updated_health_checks
// https://github.com/GoogleCloudPlatform/opentelemetry-operations-e2e-testing/blob/v0.16.0/tf/gae/gae.tf#L32-L38
.get('/alive', (_, res) => {
res.sendStatus(200);
})
.get('/ready', (_, res) => {
res.sendStatus(200);
})

.post('/', (req, res) => {
logger.info('Received push subscription request');
const payload: PubSubPushPayload = req.body;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ exports['mapOtelResourceToMonitoredResource should map to cloud_run_revision" 1'
}
}

exports['mapOtelResourceToMonitoredResource should map to gae_instance" 1'] = {
"type": "gae_instance",
"labels": {
"location": "myregion",
"module_id": "myfaasname",
"version_id": "myfaasversion",
"instance_id": "myfaasid"
}
}

exports['mapOtelResourceToMonitoredResource should map to gce_instance 1'] = {
"type": "gce_instance",
"labels": {
Expand Down
29 changes: 29 additions & 0 deletions packages/opentelemetry-resource-util/src/detector/detector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {Detector, Resource} from '@opentelemetry/resources';
import * as gce from './gce';
import * as gke from './gke';
import * as faas from './faas';
import * as gae from './gae';
import * as metadata from 'gcp-metadata';

export class GcpDetector implements Detector {
Expand All @@ -37,6 +38,8 @@ export class GcpDetector implements Detector {
return await this._cloudFunctionsResource();
} else if (await faas.onCloudRun()) {
return await this._cloudRunResource();
} else if (await gae.onAppEngine()) {
return await this._gaeResource();
} else if (await gce.onGce()) {
return await this._gceResource();
}
Expand Down Expand Up @@ -95,6 +98,32 @@ export class GcpDetector implements Detector {
});
}

private async _gaeResource(): Promise<Resource> {
let zone, region;
if (await gae.onAppEngineStandard()) {
[zone, region] = await Promise.all([
gae.standardAvailabilityZone(),
gae.standardCloudRegion(),
]);
} else {
({zone, region} = await gce.availabilityZoneAndRegion());
}
const [faasName, faasVersion, faasId] = await Promise.all([
gae.serviceName(),
gae.serviceVersion(),
gae.serviceInstance(),
]);

return new Resource({
[Semconv.CLOUD_PLATFORM]: CloudPlatformValues.GCP_APP_ENGINE,
[Semconv.FAAS_NAME]: faasName,
[Semconv.FAAS_VERSION]: faasVersion,
[Semconv.FAAS_ID]: faasId,
[Semconv.CLOUD_AVAILABILITY_ZONE]: zone,
[Semconv.CLOUD_REGION]: region,
});
}

private async _gceResource(): Promise<Resource> {
const [zoneAndRegion, hostType, hostId, hostName] = await Promise.all([
gce.availabilityZoneAndRegion(),
Expand Down
96 changes: 96 additions & 0 deletions packages/opentelemetry-resource-util/src/detector/gae.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2023 Google LLC
//
// 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.

/**
* Implementation in this file copied from
* https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/blob/v1.8.0/detectors/gcp/app_engine.go
*/

import * as metadata from 'gcp-metadata';
import * as gce from './gce';
import * as faas from './faas';

const GAE_SERVICE_ENV = 'GAE_SERVICE';
const GAE_VERSION_ENV = 'GAE_VERSION';
const GAE_INSTANCE_ENV = 'GAE_INSTANCE';
const GAE_ENV = 'GAE_ENV';
const GAE_STANDARD = 'standard';
const ZONE_METADATA_ATTR = 'zone';

export async function onAppEngineStandard(): Promise<boolean> {
return process.env[GAE_ENV] === GAE_STANDARD;
}

export async function onAppEngine(): Promise<boolean> {
return process.env[GAE_SERVICE_ENV] !== undefined;
}

/**
* The service name of the app engine service. Check that {@link onAppEngine()} is true before
* calling this, or it may throw exceptions.
*/
export async function serviceName(): Promise<string> {
return lookupEnv(GAE_SERVICE_ENV);
}

/**
* The service version of the app engine service. Check that {@link onAppEngine()} is true
* before calling this, or it may throw exceptions.
*/
export async function serviceVersion(): Promise<string> {
return lookupEnv(GAE_VERSION_ENV);
}

/**
* The service instance of the app engine service. Check that {@link onAppEngine()} is true
* before calling this, or it may throw exceptions.
*/
export async function serviceInstance(): Promise<string> {
return lookupEnv(GAE_INSTANCE_ENV);
}

/**
* The zone and region in which this program is running. Check that {@link onAppEngine()} is
* true before calling this, or it may throw exceptions.
*/
export async function flexAvailabilityZoneAndRegion(): Promise<{
zone: string;
region: string;
}> {
return await gce.availabilityZoneAndRegion();
}

/**
* The zone the app engine service is running in. Check that {@link onAppEngineStandard()} is
* true before calling this, or it may throw exceptions.
*/
export async function standardAvailabilityZone(): Promise<string> {
return await metadata.instance<string>(ZONE_METADATA_ATTR);
}

/**
* The region the app engine service is running in. Check that {@link onAppEngineStandard()} is
* true before calling this, or it may throw exceptions.
*/
export async function standardCloudRegion(): Promise<string> {
return await faas.faasCloudRegion();
}

function lookupEnv(key: string): string {
const val = process.env[key];
if (val === undefined) {
throw new Error(`Environment variable ${key} not found`);
}
return val;
}
16 changes: 16 additions & 0 deletions packages/opentelemetry-resource-util/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const CLUSTER_NAME = 'cluster_name';
const CONFIGURATION_NAME = 'configuration_name';
const CONTAINER_NAME = 'container_name';
const FUNCTION_NAME = 'function_name';
const GAE_INSTANCE = 'gae_instance';
const GAE_MODULE_ID = 'module_id';
const GAE_VERSION_ID = 'version_id';
const GCE_INSTANCE = 'gce_instance';
const GENERIC_NODE = 'generic_node';
const GENERIC_TASK = 'generic_task';
Expand Down Expand Up @@ -126,6 +129,17 @@ const MAPPINGS = {
[REGION]: {otelKeys: [SemanticResourceAttributes.CLOUD_REGION]},
[FUNCTION_NAME]: {otelKeys: [SemanticResourceAttributes.FAAS_NAME]},
},
[GAE_INSTANCE]: {
[LOCATION]: {
otelKeys: [
SemanticResourceAttributes.CLOUD_AVAILABILITY_ZONE,
SemanticResourceAttributes.CLOUD_REGION,
],
},
[GAE_MODULE_ID]: {otelKeys: [SemanticResourceAttributes.FAAS_NAME]},
[GAE_VERSION_ID]: {otelKeys: [SemanticResourceAttributes.FAAS_VERSION]},
[INSTANCE_ID]: {otelKeys: [SemanticResourceAttributes.FAAS_ID]},
},
[GENERIC_TASK]: {
[LOCATION]: {
otelKeys: [
Expand Down Expand Up @@ -202,6 +216,8 @@ export function mapOtelResourceToMonitoredResource(
} else {
mr = createMonitoredResource(K8S_CLUSTER, attrs);
}
} else if (platform === CloudPlatformValues.GCP_APP_ENGINE) {
mr = createMonitoredResource(GAE_INSTANCE, attrs);
} else if (platform === CloudPlatformValues.AWS_EC2) {
mr = createMonitoredResource(AWS_EC2_INSTANCE, attrs);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,46 @@ describe('GcpDetector', () => {
});
});

it('detects a App Engine Standard resource', async () => {
envStub.GAE_ENV = 'standard';
envStub.GAE_SERVICE = 'fake-service';
envStub.GAE_VERSION = 'fake-version';
envStub.GAE_INSTANCE = 'fake-instance';
metadataStub.instance.withArgs('zone').resolves('us-east4-b');
metadataStub.instance
.withArgs('region')
.resolves('projects/233510669999/regions/us-east4');

const resource = await new GcpDetector().detect();
assert.deepStrictEqual(resource.attributes, {
'cloud.platform': 'gcp_app_engine',
'cloud.availability_zone': 'us-east4-b',
'cloud.region': 'us-east4',
'faas.id': 'fake-instance',
'faas.name': 'fake-service',
'faas.version': 'fake-version',
});
});

it('detects a App Engine Flex resource', async () => {
envStub.GAE_SERVICE = 'fake-service';
envStub.GAE_VERSION = 'fake-version';
envStub.GAE_INSTANCE = 'fake-instance';
metadataStub.instance
.withArgs('zone')
.resolves('projects/233510669999/zones/us-east4-b');

const resource = await new GcpDetector().detect();
assert.deepStrictEqual(resource.attributes, {
'cloud.platform': 'gcp_app_engine',
'cloud.availability_zone': 'us-east4-b',
'cloud.region': 'us-east4',
'faas.id': 'fake-instance',
'faas.name': 'fake-service',
'faas.version': 'fake-version',
});
});

it('detects empty resource when nothing else can be detected', async () => {
// gcp-metadata throws when it can't access the metadata server
metadataStub.instance.rejects();
Expand Down
Loading

0 comments on commit 6f3fa8b

Please sign in to comment.