def process_action(self, action):
        assert len(
            action
        ) == self.num_nodes * self.num_sfcs * self.num_sfs * self.num_nodes

        # iterate through action array, select slice with probabilities belonging to one SF
        # processes probabilities (round low probs to 0, normalize), append and move on
        processed_action = []
        start_idx = 0
        for _ in range(self.num_nodes * self.num_sfcs * self.num_sfs):
            end_idx = start_idx + self.num_nodes
            probs = action[start_idx:end_idx]
            rounded_probs = [
                p if p >= self.schedule_threshold else 0 for p in probs
            ]
            normalized_probs = normalize_scheduling_probabilities(
                rounded_probs)
            # check that normalized probabilities sum up to 1 (accurate to specified float accuracy)
            assert (1 - sum(normalized_probs)) < np.sqrt(
                np.finfo(np.float64).eps)
            processed_action.extend(normalized_probs)
            start_idx += self.num_nodes

        assert len(processed_action) == len(action)
        return np.array(processed_action)
Esempio n. 2
0
def get_schedule(nodes_list, sf_list, sfc_list):
    """  return a dict of schedule for each node of the network
    for each node in the network, we generate floating point random numbers in the range 0 to 1
        '''
        Schedule is of the following form:
            schedule : dict
                {
                    'node id' : dict
                    {
                        'SFC id' : dict
                        {
                            'SF id' : dict
                            {
                                'node id' : float (Inclusive of zero values)
                            }
                        }
                    }
                }
        '''
    Parameters:
        nodes_list
        sf_list
        sfc_list

    Returns:
         schedule of the form shown above
    """
    schedule = defaultdict(
        lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(float))))
    for outer_node in nodes_list:
        for sfc in sfc_list:
            for sf in sf_list:
                # this list may not sum to 1
                random_prob_list = [
                    uniform(0, 1) for _ in range(len(nodes_list))
                ]
                # Because of floating point precision (.59 + .33 + .08) can be equal to .99999999
                # So we correct the sum only if the absolute diff. is more than a tolerance(0.000000014901161193847656)
                random_prob_list = normalize_scheduling_probabilities(
                    random_prob_list)
                for inner_node in nodes_list:
                    if len(random_prob_list) != 0:
                        schedule[outer_node][sfc][sf][
                            inner_node] = random_prob_list.pop()
                    else:
                        schedule[outer_node][sfc][sf][inner_node] = 0
    return schedule
def get_schedule(nodes_list, nodes_with_cap, sf_list, sfc_list):
    """  return a dict of schedule for each node of the network
       '''
        Schedule is of the following form:
            schedule : dict
                {
                    'node id' : dict
                    {
                        'SFC id' : dict
                        {
                            'SF id' : dict
                            {
                                'node id' : float (Inclusive of zero values)
                            }
                        }
                    }
                }
        '''
    Parameters:
        nodes_list
        sf_list
        sfc_list

    Returns:
         schedule of the form shown above
    """
    schedule = defaultdict(
        lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(float))))
    for outer_node in nodes_list:
        for sfc in sfc_list:
            for sf in sf_list:
                # all 0's list
                uniform_prob_list = [0 for _ in range(len(nodes_with_cap))]
                # Uniformly distributing the schedules between all nodes
                uniform_prob_list = normalize_scheduling_probabilities(
                    uniform_prob_list)
                for inner_node in nodes_list:
                    if inner_node in nodes_with_cap:
                        schedule[outer_node][sfc][sf][
                            inner_node] = uniform_prob_list.pop()
                    else:
                        schedule[outer_node][sfc][sf][inner_node] = 0
    return schedule
