示例#1
0
    def decide_next_node(self, flow: Flow):
        """
        Check for conflicting events to schedule per-flow decisions from external algorithms
        If conflicting event exists, yield a backoff time before triggering the event
        Return `External` to indicate that a decision from an external algorithm is required for the flow
        """
        if flow.ttl <= 0:
            return None

        # If flow is done processing and at egress, don't request new decision and just let flow depart
        if flow.forward_to_eg and flow.current_node_id == flow.egress_node_id:
            return flow.current_node_id

        events_list = self.env._queue
        events_now = [event for event in events_list if event[0] == self.env.now]
        for event in events_now:
            event_object = event[-1]
            if isinstance(event_object, simpy.events.Event) and event_object.value is not None:
                # There is a scheduling conflict, wait a backoff time (between 0 and 1) and restart
                backoff_time = random.random() / 10
                yield self.env.timeout(backoff_time)
        if not flow.forward_to_eg:
            sf = self.params.sfc_list[flow.sfc][flow.current_position]
        else:
            # a virtual EG sf for flows on their way to egress
            sf = "EG"
        flow.current_sf = sf
        self.params.metrics.add_requesting_flow(flow)
        # Trigger the event to register in Simpy
        self.params.flow_trigger.succeed(value=flow)
        # Reset it immediately
        self.params.flow_trigger = self.env.event()
        # Check flow TTL and drop if zero or less
        return "External"
示例#2
0
    def generate_flow(self, flow_id, node_id) -> Tuple[float, Flow]:
        """ Generate a flow for a given node_id """
        inter_arr_time, flow_dr, flow_size = self.params.get_next_flow_data(
            node_id)

        # Assign a random SFC to the flow
        flow_sfc = np.random.choice(
            [sfc for sfc in self.params.sfc_list.keys()])
        # Get the flow's creation time (current environment time)
        creation_time = self.env.now
        # Set the egress node for the flow if some are specified in the network file
        flow_egress_node = None
        if self.params.eg_nodes:
            flow_egress_node = random.choice(self.params.eg_nodes)
        # Generate flow based on given params
        ttl = random.choice(self.params.ttl_choices)
        # Generate flow based on given params
        flow = Flow(str(flow_id),
                    flow_sfc,
                    flow_dr,
                    flow_size,
                    creation_time,
                    current_node_id=node_id,
                    egress_node_id=flow_egress_node,
                    ttl=ttl)
        # Update metrics for the generated flow
        self.params.metrics.generated_flow(flow, node_id)

        return inter_arr_time, flow
示例#3
0
    def request_resources(self, flow: Flow, node_id: str, sf: str) -> bool:
        """ Request resources from the node
        Returns True if resources were successfully given to the flow
        """
        # Calculate the demanded capacity when the flow is processed at this node
        demanded_total_capacity = self.get_demanded_cap(flow.dr, node_id, sf)
        # Get node capacities
        node_cap = self.params.network.nodes[node_id]["cap"]
        node_remaining_cap = self.params.network.nodes[node_id][
            "remaining_cap"]
        assert node_remaining_cap >= 0, "Remaining node capacity cannot be less than 0 (zero)!"
        if demanded_total_capacity <= node_cap:
            self.params.logger.info(
                "Flow {} started processing at sf {} at node {}. Time: {}".
                format(flow.flow_id, sf, node_id, self.env.now))

            # Update processing level of flow
            flow.processing_index += 1

            # Metrics: Add active flow to the SF once the flow has begun processing.
            self.params.metrics.add_active_flow(flow, node_id, sf)

            # Add load to sf
            self.params.network.nodes[node_id]['available_sf'][sf][
                'load'] += flow.dr
            # Set remaining node capacity
            self.params.network.nodes[node_id][
                'remaining_cap'] = node_cap - demanded_total_capacity
            # Set max node usage
            self.params.metrics.calc_max_node_usage(node_id,
                                                    demanded_total_capacity)
            # Just for the sake of keeping lines small, the node_remaining_cap is updated again.
            node_remaining_cap = self.params.network.nodes[node_id][
                "remaining_cap"]

            # Check if startup is done
            startup_time = self.params.network.nodes[node_id]['available_sf'][
                sf]['startup_time']
            startup_delay = self.params.sf_list[sf]["startup_delay"]
            startup_done = True if (startup_time +
                                    startup_delay) <= self.env.now else False

            if not startup_done:
                # Startup is not done: wait the remaining startup time
                startup_time_remaining = (startup_time +
                                          startup_delay) - self.env.now
                # Check if startup delay will cause flow to be dropped
                if flow.ttl - startup_time_remaining <= 0:
                    flow.ttl = 0
                    return False
                flow.end2end_delay += startup_time_remaining
                flow.ttl -= startup_time_remaining
                yield self.env.timeout(startup_time_remaining)

            return True
        else:
            self.params.logger.info(
                f"Not enough capacity for flow {flow.flow_id} at node {flow.current_node_id}. Dropping flow."
            )
            return False
