Skip to content

Commit c4e58d3

Browse files
authored
Merge pull request #2449 from aboutcode-org/2437-refresh-run
Refresh stale and missing pipeline jobs
2 parents 8352eb4 + 3f7f749 commit c4e58d3

8 files changed

Lines changed: 93 additions & 27 deletions

File tree

‎vulnerabilities/management/commands/run_scheduler.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from vulnerabilities import models
1414
from vulnerabilities.schedules import clear_zombie_pipeline_schedules
15+
from vulnerabilities.schedules import refresh_runs
1516
from vulnerabilities.schedules import scheduled_job_exists
1617
from vulnerabilities.schedules import update_pipeline_schedule
1718

@@ -38,5 +39,6 @@ class Command(rqscheduler.Command):
3839
def handle(self, *args, **kwargs):
3940
clear_zombie_pipeline_schedules()
4041
update_pipeline_schedule()
42+
refresh_runs()
4143
init_pipeline_scheduled()
4244
super(Command, self).handle(*args, **kwargs)

‎vulnerabilities/pipelines/v2_importers/redhat_importer.py‎

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2
3131
from vulnerabilities.pipes import extractcode_utils
3232
from vulnerabilities.severity_systems import REDHAT_AGGREGATE
33+
from vulnerabilities.utils import get_item
3334
from vulnerabilities.utils import load_json
34-
from vulntotal import vulntotal_utils
3535

3636

3737
class RedHatImporterPipeline(VulnerableCodeBaseImporterPipelineV2):
@@ -135,7 +135,7 @@ def parse_advisory(self, record):
135135
)
136136
)
137137

138-
impacts = get_item(advisory, "product_tree", "branches", 0, "branches", default=[])
138+
impacts = get_item(advisory, "product_tree", "branches", 0, "branches") or []
139139
for impact in impacts:
140140
if impact["category"] == "product_family":
141141
continue
@@ -145,7 +145,6 @@ def parse_advisory(self, record):
145145
"product",
146146
"product_identification_helper",
147147
"purl",
148-
default=None,
149148
):
150149
if not purl.startswith("pkg:rpm/"):
151150
continue
@@ -188,11 +187,3 @@ def clean_download(self):
188187

189188
def on_failure(self):
190189
self.clean_download()
191-
192-
193-
def get_item(entity, *attributes, default=None):
194-
try:
195-
result = vulntotal_utils.get_item(entity, *attributes)
196-
except (KeyError, IndexError, TypeError) as e:
197-
result = default
198-
return result

