示例#1
0
async def _async_set_current_neighbours(
        neighbours: t.List[Server] = None,
        changed_routes: t.Dict[Server,
                               RouteContainer] = None) -> t.List[Server]:
    """Function checks and sets neighbours

    Args:
        neighbours: list of neighbours
        changed_routes: reference to a dict which will be populated with new routes

    Returns:
        list of servers which are not neighbours anymore
    """
    not_neighbours_anymore = []

    if neighbours is None:
        neighbours = Server.get_neighbours()

    if neighbours:
        resp = await asyncio.gather(
            *[async_check_gates(server) for server in neighbours])
        for route, server in zip(resp, neighbours):
            if isinstance(route, RouteContainer):
                server.set_route(route)
                if changed_routes is not None:
                    changed_routes[server] = route
            elif route is None:
                not_neighbours_anymore.append(server)
                rc = RouteContainer(None, None, None)
                server.set_route(rc)
                if changed_routes is not None:
                    changed_routes[server] = rc
    return not_neighbours_anymore
示例#2
0
    def _remove_node_from_cluster(self, server_id):
        changed_routes = {}
        server = self.session.query(Server).get(server_id)

        if server:
            server.set_route(RouteContainer(None, None, None))
            lost_routes = self.session.query(Route).filter_by(
                proxy_server_id=server.id).options(
                    orm.lazyload(Route.destination), orm.lazyload(Route.gate),
                    orm.lazyload(Route.proxy_server)).count()
            if lost_routes:
                changed_routes = self._loop.run_until_complete(
                    self._async_refresh_route_table(
                        discover_new_neighbours=False,
                        check_current_neighbours=False))

            changed_routes.update({server: RouteContainer(None, None, None)})

        return changed_routes
示例#3
0
async def async_check_gates(server,
                            timeout=5,
                            retries=1,
                            delay=1) -> t.Union[RouteContainer, Route]:
    """checks if a server is neighbour

    Args:
        server: server to discover as a new neighbour
        timeout: timeout socket
        retries: number of times to try to connect to the socket
        delay: time in seconds between retries

    Returns:
        returns Route if no change. This means that gate in inventory is still up.
                RouteContainer if there is an available gate to connect
                None if no gate to connect
    """

    # check current gate
    if server.route and server.route.gate:
        gate = server.route.gate
        if await async_check_host(host=gate.dns or str(gate.ip),
                                  port=gate.port,
                                  retry=retries + 1,
                                  delay=delay,
                                  timeout=timeout):
            return server.route

    for gate in server.gates:
        if server.route and gate != server.route.gate:
            if (gate.ip and not gate.ip.is_loopback) or (
                    gate.dns and gate.dns != 'localhost'):
                if await async_check_host(host=gate.dns or str(gate.ip),
                                          port=gate.port,
                                          retry=retries,
                                          delay=1,
                                          timeout=timeout):
                    return RouteContainer(None, gate, 0)