def get_placement_schedule(network, nodes_list, sf_list, sfc_list,
                           ingress_nodes, nodes_cap):
    """
        '''
        Schedule is of the following form:
            schedule : dict
                {
                    'node id' : dict
                    {
                        'SFC id' : dict
                        {
                            'SF id' : dict
                            {
                                'node id' : float (Inclusive of zero values)
                            }
                        }
                    }
                }
        '''

    Parameters:
        network: A NetworkX object
        nodes_list: all the nodes in the network
        sf_list: all the sf's in the network
        sfc_list: all the SFCs in the network, right now assuming to be just 1
        ingress_nodes: all the ingress nodes in the network
        nodes_cap: Capacity of each node in the network

    Returns:
        - a placement Dictionary with:
              key = nodes of the network
              value = list of all the SFs in the network
        - schedule of the form shown above
    """
    placement = defaultdict(list)
    schedule = defaultdict(
        lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(float))))
    # Initializing the schedule for all nodes, for all SFs to 0
    for src in nodes_list:
        for sfc in sfc_list:
            for sf in sf_list:
                for dstn in nodes_list:
                    schedule[src][sfc][sf][dstn] = 0
    # Getting the closest neighbours to each node in the network
    closest_neighbours = get_closest_neighbours(network, nodes_list)

    # - For each Ingress node of the network we start by placing the first VNF of the SFC on it and then place the
    #  2nd VNF of the SFC on the closest neighbour of the Ingress, then the 3rd VNF on the closest neighbour of the node
    #  where we placed the 2nd VNF and so on.
    # - The closest neighbour is chosen based on the following criteria:
    #   - while some nodes in the network has 0 VNFs , the closest neighbour cannot be an Ingress node
    #   - The closest neighbour must have some capacity
    #   - while some of the nodes in the network have 0 VNFs it chooses the closest neighbour that has 0 VNFs,
    #     If some nodes in the network has just 1 VNF, it returns the closest neighbour with just 1 VNF and so on
    for ingress in ingress_nodes:
        node = ingress
        # We choose a list with just one element because a list is mutable in python and we want 'next_neighbour'
        # function to change the value of this variable
        num_vnfs_filled = [0]
        # Placing the 1st VNF of the SFC on the ingress nodes if the ingress node has some capacity
        # Otherwise we find the closest neighbour of the Ingress that has some capacity and place the 1st VNF on it
        if nodes_cap[ingress] > 0:
            if sf_list[0] not in placement[node]:
                placement[node].append(sf_list[0])
            schedule[node][sfc_list[0]][sf_list[0]][node] += 1
        else:
            # Finding the next neighbour which is not an ingress node and has some capacity
            index = next_neighbour(0, num_vnfs_filled, ingress, placement,
                                   closest_neighbours, sf_list, nodes_cap)
            while num_vnfs_filled[0] == 0 and closest_neighbours[ingress][
                    index] in ingress_nodes:
                if index + 1 >= len(closest_neighbours[ingress]):
                    break
                index = next_neighbour(index + 1, num_vnfs_filled, ingress,
                                       placement, closest_neighbours, sf_list,
                                       nodes_cap)
            node = closest_neighbours[ingress][index]
            if sf_list[0] not in placement[node]:
                placement[node].append(sf_list[0])
            schedule[ingress][sfc_list[0]][sf_list[0]][node] += 1

        # For the remaining VNFs in the SFC we look for the closest neighbour and place the VNFs on them
        for j in range(len(sf_list) - 1):
            index = next_neighbour(0, num_vnfs_filled, node, placement,
                                   closest_neighbours, sf_list, nodes_cap)
            while num_vnfs_filled[0] == 0 and closest_neighbours[node][
                    index] in ingress_nodes:
                if index + 1 >= len(closest_neighbours[node]):
                    break
                index = next_neighbour(index + 1, num_vnfs_filled, node,
                                       placement, closest_neighbours, sf_list,
                                       nodes_cap)
            new_node = closest_neighbours[node][index]
            if sf_list[j + 1] not in placement[new_node]:
                placement[new_node].append(sf_list[j + 1])
            schedule[node][sfc_list[0]][sf_list[j + 1]][new_node] += 1
            node = new_node

    # Since the sum of schedule probabilities for each SF of each node may not be 1 , we make it 1 using the
    # 'normalize_scheduling_probabilities' function.
    for src in nodes_list:
        for sfc in sfc_list:
            for sf in sf_list:
                unnormalized_probs_list = list(schedule[src][sfc][sf].values())
                normalized_probs = normalize_scheduling_probabilities(
                    unnormalized_probs_list)
                for i in range(len(nodes_list)):
                    schedule[src][sfc][sf][nodes_list[i]] = normalized_probs[i]
    return placement, schedule
def get_placement_and_schedule(results, nodes_list, sfc_name, sf_list):
    """
    Reads the results file created by the BJointSP to create the placement and schedule for the simulator

    Parameters:
        results: Results dict from BJointSP
        nodes_list
        sfc_name
        sf_list
    Returns:
         placement
         schedule

    placement is a Dictionary with:
            key = nodes of the network
            value = list of all the SFs in the network

    schedule is of the following form:
            schedule : dict
                {
                    'node id' : dict
                    {
                        'SFC id' : dict
                        {
                            'SF id' : dict
                            {
                                'node id' : float (Inclusive of zero values)
                            }
                        }
                    }
                }

    """
    placement = defaultdict(list)

    # creating the placement for the simulator from the results of BJointSP

    for node in nodes_list:
        placement[node] = []

    # The simulator does not need the 'vnf_source', we just need it for the BJointSP.
    # In the 'apply' fx. of the simulator 'vnf_source' causes an error as it cannot find it from the network file.
    for vnf in results['placement']['vnfs']:
        if vnf['name'] != 'vnf_source':
            placement[vnf['node']].append(vnf['name'])

    # creating the schedule for the simulator from the results of BJointSP
    # we use the flows from the results file

    # 'flows' keeps track of the number of flows forwarded by BJointSP from a source_node to a dest_node with
    # dest_vnf(or requested vnf) lying in the dest_node.
    # The schedule is finally created for each vnf in the vnf_list from each node of the network to all the nodes
    # We use the probabilities normalization function 'normalize_scheduling_probabilities' such that for each SF,*
    # *the sum of Probabilities is 1
    flows = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))

    for flow in results['placement']['flows']:
        source_node = flow['src_node']
        dest_node = flow['dst_node']
        vnf = flow['dest_vnf']
        if flows[source_node][dest_node][vnf]:
            flows[source_node][dest_node][vnf] += 1
        else:
            flows[source_node][dest_node][vnf] = 1

    schedule = defaultdict(
        lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(float))))

    for src_node in nodes_list:
        for sf in sf_list:
            prob_list = []
            for dest_node in nodes_list:
                if flows[src_node][dest_node][sf]:
                    prob_list.append(flows[src_node][dest_node][sf])
                else:
                    prob_list.append(0)
            rounded_prob_list = normalize_scheduling_probabilities(prob_list)
            for i in range(len(nodes_list)):
                schedule[src_node][sfc_name][sf][
                    nodes_list[i]] = rounded_prob_list[i]
    return placement, schedule