xref: /aosp_15_r20/external/perfetto/infra/ci/common_utils.py (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker# Copyright (C) 2019 The Android Open Source Project
2*6dbdd20aSAndroid Build Coastguard Worker#
3*6dbdd20aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*6dbdd20aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*6dbdd20aSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*6dbdd20aSAndroid Build Coastguard Worker#
7*6dbdd20aSAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
8*6dbdd20aSAndroid Build Coastguard Worker#
9*6dbdd20aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*6dbdd20aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*6dbdd20aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*6dbdd20aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*6dbdd20aSAndroid Build Coastguard Worker# limitations under the License.
14*6dbdd20aSAndroid Build Coastguard Worker
15*6dbdd20aSAndroid Build Coastguard Workerimport asyncio
16*6dbdd20aSAndroid Build Coastguard Workerimport concurrent.futures
17*6dbdd20aSAndroid Build Coastguard Workerimport google.auth
18*6dbdd20aSAndroid Build Coastguard Workerimport google.auth.transport.requests
19*6dbdd20aSAndroid Build Coastguard Workerimport json
20*6dbdd20aSAndroid Build Coastguard Workerimport logging
21*6dbdd20aSAndroid Build Coastguard Workerimport os
22*6dbdd20aSAndroid Build Coastguard Workerimport requests
23*6dbdd20aSAndroid Build Coastguard Worker
24*6dbdd20aSAndroid Build Coastguard Workerfrom base64 import b64encode
25*6dbdd20aSAndroid Build Coastguard Workerfrom datetime import datetime
26*6dbdd20aSAndroid Build Coastguard Workerfrom config import PROJECT
27*6dbdd20aSAndroid Build Coastguard Worker
28*6dbdd20aSAndroid Build Coastguard Worker# Thread pool for making http requests asynchronosly.
29*6dbdd20aSAndroid Build Coastguard Workerthread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=8)
30*6dbdd20aSAndroid Build Coastguard Worker
31*6dbdd20aSAndroid Build Coastguard Worker# Caller has to initialize this
32*6dbdd20aSAndroid Build Coastguard WorkerSCOPES = []
33*6dbdd20aSAndroid Build Coastguard Workercached_gerrit_creds = None
34*6dbdd20aSAndroid Build Coastguard Workercached_oauth2_creds = None
35*6dbdd20aSAndroid Build Coastguard Worker
36*6dbdd20aSAndroid Build Coastguard Worker
37*6dbdd20aSAndroid Build Coastguard Workerclass ConcurrentModificationError(Exception):
38*6dbdd20aSAndroid Build Coastguard Worker  pass
39*6dbdd20aSAndroid Build Coastguard Worker
40*6dbdd20aSAndroid Build Coastguard Worker
41*6dbdd20aSAndroid Build Coastguard Workerdef get_access_token():
42*6dbdd20aSAndroid Build Coastguard Worker  global cached_oauth2_creds
43*6dbdd20aSAndroid Build Coastguard Worker  creds = cached_oauth2_creds
44*6dbdd20aSAndroid Build Coastguard Worker  if creds is None or not creds.valid or creds.expired:
45*6dbdd20aSAndroid Build Coastguard Worker    creds, _project = google.auth.default(scopes=SCOPES)
46*6dbdd20aSAndroid Build Coastguard Worker    request = google.auth.transport.requests.Request()
47*6dbdd20aSAndroid Build Coastguard Worker    creds.refresh(request)
48*6dbdd20aSAndroid Build Coastguard Worker    cached_oauth2_creds = creds
49*6dbdd20aSAndroid Build Coastguard Worker  return creds.token
50*6dbdd20aSAndroid Build Coastguard Worker
51*6dbdd20aSAndroid Build Coastguard Worker
52*6dbdd20aSAndroid Build Coastguard Workerdef get_gerrit_credentials():
53*6dbdd20aSAndroid Build Coastguard Worker  '''Retrieve the credentials used to authenticate Gerrit requests
54*6dbdd20aSAndroid Build Coastguard Worker
55*6dbdd20aSAndroid Build Coastguard Worker  Returns a tuple (user, gitcookie). These fields are obtained from the Gerrit
56*6dbdd20aSAndroid Build Coastguard Worker  'New HTTP password' page which generates a .gitcookie file and stored in the
57*6dbdd20aSAndroid Build Coastguard Worker  project datastore.
58*6dbdd20aSAndroid Build Coastguard Worker  user: typically looks like git-user.gmail.com.
59*6dbdd20aSAndroid Build Coastguard Worker  gitcookie: is the password after the = token.
60*6dbdd20aSAndroid Build Coastguard Worker  '''
61*6dbdd20aSAndroid Build Coastguard Worker  global cached_gerrit_creds
62*6dbdd20aSAndroid Build Coastguard Worker  if cached_gerrit_creds is None:
63*6dbdd20aSAndroid Build Coastguard Worker    body = {'query': {'kind': [{'name': 'GerritAuth'}]}}
64*6dbdd20aSAndroid Build Coastguard Worker    res = req(
65*6dbdd20aSAndroid Build Coastguard Worker        'POST',
66*6dbdd20aSAndroid Build Coastguard Worker        'https://datastore.googleapis.com/v1/projects/%s:runQuery' % PROJECT,
67*6dbdd20aSAndroid Build Coastguard Worker        body=body)
68*6dbdd20aSAndroid Build Coastguard Worker    auth = res['batch']['entityResults'][0]['entity']['properties']
69*6dbdd20aSAndroid Build Coastguard Worker    user = auth['user']['stringValue']
70*6dbdd20aSAndroid Build Coastguard Worker    gitcookie = auth['gitcookie']['stringValue']
71*6dbdd20aSAndroid Build Coastguard Worker    cached_gerrit_creds = user, gitcookie
72*6dbdd20aSAndroid Build Coastguard Worker  return cached_gerrit_creds
73*6dbdd20aSAndroid Build Coastguard Worker
74*6dbdd20aSAndroid Build Coastguard Worker
75*6dbdd20aSAndroid Build Coastguard Workerasync def req_async(method, url, body=None, gerrit=False):
76*6dbdd20aSAndroid Build Coastguard Worker  loop = asyncio.get_running_loop()
77*6dbdd20aSAndroid Build Coastguard Worker  # run_in_executor cannot take kwargs, we need to stick with order.
78*6dbdd20aSAndroid Build Coastguard Worker  return await loop.run_in_executor(thread_pool, req, method, url, body, gerrit,
79*6dbdd20aSAndroid Build Coastguard Worker                                    False, None)
80*6dbdd20aSAndroid Build Coastguard Worker
81*6dbdd20aSAndroid Build Coastguard Worker
82*6dbdd20aSAndroid Build Coastguard Workerdef req(method, url, body=None, gerrit=False, req_etag=False, etag=None):
83*6dbdd20aSAndroid Build Coastguard Worker  '''Helper function to handle authenticated HTTP requests.
84*6dbdd20aSAndroid Build Coastguard Worker
85*6dbdd20aSAndroid Build Coastguard Worker  Cloud API and Gerrit require two different types of authentication and as
86*6dbdd20aSAndroid Build Coastguard Worker  such need to be handled differently. The HTTP connection is cached in the
87*6dbdd20aSAndroid Build Coastguard Worker  TLS slot to avoid refreshing oauth tokens too often for back-to-back requests.
88*6dbdd20aSAndroid Build Coastguard Worker  Appengine takes care of clearing the TLS slot upon each frontend request so
89*6dbdd20aSAndroid Build Coastguard Worker  these connections won't be recycled for too long.
90*6dbdd20aSAndroid Build Coastguard Worker  '''
91*6dbdd20aSAndroid Build Coastguard Worker  hdr = {'Content-Type': 'application/json; charset=UTF-8'}
92*6dbdd20aSAndroid Build Coastguard Worker  if gerrit:
93*6dbdd20aSAndroid Build Coastguard Worker    creds = '%s:%s' % get_gerrit_credentials()
94*6dbdd20aSAndroid Build Coastguard Worker    auth_header = 'Basic ' + b64encode(creds.encode('utf-8')).decode('utf-8')
95*6dbdd20aSAndroid Build Coastguard Worker  elif SCOPES:
96*6dbdd20aSAndroid Build Coastguard Worker    auth_header = 'Bearer ' + get_access_token()
97*6dbdd20aSAndroid Build Coastguard Worker  logging.debug('%s %s [gerrit=%d]', method, url, gerrit)
98*6dbdd20aSAndroid Build Coastguard Worker  hdr['Authorization'] = auth_header
99*6dbdd20aSAndroid Build Coastguard Worker  if req_etag:
100*6dbdd20aSAndroid Build Coastguard Worker    hdr['X-Firebase-ETag'] = 'true'
101*6dbdd20aSAndroid Build Coastguard Worker  if etag:
102*6dbdd20aSAndroid Build Coastguard Worker    hdr['if-match'] = etag
103*6dbdd20aSAndroid Build Coastguard Worker  body = None if body is None else json.dumps(body)
104*6dbdd20aSAndroid Build Coastguard Worker  resp = requests.request(method, url, headers=hdr, data=body, timeout=60)
105*6dbdd20aSAndroid Build Coastguard Worker  res = resp.content.decode('utf-8')
106*6dbdd20aSAndroid Build Coastguard Worker  resp_etag = resp.headers.get('etag')
107*6dbdd20aSAndroid Build Coastguard Worker  if resp.status_code == 200:
108*6dbdd20aSAndroid Build Coastguard Worker    # [4:] is to strip Gerrit XSSI projection prefix.
109*6dbdd20aSAndroid Build Coastguard Worker    res = json.loads(res[4:] if gerrit else res)
110*6dbdd20aSAndroid Build Coastguard Worker    return (res, resp_etag) if req_etag else res
111*6dbdd20aSAndroid Build Coastguard Worker  elif resp.status_code == 412:
112*6dbdd20aSAndroid Build Coastguard Worker    raise ConcurrentModificationError()
113*6dbdd20aSAndroid Build Coastguard Worker  else:
114*6dbdd20aSAndroid Build Coastguard Worker    raise Exception(resp, res)
115*6dbdd20aSAndroid Build Coastguard Worker
116*6dbdd20aSAndroid Build Coastguard Worker
117*6dbdd20aSAndroid Build Coastguard Worker# Datetime functions to deal with the fact that Javascript expects a trailing
118*6dbdd20aSAndroid Build Coastguard Worker# 'Z' (Z == 'Zulu' == UTC) for timestamps.
119*6dbdd20aSAndroid Build Coastguard Workerdef parse_iso_time(time_str):
120*6dbdd20aSAndroid Build Coastguard Worker  return datetime.strptime(time_str, r'%Y-%m-%dT%H:%M:%SZ')
121*6dbdd20aSAndroid Build Coastguard Worker
122*6dbdd20aSAndroid Build Coastguard Worker
123*6dbdd20aSAndroid Build Coastguard Workerdef utc_now_iso(utcnow=None):
124*6dbdd20aSAndroid Build Coastguard Worker  return (utcnow or datetime.utcnow()).strftime(r'%Y-%m-%dT%H:%M:%SZ')
125*6dbdd20aSAndroid Build Coastguard Worker
126*6dbdd20aSAndroid Build Coastguard Worker
127*6dbdd20aSAndroid Build Coastguard Workerdef defer(coro):
128*6dbdd20aSAndroid Build Coastguard Worker  loop = asyncio.get_event_loop()
129*6dbdd20aSAndroid Build Coastguard Worker  task = loop.create_task(coro)
130*6dbdd20aSAndroid Build Coastguard Worker  task.set_name(coro.cr_code.co_name)
131*6dbdd20aSAndroid Build Coastguard Worker  return task
132*6dbdd20aSAndroid Build Coastguard Worker
133*6dbdd20aSAndroid Build Coastguard Worker
134*6dbdd20aSAndroid Build Coastguard Workerdef init_logging():
135*6dbdd20aSAndroid Build Coastguard Worker  logging.basicConfig(
136*6dbdd20aSAndroid Build Coastguard Worker      format='%(levelname)-8s %(asctime)s %(message)s',
137*6dbdd20aSAndroid Build Coastguard Worker      level=logging.DEBUG if os.getenv('VERBOSE') else logging.INFO,
138*6dbdd20aSAndroid Build Coastguard Worker      datefmt=r'%Y-%m-%d %H:%M:%S')
139