示例#4
0
    def _update_route_table_from_data(
            self,
            new_routes: t.Dict,
            auth=None) -> t.Dict[Server, RouteContainer]:
        changed_routes = {}
        routes = new_routes.get('route_list', [])
        routes.sort(key=lambda x: x.get('cost') or MAX_COST, reverse=True)
        try:
            likely_proxy_server = self.session.query(Server).get(
                new_routes.get('server_id'))
            if not likely_proxy_server:
                self.logger.warning(
                    f"Server id still '{new_routes.get('server_id')}' not created."
                )
                return changed_routes
            debug_new_routes = []
            for new_route in routes:
                target_server = self.session.query(Server).get(
                    new_route.get('destination_id'))
                proxy_server = self.session.query(Server).get(
                    new_route.get('proxy_server_id'))
                gate = self.session.query(Gate).get(new_route.get('gate_id'))
                dest_name = getattr(target_server, 'name',
                                    new_route.get('destination_id'))
                proxy_name = getattr(proxy_server, 'name',
                                     new_route.get('proxy_server_id'))
                gate_str = str(gate) if gate else new_route.get('gate_id')
                cost = new_route.get('cost')
                if gate_str and proxy_name:
                    gate_str = gate_str + '*' + proxy_name
                debug_new_routes.append(
                    f'{dest_name} -> {gate_str or proxy_name} / {cost}')
                if target_server is None:
                    self.logger.warning(
                        f"Destination server unknown {new_route.get('destination_id')}"
                    )
                    continue
                if target_server.id == self.server.id:
                    # check if server has detected me as a neighbour
                    if new_route.get('cost') == 0:
                        # check if I do not have it as a neighbour yet
                        if likely_proxy_server.route and likely_proxy_server.route.cost != 0:
                            # check if I have a gate to contact with it
                            route = check_gates(likely_proxy_server)
                            if isinstance(route, RouteContainer):
                                changed_routes[likely_proxy_server] = route
                                likely_proxy_server.set_route(route)
                else:
                    # server may be created without route (backward compatibility)
                    if target_server.route is None:
                        target_server.set_route(
                            Route(destination=target_server))
                    # process routes whose proxy_server is not me
                    if self.server.id != new_route.get('proxy_server_id'):
                        # check If I reach destination
                        if target_server.route.cost is not None:
                            if new_route.get('cost') is None:
                                #  likely proxy does not reach but I reach it. It might be shutdown unexpectedly?
                                if target_server.route.cost == 0:
                                    # check if I still have it as a neighbour
                                    route = check_gates(target_server)
                                else:
                                    if target_server.route.proxy_server == likely_proxy_server:
                                        route = RouteContainer(
                                            None, None, None)
                                    else:
                                        # check if I still have access through my proxy
                                        cost, time = ntwrk.ping(
                                            target_server,
                                            retries=1,
                                            timeout=20,
                                            session=self.session)
                                        if cost == target_server.route.cost:
                                            # still a valid route
                                            route = target_server.route
                                        elif cost is None:
                                            route = RouteContainer(
                                                None, None, None)
                                        else:
                                            route = RouteContainer(
                                                target_server.route.
                                                proxy_server, None, cost)

                                if isinstance(route, RouteContainer):
                                    # gate changed
                                    changed_routes[target_server] = route
                                    target_server.set_route(route)
                                elif route is None:
                                    # no route to host. I've lost contact too
                                    changed_routes[
                                        target_server] = RouteContainer(
                                            None, None, None)
                                    target_server.set_route(
                                        changed_routes[target_server])
                                else:
                                    # still a valid route. Send route to likely_proxy_server to tell it I have access
                                    resp = ntwrk.patch(
                                        likely_proxy_server,
                                        'api_1_0.routes',
                                        json=dict(
                                            server_id=str(self.server.id),
                                            route_list=[
                                                target_server.route.to_json()
                                            ]),
                                        auth=get_root_auth(),
                                        timeout=5)
                                    if not resp.ok:
                                        self.logger.info(
                                            f'Unable to send route to {likely_proxy_server}: {resp}'
                                        )
                            elif target_server.route.proxy_server is not None and \
                                    target_server.route.proxy_server == likely_proxy_server:
                                # my proxy is telling me the route has changed
                                rc = RouteContainer(likely_proxy_server, None,
                                                    new_route.get('cost') + 1)
                                target_server.set_route(rc)
                                changed_routes.update({target_server: rc})
                            elif new_route.get(
                                    'cost') + 1 < target_server.route.cost:
                                # if new route has less cost than actual route, take it as my new route
                                rc = RouteContainer(likely_proxy_server, None,
                                                    new_route.get('cost') + 1)
                                target_server.set_route(rc)
                                changed_routes.update({target_server: rc})
                        else:
                            # me does not reaches target_server
                            # if new route reaches the destination take it as a new one
                            if (new_route.get('cost') == 0
                                    and new_route.get('gate_id') is not None
                                ) or (new_route.get('cost') is not None
                                      and proxy_server is not None):
                                rc = RouteContainer(likely_proxy_server, None,
                                                    new_route.get('cost') + 1)
                            else:
                                # neither my route and the new route has access to the destination
                                rc = RouteContainer(None, None, None)
                            if target_server.route and (
                                    rc.proxy_server !=
                                    target_server.route.proxy_server
                                    or rc.gate != target_server.route.gate
                                    or rc.cost != target_server.route.cost):
                                target_server.set_route(rc)
                                changed_routes.update({target_server: rc})
                    else:
                        # target_server reached through me as a proxy from likely_proxy
                        pass

            query = self.session.query(Route).filter(
                Route.proxy_server_id.in_([
                    s.id for s, r in changed_routes.items() if r.cost is None
                ]))
            rc = RouteContainer(None, None, None)
            for route in query.all():
                route.set_route(rc)
                changed_routes[route.destination] = rc

            self.logger.debug(
                f"New routes processed from {likely_proxy_server.name}: {json.dumps(debug_new_routes, indent=2)}"
            )
            # if changed_routes:
            #     Parameter.set('routing_last_refresh', get_now())
        except errors.InvalidRoute as e:
            debug_new_routes = []
            routes.sort(key=lambda x: x.get('cost') or MAX_COST, reverse=True)
            for new_route in routes:
                target_server = self.session.query(Server).get(
                    new_route.get('destination_id'))
                proxy_server = self.session.query(Server).get(
                    new_route.get('proxy_server_id'))
                gate = self.session.query(Gate).get(new_route.get('gate_id'))
                dest_name = getattr(target_server, 'name',
                                    new_route.get('destination_id'))
                proxy_name = getattr(proxy_server, 'name',
                                     new_route.get('proxy_server_id'))
                gate_str = str(gate) if gate else new_route.get('gate_id')
                cost = new_route.get('cost')
                if gate_str and proxy_name:
                    gate_str = gate_str + '*' + proxy_name
                debug_new_routes.append(
                    f'{dest_name} -> {gate_str or proxy_name} / {cost}')
            self.logger.exception(
                "Error setting routes from following data: " +
                json.dumps(debug_new_routes, indent=4))
        return changed_routes
