Example #1
0
class FetchSessions(Fetch):
    """Fetch Tool for PiCN supporting sessions"""
    def __init__(self,
                 ip: str,
                 port: Optional[int],
                 log_level=255,
                 encoder: BasicEncoder = None,
                 autoconfig: bool = False,
                 interfaces=None,
                 session_keys: Optional[Dict] = None,
                 name: str = None,
                 polling_interval: float = 1.0,
                 ping_interval: float = 2.0):
        super().__init__(ip, port, log_level, encoder, autoconfig, interfaces,
                         name)
        self.ip = ip
        self._logger = Logger("FetchSession", log_level)
        self._pending_sessions: List[Name] = []
        self._running_sessions: Dict[Name:Name] = dict(
        ) if session_keys is None else session_keys
        self._has_session: bool = True if session_keys is not None else False
        self._session_initiator = 'session_connector'
        self._session_identifier = 'sid'
        self._polling_interval = polling_interval
        self._ping_interval = ping_interval
        self._manager = Manager()
        self._mutex = self._manager.Lock()

        self.receive_process = Process(target=self._receive_session,
                                       args=(
                                           self.lstack.queue_to_higher,
                                           self._polling_interval,
                                           self._mutex,
                                       ))
        self.ping_process = Process(target=self._ping_messages,
                                    args=(self._ping_interval, ))

        self.receive_process.start()
        self.ping_process.start()

    def handle_session(self, name: Name, packet: Packet) -> None:
        """
        :param name Name to be fetched
        :param packet Packet with session handshake
        """
        if isinstance(packet, Content):
            target_name: Name = Name(
                f"/{self._session_identifier}/{packet.content}")
            session_confirmation: Content = Content(target_name,
                                                    packet.content, None)

            self.send_content(content=session_confirmation)

            self._running_sessions[name] = target_name
            self._pending_sessions.remove(name)
            self._has_session = True

        return None

    def get_session_name(self, name: Name) -> Optional[Name]:
        """Fetches the session name from the session store. Returns None otherwise
        param name Name of repository to get session key for
        """
        if name in self._running_sessions:
            return self._running_sessions[name]
        else:
            return None

    def end_session(self, name: Name) -> None:
        """Terminates a session by deleting the associated id from the session store.
        param name Name to terminate session with
        """
        self._logger.debug(
            f"Terminating session with repo {name} (session was {self})")
        content = Content(self.get_session_name(name), 'terminate')
        self.send_content(content)
        self._pending_sessions.remove(name)
        del self._running_sessions[name]
        self._has_session = False if not self._running_sessions else True

        return None

    def _ping_messages(self, ping_interval: float = 2.0) -> None:
        self._logger.debug(f"--> Starting ping messages at {time.time}")
        while True:
            if self._has_session:
                for repo in self._running_sessions:
                    self._logger.debug(
                        f"Sending ping to {repo} at {time.time}")
                    conntent = Content(self.get_session_name(repo), 'ping')
                    self.send_content(conntent)
            time.sleep(ping_interval)

        return None

    def _receive_session(self, queue: Queue, polling_interval: float,
                         mutex: Lock) -> None:
        while True:
            self._logger.debug(f"--> : Waiting for mutex in loop ...")
            mutex.acquire(blocking=True)
            packet = None

            if not queue.empty():
                packet = queue.get()[1]

            mutex.release()

            if isinstance(packet, Content):
                print(f"--> : Receive loop got: {packet.content}")
            elif isinstance(packet, Nack):
                self._logger.debug(
                    f"--> One time receive got Nack: {packet.reason}")
            elif packet is None:
                self._logger.debug(f"--> : No packet in queue")
            else:
                self._logger.debug(
                    f"--> : Whoops, we just cleared a non content object from the queue! {packet}"
                )

            time.sleep(polling_interval)

        return None

    def fetch_data(self,
                   name: Name,
                   timeout: float = 4.0,
                   use_session: bool = True) -> Optional[str]:
        """Fetch data from the server
        :param name Name to be fetched
        :param timeout Timeout to wait for a response. Use 0 for infinity
        :param use_session Set to False if sessions shouldn't be used even if they are available.
        """
        if name in self._running_sessions and use_session:  # Create interest with session
            interest: Interest = Interest(self._running_sessions.get(name))
        else:  # Create normal interest
            interest: Interest = Interest(name)

        self._mutex.acquire(blocking=True)
        self.send_interest(interest)
        packet = self.receive_packet(timeout)
        self._mutex.release()

        if self._session_initiator in interest.name.to_string(
        ):  # Check if we need to handle session initiation
            new_name = Name(name.components[:-1])
            self._pending_sessions.append(new_name)
            self.handle_session(new_name, packet)

        if isinstance(packet, Content):
            self._logger.debug(
                f"--> One time receive got content: {packet.content}")
            return packet.content
        elif isinstance(packet, Nack):
            self._logger.debug(
                f"--> One time receive got nack: {packet.reason}")
            return f"Received Nack: {str(packet.reason.value)}"

        return None

    def send_interest(self, interest: Interest) -> None:
        if self.autoconfig:
            self.lstack.queue_from_higher.put([None, interest])
        else:
            self.lstack.queue_from_higher.put([self.fid, interest])

        return None

    def receive_packet(self, timeout: float) -> Packet:
        if timeout == 0:
            packet = self.lstack.queue_to_higher.get()[1]
        else:
            packet = self.lstack.queue_to_higher.get(timeout=timeout)[1]

        return packet

    def send_content(self, content: Union[Content, Tuple[Name, str]]) -> None:
        if isinstance(content, Content):
            c = content
        else:
            c = Content(content[0], content[1], None)

        if self.autoconfig:
            self.lstack.queue_from_higher.put([None, c])
        else:
            self.lstack.queue_from_higher.put([self.fid, c])

        return None

    def stop_fetch(self):
        """Close everything"""
        self.receive_process.terminate()
        self.lstack.stop_all()
        self.lstack.close_all()

    def __repr__(self):
        headers = ['Target', 'Session ID']
        data = [[k, v] for k, v in self._running_sessions.items()]
        return f"Running sessions for <<{self.name}>>:\n{tabulate(data, headers=headers, showindex=True, tablefmt='fancy_grid')}"
