Skip to content

Commit 1655bb3

Browse files
committed
ref: canoical rule cache, moving inner closure
Return rule cache from get rule cache (no dict copy), inner closure to class fnc, test updated
1 parent 65d0bd4 commit 1655bb3

2 files changed

Lines changed: 33 additions & 30 deletions

File tree

evaluator/logic.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,34 @@ async def _load_rule_cache_for_name(self, conn: AsyncConnection, name: str) -> O
128128
return RuleCache(rule["id"], rule["playbook_count"])
129129

130130
async def _get_rule_cache(self, conn: Optional[AsyncConnection] = None) -> Dict[str, RuleCache]:
131-
"""Return a snapshot of the rule cache, refreshing from DB when cache TTL expired. You can also load full cache from DB (see _load_rule_cache)"""
131+
"""Return the canoical rule cache dict, refreshing from DB when TTL expired (see _load_rule_cache)"""
132132
if self._rule_cache_expired():
133133
if conn is None:
134134
async with self.db_pool.connection() as conn:
135135
self.rule_cache = await self._load_rule_cache(conn)
136136
else:
137137
self.rule_cache = await self._load_rule_cache(conn)
138138
self._set_rule_cache_expiry()
139-
return dict(self.rule_cache)
139+
return self.rule_cache
140+
141+
async def _get_rule_from_cache(
142+
self,
143+
conn: AsyncConnection,
144+
rule_name: str,
145+
missing_rules_not_found: Set[str],
146+
) -> Optional[RuleCache]:
147+
"""Resolve rule metadata from self.rule_cache, lazy-loading by name on miss"""
148+
cached = self.rule_cache.get(rule_name)
149+
if cached:
150+
return cached
151+
if rule_name in missing_rules_not_found:
152+
return None
153+
fetched = await self._load_rule_cache_for_name(conn, rule_name)
154+
if fetched:
155+
self.rule_cache[rule_name] = fetched
156+
return fetched
157+
missing_rules_not_found.add(rule_name)
158+
return None
140159

141160
async def _load_package_name_cache(self) -> Dict[str, PackageNameCache]:
142161
"""Load package name cache from DB"""
@@ -619,28 +638,14 @@ async def _evaluate_advisor_res(
619638
self,
620639
rule_results: dict,
621640
sys_vuln_rows: Dict[str, SystemVulnerabilitiesRow],
622-
rule_cache: Dict[str, RuleCache],
623641
system_platform: SystemPlatform,
624642
unpatched_cves: Set[str],
625643
conn: AsyncConnection,
626644
) -> Dict[str, SystemVulnerabilitiesRow]:
627645
"""Merge results from vmaas package evaluation with advisor rule evaluation"""
628646

629-
missing_rules_not_found = set()
630-
631-
async def resolve_rule_cache(rule_name: str) -> Optional[RuleCache]:
632-
cached = rule_cache.get(rule_name)
633-
if cached:
634-
return cached
635-
if rule_name in missing_rules_not_found:
636-
return None
637-
fetched = await self._load_rule_cache_for_name(conn, rule_name)
638-
if fetched:
639-
self.rule_cache[rule_name] = fetched
640-
rule_cache[rule_name] = fetched
641-
return fetched
642-
missing_rules_not_found.add(rule_name)
643-
return None
647+
await self._get_rule_cache(conn)
648+
missing_rules_not_found: Set[str] = set()
644649

645650
for cve, hit_details in rule_results["rule_hits"].items():
646651
if cve in unpatched_cves:
@@ -658,7 +663,7 @@ async def resolve_rule_cache(rule_name: str) -> Optional[RuleCache]:
658663
if not isinstance(hit_details["details"], dict):
659664
hit_details["details"] = json.loads(hit_details["details"])
660665

661-
rule_db = await resolve_rule_cache(rule)
666+
rule_db = await self._get_rule_from_cache(conn, rule, missing_rules_not_found)
662667
if not rule_db:
663668
continue
664669

@@ -707,7 +712,7 @@ async def resolve_rule_cache(rule_name: str) -> Optional[RuleCache]:
707712
continue
708713

