Skip to content

Commit d0f9de4

Browse files
dylanjewhunsche
andauthored
Migrate Android Build API usage to V4 OnePlatform under feature flag (#5335)
This PR migrates the Android Build API client calls from legacy V3 (androidbuildinternal.googleapis.com) to the new V4 Private API (androidbuild-pa.googleapis.com) using standard OnePlatform discovery. Changes: Added a new Datastore Config property 'use_android_build_api_v4' to the Config model class to support runtime toggle of the API version. Updated 'fetch_artifact.py' to conditionally initialize and use either the V3 or V4 client/endpoints depending on the Datastore feature flag. Refactored logs to use structured dictionary-based entries with the '[AndroidBuildAPI]' tag to facilitate filtering and diagnosis in Cloud Logging. Patched in #5327, fixed formatting, lint and added TODO to clean up --------- Co-authored-by: Matheus Hunsche <matheushunsche@google.com>
1 parent 9364713 commit d0f9de4

2 files changed

Lines changed: 195 additions & 28 deletions

File tree

src/clusterfuzz/_internal/datastore/data_types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,10 @@ class Config(Model):
883883
# functional bugs.
884884
relax_security_bug_restrictions = ndb.BooleanProperty(default=False)
885885

886+
# TODO(b/422775458) - Clean up after completing the migration to V4
887+
# Flag to use the V4 Android Build API instead of V3.
888+
use_android_build_api_v4 = ndb.BooleanProperty(default=False)
889+
886890
# Coverage reports bucket.
887891
coverage_reports_bucket = ndb.StringProperty(default='')
888892

src/clusterfuzz/_internal/platforms/android/fetch_artifact.py

Lines changed: 191 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@
4848
"gs://android-haiku/target-cuttlefish/stable_build_info.json")
4949

5050

