Skip to content

Commit

Permalink
Implement GAE resource detection (#351)
Browse files Browse the repository at this point in the history
  • Loading branch information
aabmass authored Aug 13, 2024
1 parent 7932c01 commit 816b992
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.


# TODO: use opentelemetry-semantic-conventions package for these constants once it has
# stabilized. Right now, pinning an unstable version would cause dependency conflicts for
# users so these are copied in.
Expand All @@ -25,6 +26,7 @@ class ResourceAttributes:
FAAS_INSTANCE = "faas.instance"
FAAS_NAME = "faas.name"
FAAS_VERSION = "faas.version"
GCP_APP_ENGINE = "gcp_app_engine"
GCP_CLOUD_FUNCTIONS = "gcp_cloud_functions"
GCP_CLOUD_RUN = "gcp_cloud_run"
GCP_COMPUTE_ENGINE = "gcp_compute_engine"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from opentelemetry.resourcedetector.gcp_resource_detector import (
_faas,
_gae,
_gce,
_gke,
_metadata,
Expand All @@ -29,6 +30,7 @@

class GoogleCloudResourceDetector(ResourceDetector):
def detect(self) -> Resource:
# pylint: disable=too-many-return-statements
if not _metadata.is_available():
return Resource.get_empty()

Expand All @@ -38,6 +40,8 @@ def detect(self) -> Resource:
return _cloud_functions_resource()
if _faas.on_cloud_run():
return _cloud_run_resource()
if _gae.on_app_engine():
return _gae_resource()
if _gce.on_gce():
return _gce_resource()

Expand Down Expand Up @@ -99,6 +103,31 @@ def _cloud_functions_resource() -> Resource:
)


def _gae_resource() -> Resource:
if _gae.on_app_engine_standard():
zone = _gae.standard_availability_zone()
region = _gae.standard_cloud_region()
else:
zone_and_region = _gae.flex_availability_zone_and_region()
zone = zone_and_region.zone
region = zone_and_region.region

faas_name = _gae.service_name()
faas_version = _gae.service_version()
faas_instance = _gae.service_instance()

return _make_resource(
{
ResourceAttributes.CLOUD_PLATFORM_KEY: ResourceAttributes.GCP_APP_ENGINE,
ResourceAttributes.FAAS_NAME: faas_name,
ResourceAttributes.FAAS_VERSION: faas_version,
ResourceAttributes.FAAS_INSTANCE: faas_instance,
ResourceAttributes.CLOUD_AVAILABILITY_ZONE: zone,
ResourceAttributes.CLOUD_REGION: region,
}
)


def _make_resource(attrs: Mapping[str, AttributeValue]) -> Resource:
return Resource.create(
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright 2024 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 os

from opentelemetry.resourcedetector.gcp_resource_detector import (
_faas,
_gce,
_metadata,
)

_GAE_SERVICE_ENV = "GAE_SERVICE"
_GAE_VERSION_ENV = "GAE_VERSION"
_GAE_INSTANCE_ENV = "GAE_INSTANCE"
_GAE_ENV = "GAE_ENV"
_GAE_STANDARD = "standard"


def on_app_engine_standard() -> bool:
return os.environ.get(_GAE_ENV) == _GAE_STANDARD


def on_app_engine() -> bool:
return _GAE_SERVICE_ENV in os.environ


def service_name() -> str:
"""The service name of the app engine service.
Check that ``on_app_engine()`` is true before calling this, or it may throw exceptions.
"""
return os.environ[_GAE_SERVICE_ENV]


def service_version() -> str:
"""The service version of the app engine service.
Check that ``on_app_engine()`` is true before calling this, or it may throw exceptions.
"""
return os.environ[_GAE_VERSION_ENV]


def service_instance() -> str:
"""The service instance of the app engine service.
Check that ``on_app_engine()`` is true before calling this, or it may throw exceptions.
"""
return os.environ[_GAE_INSTANCE_ENV]


def flex_availability_zone_and_region() -> _gce.ZoneAndRegion:
"""The zone and region in which this program is running.
Check that ``on_app_engine()`` is true before calling this, or it may throw exceptions.
"""
return _gce.availability_zone_and_region()


def standard_availability_zone() -> str:
"""The zone the app engine service is running in.
Check that ``on_app_engine_standard()`` is true before calling this, or it may throw exceptions.
"""
zone = _metadata.get_metadata()["instance"]["zone"]
# zone is of the form "projects/233510669999/zones/us15"
return zone[zone.rfind("/") + 1 :]


def standard_cloud_region() -> str:
"""The region the app engine service is running in.
Check that ``on_app_engine_standard()`` is true before calling this, or it may throw exceptions.
"""
return _faas.faas_cloud_region()
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,38 @@
dict({
})
# ---
# name: test_detects_gae_flex
dict({
'cloud.account.id': 'fakeProject',
'cloud.availability_zone': 'us-east4-b',
'cloud.platform': 'gcp_app_engine',
'cloud.provider': 'gcp',
'cloud.region': 'us-east4',
'faas.instance': 'fake-instance',
'faas.name': 'fake-service',
'faas.version': 'fake-version',
'service.name': 'unknown_service',
'telemetry.sdk.language': 'python',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.20.0',
})
# ---
# name: test_detects_gae_standard
dict({
'cloud.account.id': 'fakeProject',
'cloud.availability_zone': 'us-east4-b',
'cloud.platform': 'gcp_app_engine',
'cloud.provider': 'gcp',
'cloud.region': 'us-east4',
'faas.instance': 'fake-instance',
'faas.name': 'fake-service',
'faas.version': 'fake-version',
'service.name': 'unknown_service',
'telemetry.sdk.language': 'python',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.20.0',
})
# ---
# name: test_detects_gce
dict({
'cloud.account.id': 'fakeProject',
Expand Down
42 changes: 42 additions & 0 deletions opentelemetry-resourcedetector-gcp/tests/test_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,45 @@ def test_detects_cloud_functions(
)

assert dict(GoogleCloudResourceDetector().detect().attributes) == snapshot


def test_detects_gae_standard(
snapshot,
fake_metadata: _metadata.Metadata,
monkeypatch: pytest.MonkeyPatch,
):
monkeypatch.setenv("GAE_ENV", "standard")
monkeypatch.setenv("GAE_SERVICE", "fake-service")
monkeypatch.setenv("GAE_VERSION", "fake-version")
monkeypatch.setenv("GAE_INSTANCE", "fake-instance")
fake_metadata.update(
{
"project": {"projectId": "fakeProject"},
"instance": {
"region": "projects/233510669999/regions/us-east4",
"zone": "us-east4-b",
},
}
)

assert dict(GoogleCloudResourceDetector().detect().attributes) == snapshot


def test_detects_gae_flex(
snapshot,
fake_metadata: _metadata.Metadata,
monkeypatch: pytest.MonkeyPatch,
):
monkeypatch.setenv("GAE_SERVICE", "fake-service")
monkeypatch.setenv("GAE_VERSION", "fake-version")
monkeypatch.setenv("GAE_INSTANCE", "fake-instance")
fake_metadata.update(
{
"project": {"projectId": "fakeProject"},
"instance": {
"zone": "projects/233510669999/zones/us-east4-b",
},
}
)

assert dict(GoogleCloudResourceDetector().detect().attributes) == snapshot
89 changes: 89 additions & 0 deletions opentelemetry-resourcedetector-gcp/tests/test_gae.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright 2024 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.

from unittest.mock import MagicMock

import pytest
from opentelemetry.resourcedetector.gcp_resource_detector import _gae


# Reset stuff before every test
# pylint: disable=unused-argument
@pytest.fixture(autouse=True)
def autouse(fake_get_metadata):
pass


def test_detects_on_gae(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("GAE_SERVICE", "fake-service")
assert _gae.on_app_engine()


def test_detects_not_on_gae() -> None:
assert not _gae.on_app_engine()


def test_detects_on_gae_standard(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("GAE_ENV", "standard")
assert _gae.on_app_engine_standard()


def test_detects_not_on_gae_standard(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("GAE_SERVICE", "fake-service")
assert _gae.on_app_engine()
assert not _gae.on_app_engine_standard()


def test_detects_gae_service_name(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("GAE_SERVICE", "fake-service")
assert _gae.service_name() == "fake-service"


def test_detects_gae_service_version(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("GAE_VERSION", "fake-version")
assert _gae.service_version() == "fake-version"


def test_detects_gae_service_instance(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("GAE_INSTANCE", "fake-instance")
assert _gae.service_instance() == "fake-instance"


def test_detects_gae_flex_zone_and_region(
fake_get_metadata: MagicMock,
) -> None:
fake_get_metadata.return_value = {
"instance": {"zone": "projects/233510669999/zones/us-east4-b"}
}
zone_and_region = _gae.flex_availability_zone_and_region()
assert zone_and_region.zone == "us-east4-b"
assert zone_and_region.region == "us-east4"


def test_gae_standard_zone(
fake_get_metadata: MagicMock,
) -> None:
fake_get_metadata.return_value = {
"instance": {"zone": "projects/233510669999/zones/us15"}
}
assert _gae.standard_availability_zone() == "us15"


def test_gae_standard_region(
fake_get_metadata: MagicMock,
) -> None:
fake_get_metadata.return_value = {
"instance": {"region": "projects/233510669999/regions/us-east4"}
}
assert _gae.standard_cloud_region() == "us-east4"

0 comments on commit 816b992

Please sign in to comment.