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')
Exemple #3
0
    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