Skip to content

Commit 8ce5ecf

Browse files
RangelRealeaabmass
andauthored
Resource Detector for Cloud Run/Cloud Functions (#194)
* Add support for cloud run and cloud functions Co-authored-by: Aaron Abbott <[email protected]>
1 parent c0a2062 commit 8ce5ecf

File tree

2 files changed

+312
-0
lines changed

2 files changed

+312
-0
lines changed

opentelemetry-resourcedetector-gcp/src/opentelemetry/resourcedetector/gcp_resource_detector/__init__.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,74 @@ def get_gke_resources():
9999
return common_attributes
100100

101101

102+
def get_cloudrun_resources():
103+
"""Resource finder for Cloud Run attributes"""
104+
105+
if os.getenv("K_CONFIGURATION") is None:
106+
return {}
107+
108+
(
109+
common_attributes,
110+
all_metadata,
111+
) = _get_google_metadata_and_common_attributes()
112+
113+
faas_name = os.getenv("K_SERVICE")
114+
if faas_name is not None:
115+
common_attributes["faas.name"] = str(faas_name)
116+
117+
faas_version = os.getenv("K_REVISION")
118+
if faas_version is not None:
119+
common_attributes["faas.version"] = str(faas_version)
120+
121+
common_attributes.update(
122+
{
123+
"cloud.platform": "gcp_cloud_run",
124+
"cloud.region": all_metadata["instance"]["region"].split("/")[-1],
125+
"faas.id": all_metadata["instance"]["id"],
126+
"gcp.resource_type": "cloud_run",
127+
}
128+
)
129+
return common_attributes
130+
131+
132+
def get_cloudfunctions_resources():
133+
"""Resource finder for Cloud Functions attributes"""
134+
135+
if os.getenv("FUNCTION_TARGET") is None:
136+
return {}
137+
138+
(
139+
common_attributes,
140+
all_metadata,
141+
) = _get_google_metadata_and_common_attributes()
142+
143+
faas_name = os.getenv("K_SERVICE")
144+
if faas_name is not None:
145+
common_attributes["faas.name"] = str(faas_name)
146+
147+
faas_version = os.getenv("K_REVISION")
148+
if faas_version is not None:
149+
common_attributes["faas.version"] = str(faas_version)
150+
151+
common_attributes.update(
152+
{
153+
"cloud.platform": "gcp_cloud_functions",
154+
"cloud.region": all_metadata["instance"]["region"].split("/")[-1],
155+
"faas.id": all_metadata["instance"]["id"],
156+
"gcp.resource_type": "cloud_functions",
157+
}
158+
)
159+
return common_attributes
160+
161+
102162
# Order here matters. Since a GKE_CONTAINER is a specialized type of GCE_INSTANCE
103163
# We need to first check if it matches the criteria for being a GKE_CONTAINER
104164
# before falling back and checking if its a GCE_INSTANCE.
105165
# This list should be sorted from most specialized to least specialized.
106166
_RESOURCE_FINDERS = [
107167
("gke_container", get_gke_resources),
168+
("cloud_run", get_cloudrun_resources),
169+
("cloud_functions", get_cloudfunctions_resources),
108170
("gce_instance", get_gce_resources),
109171
]
110172

opentelemetry-resourcedetector-gcp/tests/test_gcp_resource_detector.py

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
_GCP_METADATA_URL,
2121
GoogleCloudResourceDetector,
2222
NoGoogleResourcesFound,
23+
get_cloudfunctions_resources,
24+
get_cloudrun_resources,
2325
get_gce_resources,
2426
get_gke_resources,
2527
)
@@ -30,6 +32,10 @@
3032
HOSTNAME = "HOSTNAME"
3133
POD_NAME = "POD_NAME"
3234
KUBERNETES_SERVICE_HOST = "KUBERNETES_SERVICE_HOST"
35+
K_CONFIGURATION = "K_CONFIGURATION"
36+
FUNCTION_TARGET = "FUNCTION_TARGET"
37+
K_SERVICE = "K_SERVICE"
38+
K_REVISION = "K_REVISION"
3339

3440
GCE_RESOURCES_JSON_STRING = {
3541
"instance": {"id": "instance_id", "zone": "projects/123/zones/zone"},
@@ -45,6 +51,24 @@
4551
"project": {"projectId": "project_id"},
4652
}
4753

54+
CLOUDRUN_RESOURCES_JSON_STRING = {
55+
"instance": {
56+
"id": "instance_id",
57+
"zone": "projects/123/zones/zone",
58+
"region": "projects/123/regions/region",
59+
},
60+
"project": {"projectId": "project_id"},
61+
}
62+
63+
CLOUDFUNCTIONS_RESOURCES_JSON_STRING = {
64+
"instance": {
65+
"id": "instance_id",
66+
"zone": "projects/123/zones/zone",
67+
"region": "projects/123/regions/region",
68+
},
69+
"project": {"projectId": "project_id"},
70+
}
71+
4872

4973
@mock.patch(
5074
"opentelemetry.resourcedetector.gcp_resource_detector.requests.get",
@@ -197,6 +221,170 @@ def test_finding_gke_resources_with_pod_name(self, getter):
197221
)
198222

