Exemplo n.º 1
0
class ProcessingUnit(FiniteStateMachine):
    """
    Processing unit model for xDEVS
    :param str name: name of the XDEVS module
    :param ProcessingUnitConfiguration pu_config: Processing Unit Configuration
    :param str rack_id: ID of the rack that contains the processing unit
    :param int pu_index: processing unit corresponding index within the EDC
    :param dict services_config: dictionary with the configuration of every service in the scenario
    :param float env_temp: Processing unit base temperature (in Kelvin)
    """
    pu_power_factory = ProcessingUnitPowerModelFactory()
    pu_temp_factory = ProcessingUnitTemperatureModelFactory()

    def __init__(self, name, pu_config, rack_id, pu_index, services_config, env_temp=298):
        # Unwrap configuration parameters
        self.pu_id = pu_config.p_unit_id
        self.dvfs_table = pu_config.dvfs_table
        self.max_u = pu_config.max_u
        self.max_start_stop = pu_config.max_start_stop
        self.t_on = pu_config.t_on
        self.t_off = pu_config.t_off
        self.t_start = pu_config.t_start
        self.t_stop = pu_config.t_stop
        self.t_operation = pu_config.t_operation

        power_model_name = pu_config.power_model_name
        power_model_config = pu_config.power_model_config
        self.power_model = self.pu_power_factory.create_model(power_model_name, **power_model_config)

        temp_model_name = pu_config.temp_model_name
        temp_model_config = pu_config.temp_model_config
        self.temp_model = self.pu_temp_factory.create_model(temp_model_name, **temp_model_config)
        # dictionary that contains how much computing resources require a single session of a given service
        self.services_u = {service_id: service_conf.service_u for service_id, service_conf in services_config.items()}

        # Set status attributes
        self.rack_id = rack_id          # ID of the rack that contains the processing unit
        self.pu_index = pu_index        # corresponding index of the PU within the rack of the EDC
        self.status = False             # PU status. By default, all the PUs start powered off
        self.dvfs_mode = False          # DVFS mode. By default, it is not activated
        self.dvfs_index = 0             # index of current DVFS configuration
        self.utilization = 0.0          # utilization factor
        self.u_per_service = dict()     # utilization factor splitted by service {service_id: utilization}
        self.ongoing_sessions = dict()  # ongoing sessions {service_id: [session_id]}
        self.power = 0                  # PU required power consumption
        self.env_temp = env_temp        # Environment temperature of the processing unit
        self.temperature = env_temp     # PU temperature of PU. Initially, it is set to env_temp

        self.start_buffer = deque()     # Buffer of sessions that require to be started
        self.stop_buffer = deque()      # Buffer of sessions that require to be stopped

        self.starting = list()          # Sessions that are being started
        self.acking = list()            # Requests of ongoing sessions that are being processed
        self.removing = list()          # Sessions that are being stopped

        self.open_tasks = dict()        # Event schedule for services being started
        self.ongoing_tasks = dict()     # Event schedule for ongoing services that are processing requests
        self.close_tasks = dict()       # Event schedule for services being stopped

        # FSM stuff
        int_table = {
            PHASE_START: self.internal_phase_off,
            PHASE_OFF: self.internal_phase_off,
            PHASE_TO_OFF: self.internal_phase_off,
            PHASE_ON: self.internal_phase_on,
            PHASE_BUSY: self.internal_phase_on,
            PHASE_TO_ON: self.internal_phase_on,
        }
        ext_table = {
            PHASE_START: None,
            PHASE_OFF: self.external_phase_off,
            PHASE_ON: self.external_phase_on,
            PHASE_BUSY: self.external_phase_on,
            PHASE_TO_OFF: None,
            PHASE_TO_ON: None,
        }
        lambda_table = {phase: None for phase in P_UNIT_PHASES}
        lambda_table[PHASE_START] = self.lambda_send_pu_report
        lambda_table[PHASE_BUSY] = self.lambda_send_tasks
        initial_state = PHASE_START
        initial_timeout = 0
        super().__init__(P_UNIT_PHASES, int_table, ext_table, lambda_table, initial_state, initial_timeout, name)

        # I/O ports
        self.input_change_status = Port(ChangeStatus, name + '_input_change_status')
        self.input_set_dvfs_mode = Port(SetDVFSMode, name + '_input_set_dvfs_mode')
        self.input_open_session = Port(OpenSessionRequest, name + '_input_open_session')
        self.input_ongoing_session = Port(OngoingSessionRequest, name + '_input_ongoing_session')
        self.input_close_session = Port(CloseSessionRequest, name + '_input_close_session')
        self.output_p_unit_report = Port(ProcessingUnitReport, name + '_output_p_unit_report')
        self.output_change_status_response = Port(ChangeStatusResponse, name + '_output_change_status_response')
        self.output_set_dvfs_mode_response = Port(SetDVFSModeResponse, name + '_output_set_dvfs_mode_response')
        self.output_open_session_response = Port(OpenSessionResponse, name + '_output_create_session_response')
        self.output_ongoing_session_response = Port(OngoingSessionResponse, name + '_output_ongoing_session_response')
        self.output_close_session_response = Port(CloseSessionResponse, name + '_output_remove_session_response')

        self.add_in_port(self.input_change_status)                  # port for incoming change status messages
        self.add_in_port(self.input_set_dvfs_mode)                  # port for incoming new DVFS mode messages
        self.add_in_port(self.input_open_session)                   # port for incoming create session messages
        self.add_in_port(self.input_ongoing_session)                # port for incoming ongoing session messages
        self.add_in_port(self.input_close_session)                  # port for incoming remove session messages
        self.add_out_port(self.output_p_unit_report)                # port for leaving processing unit report messages
        self.add_out_port(self.output_change_status_response)       # port for leaving change status response messages
        self.add_out_port(self.output_set_dvfs_mode_response)       # port for leaving new DVFS mode response messages
        self.add_out_port(self.output_open_session_response)        # port for leaving open session response messages
        self.add_out_port(self.output_ongoing_session_response)     # port for leaving ongoing session response messages
        self.add_out_port(self.output_close_session_response)       # port for leaving close session response messages

    @staticmethod
    def add_response_to_queue(queue, t, msg):
        if t not in queue:
            queue[t] = list()
        queue[t].append(msg)

    def internal_phase_on(self):
        # Trigger pending close service processes
        while self.stop_buffer and self.enough_size():
            service_id, session_id = self.stop_buffer.popleft()
            self.removing.append((service_id, session_id))
            t = self._clock + self.t_stop
            msg = CloseSessionResponse(self.rack_id, self.pu_index, service_id, session_id, True)
            self.add_response_to_queue(self.close_tasks, t, msg)
        # Trigger pending open service processes
        delta = sum([self.services_u[service_id] for service_id, session_id in self.starting])
        while self.start_buffer and self.enough_size():
            service_id, session_id = self.start_buffer[0]
            utilization = self.services_u[service_id]
            # Check that there are enough available resources before triggering new services
            if self.utilization + delta + utilization <= self.max_u:
                delta += utilization
                service_id, session_id = self.start_buffer.popleft()
                self.starting.append((service_id, session_id))
                t = self._clock + self.t_start
                msg = OpenSessionResponse(self.rack_id, self.pu_index, service_id, session_id, True)
                self.add_response_to_queue(self.open_tasks, t, msg)
            # Otherwise, it is required to wait until other services are removed before adding new service to the queue
            else:
                break
        # Compute next event
        min_ongoing = min(self.ongoing_tasks) - self._clock if self.ongoing_tasks else INFINITY
        min_open = min(self.open_tasks) - self._clock if self.open_tasks else INFINITY
        min_close = min(self.close_tasks) - self._clock if self.close_tasks else INFINITY
        next_event = min(min_ongoing, min_open, min_close)
        next_phase = PHASE_BUSY if next_event < INFINITY else PHASE_ON
        return next_phase, next_event

    @staticmethod
    def internal_phase_off():
        return PHASE_OFF, INFINITY

    def external_phase_off(self):
        """Operations to perform in phase OFF"""
        overhead = logging_overhead(self._clock, LOGGING_OVERHEAD)
        next_phase = PHASE_OFF
        next_timeout = 0
        report = False
        # CASE 1: Processing unit received set DVFS mode message
        if self.input_set_dvfs_mode:
            dvfs = self._check_set_dvfs_mode_port(overhead)
            if dvfs != self.dvfs_mode:
                self.dvfs_mode = dvfs
                report = True
            msg = SetDVFSModeResponse(self.rack_id, self.pu_index, dvfs, True)
            self.add_msg_to_queue(self.output_set_dvfs_mode_response, msg)
        # CASE 2: Processing Unit received change status (switch on) message
        elif self.input_change_status:
            status = self._check_change_status_port(overhead)
            if status:
                self.status = status
                report = True
                next_phase = PHASE_TO_ON
                next_timeout = self.t_on
            msg = ChangeStatusResponse(self.rack_id, self.pu_index, status, True)
            self.add_msg_to_queue(self.output_change_status_response, msg)
        else:
            self._off_affirmative_stops(overhead)
            self._off_negative_ongoings(overhead)
            self._off_negative_starts(overhead)
        # Send report if required
        if report:
            self._refresh_properties()
            self.send_pu_report()
        return next_phase, next_timeout

    def external_phase_on(self):
        """Operations to perform in phase ON"""
        overhead = logging_overhead(self._clock, LOGGING_OVERHEAD)
        report = False
        # CASE 1: Any service-related request
        self._check_ongoing_session_requests(overhead)
        self._check_close_session_requests(overhead)
        self._check_open_session_requests(overhead)
        next_phase, next_timeout = self.internal_phase_on()
        # If no events are scheduled, then we can change the DVFS mode or the status
        if next_phase == PHASE_ON:
            # CASE 2: Processing Unit received change status message
            if self.input_change_status:
                next_timeout = 0
                status = self._check_change_status_port(overhead)
                response = status
                if not status and not self.ongoing_sessions and not self.starting:
                    self.status = status
                    response = True
                    report = True
                    next_phase = PHASE_TO_OFF
                    next_timeout = self.t_off
                # Add response to queue
                msg = ChangeStatusResponse(self.rack_id, self.pu_index, status, response)
                self.add_msg_to_queue(self.output_change_status_response, msg)
            # CASE 3: Processing unit received set DVFS mode message
            elif self.input_set_dvfs_mode:
                next_timeout = 0
                dvfs = self._check_set_dvfs_mode_port(overhead)
                if dvfs != self.dvfs_mode:
                    self.dvfs_mode = dvfs
                    report = True
                msg = SetDVFSModeResponse(self.rack_id, self.pu_index, dvfs, True)
                self.add_msg_to_queue(self.output_set_dvfs_mode_response, msg)
        if report:
            self._refresh_properties()
            self.send_pu_report()
        return next_phase, next_timeout

    def lambda_send_pu_report(self):
        self.send_pu_report()

    def lambda_send_tasks(self):
        ongoing_due = self.ongoing_tasks.pop(self._clock, list())
        for msg in ongoing_due:
            self.add_msg_to_queue(self.output_ongoing_session_response, msg)
            if msg.response:
                self.acking.remove((msg.service_id, msg.session_id))
        close_due = self.close_tasks.pop(self._clock, list())
        needs_report = False
        for msg in close_due:
            if msg.response:
                needs_report |= self.remove_session(msg.service_id, msg.session_id)
            self.add_msg_to_queue(self.output_close_session_response, msg)
        open_due = self.open_tasks.pop(self._clock, list())
        for msg in open_due:
            if msg.response:
                needs_report |= self.create_session(msg.service_id, msg.session_id)
            self.add_msg_to_queue(self.output_open_session_response, msg)
        if needs_report:
            self._refresh_properties()
            self.send_pu_report()

    def _check_change_status_port(self, overhead):
        """
        Checks the change status port
        :param str overhead: logging overhead
        :return bool: new requested status
        """
        if self.input_change_status:
            status = self.input_change_status.get().status
            logging.info(overhead + "%s received change status->%s message" % (self.name, status))
            if not status and (self.ongoing_sessions or self.open_tasks):
                logging.warning(overhead + "    Impossible to switch off. Sending negative response.")
            return status

    def _check_set_dvfs_mode_port(self, overhead):
        """
        Checks the DVFS mode port
        :param str overhead: logging overhead
        :return bool: result of the operation
        """
        if self.input_set_dvfs_mode:
            dvfs_mode = self.input_set_dvfs_mode.get().dvfs_mode
            logging.info(overhead + "%s received set DVFS mode->%r message" % (self.name, dvfs_mode))
            return dvfs_mode

    def _check_ongoing_session_requests(self, overhead):
        """
        Checks the close session port
        :param str overhead: logging overhead
        :return str, str: pxsch ID and session ID
        """
        for msg in self.input_ongoing_session.values:
            service_id = msg.service_id
            session_id = msg.session_id
            packet_id = msg.packet_id
            logging.info(overhead + "%s received ongoing session->(%s,%s,%s) request" % (self.name, service_id,
                                                                                         session_id, str(packet_id)))
            res = service_id in self.ongoing_sessions and session_id in self.ongoing_sessions[service_id]
            t = self._clock
            if res:
                if (service_id, session_id) in self.removing or (service_id, session_id) in self.stop_buffer:
                    res = False
                    logging.warning(overhead + "    session is being removed. Sending negative response.")
                else:
                    self.acking.append((service_id, session_id))
                    t += self.t_operation
            else:
                logging.warning(overhead + "    session not found. Sending negative response.")
            msg = OngoingSessionResponse(self.rack_id, self.pu_index, service_id, session_id, packet_id, res)
            self.add_response_to_queue(self.ongoing_tasks, t, msg)

    def _check_close_session_requests(self, overhead):
        """
        Checks the close session port
        :param str overhead: logging overhead
        :return str, str: service ID and session ID
        """
        for msg in self.input_close_session.values:
            service_id = msg.service_id
            session_id = msg.session_id
            logging.info(overhead + "%s received close session->(%s,%s) request" % (self.name, service_id, session_id))
            res = service_id in self.ongoing_sessions and session_id in self.ongoing_sessions[service_id]
            if res:
                if (service_id, session_id) in self.removing or (service_id, session_id) in self.stop_buffer:
                    logging.warning(overhead + "    session is already being removed. Ignoring request.")
                elif (service_id, session_id) in self.acking:
                    logging.warning(overhead + "    session is busy. Sending negative response.")
                    msg = CloseSessionResponse(self.rack_id, self.pu_index, service_id, session_id, False)
                    self.add_response_to_queue(self.close_tasks, self._clock, msg)
                else:
                    self.stop_buffer.append((service_id, session_id))
            else:
                logging.warning(overhead + "    session not found. Sending affirmative response.")
                msg = CloseSessionResponse(self.rack_id, self.pu_index, service_id, session_id, True)
                self.add_response_to_queue(self.close_tasks, self._clock, msg)

    def _check_open_session_requests(self, overhead):
        """
        Checks the open session port
        :param str overhead: logging overhead
        :return str, str, float: pxsch ID, session ID and std_u factor to be used
        """
        delta = self._initial_delta()
        for msg in self.input_open_session.values:
            service_id = msg.service_id
            session_id = msg.session_id
            logging.info(overhead + "%s received open session->(%s,%s) request" % (self.name, service_id, session_id))
            service_u = self.services_u[service_id]
            if (service_id, session_id) in self.starting:
                logging.warning(overhead + "    Session is already being created. Ignoring request.")
            elif service_id in self.ongoing_sessions and session_id in self.ongoing_sessions[service_id]:
                logging.warning(overhead + "    Session already exists. Sending affirmative response.")
                msg = OpenSessionResponse(self.rack_id, self.pu_index, service_id, session_id, True)
                self.add_response_to_queue(self.open_tasks, self._clock, msg)
            elif 0 <= service_u <= self.max_u - self.utilization - delta:
                delta += service_u
                self.start_buffer.append((service_id, session_id))
            else:
                logging.warning(overhead + "    Unable to create session. Sending negative response.")
                msg = OpenSessionResponse(self.rack_id, self.pu_index, service_id, session_id, False)
                self.add_response_to_queue(self.open_tasks, self._clock, msg)

    def _initial_delta(self):
        delta = 0
        for service_id, session_id in self.starting:
            delta += self.services_u[service_id]
        for service_id, session_id in self.start_buffer:
            delta += self.services_u[service_id]
        for service_id, session_id in self.removing:
            delta -= self.services_u[service_id]
        for service_id, session_id in self.stop_buffer:
            delta -= self.services_u[service_id]
        return delta

    def enough_size(self):
        return self.max_start_stop <= 0 or self.max_start_stop > len(self.starting) + len(self.removing)

    def _off_negative_ongoings(self, overhead):
        for msg in self.input_ongoing_session.values:
            service_id = msg.service_id
            session_id = msg.session_id
            packet_id = msg.packet_id
            logging.info(overhead + "%s received ongoing session->(%s,%s,%s) request" % (self.name, service_id,
                                                                                         session_id, str(packet_id)))
            logging.warning(overhead + "    PU is switched off. Sending Negative response.")
            msg = OngoingSessionResponse(self.rack_id, self.pu_index, service_id, session_id, packet_id, False)
            self.add_msg_to_queue(self.output_ongoing_session_response, msg)

    def _off_affirmative_stops(self, overhead):
        for msg in self.input_close_session.values:
            service_id = msg.service_id
            session_id = msg.session_id
            logging.info(overhead + "%s received close session->(%s,%s) request" % (self.name, service_id, session_id))
            logging.warning(overhead + "    PU is switched off. Sending affirmative response.")
            msg = CloseSessionResponse(self.rack_id, self.pu_index, service_id, session_id, True)
            self.add_msg_to_queue(self.output_close_session_response, msg)

    def _off_negative_starts(self, overhead):
        for msg in self.input_open_session.values:
            service_id = msg.service_id
            session_id = msg.session_id
            logging.info(overhead + "%s received create session->(%s,%s) request" % (self.name, service_id, session_id))
            logging.warning(overhead + "    PU is switched off. Sending Negative response.")
            msg = OpenSessionResponse(self.rack_id, self.pu_index, service_id, session_id, False)
            self.add_msg_to_queue(self.output_open_session_response, msg)

    def remove_session(self, service_id, session_id):
        """
        Removes an ongoing task and frees resources
        :param str service_id: ID of the pxsch to be removed
        :param str session_id: ID of the session to be removed
        """
        res = False
        if service_id in self.ongoing_sessions:
            if session_id in self.ongoing_sessions[service_id]:
                res = True
                self.removing.remove((service_id, session_id))
                self.ongoing_sessions[service_id].remove(session_id)
            if not self.ongoing_sessions[service_id]:
                self.ongoing_sessions.pop(service_id)
        return res

    def ack_session(self, service_id, session_id):
        self.acking.remove((service_id, session_id))

    def create_session(self, service_id, session_id):
        """
        Earmarks resources for new incoming task
        :param str service_id: ID of the task to be processed
        :param str session_id: ID of the task to be processed
        """
        res = False
        if service_id not in self.ongoing_sessions:
            self.ongoing_sessions[service_id] = list()
        if session_id not in self.ongoing_sessions[service_id]:
            res = True
            self.starting.remove((service_id, session_id))
            self.ongoing_sessions[service_id].append(session_id)
        return res

    def _refresh_properties(self):
        """Refreshes the values of the processing unit"""
        self.__compute_utilization()
        self.__compute_dvfs_index()
        self.__compute_power()
        self.__compute_temperature()

    def __compute_utilization(self):
        """Computes std_u factor of the processing unit"""
        utilization = 0
        u_per_service = dict()
        for service_id, sessions in self.ongoing_sessions.items():
            u = self.services_u[service_id] * len(sessions)
            u_per_service[service_id] = u
            utilization += u
        self.utilization = utilization
        self.u_per_service = u_per_service

    def __compute_dvfs_index(self):
        """Computes the DVFS table index that fits the best"""
        # CASE 1: processing unit is switched off -> DVFS index set to 0
        if not self.status:
            self.dvfs_index = 0
        # CASE 2: DVFS mode is set to false -> DVFS index set to 100
        elif not self.dvfs_mode:
            self.dvfs_index = 100
        # CASE 3: DVFS mode is set to true -> find lowest DVFS index that fulfills computing requirements
        else:
            relative_u = self.utilization / self.max_u * 100
            self.dvfs_index = min([i for i in self.dvfs_table if i >= relative_u])

    def __compute_power(self):
        """Computes the processing unit power consumption"""
        try:
            self.power = self.power_model.compute_power(self.status, self.utilization, self.max_u, self.dvfs_index,
                                                        self.dvfs_table)
        except AttributeError:
            self.power = 0

    def __compute_temperature(self):
        """Computes the processing unit temperature"""
        try:
            self.temperature = self.temp_model.compute_temperature(self.status, self.utilization, self.max_u,
                                                                   self.dvfs_index, self.dvfs_table)
        except AttributeError:
            self.temperature = self.env_temp

    def send_pu_report(self):
        ongoing_sessions = {service_id: [session_id for session_id in sessions]
                            for service_id, sessions in self.ongoing_sessions.items()}
        u_per_service = {service_id: u for service_id, u in self.u_per_service.items()}
        msg = ProcessingUnitReport(self.rack_id, self.pu_index, self.pu_id, self.max_u, self.max_start_stop,
                                   self.status, self.dvfs_mode, self.dvfs_index, self.utilization, self.power,
                                   self.temperature, ongoing_sessions, u_per_service)
        self.add_msg_to_queue(self.output_p_unit_report, msg)
