Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement GAE resource detection #351

Merged
merged 1 commit into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
Loading