示例#5
0
    async def _async_refresh_route_table(
            self,
            discover_new_neighbours=False,
            check_current_neighbours=False,
            max_num_discovery=None) -> t.Dict[Server, RouteContainer]:
        """Gets route tables of all neighbours and updates its own table based on jump weights.
        Needs a Flask App Context to run.

        Parameters
        ----------
        discover_new_neighbours:
            tries to discover new neighbours
        check_current_neighbours:
            checks if current neighbours are still neighbours
        max_num_discovery:
            maximum number of possible nodes to check as neighbour

        Returns
        -------
        None
        """

        self.logger.debug('Refresh Route Table')
        neighbours = Server.get_neighbours(session=self.session)
        not_neighbours = Server.get_not_neighbours(session=self.session)

        changed_routes: t.Dict[Server, RouteContainer] = {}

        not_neighbours_anymore = []
        new_neighbours = []

        aws = []
        if check_current_neighbours:
            if neighbours:
                self.logger.debug(f"Checking current neighbours: " +
                                  ', '.join([str(s) for s in neighbours]))
                aws.append(
                    _async_set_current_neighbours(neighbours, changed_routes))
            else:
                self.logger.debug(f"No neighbour to check")

        if discover_new_neighbours:
            if not_neighbours[:max_num_discovery]:
                rs = list(not_neighbours)
                random.shuffle(rs)
                target = rs[:max_num_discovery]
                target.sort(key=lambda s: s.name)
                self.logger.debug(
                    f"Checking new neighbours{f' (limited to {max_num_discovery})' if max_num_discovery else ''}: "
                    + ', '.join([str(s) for s in target]))
                aws.append(
                    _async_discover_new_neighbours(target, changed_routes))
            else:
                self.logger.debug("No new neighbours to check")

        res = await asyncio.gather(*aws, return_exceptions=False)

        if check_current_neighbours and neighbours:
            not_neighbours_anymore = res.pop(0)
            if not_neighbours_anymore:
                self.logger.info(
                    f"Lost direct connection to the following nodes: " +
                    ', '.join([str(s) for s in not_neighbours_anymore]))
        if discover_new_neighbours and not_neighbours[:max_num_discovery]:
            new_neighbours = res.pop(0)
            if new_neighbours:
                self.logger.info(f'New neighbours found: ' +
                                 ', '.join([str(s) for s in new_neighbours]))
            else:
                self.logger.debug("No new neighbours found")

        # remove routes whose proxy_server is a node that is not a neighbour
        query = self.session.query(Route).filter(
            Route.proxy_server_id.in_([
                s.id for s in list(
                    set(not_neighbours).union(set(not_neighbours_anymore)))
            ]))
        rc = RouteContainer(None, None, None)
        for route in query.all():
            route.set_route(rc)
            changed_routes[route.destination] = rc
        self.session.commit()

        # update neighbour lis

        neighbours = list(
            set(neighbours).union(set(new_neighbours)) -
            set(not_neighbours_anymore))

        if neighbours:
            self.logger.debug(
                f"Getting routing tables from {', '.join([str(s) for s in neighbours])}"
            )
            responses = await asyncio.gather(*[
                ntwrk.async_get(server, 'api_1_0.routes', auth=get_root_auth())
                for server in neighbours
            ])

            cr = self._route_table_merge(dict(zip(neighbours, responses)))
            changed_routes.update(cr)

        return changed_routes
