def test_dependency_graph(self): """Test that dependency graph gets updated correctly.""" run = runtime.Runtime() run.debug_mode() g = run.global_dependency_graph run.create_policy('test') run.insert('p(x) :- q(x), nova:q(x)', target='test') self.assertTrue(g.edge_in('test:p', 'nova:q', False)) self.assertTrue(g.edge_in('test:p', 'test:q', False)) run.insert('p(x) :- s(x)', target='test') self.assertTrue(g.edge_in('test:p', 'nova:q', False)) self.assertTrue(g.edge_in('test:p', 'test:q', False)) self.assertTrue(g.edge_in('test:p', 'test:s', False)) run.insert('q(x) :- nova:r(x)', target='test') self.assertTrue(g.edge_in('test:p', 'nova:q', False)) self.assertTrue(g.edge_in('test:p', 'test:q', False)) self.assertTrue(g.edge_in('test:p', 'test:s', False)) self.assertTrue(g.edge_in('test:q', 'nova:r', False)) run.delete('p(x) :- q(x), nova:q(x)', target='test') self.assertTrue(g.edge_in('test:p', 'test:s', False)) self.assertTrue(g.edge_in('test:q', 'nova:r', False)) run.update([ runtime.Event(helper.str2form('p(x) :- q(x), nova:q(x)'), target='test') ]) self.assertTrue(g.edge_in('test:p', 'nova:q', False)) self.assertTrue(g.edge_in('test:p', 'test:q', False)) self.assertTrue(g.edge_in('test:p', 'test:s', False)) self.assertTrue(g.edge_in('test:q', 'nova:r', False))
def test_column_references_lowlevel(self): """Test column-references with low-level checks.""" # do the first one the painful way, to ensure the parser # is doing something reasonable. run = runtime.Runtime() run.create_policy('nova') nova_schema = compile.Schema({'q': ('id', 'name', 'status')}) run.set_schema('nova', nova_schema, complete=True) code = ("p(x) :- nova:q(id=x)") actual = run.parse(code) self.assertEqual(len(actual), 1) rule = actual[0] self.assertEqual(len(rule.heads), 1) self.assertEqual(rule.head.table, "p") self.assertEqual(len(rule.head.arguments), 1) self.assertEqual(rule.head.arguments[0].name, 'x') self.assertEqual(len(rule.body), 1) lit = rule.body[0] self.assertFalse(lit.is_negated()) self.assertEqual(lit.table, "q") self.assertEqual(len(lit.arguments), 3) self.assertEqual(lit.arguments[0].name, 'x') self.assertNotEqual(lit.arguments[0].name, lit.arguments[1].name) self.assertNotEqual(lit.arguments[0].name, lit.arguments[2].name) self.assertNotEqual(lit.arguments[1].name, lit.arguments[2].name)
def test_column_references_multiple_atoms(self): """Test column references occurring in multiple atoms in a rule.""" run = runtime.Runtime() run.create_policy('nova') schema = compile.Schema({ 'q': ('id', 'name', 'status'), 'r': ('id', 'age', 'weight') }) run.set_schema('nova', schema, complete=True) # Multiple atoms code = ("p(x) :- nova:q(id=x, 2=y), nova:r(id=x)") actual = run.parse(code) correct = "p(x) :- nova:q(x, x0, y), nova:r(x, y0, y1)" eq = helper.datalog_same(helper.pol2str(actual), correct) self.assertTrue(eq, 'Multiple atoms') # Multiple atoms sharing column name but different variables code = ("p(x) :- nova:q(id=x), nova:r(id=y)") actual = run.parse(code) correct = "p(x) :- nova:q(x, x0, x1), nova:r(y, y0, y1)" eq = helper.datalog_same(helper.pol2str(actual), correct) self.assertTrue(eq, 'Multiple atoms shared column name') # Multiple atoms, same table code = ("p(x) :- nova:q(id=x, 2=y), nova:q(id=x)") actual = run.parse(code) correct = "p(x) :- nova:q(x, x0, y), nova:q(x, y0, y1)" eq = helper.datalog_same(helper.pol2str(actual), correct) self.assertTrue(eq, 'Multiple atoms, same table')
def test_policy_errors(self): """Test errors for multiple policies.""" # errors run = runtime.Runtime() run.create_policy('existent') self.assertRaises(KeyError, run.create_policy, 'existent') self.assertRaises(KeyError, run.delete_policy, 'nonexistent') self.assertRaises(KeyError, run.policy_object, 'nonexistent')
def test_initialize_tables(self): """Test initialize_tables() functionality of Runtime.""" run = runtime.Runtime() run.create_policy('test') run.insert('p(1) p(2)') run.initialize_tables(['p'], ['p(3)', 'p(4)']) e = helper.datalog_equal(run.select('p(x)'), 'p(3) p(4)') self.assertTrue(e)
def setUp(self): super(TestRuntimePerformance, self).setUp() self._runtime = runtime.Runtime() self._runtime.create_policy(NREC_THEORY, kind=base.NONRECURSIVE_POLICY_TYPE) self._runtime.create_policy(DB_THEORY, kind=base.DATABASE_POLICY_TYPE) self._runtime.debug_mode() self._runtime.insert('', target=NREC_THEORY)
def test_dump_load(self): """Test if dumping/loading theories works properly.""" run = runtime.Runtime() run.create_policy('test') run.debug_mode() policy = ('p(4,"a","bcdef ghi", 17.1) ' 'p(5,"a","bcdef ghi", 17.1) ' 'p(6,"a","bcdef ghi", 17.1)') run.insert(policy) full_path = os.path.realpath(__file__) path = os.path.dirname(full_path) path = os.path.join(path, "snapshot") run.dump_dir(path) run = runtime.Runtime() run.load_dir(path) e = helper.datalog_equal(run.theory['test'].content_string(), policy, 'Service theory dump/load') self.assertTrue(e)
def test_external(self): """Test ability to write rules that span multiple policies.""" # External theory run = runtime.Runtime() run.create_policy('test1') run.insert('q(1)', target='test1') run.insert('q(2)', target='test1') run.create_policy('test2') run.insert('p(x) :- test1:q(x)', target='test2') actual = run.select('p(x)', target='test2') e = helper.db_equal(actual, 'p(1) p(2)') self.assertTrue(e, "Basic")
def test_column_references_atom_errors(self): """Test invalid column references occurring in a single atom.""" run = runtime.Runtime() run.create_policy('nova') schema = compile.Schema({ 'q': ('id', 'name', 'status'), 'r': ('id', 'age', 'weight') }) run.set_schema('nova', schema, complete=True) def check_err(code, errmsg, msg): try: run.parse(code) self.fail("Error should have been thrown but was not: " + msg) except compile.CongressException as e: emsg = "Err message '{}' should include '{}'".format( str(e), errmsg) self.assertTrue(errmsg in str(e), msg + ": " + emsg) check_err('p(x) :- q(id=x, 0=y)', 'columns for table q have not been declared', 'Missing schema') check_err('p(x) :- nova:q(id=x, birthday=y)', 'column name birthday does not exist', 'Unknown column name') check_err('p(x) :- nova:q(id=x, status=x, id=y)', 'two values for column name id', 'Multiple values for column name') check_err('p(x) :- nova:q(4=y)', 'column number 4 is too large', 'Large column number') check_err('p(x) :- nova:q(4=y, id=w, 4=z)', 'two values for column number 4', 'Multiple values for column number') check_err('p(x) :- nova:q(id=x, 0=y)', 'column was given two values by reference parameters', 'Conflict between name and number references') check_err('p(x) :- nova:q(x, y, id=z)', 'already provided by position arguments', 'Conflict between name and position') check_err('p(x) :- nova:q(x, y, 1=z)', '1 is already provided by position arguments', 'Conflict between name and position') check_err('p(x) :- nova:q(x, 1=z, y)', 'positional parameter after a reference parameter', 'Positional parameter after reference parameter')
def test_multipolicy_head(self): """Test SELECT with different policy in the head.""" run = runtime.Runtime() run.debug_mode() run.create_policy('test1', kind='action') run.create_policy('test2', kind='action') (permitted, errors) = run.insert('test2:p+(x) :- q(x)', 'test1') self.assertTrue(permitted, "modals with policy names must be allowed") run.insert('q(1)', 'test1') run.insert('p(2)', 'test2') actual = run.select('test2:p+(x)', 'test1') e = helper.db_equal(actual, 'test2:p+(1)') self.assertTrue(e, "Policy name in the head")
def test_local(self): """Test ability to write rules that span multiple policies.""" # Local table used run = runtime.Runtime() run.create_policy('test1') run.insert('q(1)', target='test1') run.insert('q(2)', target='test1') run.create_policy('test2') run.insert('p(x) :- test1:q(x), q(x)', target='test2') run.insert('q(2)', 'test2') actual = run.select('p(x)', target='test2') e = helper.db_equal(actual, 'p(2)') self.assertTrue(e, "Local table used")
def prep_runtime(self, code=None, msg=None, target=None, theories=None): if code is None: code = "" if target is None: target = self.DEFAULT_THEORY run = runtime.Runtime() run.create_policy(self.DEFAULT_THEORY) run.create_policy(self.ACTION_THEORY, kind='action') if theories: for theory in theories: run.create_policy(theory) run.debug_mode() run.insert(code, target=target) return run
def test_multi_external(self): """Test multiple rules that span multiple policies.""" run = runtime.Runtime() run.debug_mode() run.create_policy('test1') run.create_policy('test2') run.create_policy('test3') run.insert('p(x) :- test2:p(x)', target='test1') run.insert('p(x) :- test3:p(x)', target='test1') run.insert('p(1)', target='test2') run.insert('p(2)', target='test3') actual = run.select('p(x)', target='test1') e = helper.db_equal(actual, 'p(1) p(2)') self.assertTrue(e, "Multiple external rules with multiple policies")
def prep_runtime(self, code=None, msg=None, target=None): # compile source if msg is not None: LOG.debug(msg) if code is None: code = "" if target is None: target = MAT_THEORY run = runtime.Runtime() run.theory[NREC_THEORY] = runtime.NonrecursiveRuleTheory() run.theory[DB_THEORY] = runtime.Database() run.theory[MAT_THEORY] = runtime.MaterializedViewTheory() run.debug_mode() run.insert(code, target=target) return run
def test_single_policy(self): """Test ability to create/delete single policies.""" # single policy run = runtime.Runtime() original = run.policy_names() run.create_policy('test1') run.insert('p(x) :- q(x)', 'test1') run.insert('q(1)', 'test1') self.assertEqual(run.select('p(x)', 'test1'), 'p(1)', 'Policy creation') self.assertEqual(run.select('p(x)', 'test1'), 'p(1)', 'Policy creation') run.delete_policy('test1') self.assertEqual(set(run.policy_names()), set(original), 'Policy deletion')
def prep_runtime(self, code=None, msg=None, target=None): # compile source if msg is not None: LOG.debug(msg) if code is None: code = "" if target is None: target = MAT_THEORY run = runtime.Runtime() run.create_policy(MAT_THEORY, kind=MATERIALIZED_POLICY_TYPE) run.create_policy(DB_THEORY, kind=DATABASE_POLICY_TYPE) # ensure inserts without target go to MAT_THEORY run.DEFAULT_THEORY = MAT_THEORY run.debug_mode() run.insert(code, target=target) return run
def test_multi_policy(self): """Test ability to create/delete multiple policies.""" # multiple policies run = runtime.Runtime() original = run.policy_names() run.create_policy('test2') run.create_policy('test3') self.assertEqual(set(run.policy_names()), set(original + ['test2', 'test3']), 'Multi policy creation') run.delete_policy('test2') run.create_policy('test4') self.assertEqual(set(run.policy_names()), set(original + ['test3', 'test4']), 'Multiple policy deletion') run.insert('p(x) :- q(x) q(1)', 'test4') self.assertEqual(run.select('p(x)', 'test4'), 'p(1)', 'Multipolicy deletion select')
def test_multiple_levels_external(self): """Test ability to write rules that span multiple policies.""" # Multiple levels of external theories run = runtime.Runtime() run.debug_mode() run.create_policy('test1') run.insert('p(x) :- test2:q(x), test3:q(x)', target='test1') run.insert('s(3) s(1) s(2) s(4)', target='test1') run.create_policy('test2') run.insert('q(x) :- test4:r(x)', target='test2') run.create_policy('test3') run.insert('q(x) :- test1:s(x)', target='test3') run.create_policy('test4') run.insert('r(1)', target='test4') run.insert('r(2)', target='test4') run.insert('r(5)', target='test4') actual = run.select('p(x)', target='test1') e = helper.db_equal(actual, 'p(1) p(2)') self.assertTrue(e, "Multiple levels of external theories")
def test_multipolicy_normal_errors(self): """Test errors arising from rules in multiple policies.""" run = runtime.Runtime() run.debug_mode() run.create_policy('test1') # policy in head of rule (permitted, errors) = run.insert('test2:p(x) :- q(x)', 'test1') self.assertFalse(permitted) self.assertTrue("should not reference any policy" in str(errors[0])) # policy in head of rule with update (permitted, errors) = run.insert('test2:p+(x) :- q(x)', 'test1') self.assertFalse(permitted) self.assertTrue("should not reference any policy" in str(errors[0])) # policy in head of rule with update (permitted, errors) = run.insert('test2:p-(x) :- q(x)', 'test1') self.assertFalse(permitted) self.assertTrue("should not reference any policy" in str(errors[0])) # policy in head of fact (permitted, errors) = run.insert('test2:p(1)', 'test1') self.assertFalse(permitted) self.assertTrue("should not reference any policy" in str(errors[0])) # policy in head of fact (permitted, errors) = run.insert('test2:p+(1)', 'test1') self.assertFalse(permitted) self.assertTrue("should not reference any policy" in str(errors[0])) # policy in head of fact (permitted, errors) = run.insert('test2:p-(1)', 'test1') self.assertFalse(permitted) self.assertTrue("should not reference any policy" in str(errors[0])) # recursion across policies run.insert('p(x) :- test2:q(x)', target='test1') run.create_policy('test2') (permit, errors) = run.insert('q(x) :- test1:p(x)', target='test2') self.assertFalse(permit, "Recursion across theories should fail") self.assertEqual(len(errors), 1) self.assertTrue("Rules are recursive" in str(errors[0]))
def test_policy_types(self): """Test types for multiple policies.""" # policy types run = runtime.Runtime() run.create_policy('test1', kind=NONRECURSIVE_POLICY_TYPE) self.assertTrue( isinstance(run.policy_object('test1'), runtime.NonrecursiveRuleTheory), 'Nonrecursive policy addition') run.create_policy('test2', kind=ACTION_POLICY_TYPE) self.assertTrue( isinstance(run.policy_object('test2'), runtime.ActionTheory), 'Action policy addition') run.create_policy('test3', kind=DATABASE_POLICY_TYPE) self.assertTrue( isinstance(run.policy_object('test3'), runtime.Database), 'Database policy addition') run.create_policy('test4', kind=MATERIALIZED_POLICY_TYPE) self.assertTrue( isinstance(run.policy_object('test4'), runtime.MaterializedViewTheory), 'Materialized policy addition')
def test_multi_policy_update(self): """Test updates that apply to multiple policies.""" def check_equal(actual, correct): e = helper.datalog_equal(actual, correct) self.assertTrue(e) run = runtime.Runtime() run.create_policy('th1') run.create_policy('th2') events1 = [ runtime.Event(formula=x, insert=True, target='th1') for x in helper.str2pol("p(1) p(2) q(1) q(3)") ] events2 = [ runtime.Event(formula=x, insert=True, target='th2') for x in helper.str2pol("r(1) r(2) t(1) t(4)") ] run.update(events1 + events2) check_equal(run.select('p(x)', 'th1'), 'p(1) p(2)') check_equal(run.select('q(x)', 'th1'), 'q(1) q(3)') check_equal(run.select('r(x)', 'th2'), 'r(1) r(2)') check_equal(run.select('t(x)', 'th2'), 't(1) t(4)')
def test_module_schemas(self): """Test that rules are properly checked against module schemas.""" run = runtime.Runtime() run.create_policy('mod1') run.create_policy('mod2') run.set_schema('mod1', compile.Schema({ 'p': (1, 2, 3), 'q': (1, ) }), complete=True) run.set_schema('mod2', compile.Schema({ 'p': (1, ), 'q': (1, 2) }), complete=True) def check_err(code_string, theory, emsg, msg, f=compile.rule_errors): rule = compile.parse1(code_string) errs = f(rule, run.theory, theory) self.assertTrue( any(emsg in str(err) for err in errs), msg + ":: Failed to find error message '" + emsg + "' in: " + ";".join(str(e) for e in errs)) # no errors rule = compile.parse1('p(x) :- q(x), mod1:p(x, y, z), mod2:q(x, y), ' 'mod1:q(t), mod2:p(t)') errs = compile.rule_errors(rule, run.theory) self.assertEqual(len(errs), 0, "Should not have found any errors") # unknown table within module check_err('p(x) :- q(x), mod1:r(x), r(x)', 'mod3', 'unknown table', 'Unknown table for rule') # wrong number of arguments check_err('p(x) :- q(x), mod1:p(x,y,z,w), r(x)', 'mod3', 'only 3 arguments are permitted', 'Wrong number of arguments for rule') # same tests for an atom # no errors atom = compile.parse1('p(1, 2, 2)') errs = compile.fact_errors(atom, run.theory, 'mod1') self.assertEqual(len(errs), 0, "Should not have found any errors") # unknown table within module check_err('r(1)', 'mod1', 'unknown table', 'Unknown table for atom', f=compile.fact_errors) # wrong number of arguments check_err('p(1, 2, 3, 4)', 'mod1', 'only 3 arguments are permitted', 'Wrong number of arguments for atom', f=compile.fact_errors)
def test_column_references_atom(self): """Test column references occurring in a single atom in a rule.""" run = runtime.Runtime() run.create_policy('nova') nova_schema = compile.Schema({'q': ('id', 'name', 'status')}) run.set_schema('nova', nova_schema, complete=True) # Multiple column names code = ("p(x) :- nova:q(id=x, status=y)") actual = run.parse(code) correct = "p(x) :- nova:q(x, w, y)" eq = helper.datalog_same(helper.pol2str(actual), correct) self.assertTrue(eq, 'Multiple column names') # Multiple column numbers code = ("p(x) :- nova:q(0=x, 1=y, 2=z)") actual = run.parse(code) correct = "p(x) :- nova:q(x, y, z)" eq = helper.datalog_same(helper.pol2str(actual), correct) self.assertTrue(eq, 'Multiple column numbers') # Mix column names and numbers code = ("p(x) :- nova:q(id=x, 2=y)") actual = run.parse(code) correct = "p(x) :- nova:q(x, w, y)" eq = helper.datalog_same(helper.pol2str(actual), correct) self.assertTrue(eq, 'Mix names and numbers') # Object constants code = ("p(x) :- nova:q(id=3, 2=2)") actual = run.parse(code) correct = "p(x) :- nova:q(3, w, 2)" eq = helper.datalog_same(helper.pol2str(actual), correct) self.assertTrue(eq, 'Object constants') # Out of order code = ("p(x, y) :- nova:q(status=y, id=x)") actual = run.parse(code) correct = "p(x, y) :- nova:q(x, z, y)" eq = helper.datalog_same(helper.pol2str(actual), correct) self.assertTrue(eq, 'Out of order') # Out of order with numbers code = ("p(x, y) :- nova:q(1=y, 0=x)") actual = run.parse(code) correct = "p(x, y) :- nova:q(x, y, z)" eq = helper.datalog_same(helper.pol2str(actual), correct) self.assertTrue(eq, 'Out of order with numbers') # Positional plus named code = ("p(x, y) :- nova:q(x, status=y)") actual = run.parse(code) correct = "p(x, y) :- nova:q(x, z, y)" eq = helper.datalog_same(helper.pol2str(actual), correct) self.assertTrue(eq, 'Positional plus named') # Positional plus named 2 code = ("p(x, y, z) :- nova:q(x, y, 2=z)") actual = run.parse(code) correct = "p(x, y, z) :- nova:q(x, y, z)" eq = helper.datalog_same(helper.pol2str(actual), correct) self.assertTrue(eq, 'Positional plus named 2') # Pure positional (different since we are providing schema) code = ("p(x, y, z) :- nova:q(x, y, z)") actual = run.parse(code) correct = "p(x, y, z) :- nova:q(x, y, z)" eq = helper.datalog_same(helper.pol2str(actual), correct) self.assertTrue(eq, 'Pure positional') # Pure positional (without schema) code = ("p(x) :- nova:q(x, y, z)") run.delete_policy('nova') actual = run.parse(code) correct = "p(x) :- nova:q(x, y, z)" eq = helper.datalog_same(helper.pol2str(actual), correct) self.assertTrue(eq, 'Pure positional without schema')
def prep_runtime(enforce_theory, action_theory, class_theory): run = runtime.Runtime() run.insert(enforce_theory, target=run.ENFORCEMENT_THEORY) run.insert(action_theory, target=run.ACTION_THEORY) run.insert(class_theory, target=run.CLASSIFY_THEORY) return run
def test_neutron_actions(self): """Test our encoding of the Neutron actions basics by simulation.""" def check(query, action_sequence, correct, msg): actual = run.simulate(query, action_sequence) LOG.debug("Simulate results: %s", actual) self.check_instance(actual, correct, msg) full_path = os.path.realpath(__file__) path = os.path.dirname(full_path) neutron_path = path + "/../../../examples/neutron.action" run = runtime.Runtime() run.debug_mode() permitted, errs = run.load_file(neutron_path, target=run.ACTION_THEORY) if not permitted: self.assertTrue( permitted, "Error in Neutron file: {}".format("\n".join( [str(x) for x in errs]))) return # Ports query = 'neutron:port(x1, x2, x3, x4, x5, x6, x7, x8, x9)' acts = 'neutron:create_port("net1", 17), sys:user("tim") :- true' correct = ('neutron:port(id, "net1", name, mac, "null",' '"null", z, w, "tim")') check(query, acts, correct, 'Simple port creation') query = 'neutron:port(x1, x2, x3, x4, x5, x6, x7, x8, x9)' # result(uuid): simulation-specific table that holds the results # of the last action invocation acts = ('neutron:create_port("net1", 17), sys:user("tim") :- true ' 'neutron:update_port(uuid, 18), sys:user("tim"), ' ' options:value(18, "name", "tims port") :- result(uuid) ') correct = ('neutron:port(id, "net1", "tims port", mac, "null",' '"null", z, w, "tim")') check(query, acts, correct, 'Port create, update') query = 'neutron:port(x1, x2, x3, x4, x5, x6, x7, x8, x9)' # result(uuid): simulation-specific table that holds the results # of the last action invocation acts = ('neutron:create_port("net1", 17), sys:user("tim") :- true ' 'neutron:update_port(uuid, 18), sys:user("tim"), ' ' options:value(18, "name", "tims port") :- result(uuid) ' 'neutron:delete_port(uuid), sys:user("tim")' ' :- result(uuid) ') correct = '' check(query, acts, correct, 'Port create, update, delete') # Networks query = ('neutron:network(id, name, status, admin_state, shared,' 'tenenant_id)') acts = 'neutron:create_network(17), sys:user("tim") :- true' correct = 'neutron:network(id, "", status, "true", "true", "tim")' check(query, acts, correct, 'Simple network creation') query = ('neutron:network(id, name, status, admin_state, ' 'shared, tenenant_id)') acts = ('neutron:create_network(17), sys:user("tim") :- true ' 'neutron:update_network(uuid, 18), sys:user("tim"), ' ' options:value(18, "admin_state", "false") :- result(uuid)') correct = 'neutron:network(id, "", status, "false", "true", "tim")' check(query, acts, correct, 'Network creation, update') query = ('neutron:network(id, name, status, admin_state, shared, ' 'tenenant_id)') acts = ('neutron:create_network(17), sys:user("tim") :- true ' 'neutron:update_network(uuid, 18), sys:user("tim"), ' ' options:value(18, "admin_state", "false") :- result(uuid)' 'neutron:delete_network(uuid) :- result(uuid)') correct = '' check(query, acts, correct, 'Network creation, update') # Subnets query = ('neutron:subnet(id, name, network_id, ' 'gateway_ip, ip_version, cidr, enable_dhcp, tenant_id)') acts = ('neutron:create_subnet("net1", "10.0.0.1/24", 17), ' 'sys:user("tim") :- true') correct = ('neutron:subnet(id, "", "net1", gateway_ip, 4, ' '"10.0.0.1/24", "true", "tim")') check(query, acts, correct, 'Simple subnet creation') query = ('neutron:subnet(id, name, network_id, ' 'gateway_ip, ip_version, cidr, enable_dhcp, tenant_id)') acts = ('neutron:create_subnet("net1", "10.0.0.1/24", 17), ' 'sys:user("tim") :- true ' 'neutron:update_subnet(uuid, 17), sys:user("tim"), ' ' options:value(17, "enable_dhcp", "false") :- result(uuid)') correct = ('neutron:subnet(id, "", "net1", gateway_ip, 4, ' '"10.0.0.1/24", "false", "tim")') check(query, acts, correct, 'Subnet creation, update') query = ('neutron:subnet(id, name, network_id, ' 'gateway_ip, ip_version, cidr, enable_dhcp, tenant_id)') acts = ('neutron:create_subnet("net1", "10.0.0.1/24", 17), ' 'sys:user("tim") :- true ' 'neutron:update_subnet(uuid, 17), sys:user("tim"), ' ' options:value(17, "enable_dhcp", "false") :- result(uuid)' 'neutron:delete_subnet(uuid) :- result(uuid)') correct = '' check(query, acts, correct, 'Subnet creation, update, delete')