示例#4
0
    def get_processing_delay(self, flow: Flow, sf: str) -> float:
        """ Generate a random processing delay based on mean and stdev from sf file """
        vnf_delay_mean = self.params.sf_list[sf]["processing_delay_mean"]
        vnf_delay_stdev = self.params.sf_list[sf]["processing_delay_stdev"]
        processing_delay = np.absolute(
            np.random.normal(vnf_delay_mean, vnf_delay_stdev))
        if flow.ttl - processing_delay <= 0:
            flow.ttl = 0
            return False
        self.params.metrics.add_processing_delay(processing_delay)
        flow.end2end_delay += processing_delay
        flow.ttl -= processing_delay

        return processing_delay
示例#5
0
    def generate_flow(self, node_id):
        """
        Generate flows at the ingress nodes.
        """
        while self.params.inter_arr_mean[node_id] is not None:
            self.total_flow_count += 1

            if self.params.flow_list_idx is None:
                # Poisson arrival -> exponential distributed inter-arrival time
                inter_arr_time = random.expovariate(lambd=1.0/self.params.inter_arr_mean[node_id])
                # set normally distributed flow data rate
                flow_dr = np.random.normal(self.params.flow_dr_mean, self.params.flow_dr_stdev)
                if self.params.deterministic_size:
                    flow_size = self.params.flow_size_shape
                else:
                    # heavy-tail flow size
                    flow_size = np.random.pareto(self.params.flow_size_shape) + 1
                # Skip flows with negative flow_dr or flow_size values
                if flow_dr <= 0.00 or flow_size <= 0.00:
                    continue
            # use generated list of flow arrivals
            else:
                inter_arr_time, flow_dr, flow_size = self.params.get_next_flow_data(node_id)

            # Assign a random SFC to the flow
            flow_sfc = np.random.choice([sfc for sfc in self.params.sfc_list.keys()])
            # Get the flow's creation time (current environment time)
            creation_time = self.env.now
            # Set the egress node for the flow if some are specified in the network file
            flow_egress_node = None
            if self.params.eg_nodes:
                flow_egress_node = random.choice(self.params.eg_nodes)
            # Generate flow based on given params
            flow = Flow(str(self.total_flow_count), flow_sfc, flow_dr, flow_size, creation_time,
                        current_node_id=node_id, egress_node_id=flow_egress_node)
            # Update metrics for the generated flow
            self.params.metrics.generated_flow(flow, node_id)
            # Generate flows and schedule them at ingress node
            self.env.process(self.init_flow(flow))
            yield self.env.timeout(inter_arr_time)
    def generate_flow(self, node_id):
        """
        Generate flows at the ingress nodes.
        """
        while True:
            self.total_flow_count += 1

            # set normally distributed flow data rate
            flow_dr = np.random.normal(self.params.flow_dr_mean, self.params.flow_dr_stdev)

            # set deterministic or random flow arrival times and flow sizes according to config
            if self.params.deterministic_arrival:
                inter_arr_time = self.params.inter_arr_mean
            else:
                # Poisson arrival -> exponential distributed inter-arrival time
                inter_arr_time = random.expovariate(lambd=1.0/self.params.inter_arr_mean)

            if self.params.deterministic_size:
                flow_size = self.params.flow_size_shape
            else:
                # heavy-tail flow size
                flow_size = np.random.pareto(self.params.flow_size_shape) + 1

            # Skip flows with negative flow_dr or flow_size values
            if flow_dr <= 0.00 or flow_size <= 0.00:
                continue

            # Assign a random SFC to the flow
            flow_sfc = np.random.choice([sfc for sfc in self.params.sfc_list.keys()])
            # Get the flow's creation time (current environment time)
            creation_time = self.env.now
            # Generate flow based on given params
            flow = Flow(str(self.total_flow_count), flow_sfc, flow_dr, flow_size, creation_time,
                        current_node_id=node_id)
            # Update metrics for the generated flow
            metrics.generated_flow(flow, node_id)
            # Generate flows and schedule them at ingress node
            self.env.process(self.init_flow(flow))
            yield self.env.timeout(inter_arr_time)
