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 _visit_path(self, budget: float, spent: float, target_path: Path,
                    paths: PathsTable, visited: List[AgentName],
                    comp_def: ComputationDef, footprint: float,
                    replica_count: int, hosts: List[str]):
        """
        Visit a path in the replication graph.

        Visiting can means attempting to host a replica, if we are on a
        __hosting__ node, or forwarding to another agent or answering the
        requester.

        Parameters
        ----------
        budget
        spent
        target_path
        paths
        visited
        comp_def
        footprint
        replica_count
        hosts

        Returns
        -------
        forwarded: boolean
            a boolean indicating if the request has been answered or
            forwarded to another agent.
        replica_count: int
            the updated replica count.
        """
        if target_path.last() == '__hosting__':
            # We are actually 'visiting' the '__hosting__' virtual node
            # so we must remove it form the paths.
            paths.pop(target_path)
            if self._can_host(target_path.head(), comp_def.name, footprint):
                self._accept_replica(target_path.head(), comp_def, footprint)
                hosts.append(self.agent_def.name)
                replica_count -= 1
                if replica_count == 0:
                    self.logger.info(
                        'Target resiliency reached for %s, report back to '
                        'requester , hosts : %s', comp_def.name, hosts)
                    self._send_answer(budget, spent, target_path[:-1], paths,
                                      visited, comp_def, footprint,
                                      replica_count, hosts)
                    return True, replica_count
                return False, replica_count
            # If the cheapest path was __hosting__, we can still try
            # to visit the next path (as we known __hosting__ never
            # have any other neighbor) => consider the request as not forwarded
            return False, replica_count

        self._send_request(budget, spent, target_path, paths, visited,
                           comp_def, footprint, replica_count, hosts)
        return True, replica_count
Esempio n. 4
0
    def _send_request(self, budget: float, spent: float, rq_path: Path,
                      paths: PathsTable, visited: List[AgentName],
                      comp_def: ComputationDef, footprint: float,
                      replica_count: int, hosts: List[AgentName]):
        target_agt = rq_path.last()
        cost_to_next = self.route(target_agt)
        budget_to_next = budget - cost_to_next
        spent_to_next = spent + cost_to_next

        self.logger.debug(
            'sending replica request from  %s to %s for %s - %s ('
            'budget = %s, cost to next %s)', self.name, target_agt, rq_path,
            comp_def.name, budget_to_next, cost_to_next)
        self.post_msg(
            replication_computation_name(target_agt),
            UCSReplicateMessage('replicate_request', budget_to_next,
                                spent_to_next, rq_path, paths, visited,
                                comp_def, footprint, replica_count, hosts),
            MSG_REPLICATION)

        # All request must be answered, otherwise the replication is stuck.
        # Keep track of all request sent.
        self._pending_requests[(target_agt, comp_def.name)] = \
            (budget, spent, rq_path, paths.copy(), visited[:], comp_def,
             footprint, replica_count, hosts[:])
def test_filter_missing_agents_paths():

    paths = {
        Path('_replication_a2', '_replication_a3', '__hosting__'): 4,
        Path('_replication_a2', '_replication_a5', '_replication_a6'): 3,
        Path('_replication_a3', '_replication_a4'): 1,
        Path('_replication_a5', '_replication_a3'): 3,
        Path('_replication_a1', '_replication_a4', '_replication_a2'): 3,
    }

    available = {
        '_replication_a2', '_replication_a3', '_replication_a5',
        '_replication_a6'
    }
    filtered = filter_missing_agents_paths(paths, available)

    assert len(filtered) == 3
def test_path_iter():

    path = Path('a1', 'a2', 'a3')
    it = iter(path)
    assert 'a1' == next(it)
    assert 'a2' == next(it)
    assert 'a3' == next(it)
    with pytest.raises(StopIteration):
        next(it)
Esempio n. 7
0
 def _send_answer(self, budget: float, spent: float, rq_path: Path,
                  paths: PathsTable, visited: List[AgentName],
                  comp_def: ComputationDef, footprint: float,
                  replica_count: int, hosts: List[AgentName]):
     assert rq_path.last() == self.agt_name
     target_agt = rq_path.before_last()
     cost_to_target = self.route(target_agt)
     budget += cost_to_target
     spent -= cost_to_target
     self.logger.debug(
         'sending replica answer from %s to %s for %s %s'
         '( %s %s %s )', self.name, target_agt, rq_path, comp_def.name,
         budget, spent, cost_to_target)
     self.post_msg(
         replication_computation_name(target_agt),
         UCSReplicateMessage('replicate_answer', budget, spent, rq_path,
                             paths, visited, comp_def, footprint,
                             replica_count, hosts), MSG_REPLICATION)
def test_path_creation():

    assert len(Path(['a', 'b'])) == 2
    assert len(Path(['a1'])) == 1
    assert len(Path('a1')) == 1
    assert len(Path('a', 'b', 'c')) == 3
    assert len(Path('a1', 'a2', 'a3')) == 3
    assert len(Path(['a1', 'a2'], 'a3')) == 3
    assert len(Path()) == 0
def test_path_head():

    path = Path('a1', 'a2', 'a3')
    assert 'a1' == path.head()

    path = Path([])
    assert path.head() is None
def test_path_last():

    path = Path(['a1', 'a2', 'a3'])
    assert 'a3' == path.last()

    path = Path([])
    assert path.last() is None
