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')}"
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()