示例#7
0
    def finish_processing(self, flow: Flow, node_id: str, sf: str) -> bool:
        """ Simpy process to cleanup used resources after a flow has finished processing """
        flow.current_position += 1
        if flow.current_position == len(self.params.sfc_list[flow.sfc]):
            flow.forward_to_eg = True
        # Wait flow duration for flow to fully process
        yield self.env.timeout(flow.duration)
        # Remove the active flow from the node
        self.params.metrics.remove_active_flow(flow, node_id, sf)
        # Remove flow's load from sf
        self.params.network.nodes[node_id]['available_sf'][sf][
            'load'] -= flow.dr
        assert self.params.network.nodes[node_id]['available_sf'][sf]['load'] >= 0, \
            'SF load cannot be less than 0!'

        # Remove SF gracefully from node if no load exists and SF removed from placement
        if (self.params.network.nodes[node_id]['available_sf'][sf]['load']
                == 0) and (sf not in self.params.sf_placement[node_id]):
            del self.params.network.nodes[node_id]['available_sf'][sf]

        # Recalculate used node cap before updating node rem. cap because of how simpy schedules processes
        node_cap = self.params.network.nodes[node_id]["cap"]
        used_total_capacity = 0.0
        for sf_i, sf_data in self.params.network.nodes[node_id][
                'available_sf'].items():
            if not sf_i == "EG":
                used_total_capacity += self.params.sf_list[sf_i][
                    'resource_function'](sf_data['load'])
        # Set remaining node capacity
        self.params.network.nodes[node_id][
            'remaining_cap'] = node_cap - used_total_capacity

        node_remaining_cap = self.params.network.nodes[node_id][
            "remaining_cap"]

        # We assert that remaining capacity must at all times be less than the node capacity so that
        # nodes dont put back more capacity than the node's capacity.
        assert node_remaining_cap <= node_cap, "Node remaining capacity cannot be more than node capacity!"
    def generate_flow(self, flow_id, node_id) -> Tuple[float, Flow]:
        """ Generate a flow for a given node_id """
        if self.params.deterministic_arrival:
            inter_arr_time = self.params.inter_arr_mean[node_id]
        else:
            # Poisson arrival -> exponential distributed inter-arrival time
            inter_arr_time = random.expovariate(
                lambd=1.0 / self.params.inter_arr_mean[node_id])
        flow_dr, flow_size = self.get_flow_dr_size()
        # if flow_dr <= 0.00 or flow_size <= 0.00:
        #     continue

        # Assign a random SFC to the flow
        flow_sfc = np.random.choice(
            [sfc for sfc in self.params.sfc_list.keys()])
        # Get the flow's creation time (current environment time)
        creation_time = self.env.now
        # Set the egress node for the flow if some are specified in the network file
        flow_egress_node = None
        if self.params.eg_nodes:
            flow_egress_node = random.choice(self.params.eg_nodes)
        # Generate flow based on given params
        ttl = random.choice(self.params.ttl_choices)
        # Generate flow based on given params
        flow = Flow(str(flow_id),
                    flow_sfc,
                    flow_dr,
                    flow_size,
                    creation_time,
                    current_node_id=node_id,
                    egress_node_id=flow_egress_node,
                    ttl=ttl)
        # Update metrics for the generated flow
        self.params.metrics.generated_flow(flow, node_id)

        return inter_arr_time, flow
示例#9
0
    def decide_next_node(self, flow: Flow):
        """ Load balance the flows according to the scheduling tables """
        # Blank timeout to convert it to a simpy process
        yield self.env.timeout(0)
        # Check flow TTL and drop if zero or less
        if flow.ttl <= 0:
            return None
        # If flow is to be forwarded to egress, always return egress node as "next node"
        if flow.forward_to_eg:
            if flow.egress_node_id is None:
                # If flow has no egress node: set current node_id to egress node_id
                flow.egress_node_id = flow.current_node_id
            return flow.egress_node_id

        sf = self.params.sfc_list[flow.sfc][flow.current_position]
        flow.current_sf = sf
        self.params.metrics.add_requesting_flow(flow)
        schedule = self.params.schedule
        # Check if scheduling rule exists
        if (flow.current_node_id
                in schedule) and flow.sfc in schedule[flow.current_node_id]:
            local_schedule = schedule[flow.current_node_id][flow.sfc][sf]
            dest_nodes = [sch_sf for sch_sf in local_schedule.keys()]
            dest_prob = [prob for name, prob in local_schedule.items()]
            try:
                # select next node based on weighted RR according to the scheduling weights/probabilities
                # get current flow counts per possible destination node
                flow_counts = self.params.metrics.metrics['run_flow_counts'][
                    flow.current_node_id][flow.sfc][sf]
                flow_sum = sum(flow_counts.values())
                # calculate the current ratios of flows sent to the different destination nodes
                if flow_sum > 0:
                    dest_ratios = [
                        flow_counts[v] / flow_sum for v in dest_nodes
                    ]
                else:
                    dest_ratios = [0 for v in dest_nodes]

                # calculate the difference from the scheduling weight
                # for nodes with 0 probability/weight, set the diff to be negative so they are not selected
                # otherwise all diffs may be 0 if ratio = probability and a node with probability 0 could be selected
                assert len(dest_nodes) == len(dest_prob) == len(dest_ratios)
                ratio_diffs = [
                    dest_prob[i] - dest_ratios[i] if dest_prob[i] > 0 else -1
                    for i in range(len(dest_nodes))
                ]

                # select the node that farthest away from its weight, ie, has the highest diff
                max_idx = np.argmax(ratio_diffs)
                next_node = dest_nodes[max_idx]

                # increase counter for selected node
                self.params.metrics.metrics['run_flow_counts'][
                    flow.current_node_id][flow.sfc][sf][next_node] += 1

                return next_node

            except Exception as ex:

                # Scheduling rule does not exist: drop flow
                self.params.loogger.warning(
                    f'Flow {flow.flow_id}: Scheduling rule at node {flow.current_node_id} not correct'
                    f'Dropping flow!')
                self.params.logger.warning(ex)
                return None
        else:
            # Scheduling rule does not exist: drop flow
            self.params.logger.warning(
                f'Flow {flow.flow_id}: Scheduling rule not found at {flow.current_node_id}. Dropping flow!'
            )
            return None