示例#6
0
    def _route_table_merge(self, data: t.Dict[Server, ntwrk.Response]):
        changed_routes: t.Dict[Server, RouteContainer] = {}
        temp_table_routes: t.Dict[uuid.UUID, t.List[RouteContainer]] = {}
        for s, resp in data.items():
            if resp.code == 200:
                server_id = resp.msg.get(
                    'server_id', None) or resp.msg.get('server').get('id')
                likely_proxy_server_entity = self.session.query(Server).get(
                    server_id)
                for route_json in resp.msg['route_list']:
                    route_json = convert(route_json)
                    if route_json.destination_id != self.server.id \
                            and route_json.proxy_server_id != self.server.id \
                            and route_json.gate_id not in [g.id for g in self.server.gates]:
                        if route_json.destination_id not in temp_table_routes:
                            temp_table_routes.update(
                                {route_json.destination_id: []})
                        if route_json.cost is not None:
                            route_json.cost += 1
                            route_json.proxy_server_id = likely_proxy_server_entity.id
                            route_json.gate_id = None
                            temp_table_routes[
                                route_json.destination_id].append(
                                    RouteContainer(
                                        likely_proxy_server_entity.id, None,
                                        route_json.cost))
                        elif route_json.cost is None:
                            # remove a routing if gateway cannot reach the destination
                            temp_table_routes[
                                route_json.destination_id].append(
                                    RouteContainer(route_json.proxy_server_id,
                                                   None, None))
            else:
                self.logger.error(
                    f"Error while connecting with {s}. Error: {resp}")

        # Select new routes based on neighbour routes
        neighbour_ids = [
            s.id for s in Server.get_neighbours(session=self.session)
        ]
        for destination_id in filter(lambda s: s not in neighbour_ids,
                                     temp_table_routes.keys()):
            route = self.session.query(Route).filter_by(
                destination_id=destination_id).one_or_none()
            if not route:
                server = self.session.query(Server).get(destination_id)
                if not server:
                    continue
                else:
                    route = Route(destination=server)
            temp_table_routes[destination_id].sort(
                key=lambda x: x.cost or MAX_COST)
            if len(temp_table_routes[destination_id]) > 0:
                min_route = temp_table_routes[destination_id][0]
                proxy_server: Server = self.session.query(Server).get(
                    min_route.proxy_server)
                cost = min_route.cost
                if route.proxy_server != proxy_server or route.cost != cost:
                    rc = RouteContainer(proxy_server, None, cost)
                    route.set_route(rc)
                    changed_routes[route.destination] = rc
                    self.session.add(route)

        data = {}
        for server, temp_route in changed_routes.items():
            data.update({
                str(server): {
                    'proxy_server': str(temp_route.proxy_server),
                    'gate': str(temp_route.gate),
                    'cost': str(temp_route.cost)
                }
            })
        return changed_routes