Esempio n. 1
0
    def on_replicate_answer(self, budget: float, spent: float, rq_path: Path,
                            paths: PathsTable, visited: List[AgentName],
                            comp_def: ComputationDef, footprint: float,
                            replica_count: int, hosts: List[str]):
        *_, current, sender = rq_path
        initial_path = rq_path[:-1]

        paths = filter_missing_agents_paths(
            paths,
            self.replication_neighbors() | {self.agt_name})

        # If all replica have been placed, report back to requester if any, or
        # signal that replication is done.
        if replica_count == 0:
            if len(rq_path) >= 3:
                self.logger.debug(
                    'All replica placed for %s, report back to requester',
                    comp_def.name)
                self._send_answer(budget, spent, initial_path, paths, visited,
                                  comp_def, footprint, replica_count, hosts)
                return
            else:
                self.computation_replicated(comp_def.name, hosts)
                return

        # If there are still replica to be placed, keep trying on neighbors
        back_path = rq_path[:-1]
        affordable_paths = affordable_path_from(back_path, budget + spent,
                                                paths)
        # Paths to next candidates, avoiding the path we're coming from.
        target_paths = (back_path + Path(p.head()) for _, p in affordable_paths
                        if back_path + Path(p.head()) != rq_path)

        for target_path in target_paths:
            forwarded, replica_count = \
                self._visit_path(budget, spent, target_path, paths, visited,
                                 comp_def, footprint, replica_count, hosts)
            if forwarded:
                return

        # Could not send to any neighbor: get back to requester
        if len(rq_path) >= 3:
            self._send_answer(budget, spent, initial_path, paths, visited,
                              comp_def, footprint, replica_count, hosts)
            return

        # no reachable candidate path and no ancestor to go back,
        # we are back at the start node: increase the budget
        if not paths:
            # Cannot increase budget, replica distribution is finished for
            # this computation, even if we have not reached target resiliency
            # level. Report the final replica distribution to the orchestrator.
            self.computation_replicated(comp_def.name, hosts)
        else:
            budget = min(c for p, c in paths.items() if p != rq_path)
            self.logger.info('Increase budget for computation %s : %s',
                             comp_def.name, budget)
            self.on_replicate_request(budget, 0, Path(current), paths, visited,
                                      comp_def, footprint, replica_count,
                                      hosts)
Esempio n. 2
0
    def on_replicate_request(self, budget: float, spent: float, rq_path: Path,
                             paths: PathsTable, visited: List[AgentName],
                             comp_def: ComputationDef, footprint: float,
                             replica_count: int, hosts: List[str]):
        assert self.agt_name == rq_path.last()

        if rq_path in paths:
            paths.pop(rq_path)
        if self.agt_name not in visited:  # first visit for this node
            visited.append(self.agt_name)
            self._add_hosting_path(spent, comp_def.name, rq_path, paths)

        neighbors = self.replication_neighbors()

        # If some agents have left during replication, some may still be in
        # the received paths table even though sending replication_request to
        # them would block the replication.
        paths = filter_missing_agents_paths(paths, neighbors | {self.agt_name})
        # Available & affordable with current remaining budget, paths from here:
        affordable_paths = affordable_path_from(rq_path, budget + spent, paths)

        # self.logger.debug('Affordable path %s %s %s %s \n%s', budget, spent,
        #                   rq_path, affordable_paths, paths)

        # Paths to next candidates from paths table.
        target_paths = (rq_path + Path(p.head()) for _, p in affordable_paths)

        for target_path in target_paths:
            forwarded, replica_count = \
                self._visit_path(budget, spent, target_path, paths, visited,
                                 comp_def, footprint, replica_count, hosts)
            if forwarded:
                return

        self.logger.info('No reachable path for %s with budget %s ',
                         comp_def.name, budget)

        # Either:
        #  * No path : Not on a already known path
        #  * or all paths are too expensive for our current budget (meaning
        #    that we are at the last node in the known path)
        # In both cases, we now look at neighbors costs and store them if we
        # do not already known a cheaper path to them.
        neighbors_path = ((n, self.route(n), rq_path + Path(n))
                          for n in neighbors if n not in visited)

        for n, r, p in neighbors_path:
            cheapest, cheapest_path = cheapest_path_to(n, paths)
            if cheapest > spent + r:
                if cheapest_path in paths:
                    paths.pop(cheapest_path)
                paths[p] = spent + r
            else:
                # self.logger.debug('Cheaper path known to %s : %s (%s)', p,
                #                   cheapest_path, cheapest)
                pass

        self._send_answer(budget, spent, rq_path, paths, visited, comp_def,
                          footprint, replica_count, hosts)
Esempio n. 3
0
def test_affordable_path_from():
    table = [
        (3, ("a2", "a9", "a4", "a8")),
        (2, ("a2", "a3")),
        (3, ("a2", "a3", "a4")),
        (6, ("a5", "a3", "a4")),
        (4, ("a2", "a3", "a4", "a12")),
        (9, ("a2", "a4", "a4")),
        (3, ("a2", "a4", "a4", "a8")),
        (3, ("a2", "a5", "a4", "a8")),
        (3, ("a2", "a3", "a4", "a8")),
        (3, ("a1", "a3", "a4")),
        (4, ("a2", "a3", "a4", "a1", "a5")),
    ]

    paths = affordable_path_from(("a2", "a3"), 3, table)

    assert len(list(paths)) == 3
Esempio n. 4
0
    def to_bench():
        paths = affordable_path_from(("a2", "a3"), 3, table)

        assert len(paths) == 3
