def get_target(self, name): if name is None: if len(self.theory) == 1: name = self.theory.keys()[0] elif len(self.theory) == 0: raise compile.CongressException("No policies exist.") else: raise compile.CongressException( "Must choose a policy to operate on") if name not in self.theory: raise compile.CongressException("Unknown policy " + str(name)) return self.theory[name]
def update_would_cause_errors(self, events): """Return a list of compile.CongressException. Return a list of compile.CongressException if we were to apply the insert/deletes of policy statements dictated by EVENTS to the current policy. """ self.log(None, "update_would_cause_errors %s", iterstr(events)) errors = [] for event in events: if not compile.is_datalog(event.formula): errors.append( compile.CongressException("Non-formula found: {}".format( str(event.formula)))) else: if event.formula.is_atom(): errors.extend( compile.fact_errors(event.formula, self.theories, self.name)) else: errors.extend( compile.rule_errors(event.formula, self.theories, self.name)) # Would also check that rules are non-recursive, but that # is currently being handled by Runtime. The current implementation # disallows recursion in all theories. return errors
def update_would_cause_errors(self, events): """Return a list of compile.CongressException. Return a list of compile.CongressException if we were to apply the events EVENTS to the current policy. """ self.log(None, "update_would_cause_errors %s", iterstr(events)) errors = [] for event in events: if not compile.is_datalog(event.formula): errors.append( compile.CongressException("Non-formula found: {}".format( str(event.formula)))) else: if event.formula.is_atom(): errors.extend( compile.fact_errors(event.formula, self.theories, self.name)) else: errors.extend( compile.rule_head_has_no_theory( event.formula, permit_head=lambda lit: lit.is_update())) # Should put this back in place, but there are some # exceptions that we don't handle right now. # Would like to mark some tables as only being defined # for certain bound/free arguments and take that into # account when doing error checking. # errors.extend(compile.rule_negation_safety(event.formula)) return errors
def update_obj(self, events): """Do the updating. Checks if applying EVENTS is permitted and if not returns a list of errors. If it is permitted, it applies it and then returns a list of changes. In both cases, the return is a 2-tuple (if-permitted, list). """ self.table_log(None, "Updating with %s", iterstr(events)) by_theory = self.group_events_by_target(events) # check that the updates would not cause an error errors = [] actual_events = [] for th, th_events in by_theory.items(): th_obj = self.get_target(th) errors.extend(th_obj.update_would_cause_errors(th_events)) actual_events.extend(th_obj.actual_events(th_events)) # update dependency graph (and undo it if errors) changes = self.global_dependency_graph.formula_update(events) if changes: if self.global_dependency_graph.has_cycle(): # TODO(thinrichs): include path errors.append(compile.CongressException( "Rules are recursive")) self.global_dependency_graph.undo_changes(changes) if len(errors) > 0: return (False, errors) # actually apply the updates changes = [] for th, th_events in by_theory.items(): changes.extend(self.get_target(th).update(events)) return (True, changes)
def create_policy(self, name, abbr=None, kind=None): """Create a new policy and add it to the runtime. ABBR is a shortened version of NAME that appears in traces. KIND is the name of the datastructure used to represent a policy. """ if not isinstance(name, basestring): raise KeyError("Policy name %s must be a string" % name) if name in self.theory: raise KeyError("Policy with name %s already exists" % name) if not isinstance(abbr, basestring): abbr = name[0:5] LOG.debug("Creating policy <%s> with abbr <%s> and kind <%s>", name, abbr, kind) if kind is None: kind = NONRECURSIVE_POLICY_TYPE else: kind = kind.lower() if kind is None or kind == NONRECURSIVE_POLICY_TYPE: PolicyClass = NonrecursiveRuleTheory elif kind == ACTION_POLICY_TYPE: PolicyClass = ActionTheory elif kind == DATABASE_POLICY_TYPE: PolicyClass = Database elif kind == MATERIALIZED_POLICY_TYPE: PolicyClass = MaterializedViewTheory else: raise compile.CongressException( "Unknown kind of policy: %s" % kind) policy_obj = PolicyClass(name=name, abbr=abbr, theories=self.theory) policy_obj.set_tracer(self.tracer) self.theory[name] = policy_obj return policy_obj
def policy_type(self, name): """Return type of policy NAME. Throws KeyError if does not exist.""" policy = self.policy_object(name) if isinstance(policy, NonrecursiveRuleTheory): return NONRECURSIVE_POLICY_TYPE if isinstance(policy, MaterializedViewTheory): return MATERIALIZED_POLICY_TYPE if isinstance(policy, ActionTheory): return ACTION_POLICY_TYPE if isinstance(policy, Database): return DATABASE_POLICY_TYPE raise compile.CongressException("Policy %s has unknown type" % name)
def change_rule(self, parsed_rule, context, insert=True): policy_name = self.policy_name(context) if policy_name not in self.engine.theory: raise KeyError("Policy with ID '%s' does not exist", policy_name) event = runtime.Event(formula=parsed_rule, insert=insert, target=policy_name) (permitted, changes) = self.engine.process_policy_update([event]) if not permitted: raise compile.CongressException("Errors: " + ";".join((str(x) for x in changes))) return changes
def update_would_cause_errors(self, events): """Return a list of compile.CongressException. Return a list of compile.CongressException if we were to apply the events EVENTS to the current policy. """ self.log(None, "update_would_cause_errors %s", iterstr(events)) errors = [] for event in events: if not compile.is_atom(event.formula): errors.append(compile.CongressException( "Non-atomic formula is not permitted: {}".format( str(event.formula)))) else: errors.extend(compile.fact_errors( event.formula, self.theories, self.name)) return errors
def project(self, sequence, policy_theory, action_theory): """Apply the list of updates SEQUENCE. Apply the list of updates SEQUENCE, where actions are described in ACTION_THEORY. Return an update sequence that will undo the projection. SEQUENCE can include atom insert/deletes, rule insert/deletes, and action invocations. Projecting an action only simulates that action's invocation using the action's description; the results are therefore only an approximation of executing actions directly. Elements of SEQUENCE are just formulas applied to the given THEORY. They are NOT Event()s. SEQUENCE is really a program in a mini-programming language--enabling results of one action to be passed to another. Hence, even ignoring actions, this functionality cannot be achieved by simply inserting/deleting. """ actth = self.theory[action_theory] policyth = self.theory[policy_theory] # apply changes to the state newth = NonrecursiveRuleTheory(abbr="Temp") newth.tracer.trace('*') actth.includes.append(newth) # TODO(thinrichs): turn 'includes' into an object that guarantees # there are no cycles through inclusion. Otherwise we get # infinite loops if actth is not policyth: actth.includes.append(policyth) actions = self.get_action_names(action_theory) self.table_log(None, "Actions: %s", iterstr(actions)) undos = [] # a list of updates that will undo SEQUENCE self.table_log(None, "Project: %s", sequence) last_results = [] for formula in sequence: self.table_log(None, "** Updating with %s", formula) self.table_log(None, "Actions: %s", iterstr(actions)) self.table_log(None, "Last_results: %s", iterstr(last_results)) tablename = formula.tablename() if tablename not in actions: if not formula.is_update(): raise compile.CongressException( "Sequence contained non-action, non-update: " + str(formula)) updates = [formula] else: self.table_log(tablename, "Projecting %s", formula) # define extension of current Actions theory if formula.is_atom(): assert formula.is_ground(), ( "Projection atomic updates must be ground") assert not formula.is_negated(), ( "Projection atomic updates must be positive") newth.define([formula]) else: # instantiate action using prior results newth.define(last_results) self.table_log(tablename, "newth (with prior results) %s", iterstr(newth.content())) bindings = actth.top_down_evaluation( formula.variables(), formula.body, find_all=False) if len(bindings) == 0: continue grounds = formula.plug_heads(bindings[0]) grounds = [act for act in grounds if act.is_ground()] assert all(not lit.is_negated() for lit in grounds) newth.define(grounds) self.table_log(tablename, "newth contents (after action insertion): %s", iterstr(newth.content())) # self.table_log(tablename, "action contents: %s", # iterstr(actth.content())) # self.table_log(tablename, "action.includes[1] contents: %s", # iterstr(actth.includes[1].content())) # self.table_log(tablename, "newth contents: %s", # iterstr(newth.content())) # compute updates caused by action updates = actth.consequences(compile.is_update) updates = self.resolve_conflicts(updates) updates = unify.skolemize(updates) self.table_log(tablename, "Computed updates: %s", iterstr(updates)) # compute results for next time for update in updates: newth.insert(update) last_results = actth.consequences(compile.is_result) last_results = set([atom for atom in last_results if atom.is_ground()]) # apply updates for update in updates: undo = self.project_updates(update, policy_theory) if undo is not None: undos.append(undo) undos.reverse() if actth is not policyth: actth.includes.remove(policyth) actth.includes.remove(newth) return undos