Example #2
0
class MobilitySimulation(object):
    """This simulation setup is used to simulate a set of mobile nodes within a NFN based infrastructure"""
    def __init__(self,
                 run_id: int,
                 mobile_nodes: List[MobileNode],
                 stationary_nodes: List[StationaryNode],
                 stationary_node_distance: float,
                 named_functions: dict,
                 function_names: list,
                 forwarder: str = "NFNForwarder",
                 optimizer: str = "ToDataFirstOptimizer",
                 use_distribution_helper: bool = False,
                 log_level=logging.DEBUG):
        """
        Configuration of the mobility simulation

        :param run_id the identifier of the simulation run
        :param mobile_nodes a list of mobile nodes part of the simulation
        :param stationary_nodes a list of stationary nodes forming the infrastructure
        :param stationary_node_distance the distance between the stationary nodes
        :param named_functions a dictionary of named function definitions used to be executed
        :param function_names a list of function names to be assigned to the mobile nodes
        :param forwarder the NFN forwarder to be used
        :param optimizer the NFN resolution strategy optimizer to be used in the simulation
        :param use_distribution_helper A flag indicating if the default distribution helper (ZipfMandelbrotDistribution)
        shall be used or not; default = False
        :param log_level the log level of the logger to be used; default: logging.DEBUG
        """
        self._run_id = run_id
        self._forwarder = forwarder
        self._optimizer = optimizer
        self._mobile_nodes = mobile_nodes
        self._stationary_nodes = stationary_nodes
        self._stationary_node_distance = stationary_node_distance
        self.logger = Logger("MobilitySimulation", log_level)
        self.to_car_faces = [[0] * len(self._mobile_nodes)
                             for i in range(len(self._stationary_nodes))
                             ]  # rsu, car -> faceid
        self.to_rsu_faces = [[0] * len(self._mobile_nodes)
                             for i in range(len(self._stationary_nodes))
                             ]  # rsu, car -> faceid
        self._velocities = []
        self._heading_directions = []
        self._starting_points = []
        for node in self._mobile_nodes:
            self._velocities.append(node.speed)
            self._heading_directions.append(node.direction)
            self._starting_points.append(node.spawn_point)

        self._is_running = False  # flag indicating if the simulation is running or not
        self._function_names = function_names  # list of function names to be invoked by the nodes
        self._named_functions = named_functions  # a dict of function definitions to be invoked
        self._chunk_size = 8192

        self._simulation_bus = SimulationBus(
            packetencoder=SimpleStringEncoder())

        self._stationary_node_name_prefix = Name("/rsu")
        self._mobile_node_to_computation = [0] * len(
            mobile_nodes)  # index which mobile node issues which computation
        if use_distribution_helper:
            # TODO in the future support more distribution types, e.g., uniform, gaussian, etc.
            dist_array = ZipfMandelbrotDistribution.create_zipf_mandelbrot_distribution(
                len(self._function_names), 0.7, 0.7)
            for i in range(0, len(mobile_nodes)):
                self._mobile_node_to_computation[i] = ZipfMandelbrotDistribution.\
                    get_next_zipfmandelbrot_random_number(dist_array, len(self._function_names)) - 1
                # get_next_zipfmandelbrot_random_number(dist_array, len(self._function_names), run_id) - 1

        self._compute_rsu_connection_time()
        self._setup_simulation_network()

    ###########################
    # METHODS
    ###########################

    def _compute_rsu_connection_time(self):
        """this method computes the connection time of a mobile node to a stationary node based on velocity of the mobile
        node and the communication range of the stationary node. Further enhancements of this simulation should include
        physical and MAC layer related communication conditions (e.g., propagation delay, fading, etc.) """

        self._contact_time = []
        for mobile_node in self._mobile_nodes:
            speed_in_ms = mobile_node.speed / 3.6
            distance_in_m = 1000 * self._stationary_node_distance
            self._contact_time.append(distance_in_m / speed_in_ms * 1e9)

    def _setup_stationary_nodes(self):
        """configure the NFN com. stack at the stationary nodes"""

        for node in self._stationary_nodes:
            # install the NFN forwarder and the mgmt client tool at the stationary node
            if self._forwarder == "NFNForwarder":
                node.nfn_forwarder = NFNForwarder(
                    0,
                    encoder=SimpleStringEncoder(),
                    interfaces=[
                        self._simulation_bus.add_interface(
                            f"rsu{node.node_id}")
                    ],
                    ageing_interval=10)
            elif self._forwarder == "NFNForwarderData":
                node.nfn_forwarder = NFNForwarderData(
                    0,
                    encoder=SimpleStringEncoder(),
                    interfaces=[
                        self._simulation_bus.add_interface(
                            f"rsu{node.node_id}")
                    ],
                    chunk_size=self._chunk_size,
                    num_of_forwards=1,
                    ageing_interval=10)
            else:
                self.logger.error(
                    "Forwarder: " + self._forwarder +
                    " is not supported! Use 'NFNForwarder' or 'NFNForwarderData'!"
                )

            # install the optimizer
            if self._optimizer == "ToDataFirstOptimizer":
                node.nfn_forwarder.nfnlayer.optimizer = ToDataFirstOptimizer(
                    node.nfn_forwarder.icnlayer.cs,
                    node.nfn_forwarder.icnlayer.fib,
                    node.nfn_forwarder.icnlayer.pit,
                    node.nfn_forwarder.linklayer.faceidtable)
            elif self._optimizer == "EdgeComputingOptimizer":
                node.nfn_forwarder.nfnlayer.optimizer = EdgeComputingOptimizer(
                    node.nfn_forwarder.icnlayer.cs,
                    node.nfn_forwarder.icnlayer.fib,
                    node.nfn_forwarder.icnlayer.pit,
                    node.nfn_forwarder.linklayer.faceidtable)
            # install the mgmt client tool at the node
            node.mgmt_tool = MgmtClient(
                node.nfn_forwarder.mgmt.mgmt_sock.getsockname()[1])
            node.nfn_forwarder.icnlayer.cs.set_cs_timeout(60)

    def _setup_connections_for_stationary_nodes(self):
        """configure the connections """

        loop_variable = 0
        for node in self._stationary_nodes:
            if loop_variable == 0:
                # setup first RSU
                faceid_rsu_1st = node.nfn_forwarder.linklayer.faceidtable.get_or_create_faceid(
                    AddressInfo("rsu" + str(1), 0))
                node.nfn_forwarder.icnlayer.fib.add_fib_entry(
                    Name("/nR"), [faceid_rsu_1st])
            elif loop_variable == (len(self._stationary_nodes) - 1):
                # setup last RSU
                faceid_rsu_last = node.nfn_forwarder.linklayer.faceidtable.get_or_create_faceid(
                    AddressInfo("rsu" + str(loop_variable - 2), 0))
                node.nfn_forwarder.icnlayer.fib.add_fib_entry(
                    Name("/nL"), [faceid_rsu_last])
            else:
                faceid_node_left = node.nfn_forwarder.linklayer.faceidtable.get_or_create_faceid(
                    AddressInfo("rsu" + str(loop_variable - 1), 0))
                faceid_node_right = node.nfn_forwarder.linklayer.faceidtable.get_or_create_faceid(
                    AddressInfo("rsu" + str(loop_variable + 1), 0))

                node.nfn_forwarder.icnlayer.fib.add_fib_entry(
                    Name("/nL"), [faceid_node_left])
                node.nfn_forwarder.icnlayer.fib.add_fib_entry(
                    Name("/nR"), [faceid_node_right])
            loop_variable = +1

    def _assign_named_functions_to_stationary_execution_nodes(self):
        """configure executables to the stationary nodes"""

        for node in self._stationary_nodes:
            for function in zip(self._named_functions.keys(),
                                self._named_functions.values()):
                node.nfn_forwarder.icnlayer.cs.add_content_object(Content(
                    Name(function[0]), function[1]),
                                                                  static=True)

    def _setup_mobile_nodes(self):
        """configure the mobile nodes"""

        for node in self._mobile_nodes:
            node.forwarder = ICNForwarder(
                0,
                encoder=SimpleStringEncoder(),
                routing=True,
                interfaces=[
                    self._simulation_bus.add_interface(f"car{node.node_id}")
                ])
            node.fetch = Fetch(
                f"car{node.node_id}",
                None,
                255,
                SimpleStringEncoder(),
                interfaces=[
                    self._simulation_bus.add_interface(f"ftcar{node.node_id}")
                ])
            node.mgmt_tool = MgmtClient(
                node.forwarder.mgmt.mgmt_sock.getsockname()[1])

            for stationary_node in self._stationary_nodes:
                car_face_id = node.forwarder.linklayer.faceidtable.get_or_create_faceid(
                    AddressInfo(f"rsu{stationary_node.node_id}", 0))
                self.to_rsu_faces[stationary_node.node_id][
                    node.node_id] = car_face_id

                rsu_face_id = node.forwarder.linklayer.faceidtable.get_or_create_faceid(
                    AddressInfo(f"car{stationary_node.node_id}", 0))
                self.to_car_faces[stationary_node.node_id][
                    node.node_id] = rsu_face_id

    def _setup_simulation_network(self):
        """configure a network according to the configuration"""

        self.logger.debug("Setup simulation network ...")
        # setup stationary nodes
        self._setup_stationary_nodes()
        self.logger.debug("\t setup stationary nodes done")

        # setup connections
        self._setup_connections_for_stationary_nodes()
        self.logger.debug("\t setup connections between stationary nodes done")

        # assign functions to stationary nodes
        self._assign_named_functions_to_stationary_execution_nodes()
        self.logger.debug("\t assign named functions to stationary nodes done")

        # setup mobile nodes
        self._setup_mobile_nodes()
        self.logger.debug("\t setup mobile nodes done")

        # start node
        self.start_nodes()
        self.logger.debug("\t setup complete -> start nodes")

    def reconnect_car(self, mobile_node_number, new_rsu_number):

        if len(self._stationary_nodes) <= new_rsu_number or new_rsu_number < 0:
            self.logger.error(
                f"{time.time():.5f} --- Cannot reconnect mobile node with id {mobile_node_number} "
                f"to stationary node with id {new_rsu_number}, not part of this simulation"
            )
            return

        connected_rsu = self.connected_rsu[mobile_node_number]

        self._mobile_nodes[
            mobile_node_number].forwarder.icnlayer.fib.remove_fib_entry(
                self._stationary_node_name_prefix)
        self._mobile_nodes[
            mobile_node_number].forwarder.icnlayer.fib.add_fib_entry(
                self._stationary_node_name_prefix,
                [self.to_rsu_faces[new_rsu_number][mobile_node_number]])
        self._stationary_nodes[
            connected_rsu].nfn_forwarder.icnlayer.fib.remove_fib_entry(
                Name(f"/car/car{mobile_node_number}"))

        self._stationary_nodes[
            connected_rsu].nfn_forwarder.icnlayer.pit.remove_pit_entry_by_fid(
                self.to_car_faces[connected_rsu][mobile_node_number])

        self._stationary_nodes[
            new_rsu_number].nfn_forwarder.icnlayer.fib.add_fib_entry(
                Name(f"/car/car{mobile_node_number}"),
                [self.to_car_faces[new_rsu_number][mobile_node_number]])
        self.connected_rsu[mobile_node_number] = connected_rsu + \
                                                 self._heading_directions[mobile_node_number]

        self._car_send_interest(
            self._mobile_nodes[mobile_node_number], self._function_names[
                self._mobile_node_to_computation[mobile_node_number]])

    def _car_send_interest(self, mobile_node, name):
        try:
            mobile_node.fetch.fetch_data(
                name, timeout=1
            )  # if trouble reduce timeout to 0.1. Parse result from log
        except:
            pass

    def start_nodes(self):
        # Starting nodes
        for stationary_node in self._stationary_nodes:
            stationary_node.nfn_forwarder.start_forwarder()
        for mobile_node in self._mobile_nodes:
            mobile_node.forwarder.start_forwarder()
        self._simulation_bus.start_process()

    def stop_nodes(self):
        # stop nodes
        if not self._is_running:
            for stationary_node in self._stationary_nodes:
                stationary_node.nfn_forwarder.stop_forwarder()
            for mobile_node in self._mobile_nodes:
                mobile_node.forwarder.stop_forwarder()
            self._simulation_bus.stop_process()
        else:
            self.logger.error(
                "Simulation not started yet -- cleaning resources is necessary!"
            )

    def run(self):
        """run the experiment, hand over the cars"""
        self._is_running = True
        self.connected_rsu = []

        self.logger.debug("Start Simulation")

        for i in range(0, len(self._mobile_nodes)):
            self.connected_rsu.append(self._starting_points[i])
            self._mobile_nodes[i].forwarder.icnlayer.fib.add_fib_entry(
                self._stationary_node_name_prefix,
                [self.to_rsu_faces[self.connected_rsu[i]][i]])
            self._stationary_nodes[self.connected_rsu[
                i]].nfn_forwarder.icnlayer.fib.add_fib_entry(
                    Name(f"car{i}"),
                    [self.to_car_faces[self.connected_rsu[i]][i]])
            self._car_send_interest(
                self._mobile_nodes[i],
                self._function_names[self._mobile_node_to_computation[i]])

        self.connection_time = [time.time()] * len(self._mobile_nodes)

        steps = 5 * len(self._mobile_nodes)
        while (self._is_running):
            time_ns = time.time_ns()
            for i in range(0, len(self._mobile_nodes)):

                if time_ns - self.connection_time[i] > self._contact_time[i]:
                    #print(f"{time.time():.5f} -- " + "Car", i, "reconnects from", self.connected_rsu[i], "to", self.connected_rsu[i] + self._heading_directions[i])
                    self.logger.info("Car " + str(i) + " reconnects from " +
                                     str(self.connected_rsu[i]) + " to " +
                                     str(self.connected_rsu[i] +
                                         self._heading_directions[i]))
                    new_rsu_number = self.connected_rsu[
                        i] + self._heading_directions[i]
                    self.reconnect_car(i, new_rsu_number)
                    self.connection_time[i] = time.time_ns()
                steps -= 1

            if steps <= 0:
                self._is_running = False

        self.logger.debug("Simulation Terminated!")
        self.stop_nodes()