Skip to content

Commit

Permalink
Implement Cloud Run and Cloud Functions faas resource detection (#346)
Browse files Browse the repository at this point in the history
  • Loading branch information
aabmass authored Jul 31, 2024
1 parent a7fcc52 commit 33c333f
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ class ResourceAttributes:
CLOUD_PLATFORM_KEY = "cloud.platform"
CLOUD_PROVIDER = "cloud.provider"
CLOUD_REGION = "cloud.region"
FAAS_INSTANCE = "faas.instance"
FAAS_NAME = "faas.name"
FAAS_VERSION = "faas.version"
GCP_CLOUD_FUNCTIONS = "gcp_cloud_functions"
GCP_CLOUD_RUN = "gcp_cloud_run"
GCP_COMPUTE_ENGINE = "gcp_compute_engine"
GCP_KUBERNETES_ENGINE = "gcp_kubernetes_engine"
HOST_ID = "host.id"
Expand All @@ -35,8 +40,6 @@ class ResourceAttributes:
SERVICE_INSTANCE_ID = "service.instance.id"
SERVICE_NAME = "service.name"
SERVICE_NAMESPACE = "service.namespace"
FAAS_INSTANCE = "faas.instance"
FAAS_NAME = "faas.name"


AWS_ACCOUNT = "aws_account"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from typing import Mapping

from opentelemetry.resourcedetector.gcp_resource_detector import (
_faas,
_gce,
_gke,
_metadata,
Expand All @@ -33,6 +34,10 @@ def detect(self) -> Resource:

if _gke.on_gke():
return _gke_resource()
if _faas.on_cloud_functions():
return _cloud_functions_resource()
if _faas.on_cloud_run():
return _cloud_run_resource()
if _gce.on_gce():
return _gce_resource()

Expand Down Expand Up @@ -70,6 +75,30 @@ def _gce_resource() -> Resource:
)


def _cloud_run_resource() -> Resource:
return _make_resource(
{
ResourceAttributes.CLOUD_PLATFORM_KEY: ResourceAttributes.GCP_CLOUD_RUN,
ResourceAttributes.FAAS_NAME: _faas.faas_name(),
ResourceAttributes.FAAS_VERSION: _faas.faas_version(),
ResourceAttributes.FAAS_INSTANCE: _faas.faas_instance(),
ResourceAttributes.CLOUD_REGION: _faas.faas_cloud_region(),
}
)


def _cloud_functions_resource() -> Resource:
return _make_resource(
{
ResourceAttributes.CLOUD_PLATFORM_KEY: ResourceAttributes.GCP_CLOUD_FUNCTIONS,
ResourceAttributes.FAAS_NAME: _faas.faas_name(),
ResourceAttributes.FAAS_VERSION: _faas.faas_version(),
ResourceAttributes.FAAS_INSTANCE: _faas.faas_instance(),
ResourceAttributes.CLOUD_REGION: _faas.faas_cloud_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,60 @@
# 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/faas.go

import os

from opentelemetry.resourcedetector.gcp_resource_detector import _metadata

_CLOUD_RUN_CONFIG_ENV = "K_CONFIGURATION"
_CLOUD_FUNCTION_TARGET_ENV = "FUNCTION_TARGET"
_FAAS_SERVICE_ENV = "K_SERVICE"
_FAAS_REVISION_ENV = "K_REVISION"


def on_cloud_run() -> bool:
return _CLOUD_RUN_CONFIG_ENV in os.environ


def on_cloud_functions() -> bool:
return _CLOUD_FUNCTION_TARGET_ENV in os.environ


def faas_name() -> str:
"""The name of the Cloud Run or Cloud Function.
Check that on_cloud_run() or on_cloud_functions() is true before calling this, or it may
throw exceptions.
"""
return os.environ[_FAAS_SERVICE_ENV]


def faas_version() -> str:
"""The version/revision of the Cloud Run or Cloud Function.
Check that on_cloud_run() or on_cloud_functions() is true before calling this, or it may
throw exceptions.
"""
return os.environ[_FAAS_REVISION_ENV]


def faas_instance() -> str:
return str(_metadata.get_metadata()["instance"]["id"])


def faas_cloud_region() -> str:
region = _metadata.get_metadata()["instance"]["region"]
return region[region.rfind("/") + 1 :]
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class Instance(TypedDict):
id: Union[int, str]
machineType: str
name: str
region: str
zone: str


Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
# name: test_detects_cloud_functions
dict({
'cloud.account.id': 'fakeProject',
'cloud.platform': 'gcp_cloud_functions',
'cloud.provider': 'gcp',
'cloud.region': 'us-east4',
'faas.instance': '0087244a',
'faas.name': 'fake-service',
'faas.version': 'fake-revision',
'service.name': 'unknown_service',
'telemetry.sdk.language': 'python',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.20.0',
})
# ---
# name: test_detects_cloud_run
dict({
'cloud.account.id': 'fakeProject',
'cloud.platform': 'gcp_cloud_run',
'cloud.provider': 'gcp',
'cloud.region': 'us-east4',
'faas.instance': '0087244a',
'faas.name': 'fake-service',
'faas.version': 'fake-revision',
'service.name': 'unknown_service',
'telemetry.sdk.language': 'python',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.20.0',
})
# ---
# name: test_detects_empty_as_fallback
dict({
})
Expand All @@ -13,7 +43,7 @@
'cloud.platform': 'gcp_compute_engine',
'cloud.provider': 'gcp',
'cloud.region': 'us-east4',
'host.id': '12345',
'host.id': '0087244a',
'host.name': 'fakeName',
'host.type': 'fakeMachineType',
'service.name': 'unknown_service',
Expand Down
49 changes: 48 additions & 1 deletion opentelemetry-resourcedetector-gcp/tests/test_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def test_detects_gce(snapshot, fake_metadata: _metadata.Metadata):
"project": {"projectId": "fakeProject"},
"instance": {
"name": "fakeName",
"id": 12345,
"id": "0087244a",
"machineType": "fakeMachineType",
"zone": "projects/233510669999/zones/us-east4-b",
"attributes": {},
Expand Down Expand Up @@ -108,3 +108,50 @@ def test_detects_gke(
)

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


def test_detects_cloud_run(
snapshot,
fake_metadata: _metadata.Metadata,
monkeypatch: pytest.MonkeyPatch,
):
monkeypatch.setenv("K_CONFIGURATION", "fake-configuration")
monkeypatch.setenv("K_SERVICE", "fake-service")
monkeypatch.setenv("K_REVISION", "fake-revision")
fake_metadata.update(
{
"project": {"projectId": "fakeProject"},
"instance": {
# this will not be numeric on FaaS
"id": "0087244a",
"region": "projects/233510669999/regions/us-east4",
},
}
)

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


def test_detects_cloud_functions(
snapshot,
fake_metadata: _metadata.Metadata,
monkeypatch: pytest.MonkeyPatch,
):
monkeypatch.setenv("FUNCTION_TARGET", "fake-function-target")
# Note all K_* environment variables are set since Cloud Functions executes within Cloud
# Run. This tests that the detector can differentiate between them
monkeypatch.setenv("K_CONFIGURATION", "fake-configuration")
monkeypatch.setenv("K_SERVICE", "fake-service")
monkeypatch.setenv("K_REVISION", "fake-revision")
fake_metadata.update(
{
"project": {"projectId": "fakeProject"},
"instance": {
# this will not be numeric on FaaS
"id": "0087244a",
"region": "projects/233510669999/regions/us-east4",
},
}
)

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


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


def test_detects_on_cloud_run(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("K_CONFIGURATION", "fake-configuration")
assert _faas.on_cloud_run()


def test_detects_not_on_cloud_run() -> None:
assert not _faas.on_cloud_run()


def test_detects_on_cloud_functions(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("FUNCTION_TARGET", "fake-function-target")
assert _faas.on_cloud_functions()


def test_detects_not_on_cloud_functions() -> None:
assert not _faas.on_cloud_functions()


def test_detects_faas_name(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("K_SERVICE", "fake-service")
assert _faas.faas_name() == "fake-service"


def test_detects_faas_version(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("K_REVISION", "fake-revision")
assert _faas.faas_version() == "fake-revision"


def test_detects_faas_instance(fake_get_metadata: MagicMock) -> None:
fake_get_metadata.return_value = {"instance": {"id": "0087244a"}}
assert _faas.faas_instance() == "0087244a"


def test_detects_faas_region(fake_get_metadata: MagicMock) -> None:
fake_get_metadata.return_value = {
"instance": {"region": "projects/233510669999/regions/us-east4"}
}
assert _faas.faas_cloud_region() == "us-east4"

0 comments on commit 33c333f

Please sign in to comment.