Skip to content

Commit f34b249

Browse files
committed
Optimize V3 API
Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent 3c73e6f commit f34b249

3 files changed

Lines changed: 88 additions & 23 deletions

File tree

vulnerabilities/api_v3.py

Lines changed: 85 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,11 @@
1111
from urllib.parse import urlencode
1212

1313
from django.contrib.postgres.aggregates import ArrayAgg
14-
from django.contrib.postgres.aggregates import JSONBAgg
1514
from django.db.models import Exists
16-
from django.db.models import F
1715
from django.db.models import Max
1816
from django.db.models import OuterRef
1917
from django.db.models import Prefetch
2018
from django.db.models import Q
21-
from django.db.models.functions import JSONObject
2219
from django_filters import rest_framework as filters
2320
from drf_spectacular.utils import extend_schema
2421
from packageurl import PackageURL
@@ -31,6 +28,7 @@
3128
from vulnerabilities.models import AdvisoryAlias
3229
from vulnerabilities.models import AdvisoryReference
3330
from vulnerabilities.models import AdvisorySet
31+
from vulnerabilities.models import AdvisorySetMember
3432
from vulnerabilities.models import AdvisorySeverity
3533
from vulnerabilities.models import AdvisoryV2
3634
from vulnerabilities.models import AdvisoryWeakness
@@ -503,7 +501,10 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url):
503501
)
504502
.select_related("primary_advisory")
505503
.prefetch_related(
506-
Prefetch("aliases", queryset=AdvisoryAlias.objects.only("alias")),
504+
Prefetch(
505+
"aliases",
506+
queryset=AdvisoryAlias.objects.only("alias"),
507+
),
507508
)
508509
.annotate(
509510
max_severity=Max(
@@ -512,16 +513,6 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url):
512513
max_exploitability=Max(
513514
"members__advisory__exploitability",
514515
),
515-
ssvc_trees=JSONBAgg(
516-
JSONObject(
517-
vector=F("members__advisory__related_ssvcs__vector"),
518-
decision=F("members__advisory__related_ssvcs__decision"),
519-
options=F("members__advisory__related_ssvcs__options"),
520-
source_url=F("members__advisory__related_ssvcs__source_advisory__url"),
521-
),
522-
filter=Q(members__advisory__related_ssvcs__decision__isnull=False),
523-
distinct=True,
524-
),
525516
)
526517
.only(
527518
"id",
@@ -532,10 +523,88 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url):
532523
)
533524
)
534525

526+
advisory_set_ids = [a.id for a in advisory_sets]
527+
528+
member_rows = AdvisorySetMember.objects.filter(
529+
advisory_set_id__in=advisory_set_ids,
530+
).values(
531+
"advisory_set_id",
532+
"advisory_id",
533+
)
534+
535+
advisory_ids_by_set = defaultdict(set)
536+
all_advisory_ids = set()
537+
538+
for row in member_rows:
539+
advisory_set_id = row["advisory_set_id"]
540+
advisory_id = row["advisory_id"]
541+
542+
advisory_ids_by_set[advisory_set_id].add(advisory_id)
543+
all_advisory_ids.add(advisory_id)
544+
545+
ssvc_rows = (
546+
SSVC.objects.filter(
547+
related_advisories__id__in=all_advisory_ids,
548+
decision__isnull=False,
549+
)
550+
.select_related(
551+
"source_advisory",
552+
)
553+
.values(
554+
"related_advisories__id",
555+
"vector",
556+
"decision",
557+
"options",
558+
"source_advisory__url",
559+
)
560+
)
561+
562+
ssvc_by_advisory = defaultdict(list)
563+
564+
for row in ssvc_rows:
565+
advisory_id = row["related_advisories__id"]
566+
567+
ssvc_by_advisory[advisory_id].append(
568+
{
569+
"vector": row["vector"],
570+
"decision": row["decision"],
571+
"options": row["options"],
572+
"source_url": row["source_advisory__url"],
573+
}
574+
)
575+
535576
package_map = defaultdict(list)
536577

537578
for adv in advisory_sets:
538579
adv._aliases_cache = [a.alias for a in adv.aliases.all()]
580+
581+
advisory_ids = advisory_ids_by_set.get(adv.id, set())
582+
583+
seen = set()
584+
ssvc_trees = []
585+
586+
for advisory_id in advisory_ids:
587+
for ssvc in ssvc_by_advisory.get(advisory_id, []):
588+
589+
key = (
590+
ssvc["vector"],
591+
ssvc["decision"],
592+
(
593+
tuple(sorted(ssvc["options"].items()))
594+
if isinstance(ssvc["options"], dict)
595+
else str(ssvc["options"])
596+
),
597+
ssvc["source_url"],
598+
)
599+
600+
if key in seen:
601+
continue
602+
603+
seen.add(key)
604+
ssvc_trees.append(ssvc)
605+
606+
adv.ssvc_trees = ssvc_trees
607+
539608
package_map[adv.package_id].append(adv)
540609

541610
for package in packages:
@@ -549,6 +618,7 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url):
549618
(package.id, primary.id),
550619
[],
551620
)
621+
552622
max_sev = adv.max_severity or 0.0
553623
max_exp = adv.max_exploitability or 0.0
554624

@@ -577,7 +647,7 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url):
577647
"exploitability": exploitability,
578648
"risk_score": risk_score,
579649
"fixed_by_packages": fixed_by_packages,
580-
"ssvc_trees": adv.ssvc_trees or [],
650+
"ssvc_trees": adv.ssvc_trees,
581651
"resource_url": resource_url,
582652
}
583653
)

vulnerabilities/pipelines/v2_improvers/mark_unfurl_version_range.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def mark_all_impacts_unfurled(self):
5050
impacted_packages=impacted_packages
5151
)
5252

53-
batch_size = 100
53+
batch_size = 1000
5454
batch = []
5555

5656
successful_qs = latest_advisories_with_all_impacts_unfurled_successfully(

vulnerabilities/tests/pipelines/v2_improvers/test_mark_all_impacts_unfurled.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,10 @@ def test_attempted_advisories_are_chunked_in_batches_of_100(
194194

195195
pipeline.mark_all_impacts_unfurled()
196196

197-
assert mock_complete_advisories_import.call_count == 3
197+
assert mock_complete_advisories_import.call_count == 1
198198

199199
first_call_ids = mock_complete_advisories_import.call_args_list[0][1]["advisory_ids"]
200-
second_call_ids = mock_complete_advisories_import.call_args_list[1][1]["advisory_ids"]
201-
third_call_ids = mock_complete_advisories_import.call_args_list[2][1]["advisory_ids"]
202-
203-
assert len(first_call_ids) == 100
204-
assert len(second_call_ids) == 100
205-
assert len(third_call_ids) == 50
200+
assert len(first_call_ids) == 250
206201

207202

208203
@pytest.mark.django_db

0 commit comments

Comments
 (0)