Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit d665a14

Browse files
mayurkale22liyanhui1228
authored andcommitted
Add utils to automatically detect monitored resources. (#292)
1 parent 0d23c69 commit d665a14

15 files changed

Lines changed: 1230 additions & 27 deletions

opencensus/common/http_handler.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2018 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
try:
16+
# For Python 3.0 and later
17+
from urllib.request import urlopen, Request
18+
from urllib.error import HTTPError, URLError
19+
except ImportError:
20+
# Fall back to Python 2's urllib2
21+
from urllib2 import urlopen, Request
22+
from urllib2 import HTTPError, URLError
23+
24+
25+
import socket
26+
27+
_REQUEST_TIMEOUT = 2 # in secs
28+
29+
30+
def get_request(request_url, request_headers=dict()):
31+
"""Execute http get request on given request_url with optional headers
32+
"""
33+
request = Request(request_url)
34+
for key, val in request_headers.items():
35+
request.add_header(key, val)
36+
37+
try:
38+
response = urlopen(request, timeout=_REQUEST_TIMEOUT)
39+
response_content = response.read()
40+
except (HTTPError, URLError, socket.timeout):
41+
response_content = None
42+
43+
return response_content

opencensus/common/monitored_resource_util/__init__.py

Whitespace-only changes.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Copyright 2018 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from opencensus.common.http_handler import get_request
16+
import json
17+
18+
# AWS provides Instance Metadata via below url
19+
_AWS_INSTANCE_IDENTITY_DOCUMENT_URI = \
20+
"http://169.254.169.254/latest/dynamic/instance-identity/document"
21+
22+
_AWS_ATTRIBUTES = {
23+
# Region is the AWS region for the VM. The format of this field is
24+
# "aws:{region}", where supported values for {region} are listed at
25+
# http://docs.aws.amazon.com/general/latest/gr/rande.html.
26+
'region': 'region',
27+
28+
# accountId is the AWS account number for the VM.
29+
'accountId': 'aws_account',
30+
31+
# instanceId is the instance id of the instance.
32+
'instanceId': 'instance_id'
33+
}
34+
35+
# inited is used to make sure AWS initialize executes only once.
36+
inited = False
37+
38+
# Detects if the application is running on EC2 by making a connection to AWS
39+
# instance identity document URI.If connection is successful, application
40+
# should be on an EC2 instance.
41+
is_running_on_aws = False
42+
43+
aws_metadata_map = {}
44+
45+
46+
class AwsIdentityDocumentUtils(object):
47+
"""Util methods for getting and parsing AWS instance identity document."""
48+
49+
inited = False
50+
is_running = False
51+
52+
@classmethod
53+
def _initialize_aws_identity_document(cls):
54+
"""This method, tries to establish an HTTP connection to AWS instance
55+
identity document url. If the application is running on an EC2
56+
instance, we should be able to get back a valid JSON document. Make a
57+
http get request call and store data in local map.
58+
This method should only be called once.
59+
"""
60+
61+
if cls.inited:
62+
return
63+
64+
content = get_request(_AWS_INSTANCE_IDENTITY_DOCUMENT_URI)
65+
if content is not None:
66+
content = json.loads(content)
67+
for env_var, attribute_key in _AWS_ATTRIBUTES.items():
68+
attribute_value = content.get(env_var)
69+
if attribute_value is not None:
70+
aws_metadata_map[attribute_key] = attribute_value
71+
72+
cls.is_running = True
73+
74+
cls.inited = True
75+
76+
@classmethod
77+
def is_running_on_aws(cls):
78+
cls._initialize_aws_identity_document()
79+
return cls.is_running
80+
81+
def get_aws_metadata(self):
82+
"""AWS Instance Identity Document is a JSON file.
83+
See docs.aws.amazon.com/AWSEC2/latest/UserGuide/
84+
instance-identity-documents.html.
85+
:return:
86+
"""
87+
if self.is_running_on_aws():
88+
return aws_metadata_map
89+
90+
return dict()
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Copyright 2018 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from opencensus.common.http_handler import get_request
16+
import os
17+
18+
_GCP_METADATA_URI = 'http://metadata/computeMetadata/v1/'
19+
_GCP_METADATA_URI_HEADER = {'Metadata-Flavor': 'Google'}
20+
21+
# GCE common attributes
22+
# See: https://cloud.google.com/appengine/docs/flexible/python/runtime#
23+
# environment_variables
24+
_GCE_ATTRIBUTES = {
25+
# ProjectID is the identifier of the GCP project associated with this
26+
# resource, such as "my-project".
27+
'project_id': 'project/project-id',
28+
29+
# instance_id is the numeric VM instance identifier assigned by
30+
# Compute Engine.
31+
'instance_id': 'instance/id',
32+
33+
# zone is the Compute Engine zone in which the VM is running.
34+
'zone': 'instance/zone'
35+
}
36+
37+
_GKE_ATTRIBUTES = {
38+
# ProjectID is the identifier of the GCP project associated with this
39+
# resource, such as "my-project".
40+
'project_id': 'project/project-id',
41+
42+
# instance_id is the numeric VM instance identifier assigned by
43+
# Compute Engine.
44+
'instance_id': 'instance/id',
45+
46+
# zone is the Compute Engine zone in which the VM is running.
47+
'zone': 'instance/zone',
48+
49+
# cluster_name is the name for the cluster the container is running in.
50+
'cluster_name': 'instance/attributes/cluster-name'
51+
}
52+
53+
# Following attributes are derived from environment variables. They are
54+
# configured via yaml file. For details refer to:
55+
# https://cloud.google.com/kubernetes-engine/docs/tutorials/
56+
# custom-metrics-autoscaling#exporting_metrics_from_the_application
57+
_GKE_ENV_ATTRIBUTES = {
58+
# ContainerName is the name of the container.
59+
'container_name': 'CONTAINER_NAME',
60+
61+
# namespace_id is the identifier for the cluster namespace the container
62+
# is running in
63+
'namespace_id': 'NAMESPACE',
64+
65+
# pod_id is the identifier for the pod the container is running in.
66+
'pod_id': 'HOSTNAME'
67+
}
68+
69+
# Kubenertes environment variables
70+
_KUBERNETES_SERVICE_HOST = 'KUBERNETES_SERVICE_HOST'
71+
72+
gcp_metadata_map = {}
73+
74+
75+
class GcpMetadataConfig(object):
76+
"""GcpMetadata represents metadata retrieved from GCP (GKE and GCE)
77+
environment. Some attributes are retrieved from the system environment.
78+
see : <a href="https://cloud.google.com/compute/docs/
79+
storing-retrieving-metadata"> https://cloud.google.com/compute/docs/storing
80+
-retrieving-metadata</a>
81+
"""
82+
inited = False
83+
is_running = False
84+
85+
@classmethod
86+
def _initialize_metadata_service(cls):
87+
"""Initialize metadata service once and load gcp metadata into map
88+
This method should only be called once.
89+
"""
90+
if cls.inited:
91+
return
92+
93+
instance_id = cls._get_attribute('instance_id')
94+
95+
if instance_id is not None:
96+
cls.is_running = True
97+
98+
gcp_metadata_map['instance_id'] = instance_id
99+
100+
attributes = _GCE_ATTRIBUTES
101+
if _KUBERNETES_SERVICE_HOST in os.environ:
102+
attributes = _GKE_ATTRIBUTES
103+
104+
# fetch attributes from metadata request
105+
for attribute_key, attribute_uri in attributes.items():
106+
if attribute_key not in gcp_metadata_map:
107+
attribute_value = cls._get_attribute(attribute_key)
108+
if attribute_value is not None:
109+
gcp_metadata_map[attribute_key] = attribute_value
110+
111+
cls.inited = True
112+
113+
@classmethod
114+
def is_running_on_gcp(cls):
115+
cls._initialize_metadata_service()
116+
return cls.is_running
117+
118+
def get_gce_metadata(self):
119+
"""for GCP GCE instance"""
120+
if self.is_running_on_gcp():
121+
return gcp_metadata_map
122+
123+
return dict()
124+
125+
def get_gke_metadata(self):
126+
"""for GCP GKE container."""
127+
gke_metadata = {}
128+
129+
if self.is_running_on_gcp():
130+
gke_metadata = gcp_metadata_map
131+
132+
# fetch attributes from Environment Variables
133+
for attribute_key, attribute_env in _GKE_ENV_ATTRIBUTES.items():
134+
attribute_value = os.environ.get(attribute_env)
135+
if attribute_value is not None:
136+
gke_metadata[attribute_key] = attribute_value
137+
138+
return gke_metadata
139+
140+
@staticmethod
141+
def _get_attribute(attribute_key):
142+
"""
143+
Fetch the requested instance metadata entry.
144+
:param attribute_uri: attribute_uri: attribute name relative to the
145+
computeMetadata/v1 prefix
146+
:return: The value read from the metadata service or None
147+
"""
148+
attribute_value = get_request(_GCP_METADATA_URI +
149+
_GKE_ATTRIBUTES[attribute_key],
150+
_GCP_METADATA_URI_HEADER)
151+
152+
return attribute_value
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Copyright 2018 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
from opencensus.common.monitored_resource_util.gcp_metadata_config \
17+
import GcpMetadataConfig
18+
from opencensus.common.monitored_resource_util.aws_identity_doc_utils \
19+
import AwsIdentityDocumentUtils
20+
21+
# supported environments (resource types)
22+
_GCE_INSTANCE = "gce_instance"
23+
_GKE_CONTAINER = "gke_container"
24+
_AWS_EC2_INSTANCE = "aws_ec2_instance"
25+
26+
27+
class MonitoredResource(object):
28+
"""MonitoredResource returns the resource type and resource labels.
29+
"""
30+
31+
@property
32+
def resource_type(self):
33+
"""Returns the resource type this MonitoredResource.
34+
:return:
35+
"""
36+
raise NotImplementedError # pragma: NO COVER
37+
38+
def get_resource_labels(self):
39+
"""Returns the resource labels for this MonitoredResource.
40+
:return:
41+
"""
42+
raise NotImplementedError # pragma: NO COVER
43+
44+
45+
class GcpGceMonitoredResource(MonitoredResource):
46+
"""GceMonitoredResource represents gce_instance type monitored resource.
47+
For definition refer to
48+
https://cloud.google.com/monitoring/api/resources#tag_gce_instance
49+
"""
50+
51+
@property
52+
def resource_type(self):
53+
return _GCE_INSTANCE
54+
55+
def get_resource_labels(self):
56+
gcp_config = GcpMetadataConfig()
57+
return gcp_config.get_gce_metadata()
58+
59+
60+
class GcpGkeMonitoredResource(MonitoredResource):
61+
"""GkeMonitoredResource represents gke_container type monitored resource.
62+
For definition refer to
63+
https://cloud.google.com/monitoring/api/resources#tag_gke_container
64+
"""
65+
66+
@property
67+
def resource_type(self):
68+
return _GKE_CONTAINER
69+
70+
def get_resource_labels(self):
71+
gcp_config = GcpMetadataConfig()
72+
return gcp_config.get_gke_metadata()
73+
74+
75+
class AwsMonitoredResource(MonitoredResource):
76+
"""AwsMonitoredResource represents aws_ec2_instance type monitored resource.
77+
For definition refer to
78+
https://cloud.google.com/monitoring/api/resources#tag_aws_ec2_instance
79+
"""
80+
@property
81+
def resource_type(self):
82+
return _AWS_EC2_INSTANCE
83+
84+
def get_resource_labels(self):
85+
aws_util = AwsIdentityDocumentUtils()
86+
return aws_util.get_aws_metadata()

0 commit comments

Comments
 (0)