Skip to content

Commit 0dd7249

Browse files
liyanhui1228Jon Wayne Parrott
authored andcommitted
Logging: Use metadata server to detect GKE environment (#3856)
1 parent 357bfeb commit 0dd7249

4 files changed

Lines changed: 122 additions & 10 deletions

File tree

logging/google/cloud/logging/_helpers.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,17 @@
1414

1515
"""Common logging helpers."""
1616

17+
import requests
1718

1819
from google.cloud.logging.entries import ProtobufEntry
1920
from google.cloud.logging.entries import StructEntry
2021
from google.cloud.logging.entries import TextEntry
2122

23+
METADATA_URL = 'http://metadata/computeMetadata/v1/'
24+
METADATA_HEADERS = {
25+
'Metadata-Flavor': 'Google'
26+
}
27+
2228

2329
def entry_from_resource(resource, client, loggers):
2430
"""Detect correct entry type from resource and instantiate.
@@ -46,3 +52,32 @@ def entry_from_resource(resource, client, loggers):
4652
return ProtobufEntry.from_api_repr(resource, client, loggers)
4753

4854
raise ValueError('Cannot parse log entry resource.')
55+
56+
57+
def retrieve_metadata_server(metadata_key):
58+
"""Retrieve the metadata key in the metadata server.
59+
60+
See: https://cloud.google.com/compute/docs/storing-retrieving-metadata
61+
62+
:type metadata_key: str
63+
:param metadata_key: Key of the metadata which will form the url. You can
64+
also supply query parameters after the metadata key.
65+
e.g. "tags?alt=json"
66+
67+
:rtype: str
68+
:returns: The value of the metadata key returned by the metadata server.
69+
"""
70+
url = METADATA_URL + metadata_key
71+
72+
try:
73+
response = requests.get(url, headers=METADATA_HEADERS)
74+
75+
if response.status_code == requests.codes.ok:
76+
return response.text
77+
78+
except requests.exceptions.RequestException:
79+
# Ignore the exception, connection failed means the attribute does not
80+
# exist in the metadata server.
81+
pass
82+
83+
return None

logging/google/cloud/logging/client.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
from google.cloud.client import ClientWithProject
3333
from google.cloud.environment_vars import DISABLE_GRPC
34+
from google.cloud.logging._helpers import retrieve_metadata_server
3435
from google.cloud.logging._http import Connection
3536
from google.cloud.logging._http import _LoggingAPI as JSONLoggingAPI
3637
from google.cloud.logging._http import _MetricsAPI as JSONMetricsAPI
@@ -55,8 +56,8 @@
5556
_APPENGINE_FLEXIBLE_ENV_FLEX = 'GAE_INSTANCE'
5657
"""Environment variable set in App Engine when env:flex is set."""
5758

58-
_CONTAINER_ENGINE_ENV = 'KUBERNETES_SERVICE'
59-
"""Environment variable set in a Google Container Engine environment."""
59+
_GKE_CLUSTER_NAME = 'instance/attributes/cluster-name'
60+
"""Attribute in metadata server when in GKE environment."""
6061

6162

6263
class Client(ClientWithProject):
@@ -301,10 +302,12 @@ def get_default_handler(self):
301302
:rtype: :class:`logging.Handler`
302303
:returns: The default log handler based on the environment
303304
"""
305+
gke_cluster_name = retrieve_metadata_server(_GKE_CLUSTER_NAME)
306+
304307
if (_APPENGINE_FLEXIBLE_ENV_VM in os.environ or
305308
_APPENGINE_FLEXIBLE_ENV_FLEX in os.environ):
306309
return AppEngineHandler(self)
307-
elif _CONTAINER_ENGINE_ENV in os.environ:
310+
elif gke_cluster_name is not None:
308311
return ContainerEngineHandler()
309312
else:
310313
return CloudLoggingHandler(self)

logging/tests/unit/test__helpers.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
import unittest
1717

18+
import mock
19+
1820

1921
class Test_entry_from_resource(unittest.TestCase):
2022

@@ -53,6 +55,69 @@ def test_proto_payload(self):
5355
self._payload_helper('protoPayload', 'ProtobufEntry')
5456

5557

58+
class Test_retrieve_metadata_server(unittest.TestCase):
59+
60+
@staticmethod
61+
def _call_fut(metadata_key):
62+
from google.cloud.logging._helpers import retrieve_metadata_server
63+
64+
return retrieve_metadata_server(metadata_key)
65+
66+
def test_metadata_exists(self):
67+
status_code_ok = 200
68+
response_text = 'my-gke-cluster'
69+
metadata_key = 'test_key'
70+
71+
response_mock = ResponseMock(status_code=status_code_ok)
72+
response_mock.text = response_text
73+
74+
requests_mock = mock.Mock()
75+
requests_mock.get.return_value = response_mock
76+
requests_mock.codes.ok = status_code_ok
77+
78+
patch = mock.patch(
79+
'google.cloud.logging._helpers.requests',
80+
requests_mock)
81+
82+
with patch:
83+
metadata = self._call_fut(metadata_key)
84+
85+
self.assertEqual(metadata, response_text)
86+
87+
def test_metadata_does_not_exist(self):
88+
status_code_ok = 200
89+
status_code_not_found = 404
90+
metadata_key = 'test_key'
91+
92+
response_mock = ResponseMock(status_code=status_code_not_found)
93+
94+
requests_mock = mock.Mock()
95+
requests_mock.get.return_value = response_mock
96+
requests_mock.codes.ok = status_code_ok
97+
98+
patch = mock.patch(
99+
'google.cloud.logging._helpers.requests',
100+
requests_mock)
101+
102+
with patch:
103+
metadata = self._call_fut(metadata_key)
104+
105+
self.assertIsNone(metadata)
106+
107+
def test_request_exception(self):
108+
metadata_key = 'test_url_cannot_connect'
109+
metadata_url = 'http://metadata.invalid/'
110+
111+
patch = mock.patch(
112+
'google.cloud.logging._helpers.METADATA_URL',
113+
new=metadata_url)
114+
115+
with patch:
116+
metadata = self._call_fut(metadata_key)
117+
118+
self.assertIsNone(metadata)
119+
120+
56121
class EntryMock(object):
57122

58123
def __init__(self):
@@ -62,3 +127,10 @@ def __init__(self):
62127
def from_api_repr(self, resource, client, loggers):
63128
self.called = (resource, client, loggers)
64129
return self.sentinel
130+
131+
132+
class ResponseMock(object):
133+
134+
def __init__(self, status_code, text='test_response_text'):
135+
self.status_code = status_code
136+
self.text = text

logging/tests/unit/test_client.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -581,16 +581,18 @@ def test_get_default_handler_app_engine(self):
581581
self.assertIsInstance(handler, AppEngineHandler)
582582

583583
def test_get_default_handler_container_engine(self):
584-
import os
585-
from google.cloud._testing import _Monkey
586-
from google.cloud.logging.client import _CONTAINER_ENGINE_ENV
587584
from google.cloud.logging.handlers import ContainerEngineHandler
588585

589-
client = self._make_one(project=self.PROJECT,
590-
credentials=_make_credentials(),
591-
_use_grpc=False)
586+
client = self._make_one(
587+
project=self.PROJECT,
588+
credentials=_make_credentials(),
589+
_use_grpc=False)
590+
591+
patch = mock.patch(
592+
'google.cloud.logging.client.retrieve_metadata_server',
593+
return_value='test-gke-cluster')
592594

593-
with _Monkey(os, environ={_CONTAINER_ENGINE_ENV: 'True'}):
595+
with patch:
594596
handler = client.get_default_handler()
595597

596598
self.assertIsInstance(handler, ContainerEngineHandler)

0 commit comments

Comments
 (0)