709714
# system was marked vulnerable from vmaas but not from by rules -> abnv
710-
rule_db = await resolve_rule_cache(rule)
715+
rule_db = await self._get_rule_from_cache(conn, rule, missing_rules_not_found)
711716
if not rule_db:
712717
continue
713718

@@ -740,8 +745,7 @@ async def evaluate_vulnerabilities(self, system_platform: SystemPlatform, conn:
740745
unpatched_cves_set = set(x.cve for x in unpatched_cves)
741746
# Rule metadata is cached in memory (warm in init), TTL controls refresh while evaluator runs
742747
with RULES_EVAL_TIME.time():
743-
rule_cache = await self._get_rule_cache(conn)
744748
sys_vuln_rows = await self._evaluate_advisor_res(
745-
system_platform.rule_results, sys_vuln_rows, rule_cache, system_platform, unpatched_cves_set, conn
749+
system_platform.rule_results, sys_vuln_rows, system_platform, unpatched_cves_set, conn
746750
)
747751
return sys_vuln_rows

tests/common_tests/test_evaluator_rule_cache.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ async def fake_load_rule_cache(_self, _conn):
4444
# How many times the fake full-load ran
4545
# For live warm cache two loads result into a single db fetch
4646
assert len(loads) == 1
47-
assert snap1 == snap2 == {"RULE_A": RuleCache(42, 1)}
47+
assert snap1 is snap2 is logic.rule_cache
48+
assert snap1 == {"RULE_A": RuleCache(42, 1)}
4849

4950

5051
async def test_get_rule_cache_always_reloads_when_ttl_disabled(monkeypatch):
@@ -171,7 +172,7 @@ async def fake_load_rule_for_name(_self, _conn, name: str):
171172
logic._load_rule_cache_for_name = MethodType(fake_load_rule_for_name, logic)
172173

173174
# Empty sys_vuln_rows: rule-only hit branch (no prior VMAAS row for this CVE)
174-
out = await logic._evaluate_advisor_res(rule_results, {}, {}, platform, set(), conn)
175+
out = await logic._evaluate_advisor_res(rule_results, {}, platform, set(), conn)
175176

176177
assert len(full_load_calls) == 1 # warm TTL: advisor merge must not trigger another full refresh
177178
assert lazy_names == [rule_id] # one lazy fetch for the missing name
@@ -218,7 +219,7 @@ async def empty_full_load(_self, _conn):
218219
logic._load_rule_cache = MethodType(empty_full_load, logic)
219220
await logic._get_rule_cache()
220221

221-
out_lazy = await logic._evaluate_advisor_res(rule_results, {}, {}, platform, set(), conn)
222+
out_lazy = await logic._evaluate_advisor_res(rule_results, {}, platform, set(), conn)
222223

223224
assert lazy_names == [rule_id]
224225
row_lazy = out_lazy[cve]
@@ -228,13 +229,11 @@ async def empty_full_load(_self, _conn):
228229
assert row_lazy.cve_id == 500
229230
assert json.loads(row_lazy.rule_hit_details) == rule_results["rule_hits"][cve]["details"]
230231

231-
# Second run: pretend the rule row was already in the dict returned by _get_rule_cache (should mimic old behavior before cache change)
232+
# Second run: canoical cache holds rule_id from lazy path, canoical name as instance
232233
lazy_names.clear()
233-
logic.rule_cache.clear() # drop instance cache so we only measure the explicit snapshot dict
234-
snapshot = {rule_id: expected_rule} # same data as lazy path returned, but supplied up front
235-
out_pre = await logic._evaluate_advisor_res(rule_results, {}, dict(snapshot), platform, set(), conn)
234+
out_pre = await logic._evaluate_advisor_res(rule_results, {}, platform, set(), conn)
236235

237-
assert lazy_names == [] # no per-name fetch: rule_id was already in the merge snapshot
236+
assert lazy_names == [] # cache hit on self.rule_cache
238237
row_pre = out_pre[cve]
239238
# Row must match exactly: lazy vs preloaded must not change vulnerability semantics
240239
assert row_pre.state == row_lazy.state

0 commit comments

Comments
 (0)