def test_inverted_expression(self): expression = 'foo=value' inverted = ast.invert_expression(expression) self.logger.debug('Got inverted expression "%s" for "%s"' % (inverted, expression)) self.assertEquals(len(inverted), 1) self.assertEquals(inverted[0], 'foo := value')
def test_inverted_remove(self): expression = 'facts.test := remove(facts.test, test)' inverted = ast.invert_expression(expression) self.logger.debug('Got inverted expression "%s" for "%s"' % (inverted, expression)) self.assertEquals(len(inverted), 1) self.assertEquals(inverted[0], 'test !in facts.test')
def solve_one(self, proposed_plan=None): """ run a single pass of the solver, trying to find all the available primitives that can solve any existing constraint. """ self.logger.debug("solving %s with plan %s" % (self.constraints, proposed_plan)) # first, build up asts of all my unsolved constraints constraint_list = self._build_constraints(self.constraints) # fix/regularize our internal constraint list self.constraints = [x['constraint'] for x in constraint_list] self.logger.info('New solver for constraints: %s (plan: %s)' % (self.constraints, proposed_plan)) # walk through all the primitives, and see what primitives # have constraints that are met, and spin off a new solver # from that state. primitives = [] if proposed_plan: # fix this up so it looks more like it used to. ;) prim_item = self._get_primitive_by_name(proposed_plan['primitive']) if prim_item: primitives = [prim_item] # primitives = self.api._model_query( # 'primitives', # 'name="%s"' % proposed_plan['primitive']) else: primitives = self._get_all_primitives() # strip out the primitives that won't solve anything primitives = [x for x in primitives if x['consequences'] != []] primitives = [x for x in primitives if self._can_meet_constraints(x)] # get all the primitives capable of being run, given the # primitive constraints # see if any of the appliable primitives have consequences that # could forward us to our goal. all_solutions = [] if len(primitives) == 0: # we can't meet constraints of any primitives, or we specified # a bogus primitive in a plan... self.logger.debug('no valid primitives') return None if proposed_plan: # plan_prim = primitives[0] plan_ns = proposed_plan['ns'] primitive = primitives[0] # primitive = self.api._model_get_by_id( # 'primitives', # plan_prim['id']) solution = self._is_forwarding_solution(primitive, constraint_list, plan_ns) if not solution: solution = {'primitive': primitive, 'ns': plan_ns, 'solves': None, 'consequence': None} # self.logger.debug('plan item: %s' % proposed_plan) # self.logger.debug('constraint_list: %s' % constraint_list) # raise ValueError('Bad plan: %s does not forward' % # plan_prim['name']) all_solutions = [solution] else: for primitive in primitives: solutions = self._potential_solutions(primitive, constraint_list) all_solutions += solutions # Now that we know all possible solutions, let's # spin up sub-solvers so we can continue to work # through them. self.logger.info("Found %d helpful solutions: %s" % (len(all_solutions), [x['primitive']['name'] for x in all_solutions])) ### # This might be invalid, but we'll classify all solutions into # one of two groups -- those that have discovered consequences, # and those that don't. Those that don't, we'll consider # interchangable, and just choose one. # # This might not be right. # unconstrained_solutions = [] constrained_solutions = [] if not proposed_plan: for solution in all_solutions: solution['discovered'] = self._get_additional_constraints( solution['primitive']['id'], solution['ns']) if solution['discovered'] is None: # we'll just drop it self.logger.debug( 'Cannot solve %s due to addl constraints' % (solution['primitive']['name'], )) pass elif len(solution['discovered']) != 0: constrained_solutions.append(solution) else: unconstrained_solutions.append(solution) candidate_solutions = constrained_solutions if len(unconstrained_solutions) > 0: candidate_solutions.append(unconstrained_solutions[0]) else: candidate_solutions = all_solutions self.logger.info('All candidate solutions: %s' % candidate_solutions) for solution in candidate_solutions: # yield for gevent gevent.sleep(0) self.logger.info("%s with %s, solving %s" % (solution['primitive']['name'], solution['ns'], solution['solves'])) constraints = copy.deepcopy(self.constraints) applied_consequences = copy.deepcopy(self.applied_consequences) # if not solution['solves'] in constraints: # raise RuntimeError('constraint disappeared?!?!') # get additional constraints from the primitive itself. self.logger.debug("finding addl constraints for %s using ns %s" % (solution['primitive']['name'], solution['ns'])) # new_constraints = self._get_additional_constraints( # solution['primitive']['id'], # solution['ns']) if proposed_plan is not None: new_constraints = [] else: new_constraints = solution['discovered'] solution.pop('discovered') # FIXME(rp) # pull in backends for primitives that can solve constraints # this should probably instead roll the consequence of the task # forward and re-run through satisifes constaraints if not solution['primitive']['id'] in self.task_primitives: be, prim_name = solution['primitive']['name'].split('.') if new_constraints is not None: if prim_name != "add_backend": new_expr = '"%s" in facts.backends' % be if not new_expr in new_constraints: new_constraints.append(new_expr) self.logger.info( ' - New constraints from primitive: %s' % new_constraints) if proposed_plan is not None: new_constraints = [] if new_constraints: new_constraints = [self.api.regularize_expression(x) for x in new_constraints] # FIXME(rp): we should be carrying constraints and # consequences around as sets rather than lists anyway new_constraints = [x for x in list(set(new_constraints)) if not self._constraint_satisfied(x)] new_solver = None if new_constraints is None: self.logger.info(' - abandoning solution %s -- unsolvable' % solution['primitive']['name']) else: # find the concrete consequence so we can roll forward # the cluster api representation # these should really be the consequences of the primitive. consequences = solution['primitive']['consequences'] self.logger.info(' - old constraints: %s' % constraints) for consequence in consequences: concrete_consequence = ast.concrete_expression( consequence, solution['ns']) applied_consequences.append(concrete_consequence) concrete_constraints = ast.invert_expression( concrete_consequence) self.logger.info( 'Adding consequence %s, solving constraints %s' % (concrete_consequence, concrete_constraints)) # for concrete_constraint in concrete_constraints: # if concrete_constraint in constraints: # constraints.remove(concrete_constraint) if solution['solves'] in constraints: constraints.remove(solution['solves']) # consequence = solution['consequence'] # if consequence: # concrete_consequence = ast.concrete_expression( # consequence, solution['ns']) # applied_consequences.append(concrete_consequence) # constraints.remove(solution['solves']) self.logger.info(' - Implementing as new solve step: %s' % constraints) new_solver = Solver(self.base_api, self.node_id, constraints + new_constraints, self, solution['primitive'], ns=solution['ns'], applied_consequences=applied_consequences) self.children.append(new_solver) if new_solver.constraints == []: return new_solver return None