Exclude atomic rule from dependency graph

Dependency graph used by agnostic excludes positive literals
of type compile.Literal. But when a policy is deleted, the facts
in the policy are deleted as atomic rules of type compile.Rule. As
a result, some nodes in the dependency graph are prematurely deleted
because the deleting of facts (type compile.Rule) via policy delete
decreased ref counters that had not been correspondingly increased
by the adding of those same facts (type compile.Literal). When a
dependency graph is thus corrupted, congress gives Internal Server
Error on all future attempts to add/delete/sync rules.

This patch fixes the problem by having agnostic treat an atomic rule
(type compile.Rule) the same way it treats an atom
(type compile.Literal) in the dependency graph -- ignore both.

Closes-Bug: 1662809

Change-Id: I8080cbb7c375d90259f7b8a2a62d714ebe4aee5f
This commit is contained in:
Eric K 2017-02-17 00:04:49 -08:00
parent 34a9e5fdec
commit d635bad1ae
2 changed files with 37 additions and 1 deletions

View File

@ -1364,7 +1364,7 @@ class RuleDependencyGraph(utility.BagGraph):
# TODO(thinrichs): should be able to have global_tablename
# return a Tablename object and therefore build a graph
# of Tablename objects instead of strings.
if is_atom(formula):
if is_atom_like(formula):
if include_atoms:
table = formula.table.global_tablename(theory)
nodes.add(table)
@ -1804,6 +1804,22 @@ def is_regular_rule(x):
return (is_rule(x) and len(x.heads) == 1)
def is_atom_rule(x):
return is_regular_rule(x) and len(x.body) == 0 and is_literal(x.heads[0])
def is_literal_rule(x):
return is_regular_rule(x) and len(x.body) == 0 and is_literal(x.heads[0])
def is_atom_like(x):
return is_atom(x) or is_atom_rule(x)
def is_literal_like(x):
return is_literal(x) or is_literal_rule(x)
def is_multi_rule(x):
"""Returns True if X is a rule with multiple heads."""
return (is_rule(x) and len(x.heads) != 1)

View File

@ -919,6 +919,26 @@ class TestMultipolicyRules(base.TestCase):
self.assertEqual(len(errors), 1)
self.assertIn("Rules are recursive", str(errors[0]))
def test_dependency_graph_policy_deletion(self):
run = agnostic.Runtime()
g = run.global_dependency_graph
run.create_policy('test')
rule = 'execute[nova:flavors.delete(id)] :- nova:flavors(id)'
permitted, changes = run.insert(rule, target='test')
self.assertTrue(permitted)
run.create_policy('nova')
run.insert('flavors(1)', target="nova")
run.insert('flavors(2)', target="nova")
run.insert('flavors(3)', target="nova")
run.insert('flavors(4)', target="nova")
self.assertEqual(g.dependencies('test:nova:flavors.delete'),
set(['nova:flavors', 'test:nova:flavors.delete']))
run.delete_policy('nova')
self.assertTrue(g.node_in('nova:flavors'))
self.assertEqual(g.dependencies('test:nova:flavors.delete'),
set(['nova:flavors', 'test:nova:flavors.delete']))
def test_dependency_graph(self):
"""Test that dependency graph gets updated correctly."""
run = agnostic.Runtime()