199223

224+
def clear_cloudrun_env_vars():
225+
pop_environ_key(K_CONFIGURATION)
226+
pop_environ_key(K_SERVICE)
227+
pop_environ_key(K_REVISION)
228+
229+
230+
@mock.patch(
231+
"opentelemetry.resourcedetector.gcp_resource_detector.requests.get",
232+
**{"return_value.json.return_value": CLOUDRUN_RESOURCES_JSON_STRING}
233+
)
234+
class TestCloudRunResourceFinder(unittest.TestCase):
235+
def tearDown(self) -> None:
236+
clear_cloudrun_env_vars()
237+
238+
# pylint: disable=unused-argument
239+
def test_not_running_on_cloudrun(self, getter):
240+
pop_environ_key(K_CONFIGURATION)
241+
found_resources = get_cloudrun_resources()
242+
self.assertEqual(found_resources, {})
243+
244+
# pylint: disable=unused-argument
245+
def test_missing_service_name(self, getter):
246+
os.environ[K_CONFIGURATION] = "cloudrun_config"
247+
pop_environ_key(K_SERVICE)
248+
pop_environ_key(K_REVISION)
249+
found_resources = get_cloudrun_resources()
250+
self.assertEqual(
251+
found_resources,
252+
{
253+
"cloud.account.id": "project_id",
254+
"cloud.platform": "gcp_cloud_run",
255+
"cloud.region": "region",
256+
"faas.id": "instance_id",
257+
"cloud.zone": "zone",
258+
"cloud.provider": "gcp",
259+
"gcp.resource_type": "cloud_run",
260+
},
261+
)
262+
263+
# pylint: disable=unused-argument
264+
def test_environment_empty_strings(self, getter):
265+
os.environ[K_CONFIGURATION] = "cloudrun_config"
266+
os.environ[K_SERVICE] = ""
267+
os.environ[K_REVISION] = ""
268+
found_resources = get_cloudrun_resources()
269+
self.assertEqual(
270+
found_resources,
271+
{
272+
"cloud.account.id": "project_id",
273+
"cloud.platform": "gcp_cloud_run",
274+
"cloud.region": "region",
275+
"faas.id": "instance_id",
276+
"faas.name": "",
277+
"faas.version": "",
278+
"cloud.zone": "zone",
279+
"cloud.provider": "gcp",
280+
"gcp.resource_type": "cloud_run",
281+
},
282+
)
283+
284+
def test_finding_cloudrun_resources(self, getter):
285+
os.environ[K_CONFIGURATION] = "cloudrun_config"
286+
os.environ[K_SERVICE] = "service"
287+
os.environ[K_REVISION] = "revision"
288+
found_resources = get_cloudrun_resources()
289+
self.assertEqual(getter.call_args_list[0][0][0], _GCP_METADATA_URL)
290+
self.assertEqual(
291+
found_resources,
292+
{
293+
"cloud.account.id": "project_id",
294+
"cloud.platform": "gcp_cloud_run",
295+
"cloud.region": "region",
296+
"faas.id": "instance_id",
297+
"faas.name": "service",
298+
"faas.version": "revision",
299+
"cloud.zone": "zone",
300+
"cloud.provider": "gcp",
301+
"gcp.resource_type": "cloud_run",
302+
},
303+
)
304+
305+
306+
def clear_cloudfunctions_env_vars():
307+
pop_environ_key(FUNCTION_TARGET)
308+
pop_environ_key(K_SERVICE)
309+
pop_environ_key(K_REVISION)
310+
311+
312+
@mock.patch(
313+
"opentelemetry.resourcedetector.gcp_resource_detector.requests.get",
314+
**{"return_value.json.return_value": CLOUDFUNCTIONS_RESOURCES_JSON_STRING}
315+
)
316+
class TestCloudFunctionsResourceFinder(unittest.TestCase):
317+
def tearDown(self) -> None:
318+
clear_cloudfunctions_env_vars()
319+
320+
# pylint: disable=unused-argument
321+
def test_not_running_on_cloudfunctions(self, getter):
322+
pop_environ_key(FUNCTION_TARGET)
323+
found_resources = get_cloudfunctions_resources()
324+
self.assertEqual(found_resources, {})
325+
326+
# pylint: disable=unused-argument
327+
def test_missing_service_name(self, getter):
328+
os.environ[FUNCTION_TARGET] = "function"
329+
pop_environ_key(K_SERVICE)
330+
pop_environ_key(K_REVISION)
331+
found_resources = get_cloudfunctions_resources()
332+
self.assertEqual(
333+
found_resources,
334+
{
335+
"cloud.account.id": "project_id",
336+
"cloud.platform": "gcp_cloud_functions",
337+
"cloud.region": "region",
338+
"faas.id": "instance_id",
339+
"cloud.zone": "zone",
340+
"cloud.provider": "gcp",
341+
"gcp.resource_type": "cloud_functions",
342+
},
343+
)
344+
345+
# pylint: disable=unused-argument
346+
def test_environment_empty_strings(self, getter):
347+
os.environ[FUNCTION_TARGET] = "function"
348+
os.environ[K_SERVICE] = ""
349+
os.environ[K_REVISION] = ""
350+
found_resources = get_cloudfunctions_resources()
351+
self.assertEqual(
352+
found_resources,
353+
{
354+
"cloud.account.id": "project_id",
355+
"cloud.platform": "gcp_cloud_functions",
356+
"cloud.region": "region",
357+
"faas.id": "instance_id",
358+
"faas.name": "",
359+
"faas.version": "",
360+
"cloud.zone": "zone",
361+
"cloud.provider": "gcp",
362+
"gcp.resource_type": "cloud_functions",
363+
},
364+
)
365+
366+
def test_finding_cloudfunctions_resources(self, getter):
367+
os.environ[FUNCTION_TARGET] = "function"
368+
os.environ[K_SERVICE] = "service"
369+
os.environ[K_REVISION] = "revision"
370+
found_resources = get_cloudfunctions_resources()
371+
self.assertEqual(getter.call_args_list[0][0][0], _GCP_METADATA_URL)
372+
self.assertEqual(
373+
found_resources,
374+
{
375+
"cloud.account.id": "project_id",
376+
"cloud.platform": "gcp_cloud_functions",
377+
"cloud.region": "region",
378+
"faas.id": "instance_id",
379+
"faas.name": "service",
380+
"faas.version": "revision",
381+
"cloud.zone": "zone",
382+
"cloud.provider": "gcp",
383+
"gcp.resource_type": "cloud_functions",
384+
},
385+
)
386+
387+
200388
@mock.patch(
201389
"opentelemetry.resourcedetector.gcp_resource_detector.requests.get"
202390
)
@@ -208,6 +396,8 @@ def test_finding_gce_resources(self, getter):
208396
# The necessary env variables were not set for GKE resource detection
209397
# to succeed. We should be falling back to detecting GCE resources
210398
pop_environ_key(KUBERNETES_SERVICE_HOST)
399+
pop_environ_key(K_CONFIGURATION)
400+
pop_environ_key(FUNCTION_TARGET)
211401
resource_finder = GoogleCloudResourceDetector()
212402
getter.return_value.json.return_value = GCE_RESOURCES_JSON_STRING
213403
found_resources = resource_finder.detect()
@@ -272,6 +462,66 @@ def test_finding_gke_resources(self, getter):
272462
)
273463
self.assertEqual(getter.call_count, 1)
274464