51+
def _use_v4():
52+
"""Return True if we should use V4 Android Build API."""
53+
try:
54+
use_v4 = db_config.get_value('use_android_build_api_v4') or False
55+
logs.info(
56+
'AndroidBuildAPI feature flag status read.',
57+
use_android_build_api_v4=use_v4)
58+
return use_v4
59+
except Exception as e:
60+
logs.error(
61+
'AndroidBuildAPI error reading feature flag use_android_build_api_v4. '
62+
'Defaulting to False.',
63+
error=str(e))
64+
return False
65+
66+
5167
def execute_request_with_retries(request):
5268
"""Executes request and retries on failure."""
5369
result = None
@@ -68,24 +84,68 @@ def download_artifact(client, bid, target, attempt_id, name, output_directory,
6884
logs.info('artifact to download: %s' % name)
6985
logs.info('output_directory: %s' % output_directory)
7086
logs.info('output_filename: %s' % output_filename)
71-
artifact_query = client.buildartifact().get(
72-
buildId=bid, target=target, attemptId=attempt_id, resourceId=name)
87+
88+
version_tag = 'V4' if _use_v4() else 'V3'
89+
logs.info(
90+
'AndroidBuildAPI download_artifact started.',
91+
api_version=version_tag,
92+
operation='download_artifact',
93+
build_id=bid,
94+
target=target,
95+
attempt_id=attempt_id,
96+
artifact_name=name)
97+
98+
if _use_v4():
99+
artifact_query = client.buildartifacts().get(
100+
buildId=bid, target=target, attemptId=attempt_id, resourceId=name)
101+
else:
102+
artifact_query = client.buildartifact().get(
103+
buildId=bid, target=target, attemptId=attempt_id, resourceId=name)
73104
artifact = execute_request_with_retries(artifact_query)
74105
if artifact is None:
75-
logs.error(f'Artifact unreachable with name {name}, target {target} '
76-
f'and build id {bid}.')
106+
logs.error(
107+
'AndroidBuildAPI download_artifact failed: artifact metadata '
108+
'unreachable.',
109+
api_version=version_tag,
110+
operation='download_artifact',
111+
build_id=bid,
112+
target=target,
113+
attempt_id=attempt_id,
114+
artifact_name=name,
115+
status='failed')
77116
return None
78117

79118
# Lucky us, we always have the size.
80119
size = int(artifact['size'])
120+
logs.info(
121+
'AndroidBuildAPI download_artifact metadata retrieved successfully.',
122+
api_version=version_tag,
123+
operation='download_artifact',
124+
build_id=bid,
125+
target=target,
126+
attempt_id=attempt_id,
127+
artifact_name=name,
128+
size=size)
81129

82130
chunksize = -1
83131
if size >= DEFAULT_CHUNK_SIZE:
84132
chunksize = DEFAULT_CHUNK_SIZE
85133

86134
# Just like get, except get_media.
87-
dl_request = client.buildartifact().get_media(
88-
buildId=bid, target=target, attemptId=attempt_id, resourceId=name)
135+
logs.info(
136+
'AndroidBuildAPI download_artifact media download started.',
137+
api_version=version_tag,
138+
operation='download_artifact_media',
139+
build_id=bid,
140+
target=target,
141+
attempt_id=attempt_id,
142+
artifact_name=name)
143+
if _use_v4():
144+
dl_request = client.buildartifacts().get_media(
145+
buildId=bid, target=target, attemptId=attempt_id, resourceId=name)
146+
else:
147+
dl_request = client.buildartifact().get_media(
148+
buildId=bid, target=target, attemptId=attempt_id, resourceId=name)
89149

90150
if output_filename:
91151
file_name = output_filename
@@ -95,7 +155,16 @@ def download_artifact(client, bid, target, attempt_id, name, output_directory,
95155
output_path = os.path.join(output_directory, file_name)
96156
# If the artifact already exists, then bail out.
97157
if os.path.exists(output_path) and os.path.getsize(output_path) == size:
98-
logs.info('Artifact %s already exists, skipping download.' % name)
158+
logs.info(
159+
'AndroidBuildAPI download_artifact skipped (file already exists).',
160+
api_version=version_tag,
161+
operation='download_artifact',
162+
build_id=bid,
163+
target=target,
164+
attempt_id=attempt_id,
165+
artifact_name=name,
166+
output_path=output_path,
167+
status='skipped_exists')
99168
return output_path
100169

101170
logs.info('Downloading artifact %s.' % name)
@@ -118,6 +187,16 @@ def download_artifact(client, bid, target, attempt_id, name, output_directory,
118187
percent_completed = (size_completed * 100.0) / size
119188
logs.info('%.1f%% complete.' % percent_completed)
120189

190+
logs.info(
191+
'AndroidBuildAPI download_artifact completed successfully.',
192+
api_version=version_tag,
193+
operation='download_artifact',
194+
build_id=bid,
195+
target=target,
196+
attempt_id=attempt_id,
197+
artifact_name=name,
198+
output_path=output_path,
199+
status='success')
121200
return output_path
122201

123202

@@ -127,21 +206,44 @@ def get_artifacts_for_build(client,
127206
attempt_id: str = 'latest',
128207
regexp: Optional[str] = None) -> List[str]:
129208
"""Return list of artifacts for a given build."""
130-
if not regexp:
131-
request = client.buildartifact().list(
132-
buildId=bid, target=target, attemptId=attempt_id)
209+
version_tag = 'V4' if _use_v4() else 'V3'
210+
logs.info(
211+
'AndroidBuildAPI get_artifacts_for_build started.',
212+
api_version=version_tag,
213+
operation='get_artifacts_for_build',
214+
build_id=bid,
215+
target=target,
216+
attempt_id=attempt_id,
217+
regexp=regexp)
218+
219+
if _use_v4():
220+
if not regexp:
221+
request = client.buildartifacts().list(
222+
buildId=bid, target=target, attemptId=attempt_id)
223+
else:
224+
request = client.buildartifacts().list(
225+
buildId=bid,
226+
target=target,
227+
attemptId=attempt_id,
228+
nameRegexp=regexp,
229+
maxResults=100)
133230
else:
134-
request = client.buildartifact().list(
135-
buildId=bid,
136-
target=target,
137-
attemptId=attempt_id,
138-
nameRegexp=regexp,
139-
maxResults=100)
231+
if not regexp:
232+
request = client.buildartifact().list(
233+
buildId=bid, target=target, attemptId=attempt_id)
234+
else:
235+
request = client.buildartifact().list(
236+
buildId=bid,
237+
target=target,
238+
attemptId=attempt_id,
239+
nameRegexp=regexp,
240+
maxResults=100)
140241

141242
request_str = (f'{request.uri}, {request.method}, '
142243
f'{request.body}, {request.methodId}')
143244

144245
artifacts = []
246+
145247
results = []
146248
while request:
147249
result = execute_request_with_retries(request)
@@ -151,7 +253,21 @@ def get_artifacts_for_build(client,
151253
if result and 'artifacts' in result:
152254
for artifact in result['artifacts']:
153255
artifacts.append(artifact)
154-
request = client.buildartifact().list_next(request, result)
256+
if _use_v4():
257+
request = client.buildartifacts().list_next(request, result)
258+
else:
259+
request = client.buildartifact().list_next(request, result)
260+
261+
logs.info(
262+
'AndroidBuildAPI get_artifacts_for_build completed.',
263+
api_version=version_tag,
264+
operation='get_artifacts_for_build',
265+
build_id=bid,
266+
target=target,
267+
attempt_id=attempt_id,
268+
regexp=regexp,
269+
artifacts_count=len(artifacts),
270+
status='success' if artifacts else 'empty')
155271

156272
if not artifacts:
157273
logs.error(f'No artifact found for target {target}, build id {bid}.\n'
@@ -174,11 +290,24 @@ def get_client():
174290
credentials = ServiceAccountCredentials.from_json_keyfile_dict(
175291
json.loads(build_apiary_service_account_private_key),
176292
scopes='https://www.googleapis.com/auth/androidbuild.internal')
177-
client = apiclient.discovery.build(
178-
'androidbuildinternal',
179-
'v3',
180-
credentials=credentials,
181-
static_discovery=False)
293+
if _use_v4():
294+
logs.info(
295+
'AndroidBuildAPI client initialization started.', api_version='V4')
296+
client = apiclient.discovery.build(
297+
'androidbuildinternal',
298+
'v4',
299+
discoveryServiceUrl=
300+
'https://androidbuild-pa.googleapis.com/$discovery/rest?version=v4',
301+
credentials=credentials,
302+
static_discovery=False)
303+
else:
304+
logs.info(
305+
'AndroidBuildAPI client initialization started.', api_version='V3')
306+
client = apiclient.discovery.build(
307+
'androidbuildinternal',
308+
'v3',
309+
credentials=credentials,
310+
static_discovery=False)
182311

183312
return client
184313

@@ -218,25 +347,59 @@ def get_latest_artifact_info(branch, target, signed=False, stable_build=False):
218347
if 'bid' in build_info and build_info['bid'] != '0':
219348
return build_info
220349

221-
request = client.build().list( # pylint: disable=no-member
222-
buildType='submitted',
350+
version_tag = 'V4' if _use_v4() else 'V3'
351+
logs.info(
352+
'AndroidBuildAPI get_latest_artifact_info started.',
353+
api_version=version_tag,
354+
operation='get_latest_artifact_info',
223355
branch=branch,
224356
target=target,
225-
successful=True,
226-
maxResults=1,
227357
signed=signed)
358+
if _use_v4():
359+
request = client.builds().list( # pylint: disable=no-member
360+
buildType='submitted',
361+
branch=branch,
362+
target=target,
363+
successful=True,
364+
maxResults=1,
365+
signed=signed)
366+
else:
367+
request = client.build().list( # pylint: disable=no-member
368+
buildType='submitted',
369+
branch=branch,
370+
target=target,
371+
successful=True,
372+
maxResults=1,
373+
signed=signed)
228374
request_str = (f'{request.uri}, {request.method}, '
229375
f'{request.body}, {request.methodId}')
230376

231377
builds = execute_request_with_retries(request)
232378
if not builds:
233-
logs.error(f'No build found for target {target}, branch {branch}, '
234-
f'request: {request_str}.')
379+
logs.error(
380+
'AndroidBuildAPI get_latest_artifact_info failed: no builds found.',
381+
api_version=version_tag,
382+
operation='get_latest_artifact_info',
383+
branch=branch,
384+
target=target,
385+
signed=signed,
386+
status='failed',
387+
request_str=request_str)
235388
return None
236389

237390
build = builds['builds'][0]
238391
bid = build['buildId']
239392
target = build['target']['name']
393+
394+
logs.info(
395+
'AndroidBuildAPI get_latest_artifact_info completed.',
396+
api_version=version_tag,
397+
operation='get_latest_artifact_info',
398+
branch=branch,
399+
target=target,
400+
signed=signed,
401+
build_id=bid,
402+
status='success')
240403
return {'bid': bid, 'branch': branch, 'target': target}
241404

242405

0 commit comments

Comments
 (0)