Exemplo n.º 2
0
class Transport(Stateless):
    def __init__(self, name, ap_id):
        super().__init__(name=name)
        self.ap_id = ap_id
        self.service_routing = dict()
        self.ue_connected = list()

        self.input_new_sdn_path = Port(NewSDNPath,
                                       name + '_input_new_sdn_path')
        self.input_connected_ue_list = Port(EnableChannels,
                                            name + '_input_connected_ue_list')
        self.input_service_routing_request = Port(
            GetDataCenterRequest, name + '_input_service_routing_request')
        self.input_radio = Port(NetworkPacket, name + '_input_radio')
        self.input_crosshaul = Port(NetworkPacket, name + '_input_crosshaul')
        self.output_service_routing_response = Port(
            GetDataCenterResponse, name + '_output_service_routing_response')
        self.output_crosshaul = Port(NetworkPacket, name + '_output_crosshaul')
        self.output_radio = Port(NetworkPacket, name + '_output_radio')
        self.add_in_port(self.input_new_sdn_path)
        self.add_in_port(self.input_connected_ue_list)
        self.add_in_port(self.input_service_routing_request)
        self.add_in_port(self.input_radio)
        self.add_in_port(self.input_crosshaul)
        self.add_out_port(self.output_service_routing_response)
        self.add_out_port(self.output_crosshaul)
        self.add_out_port(self.output_radio)

    def check_in_ports(self):
        self._process_new_connected_ue_list()
        self._process_new_sdn_path()
        self._process_service_routing()
        self._process_radio_messages()
        self._process_crosshaul_messages()

    def process_internal_messages(self):
        pass

    def _process_new_connected_ue_list(self):
        if self.input_connected_ue_list:
            ue_list = self.input_connected_ue_list.get().nodes_to
            self.ue_connected = [ue for ue in ue_list]

    def _process_new_sdn_path(self):
        if self.input_new_sdn_path:
            sdn_path = self.input_new_sdn_path.get()
            overhead = logging_overhead(self._clock, LOGGING_OVERHEAD)
            logging.info(overhead +
                         '%s<---SDN Controller: new SDN path' % self.ap_id)
            self.service_routing = {
                service_id: dc
                for service_id, dc in sdn_path.service_route.items()
            }

    def _process_service_routing(self):
        overhead = logging_overhead(self._clock, "")
        for msg in self.input_service_routing_request.values:
            ue_id = msg.ue_id
            service_id = msg.service_id
            logging.info(overhead + '%s-->%s: service %s routing request' %
                         (ue_id, self.ap_id, service_id))
            if ue_id not in self.ue_connected:
                logging.warning(
                    overhead +
                    '    message from disconnected UE %s. Ignoring request' %
                    ue_id)
                continue
            dc_id = self.service_routing.get(service_id, None)
            if dc_id is None:
                logging.warning(
                    overhead +
                    '    no service routing for %s. Ignoring request' %
                    service_id)
            else:
                app_msg = GetDataCenterResponse(ue_id, service_id, dc_id,
                                                msg.header)
                self.add_msg_to_queue(self.output_service_routing_response,
                                      app_msg)

    def _process_radio_messages(self):
        overhead = logging_overhead(self._clock, "")
        for msg in self.input_radio.values:
            ue_id = msg.node_from
            service_id = msg.data.service_id
            logging.info(overhead + '%s--->%s: %s-related message' %
                         (ue_id, self.ap_id, service_id))
            if ue_id not in self.ue_connected:
                logging.warning(
                    overhead +
                    '    message from disconnected UE %s. Ignoring request' %
                    ue_id)
                continue
            self.add_msg_to_queue(self.output_crosshaul, msg)

    def _process_crosshaul_messages(self):
        overhead = logging_overhead(self._clock, LOGGING_OVERHEAD)
        for msg in self.input_crosshaul.values:
            ue_id = msg.node_to
            dc_id = msg.node_from
            service_id = msg.data.service_id
            logging.info(overhead + '%s<---%s: %s-related message' %
                         (self.ap_id, dc_id, service_id))
            if ue_id not in self.ue_connected:
                logging.warning(
                    overhead +
                    '    message to disconnected UE %s. Ignoring request' %
                    ue_id)
                continue
            self.add_msg_to_queue(self.output_radio, msg)