def test_paths_starting_with():
    paths_starting_a2 = path_starting_with(
        Path('_replication_a2', ), {
            Path('_replication_a2', '_replication_a3'): 4,
            Path('_replication_a2', '_replication_a5', '_replication_a6'): 3,
            Path('_replication_a3', '_replication_a4'): 1,
            Path('_replication_a4', '_replication_a3'): 3,
        })

    cost, path = paths_starting_a2[0]
    assert cost == 3
    assert path == Path('_replication_a5', '_replication_a6')
    paths = [path for _, path in paths_starting_a2]
    assert Path('_replication_a3', ) in paths
    assert Path('_replication_a5', '_replication_a6') in paths
Esempio n. 12
0
 def _add_hosting_path(self, spent: float, computation: ComputationName,
                       rq_path: Path, paths: PathsTable):
     if computation not in self.computations:
         # Add a path to a virtual node with a route corresponding to the
         # hosting cost
         hosting_path = rq_path + Path('__hosting__')
         hosting_cost = spent + self.agent_def.hosting_cost(computation)
         self.logger.debug(
             'Add path to host %s on local hosting node %s '
             'with cost %s ',
             computation,
             hosting_path,
             hosting_cost,
         )
         paths[hosting_path] = hosting_cost
def test_path_add():

    p1 = Path('a1')
    p2 = Path('a2', 'a3')
    assert p1 + p2 == Path('a1', 'a2', 'a3')
    assert p2 + p1 == Path('a2', 'a3', 'a1')

    p1 = Path()
    p2 = Path('a2', 'a3')
    assert p1 + p2 == Path('a2', 'a3')
    assert p2 + p1 == Path('a2', 'a3')

    p1 = Path([])
    p2 = Path([])
    assert p1 + p2 == Path()
    assert p2 + p1 == Path()
def test_path_item():

    path = Path('a1', 'a2', 'a3')
    assert 'a1' == path[0]
    assert Path('a1', 'a2') == path[:-1]
def test_path_empty():
    path = Path()
    assert path.empty

    path = Path('a1', 'a2', 'a3')
    assert not path.empty
def test_path_tail_if_start_with():
    path = Path(('A', 'B'))
    obtained = path.tail_if_start_with(Path('A'))
    assert obtained == Path(('B', ))

    obtained = path.tail_if_start_with(Path('A', 'B'))
    assert obtained == Path(())

    obtained = Path('A', 'B', 'C', 'D')\
        .tail_if_start_with(Path('A', 'B'))
    assert obtained == Path('C', 'D')

    obtained = Path('A', 'B', 'C', 'D')\
        .tail_if_start_with(Path('A', 'D'))
    assert obtained is None

    obtained = Path(()).tail_if_start_with(Path('A', 'B'))
    assert obtained is None

    obtained = Path(('A', 'B', 'C', 'D'))\
        .tail_if_start_with(Path())
    assert obtained == Path(('A', 'B', 'C', 'D'))
Esempio n. 17
0
    def replicate(self,
                  k_target: int,
                  computations: Union[None, ComputationName,
                                      List[ComputationName]] = None):
        """
        Launch replication process for the computation(s) passed as argument.

        Parameters
        ----------
        k_target: int
            target number of replicas
        computations: None, string or list of strings.
            If computations is a string, is is considered as a single
            computation to be replicated/
            If it is a list of string, it is the list of computations
            that must be registered.
            If computations is None, all computations are registered.

        """
        if computations is None:
            self.logger.info(
                'Request for replications of all computations %s -'
                ' %s', computations, k_target)
            computations = [c for c in self.computations]
        elif not computations:
            self.logger.info('No computation to replicate for %s ', self.name)
            self.replication_done(dict(deepcopy(self._replica_hosts)))
            return
        elif type(computations) == ComputationName:
            if computations not in self.computations:
                msg = 'Requesting replication of unknown computation {}' \
                      .format(computations)
                self.logger.error(msg)
                raise ValueError(msg)
            computations = [computations]
        else:
            unknown = [c for c in computations if c not in self.computations]
            if unknown:
                msg = 'Requesting replication of unknown computation {}' \
                    .format(unknown)
                self.logger.error(msg)
                raise ValueError(msg)

        self._replication_in_progress.add(computations)
        neighbors = self.replication_neighbors()
        if not neighbors:
            self.logger.warning(
                'Cannot replicate computations %s : no '
                'neighbor', computations)
            self.replication_done(dict(deepcopy(self._replica_hosts)))
            return

        self.logger.info(
            'Starting replications of computations %s on '
            'neighbors %s - %s', computations, neighbors, k_target)

        for c in computations:
            # initialize paths with our neighbors and their costs
            paths = {Path(self.agt_name, n): self.route(n) for n in neighbors}
            budget = min(c for c in paths.values())
            visited = [self.agt_name]
            comp_def, footprint = self.computations[c]
            self.on_replicate_request(budget,
                                      0,
                                      Path(self.agt_name),
                                      paths,
                                      visited,
                                      comp_def,
                                      footprint,
                                      replica_count=k_target,
                                      hosts=[])
def test_path_before_last():

    path = Path(['a1', 'a2', 'a3'])
    assert 'a2' == path.before_last()

    path = Path(['a1'])
    with pytest.raises(IndexError):
        path.before_last()

    path = Path([])
    with pytest.raises(IndexError):
        path.before_last()