465+
def test_finding_cloudrun_resources(self, getter):
466+
# The necessary env variables were set for CloudRun resource detection
467+
# to succeed. No GCE resource info should be extracted
468+
os.environ[K_CONFIGURATION] = "cloudrun_config"
469+
os.environ[K_SERVICE] = "service"
470+
os.environ[K_REVISION] = "revision"
471+
472+
resource_finder = GoogleCloudResourceDetector()
473+
getter.return_value.json.return_value = CLOUDRUN_RESOURCES_JSON_STRING
474+
found_resources = resource_finder.detect()
475+
self.assertEqual(getter.call_args_list[0][0][0], _GCP_METADATA_URL)
476+
self.assertEqual(
477+
found_resources,
478+
Resource(
479+
attributes={
480+
"cloud.account.id": "project_id",
481+
"cloud.platform": "gcp_cloud_run",
482+
"cloud.region": "region",
483+
"faas.id": "instance_id",
484+
"faas.name": "service",
485+
"faas.version": "revision",
486+
"cloud.zone": "zone",
487+
"cloud.provider": "gcp",
488+
"gcp.resource_type": "cloud_run",
489+
}
490+
),
491+
)
492+
self.assertEqual(getter.call_count, 1)
493+
494+
def test_finding_cloudfunctions_resources(self, getter):
495+
# The necessary env variables were set for Cloudfunctions resource detection
496+
# to succeed. No GCE resource info should be extracted
497+
os.environ[FUNCTION_TARGET] = "function"
498+
os.environ[K_SERVICE] = "service"
499+
os.environ[K_REVISION] = "revision"
500+
501+
resource_finder = GoogleCloudResourceDetector()
502+
getter.return_value.json.return_value = (
503+
CLOUDFUNCTIONS_RESOURCES_JSON_STRING
504+
)
505+
found_resources = resource_finder.detect()
506+
self.assertEqual(getter.call_args_list[0][0][0], _GCP_METADATA_URL)
507+
self.assertEqual(
508+
found_resources,
509+
Resource(
510+
attributes={
511+
"cloud.account.id": "project_id",
512+
"cloud.platform": "gcp_cloud_functions",
513+
"cloud.region": "region",
514+
"faas.id": "instance_id",
515+
"faas.name": "service",
516+
"faas.version": "revision",
517+
"cloud.zone": "zone",
518+
"cloud.provider": "gcp",
519+
"gcp.resource_type": "cloud_functions",
520+
}
521+
),
522+
)
523+
self.assertEqual(getter.call_count, 1)
524+
275525
def test_resource_finding_fallback(self, getter):
276526
# The environment variables imply its on GKE, but the metadata doesn't
277527
# have GKE information

0 commit comments

Comments
 (0)