Skip to content

Commit 246dd07

Browse files
authoredMay 23, 2023
feat: add metrics (part 1) (#1298)
This PR: (1) list the metrics values needed (2) add the `_metric_header_for_usage` method to the base credential class, which is used by the `before_request` method to add the metrics header for token usage. Children credentials classes can override this method for token usage metrics. internal doc: go/googleapis-auth-metric-design
1 parent 3bac683 commit 246dd07

File tree

5 files changed

+255
-0
lines changed

5 files changed

+255
-0
lines changed
 

‎google/auth/credentials.py

+17
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from google.auth import _helpers, environment_vars
2424
from google.auth import exceptions
25+
from google.auth import metrics
2526

2627

2728
@six.add_metaclass(abc.ABCMeta)
@@ -100,6 +101,21 @@ def refresh(self, request):
100101
# (pylint doesn't recognize that this is abstract)
101102
raise NotImplementedError("Refresh must be implemented")
102103

104+
def _metric_header_for_usage(self):
105+
"""The x-goog-api-client header for token usage metric.
106+
107+
This header will be added to the API service requests in before_request
108+
method. For example, "cred-type/sa-jwt" means service account self
109+
signed jwt access token is used in the API service request
110+
authorization header. Children credentials classes need to override
111+
this method to provide the header value, if the token usage metric is
112+
needed.
113+
114+
Returns:
115+
str: The x-goog-api-client header value.
116+
"""
117+
return None
118+
103119
def apply(self, headers, token=None):
104120
"""Apply the token to the authentication header.
105121
@@ -133,6 +149,7 @@ def before_request(self, request, method, url, headers):
133149
# the http request.)
134150
if not self.valid:
135151
self.refresh(request)
152+
metrics.add_metric_header(headers, self._metric_header_for_usage())
136153
self.apply(headers)
137154

138155

‎google/auth/metrics.py

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Copyright 2023 Google LLC
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+
""" We use x-goog-api-client header to report metrics. This module provides
16+
the constants and helper methods to construct x-goog-api-client header.
17+
"""
18+
19+
import platform
20+
21+
from google.auth import version
22+
23+
24+
API_CLIENT_HEADER = "x-goog-api-client"
25+
26+
# Auth request type
27+
REQUEST_TYPE_ACCESS_TOKEN = "auth-request-type/at"
28+
REQUEST_TYPE_ID_TOKEN = "auth-request-type/it"
29+
REQUEST_TYPE_MDS_PING = "auth-request-type/mds"
30+
REQUEST_TYPE_REAUTH_START = "auth-request-type/re-start"
31+
REQUEST_TYPE_REAUTH_CONTINUE = "auth-request-type/re-cont"
32+
33+
# Credential type
34+
CRED_TYPE_USER = "cred-type/u"
35+
CRED_TYPE_SA_ASSERTION = "cred-type/sa"
36+
CRED_TYPE_SA_JWT = "cred-type/jwt"
37+
CRED_TYPE_SA_MDS = "cred-type/mds"
38+
CRED_TYPE_SA_IMPERSONATE = "cred-type/imp"
39+
40+
41+
# Versions
42+
def python_and_auth_lib_version():
43+
return "gl-python/{} auth/{}".format(platform.python_version(), version.__version__)
44+
45+
46+
# Token request metric header values
47+
48+
# x-goog-api-client header value for access token request via metadata server.
49+
# Example: "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/mds"
50+
def token_request_access_token_mds():
51+
return "{} {} {}".format(
52+
python_and_auth_lib_version(), REQUEST_TYPE_ACCESS_TOKEN, CRED_TYPE_SA_MDS
53+
)
54+
55+
56+
# x-goog-api-client header value for ID token request via metadata server.
57+
# Example: "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/mds"
58+
def token_request_id_token_mds():
59+
return "{} {} {}".format(
60+
python_and_auth_lib_version(), REQUEST_TYPE_ID_TOKEN, CRED_TYPE_SA_MDS
61+
)
62+
63+
64+
# x-goog-api-client header value for impersonated credentials access token request.
65+
# Example: "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp"
66+
def token_request_access_token_impersonate():
67+
return "{} {} {}".format(
68+
python_and_auth_lib_version(),
69+
REQUEST_TYPE_ACCESS_TOKEN,
70+
CRED_TYPE_SA_IMPERSONATE,
71+
)
72+
73+
74+
# x-goog-api-client header value for impersonated credentials ID token request.
75+
# Example: "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/imp"
76+
def token_request_id_token_impersonate():
77+
return "{} {} {}".format(
78+
python_and_auth_lib_version(), REQUEST_TYPE_ID_TOKEN, CRED_TYPE_SA_IMPERSONATE
79+
)
80+
81+
82+
# x-goog-api-client header value for service account credentials access token
83+
# request (assertion flow).
84+
# Example: "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/sa"
85+
def token_request_access_token_sa_assertion():
86+
return "{} {} {}".format(
87+
python_and_auth_lib_version(), REQUEST_TYPE_ACCESS_TOKEN, CRED_TYPE_SA_ASSERTION
88+
)
89+
90+
91+
# x-goog-api-client header value for service account credentials ID token
92+
# request (assertion flow).
93+
# Example: "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/sa"
94+
def token_request_id_token_sa_assertion():
95+
return "{} {} {}".format(
96+
python_and_auth_lib_version(), REQUEST_TYPE_ID_TOKEN, CRED_TYPE_SA_ASSERTION
97+
)
98+
99+
100+
# x-goog-api-client header value for user credentials token request.
101+
# Example: "gl-python/3.7 auth/1.1 cred-type/u"
102+
def token_request_user():
103+
return "{} {}".format(python_and_auth_lib_version(), CRED_TYPE_USER)
104+
105+
106+
# Miscellenous metrics
107+
108+
# x-goog-api-client header value for metadata server ping.
109+
# Example: "gl-python/3.7 auth/1.1 auth-request-type/mds"
110+
def mds_ping():
111+
return "{} {}".format(python_and_auth_lib_version(), REQUEST_TYPE_MDS_PING)
112+
113+
114+
# x-goog-api-client header value for reauth start endpoint calls.
115+
# Example: "gl-python/3.7 auth/1.1 auth-request-type/re-start"
116+
def reauth_start():
117+
return "{} {}".format(python_and_auth_lib_version(), REQUEST_TYPE_REAUTH_START)
118+
119+
120+
# x-goog-api-client header value for reauth continue endpoint calls.
121+
# Example: "gl-python/3.7 auth/1.1 cred-type/re-cont"
122+
def reauth_continue():
123+
return "{} {}".format(python_and_auth_lib_version(), REQUEST_TYPE_REAUTH_CONTINUE)
124+
125+
126+
def add_metric_header(headers, metric_header_value):
127+
"""Add x-goog-api-client header with the given value.
128+
129+
Args:
130+
headers (Mapping[str, str]): The headers to which we will add the
131+
metric header.
132+
metric_header_value (Optional[str]): If value is None, do nothing;
133+
if headers already has a x-goog-api-client header, append the value
134+
to the existing header; otherwise add a new x-goog-api-client
135+
header with the given value.
136+
"""
137+
if not metric_header_value:
138+
return
139+
if API_CLIENT_HEADER not in headers:
140+
headers[API_CLIENT_HEADER] = metric_header_value
141+
else:
142+
headers[API_CLIENT_HEADER] += " " + metric_header_value

‎system_tests/secrets.tar.enc

0 Bytes
Binary file not shown.

‎tests/test_credentials.py

+17
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ def with_quota_project(self, quota_project_id):
2828
raise NotImplementedError()
2929

3030

31+
class CredentialsImplWithMetrics(credentials.Credentials):
32+
def refresh(self, request):
33+
self.token = request
34+
35+
def _metric_header_for_usage(self):
36+
return "foo"
37+
38+
3139
def test_credentials_constructor():
3240
credentials = CredentialsImpl()
3341
assert not credentials.token
@@ -83,6 +91,15 @@ def test_before_request():
8391
assert headers["authorization"] == "Bearer token"
8492

8593

94+
def test_before_request_metrics():
95+
credentials = CredentialsImplWithMetrics()
96+
request = "token"
97+
headers = {}
98+
99+
credentials.before_request(request, "http://example.com", "GET", headers)
100+
assert headers["x-goog-api-client"] == "foo"
101+
102+
86103
def test_anonymous_credentials_ctor():
87104
anon = credentials.AnonymousCredentials()
88105
assert anon.token is None

‎tests/test_metrics.py

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright 2014 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+
import platform
16+
17+
import mock
18+
19+
from google.auth import metrics
20+
from google.auth import version
21+
22+
23+
def test_add_metric_header():
24+
headers = {}
25+
metrics.add_metric_header(headers, None)
26+
assert headers == {}
27+
28+
headers = {"x-goog-api-client": "foo"}
29+
metrics.add_metric_header(headers, "bar")
30+
assert headers == {"x-goog-api-client": "foo bar"}
31+
32+
headers = {}
33+
metrics.add_metric_header(headers, "bar")
34+
assert headers == {"x-goog-api-client": "bar"}
35+
36+
37+
@mock.patch.object(platform, "python_version", return_value="3.7")
38+
def test_versions(mock_python_version):
39+
version_save = version.__version__
40+
version.__version__ = "1.1"
41+
assert metrics.python_and_auth_lib_version() == "gl-python/3.7 auth/1.1"
42+
version.__version__ = version_save
43+
44+
45+
@mock.patch(
46+
"google.auth.metrics.python_and_auth_lib_version",
47+
return_value="gl-python/3.7 auth/1.1",
48+
)
49+
def test_metric_values(mock_python_and_auth_lib_version):
50+
assert (
51+
metrics.token_request_access_token_mds()
52+
== "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/mds"
53+
)
54+
assert (
55+
metrics.token_request_id_token_mds()
56+
== "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/mds"
57+
)
58+
assert (
59+
metrics.token_request_access_token_impersonate()
60+
== "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp"
61+
)
62+
assert (
63+
metrics.token_request_id_token_impersonate()
64+
== "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/imp"
65+
)
66+
assert (
67+
metrics.token_request_access_token_sa_assertion()
68+
== "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/sa"
69+
)
70+
assert (
71+
metrics.token_request_id_token_sa_assertion()
72+
== "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/sa"
73+
)
74+
assert metrics.token_request_user() == "gl-python/3.7 auth/1.1 cred-type/u"
75+
assert metrics.mds_ping() == "gl-python/3.7 auth/1.1 auth-request-type/mds"
76+
assert metrics.reauth_start() == "gl-python/3.7 auth/1.1 auth-request-type/re-start"
77+
assert (
78+
metrics.reauth_continue() == "gl-python/3.7 auth/1.1 auth-request-type/re-cont"
79+
)

0 commit comments

Comments
 (0)
Please sign in to comment.