Skip to content

Commit 1980164

Browse files
Support engineless fuzzers in project setup (#4643)
We want to use subqueues #3347 for our android jobs/bots as we waste a lot of machine time picking up incompatible tasks. Currently the subqueues can be only assigned through project setup. Our Android fuzzing uses engineless fuzzers, which are not supported by project setup. This adds support for the later.
1 parent 9b0fe2f commit 1980164

File tree

2 files changed

+332
-30
lines changed

2 files changed

+332
-30
lines changed

src/clusterfuzz/_internal/cron/project_setup.py

Lines changed: 67 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ def job_name(self, project_name, config_suffix):
128128
'address', ['afl', 'engine_asan'],
129129
minimize_job_override=LIBFUZZER_ASAN_JOB)
130130
NO_ENGINE_ASAN_JOB = JobInfo('asan_', 'none', 'address', [])
131+
NO_ENGINE_HWASAN_JOB = JobInfo('noengine_hwasan_', 'none', 'hardware', [])
132+
NO_ENGINE_NONE_JOB = JobInfo('noengine_nosanitizer_', 'none', 'none', [])
131133

132134
HONGGFUZZ_ASAN_JOB = JobInfo(
133135
'honggfuzz_asan_',
@@ -191,6 +193,11 @@ def job_name(self, project_name, config_suffix):
191193
'x86_64': {
192194
'address': NO_ENGINE_ASAN_JOB,
193195
},
196+
'arm': {
197+
'address': NO_ENGINE_ASAN_JOB,
198+
'hardware': NO_ENGINE_HWASAN_JOB,
199+
'none': NO_ENGINE_NONE_JOB,
200+
},
194201
},
195202
'centipede': {
196203
'x86_64': {
@@ -473,34 +480,40 @@ def _get_ccs(field_name, allow_list=True):
473480
return [utils.normalize_email(cc) for cc in ccs]
474481

475482

476-
def update_fuzzer_jobs(fuzzer_entities, job_names):
483+
def update_fuzzer_jobs(job_names):
477484
"""Update fuzzer job mappings."""
478485
to_delete = {}
479486

480-
for fuzzer_entity_key in fuzzer_entities:
481-
fuzzer_entity = fuzzer_entity_key.get()
487+
# First, find all jobs that need to be deleted
488+
for job in data_types.Job.query():
489+
if not job.environment_string:
490+
continue
482491

483-
for job in data_types.Job.query():
484-
if not job.environment_string:
485-
continue
492+
job_environment = job.get_environment()
493+
if not utils.string_is_true(job_environment.get('MANAGED', 'False')):
494+
continue
486495

487-
job_environment = job.get_environment()
488-
if not utils.string_is_true(job_environment.get('MANAGED', 'False')):
489-
continue
496+
if job.name in job_names:
497+
continue
490498

491-
if job.name in job_names:
492-
continue
499+
logs.info(f'Deleting job {job.name}')
493500

494-
logs.info(f'Deleting job {job.name}')
495-
to_delete[job.name] = job.key
501+
print(f'Deleting job {job.name}')
502+
to_delete[job.name] = job.key
496503

504+
# Clean up job references from all fuzzer entities
505+
for fuzzer in data_types.Fuzzer.query():
506+
# modified = False
507+
for job_name in to_delete:
497508
try:
498-
fuzzer_entity.jobs.remove(job.name)
509+
fuzzer.jobs.remove(job_name)
510+
# modified = True
499511
except ValueError:
500512
pass
501513

502-
fuzzer_entity.put()
503-
fuzzer_selection.update_mappings_for_fuzzer(fuzzer_entity)
514+
# if modified:
515+
fuzzer.put()
516+
fuzzer_selection.update_mappings_for_fuzzer(fuzzer)
504517

505518
if to_delete:
506519
ndb_utils.delete_multi(to_delete.values())
@@ -759,12 +772,19 @@ def _sync_job(self, project, info, corpus_bucket_name, quarantine_bucket_name,
759772

760773
for template in get_jobs_for_project(project, info):
761774
if template.engine == 'none':
762-
# Engine-less jobs are not automatically managed.
763-
continue
764-
765-
fuzzer_entity = self._fuzzer_entities.get(template.engine).get()
766-
if not fuzzer_entity:
767-
raise ProjectSetupError('Invalid fuzzing engine ' + template.engine)
775+
if not info.get('managed_engineless', False):
776+
# Engine-less jobs are not automatically managed unless explicitly
777+
# enabled
778+
continue
779+
# For engineless jobs, we don't need a fuzzer entity
780+
fuzzer_entity = None
781+
# Get fuzzers specified for this project
782+
fuzzers = info.get('fuzzers', [])
783+
else:
784+
fuzzer_entity = self._fuzzer_entities.get(template.engine).get()
785+
if not fuzzer_entity:
786+
raise ProjectSetupError('Invalid fuzzing engine ' + template.engine)
787+
fuzzers = []
768788

769789
job_name = template.job_name(project, self._config_suffix)
770790
job = data_types.Job.query(data_types.Job.name == job_name).get()
@@ -786,10 +806,21 @@ def _sync_job(self, project, info, corpus_bucket_name, quarantine_bucket_name,
786806

787807
if not info.get('disabled', False):
788808
job_names.append(job_name)
789-
if job_name not in fuzzer_entity.jobs and not job.is_external():
809+
if (fuzzer_entity and job_name not in fuzzer_entity.jobs and
810+
not job.is_external()):
790811
# Enable new job.
791812
fuzzer_entity.jobs.append(job_name)
792813
fuzzer_entity.put()
814+
# For engineless jobs, update the fuzzer-job mappings
815+
for fuzzer_name in fuzzers:
816+
fuzzer = data_types.Fuzzer.query(
817+
data_types.Fuzzer.name == fuzzer_name).get()
818+
if not fuzzer:
819+
raise ProjectSetupError(
820+
f'Invalid fuzzer {fuzzer_name} specified for engineless job')
821+
if job_name not in fuzzer.jobs:
822+
fuzzer.jobs.append(job_name)
823+
fuzzer.put()
793824

794825
job.name = job_name
795826
if self._segregate_projects:
@@ -803,6 +834,7 @@ def _sync_job(self, project, info, corpus_bucket_name, quarantine_bucket_name,
803834
if template.engine == 'centipede':
804835
build_bucket_path = self._get_build_bucket_path(
805836
project, info, template.engine, 'none', template.architecture)
837+
print(template, build_bucket_path)
806838
else:
807839
build_bucket_path = self._get_build_bucket_path(
808840
project, info, template.engine, template.memory_tool,
@@ -914,8 +946,16 @@ def _sync_job(self, project, info, corpus_bucket_name, quarantine_bucket_name,
914946
f'{key} = {str(value).encode("unicode-escape").decode("utf-8")}\n'
915947
)
916948

917-
job.put()
949+
if info.get('additional_vars'):
950+
additional_vars = {}
951+
additional_vars.update(info.get('additional_vars', {}))
952+
for key, value in sorted(additional_vars.items()):
953+
job.environment_string += (
954+
f'{key} = {str(value).encode("unicode-escape").decode("utf-8")}\n'
955+
)
918956

957+
job.put()
958+
print(project, job_names)
919959
return job_names
920960

921961
def sync_user_permissions(self, project, info):
@@ -996,10 +1036,9 @@ def set_up(self, projects):
9961036
return SetupResult(enabled_projects, job_names)
9971037

9981038

999-
def cleanup_stale_projects(fuzzer_entities, project_names, job_names,
1000-
segregate_projects):
1039+
def cleanup_stale_projects(project_names, job_names, segregate_projects):
10011040
"""Clean up stale projects."""
1002-
update_fuzzer_jobs(fuzzer_entities, job_names)
1041+
update_fuzzer_jobs(job_names)
10031042
cleanup_old_projects_settings(project_names)
10041043

10051044
if segregate_projects:
@@ -1095,9 +1134,7 @@ def main():
10951134
project_names.update(result.project_names)
10961135
job_names.update(result.job_names)
10971136

1098-
cleanup_stale_projects(
1099-
list(fuzzer_entities.values()), project_names, job_names,
1100-
segregate_projects)
1137+
cleanup_stale_projects(project_names, job_names, segregate_projects)
11011138

11021139
logs.info('Project setup succeeded.')
11031140
return True

0 commit comments

Comments
 (0)