‎vulnerabilities/pipelines/v2_improvers/relate_severities.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ def relate_severities(self):
103103
matches = advisory_id_map.get(advisory.advisory_id, set())
104104
for target_id in matches:
105105
if target_id != advisory.id:
106-
self.log(f"Relating advisory {advisory.avid} to {target_id}")
107106
relations.append(
108107
through(
109108
from_advisoryv2_id=target_id,

‎vulnerabilities/pipes/group_advisories.py‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323

2424
@transaction.atomic
2525
def delete_and_save_advisory_set(groups, package, relation=None):
26-
print(f"Grouping advisories for package: {package.purl}")
27-
2826
AdvisorySet.objects.filter(
2927
package=package,
3028
relation_type=relation,
@@ -101,8 +99,6 @@ def delete_and_save_advisory_set(groups, package, relation=None):
10199
batch_size=10000,
102100
)
103101

104-
print(f"Successfully saved advisory sets for package: {package.purl}")
105-
106102

107103
def group_advisory_for_package(package, logger=None):
108104
"""
@@ -133,7 +129,6 @@ def group_advisory_for_package(package, logger=None):
133129
group_single_package_with_provided_advisories(
134130
package, affecting_advisories, fixed_by_advisories
135131
)
136-
logger(f"Successfully rebuilt advisory sets for package {package.purl}")
137132
except Exception as e:
138133
if logger:
139134
logger(f"Failed rebuilding advisory sets for package {package.purl}: {e!r}")

‎vulnerabilities/schedules.py‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,46 @@ def update_pipeline_schedule():
116116
pipeline.run_priority = run_priority
117117
pipeline.run_interval = run_interval
118118
pipeline.save()
119+
120+
121+
def mark_stale_runs():
122+
"""Mark unfinished pipeline runs as stale."""
123+
from django.db.models import Q
124+
125+
from vulnerabilities.models import PipelineRun
126+
127+
stale_jobs = PipelineRun.objects.filter(run_start_date__isnull=False).filter(
128+
Q(run_end_date__isnull=True) | Q(run_exitcode__isnull=True)
129+
)
130+
stale_jobs_count = stale_jobs.count()
131+
132+
for job in stale_jobs.iterator(chunk_size=1000):
133+
job.set_run_staled()
134+
135+
log.info(f"Marked {stale_jobs_count} unfinished jobs as stale.")
136+
137+
138+
def requeue_missing_jobs():
139+
"""Requeue pipeline runs that have not started and are missing from the queue."""
140+
141+
from vulnerabilities.models import PipelineRun
142+
from vulnerabilities.tasks import enqueue_run
143+
from vulnerabilities.tasks import is_job_in_queue
144+
145+
missing_jobs_count = 0
146+
for job in PipelineRun.objects.filter(run_start_date__isnull=True).iterator(chunk_size=1000):
147+
if not is_job_in_queue(job_id=job.run_id):
148+
pipeline_latest_run = job.pipeline.latest_run
149+
if pipeline_latest_run and pipeline_latest_run.run_id == job.run_id:
150+
enqueue_run(run=job)
151+
missing_jobs_count += 1
152+
else:
153+
job.set_run_staled()
154+
155+
log.info(f"Requeued {missing_jobs_count} missing jobs.")
156+
157+
158+
def refresh_runs():
159+
"""Refresh the stale and missing jobs."""
160+
mark_stale_runs()
161+
requeue_missing_jobs()

‎vulnerabilities/tasks.py‎

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,22 @@ def set_run_failure(job, connection, type, value, traceback):
115115
run.set_run_ended(exitcode=1, output=f"value={value} trace={traceback}")
116116

117117

118+
def enqueue_run(run):
119+
pipeline_schedule = run.pipeline
120+
queue = queues.get(pipeline_schedule.get_run_priority_display())
121+
122+
job = queue.enqueue(
123+
execute_pipeline,
124+
pipeline_schedule.pipeline_id,
125+
run.run_id,
126+
job_id=str(run.run_id),
127+
on_failure=set_run_failure,
128+
job_timeout=f"{pipeline_schedule.execution_timeout}h",
129+
)
130+
131+
118132
def enqueue_pipeline(pipeline_id):
119133
pipeline_schedule = models.PipelineSchedule.objects.get(pipeline_id=pipeline_id)
120-
queue = queues.get(pipeline_schedule.get_run_priority_display())
121134

122135
if pipeline_schedule.status in [
123136
models.PipelineRun.Status.RUNNING,
@@ -134,14 +147,8 @@ def enqueue_pipeline(pipeline_id):
134147
run = models.PipelineRun.objects.create(
135148
pipeline=pipeline_schedule,
136149
)
137-
job = queue.enqueue(
138-
execute_pipeline,
139-
pipeline_id,
140-
run.run_id,
141-
job_id=str(run.run_id),
142-
on_failure=set_run_failure,
143-
job_timeout=f"{pipeline_schedule.execution_timeout}h",
144-
)
150+
151+
enqueue_run(run)
145152

146153

147154
def dequeue_job(job_id):
@@ -152,6 +159,12 @@ def dequeue_job(job_id):
152159
queue.remove(job_id)
153160

154161

162+
def is_job_in_queue(job_id):
163+
"""Return whether a job exists in any queue."""
164+
165+
return any(job_id in queue.jobs for queue in queues.values())
166+
167+
155168
def compute_queue_load_factor():
156169
"""
157170
Compute worker load per queue.

‎vulnerabilities/tests/pipelines/v2_importers/test_redhat_importer_v2.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from vulnerabilities.models import PackageV2
1818
from vulnerabilities.pipelines.v2_importers.redhat_importer import RedHatImporterPipeline
1919
from vulnerabilities.tests import util_tests
20+
from vulnerabilities.tests.pipelines import TestLogger
2021
from vulnerabilities.utils import normalize_list
2122

2223
TEST_DATA = Path(__file__).parent.parent.parent / "test_data" / "redhat" / "csaf_2_0"
@@ -25,10 +26,15 @@
2526
class TestArchLinuxImporterPipeline(TestCase):
2627
@patch("vulnerabilities.pipelines.v2_importers.redhat_importer.RedHatImporterPipeline.fetch")
2728
def test_redhat_advisories_v2(self, mock_fetch):
29+
logger = TestLogger()
30+
2831
mock_fetch.__name__ = "fetch"
2932
pipeline = RedHatImporterPipeline()
3033
pipeline.location = TEST_DATA
31-
pipeline.execute()
34+
pipeline.log = logger.write
35+
exit_code, _ = pipeline.execute()
36+
37+
self.assertEqual(exit_code, 0)
3238
self.assertEqual(6, AdvisoryV2.objects.count())
3339
self.assertEqual(93, PackageV2.objects.count())
3440
expected_file = TEST_DATA.parent / "redhat_advisoryv2-expected.json"

‎vulnerabilities/tests/test_models.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,23 @@ def test_pipelinerun_success(self):
688688
self.run1.set_run_ended(exitcode=0)
689689
self.assertEqual(self.run1.status, self.run1.Status.SUCCESS)
690690

691+
def test_pipelinerun_schedules_mark_stale_runs_unfinished_job(self):
692+
from vulnerabilities.schedules import mark_stale_runs
693+
694+
self.run1.set_run_started()
695+
self.assertEqual(self.run1.run_exitcode, None)
696+
mark_stale_runs()
697+
self.run1.refresh_from_db()
698+
self.assertEqual(self.run1.run_exitcode, 88)
699+
700+
def test_pipelinerun_schedules_mark_stale_runs_job_not_yet_started(self):
701+
from vulnerabilities.schedules import mark_stale_runs
702+
703+
self.assertEqual(self.run1.run_exitcode, None)
704+
mark_stale_runs()
705+
self.run1.refresh_from_db()
706+
self.assertEqual(self.run1.run_exitcode, None)
707+
691708

692709
class TestPipelineScheduleModel(DjangoTestCase):
693710
def setUp(self):

0 commit comments

Comments
 (0)