Esempio n. 5
0
    def on_replicate_answer(
        self,
        budget: float,
        spent: float,
        rq_path: Path,
        paths: PathsTable,
        visited: List[AgentName],
        comp_def: ComputationDef,
        footprint: float,
        replica_count: int,
        hosts: List[str],
    ):
        *_, current, sender = rq_path
        comp_name = comp_def.name
        initial_path = rq_path[:-1]

        if self._removed_agents:
            paths = filter_missing_agents_paths(paths, self._removed_agents)

        # If all replica have been placed, report back to requester if any, or
        # signal that replication is done.
        if replica_count == 0:
            if len(rq_path) >= 3:
                if self.logger.isEnabledFor(logging.DEBUG):
                    self.logger.debug(
                        f"All replica placed for {comp_name}, report back to requester"
                    )
                self._send_answer(
                    budget,
                    spent,
                    initial_path,
                    paths,
                    visited,
                    comp_def,
                    footprint,
                    replica_count,
                    hosts,
                )
                return
            else:
                self.computation_replicated(comp_name, hosts)
                return

        # If there are still replica to be placed, keep trying on neighbors
        back_path = rq_path[:-1]
        # affordable_paths = affordable_path_from(back_path, budget + spent, paths)

        # Paths to next candidates, avoiding the path we're coming from.
        target_paths = (back_path + (p[0], )
                        for p in affordable_path_from(back_path, budget +
                                                      spent, paths)
                        if back_path + (p[0], ) != rq_path)

        for target_path in target_paths:
            if self.logger.isEnabledFor(logging.DEBUG):
                self.logger.debug(
                    f"Visiting for {comp_name} path {target_path}")
            forwarded, replica_count = self._visit_path(
                budget,
                spent,
                target_path,
                paths,
                visited,
                comp_def,
                footprint,
                replica_count,
                hosts,
            )
            if forwarded:
                return

        # Could not send to any neighbor: get back to requester
        if len(rq_path) >= 3:
            self._send_answer(
                budget,
                spent,
                initial_path,
                paths,
                visited,
                comp_def,
                footprint,
                replica_count,
                hosts,
            )
            return

        # no reachable candidate path and no ancestor to go back,
        # we are back at the start node: increase the budget
        if not paths:
            # Cannot increase budget, replica distribution is finished for
            # this computation, even if we have not reached target resiliency
            # level. Report the final replica distribution to the orchestrator.
            self.logger.warning(
                f"Could not reach target resiliency level for {comp_name}, "
                f"replicated on {hosts}")
            self.computation_replicated(comp_name, hosts)
        else:
            budget = min(c for c, p in paths if p != rq_path)
            if self.logger.isEnabledFor(logging.INFO):
                self.logger.info(
                    f"Increase budget for computation {comp_name} : {budget}")
            self.on_replicate_request(
                budget,
                0,
                (current, ),
                paths,
                visited,
                comp_def,
                footprint,
                replica_count,
                hosts,
            )
Esempio n. 6
0
    def on_replicate_request(
        self,
        budget: float,
        spent: float,
        rq_path: Path,
        paths: PathsTable,
        visited: List[AgentName],
        comp_def: ComputationDef,
        footprint: float,
        replica_count: int,
        hosts: List[str],
    ):
        assert self.agt_name == rq_path[-1]  # last()
        comp_name = comp_def.name
        remove_path(paths, rq_path)
        if self.agt_name not in visited:  # first visit for this node
            visited.append(self.agt_name)
            self._add_hosting_path(spent, comp_name, rq_path, paths)

        neighbors = self.replication_neighbors()

        # If some agents have left during replication, some may still be in
        # the received paths table even though sending replication_request to
        # them would block the replication.
        if self._removed_agents:
            paths = filter_missing_agents_paths(paths, self._removed_agents)

        # Available & affordable with current remaining budget, paths from here:
        # affordable_paths = affordable_path_from(rq_path, budget + spent, paths)
        # self.logger.debug(
        #     f"Affordable paths for {comp_name} on rq (b:{budget}, s:{spent}, {rq_path}) : "
        #     f": {affordable_paths} out of {paths}"
        # )

        # Paths to next candidates from paths table.
        target_paths = (rq_path + (p[0], )
                        for p in affordable_path_from(rq_path, budget +
                                                      spent, paths))

        for target_path in target_paths:
            if self.logger.isEnabledFor(logging.DEBUG):
                self.logger.debug(
                    f"Visiting for {comp_name} path {target_path}")
            forwarded, replica_count = self._visit_path(
                budget,
                spent,
                target_path,
                paths,
                visited,
                comp_def,
                footprint,
                replica_count,
                hosts,
            )
            if forwarded:
                return

        if self.logger.isEnabledFor(logging.INFO):
            self.logger.info("No reachable path for %s with budget %s ",
                             comp_name, budget)

        # Either:
        #  * No path : Not on a already known path
        #  * or all paths are too expensive for our current budget (meaning
        #    that we are at the last node in the known path)
        # In both cases, we now look at neighbors costs and store them if we
        # do not already known a cheaper path to them.
        neighbors_path = ((n, c, rq_path + (n, )) for n, c in neighbors
                          if n not in visited)

        for n, r, p in neighbors_path:
            cheapest, cheapest_path = cheapest_path_to(n, paths)
            if cheapest > spent + r:
                remove_path(paths, cheapest_path)
                paths.append((spent + r, p))
                paths.sort()
            else:
                # self.logger.debug('Cheaper path known to %s : %s (%s)', p,
                #                   cheapest_path, cheapest)
                pass

        self._send_answer(
            budget,
            spent,
            rq_path,
            paths,
            visited,
            comp_def,
            footprint,
            replica_count,
            hosts,
        )