Source code for openoptics.utils

# Copyright (c) Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V.
# Developed at the Max Planck Institute for Informatics, Network and Cloud Systems Group
#
# Author: Yiming Lei (ylei@mpi-inf.mpg.de)
#
# This software is licensed for non-commercial scientific research purposes only.
#
# License text: Creative Commons NC BY SA 4.0
# https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en

from typing import List, Dict
import networkx as nx

from openoptics.TimeFlowTable import TimeFlowEntry, TimeFlowHop, Path
from openoptics.OpticalRouting import find_direct_path
from openoptics.backends.base import TableEntry


[docs] def path2entries( paths: List[Path], routing_mode, arch_mode="TO" ) -> Dict[int, TimeFlowEntry]: """ Convert paths to time flow table entries Args: paths: A list of paths routing_mode: Per-hop or Source. Trim path if Per-hop. arch_mode: TA or TO. In TA, packets for each dst has a dedicated queue. send_ts in TimeFlowHop is dst. In TO, send_ts is actual sending time slice. Returns: The dictionary of {src_id : entries} """ assert arch_mode == "TO" or arch_mode == "TA", ( f"Unsupported architecture mode {arch_mode}" ) entries = {} for path in paths: hops = [] if routing_mode == "Per-hop": path.steps = [path.steps[0]] for step in path.steps: if step.step_type == "port": hops.append( TimeFlowHop( cur_node=step.cur_node, # In TA, packets for each dst has a dedicated queue. send_ts=step.send_ts if arch_mode == "TO" else path.dst, send_port=step.send_port, ) ) elif step.step_type == "node": assert arch_mode != "TA", ( "Forward based on node is not supported for TA architectures" ) hops.append( TimeFlowHop( cur_node=step.cur_node, send_node=step.send_node ) ) if path.src not in entries.keys(): entries[path.src] = [] entries[path.src].append( TimeFlowEntry(dst=path.dst, arrival_ts=path.arrival_ts, hops=hops) ) return entries
[docs] def gen_ocs_commands(ocs_schedule_entries) -> List[TableEntry]: """ Generate table entries for OCS (Optical Circuit Switching) scheduling. Args: ocs_schedule_entries: List of tuples (slice_id, ingress_port, egress_port) Returns: List of TableEntry objects for the OCS switch. """ entries = [ TableEntry( table="ocs_schedule", action="drop", match_keys={}, action_params={}, is_default_action=True, ) ] for slice_id, ingress_port, egress_port in ocs_schedule_entries: entries.append(TableEntry( table="ocs_schedule", action="ocs_forward", match_keys={"ingress_port": ingress_port, "slice_id": slice_id}, action_params={"egress_port": egress_port}, )) return entries
[docs] def tor_table_ip_to_dst(ip_to_tor) -> List[TableEntry]: """ Generate table entries mapping IP addresses to ToR (Top of Rack) switch IDs. Args: ip_to_tor: Dictionary mapping IP addresses to ToR switch IDs Returns: List of TableEntry objects. """ return [ TableEntry( table="ip_to_dst_node", action="write_dst", match_keys={"ip": ip}, action_params={"dst_node": tor_id}, ) for ip, tor_id in ip_to_tor.items() ]
[docs] def tor_table_arrive_at_dst(tor_id, to_host_port) -> List[TableEntry]: """ Generate table entries for checking if a packet arrives at the dst ToR. If so, send it to the host. Args: tor_id: ID of the ToR switch to_host_port: Port number connected to host Returns: List of TableEntry objects. """ return [ TableEntry( table="arrive_at_dst", action="send_to_host", match_keys={"tor_id": tor_id}, action_params={"host_port": to_host_port}, ) ]
[docs] def tor_table_verify_desired_node(tor_id) -> List[TableEntry]: """ Generate table entries for checking if the receiver is the dst node. Args: tor_id: ID of the ToR switch Returns: List of TableEntry objects (two entries: one for tor_id, one wildcard 255). """ return [ TableEntry( table="verify_desired_node", action="NoAction", match_keys={"tor_id": tor_id}, action_params={}, ), TableEntry( table="verify_desired_node", action="NoAction", match_keys={"tor_id": 255}, action_params={}, ), ]
[docs] def tor_table_cal_port_slice_to_node( tor_id: int, slice_to_topo: Dict[int, nx.Graph] ) -> List[TableEntry]: """ Generate table entries for (src, next_node, send_slice)->send_port. Args: tor_id: ID of the ToR switch slice_to_topo: Dictionary mapping time slices to network topology graphs Returns: List of TableEntry objects. """ result = [] if 0 not in slice_to_topo.keys(): return result for dst in slice_to_topo[0].nodes(): if tor_id == dst: continue paths = find_direct_path(slice_to_topo, node1=tor_id, node2=dst) entries = path2entries(paths, routing_mode="Per-hop") if tor_id not in entries.keys(): continue for entry in entries[tor_id]: arrival_ts = entry.arrival_ts send_ts = entry.hops[0].send_ts send_port = entry.hops[0].send_port_or_node result.append(TableEntry( table="cal_port_slice_to_node", action="to_calendar_q_table_action", match_keys={"dst": dst, "arrival_ts": arrival_ts}, action_params={"send_port": send_port, "send_ts": send_ts}, )) return result
[docs] def tor_table_routing_source(entry: TimeFlowEntry, nb_time_slices=None) -> List[TableEntry]: """ Generate table entries for source routing. Args: entry: TimeFlowEntry object containing routing information nb_time_slices: Number of time slices. Required when arrival_ts is None (wildcard), to generate one entry per time slice. Returns: List of TableEntry objects. action_params["hops"] is a list of (cur_node, send_ts, send_port) tuples. """ hop_count = len(entry.hops) action = f"write_ssrr_header_{hop_count - 1}" if entry.arrival_ts is None: return [ TableEntry( table="add_source_routing_entries", action=action, match_keys={"dst": entry.dst, "arrival_ts": arrival_ts}, action_params={"hops": [ (hop.cur_node, arrival_ts, hop.send_port_or_node) for hop in entry.hops ]}, ) for arrival_ts in range(nb_time_slices) ] else: return [ TableEntry( table="add_source_routing_entries", action=action, match_keys={"dst": entry.dst, "arrival_ts": entry.arrival_ts}, action_params={"hops": [ (hop.cur_node, hop.send_ts, hop.send_port_or_node) for hop in entry.hops ]}, ) ]
[docs] def tor_table_routing_per_hop(entry: TimeFlowEntry, nb_time_slices=None) -> List[TableEntry]: """ Generate table entries for per-hop routing. Args: entry: TimeFlowEntry object containing routing information nb_time_slices: Number of time slices. Required when arrival_ts is None (wildcard), to generate one entry per time slice. Returns: List of TableEntry objects. """ if len(entry.hops) != 1: print( f"Warning: Find multi-hop time flow entry ({entry}) in Per-hop forwarding mode. Trim following hops." ) hop = entry.hops[0] if entry.arrival_ts is None: # Wildcard arrival_ts: generate one entry per time slice. # Both match key and send_ts action param use the same loop variable. return [ TableEntry( table="per_hop_routing", action="write_time_flow_entry", match_keys={"dst": entry.dst, "arrival_ts": arrival_ts}, action_params={ "cur_node": hop.cur_node, "send_ts": arrival_ts, "send_port": hop.send_port_or_node, }, ) for arrival_ts in range(nb_time_slices) ] else: return [ TableEntry( table="per_hop_routing", action="write_time_flow_entry", match_keys={"dst": entry.dst, "arrival_ts": entry.arrival_ts}, action_params={ "cur_node": hop.cur_node, "send_ts": hop.send_ts, "send_port": hop.send_port_or_node, }, ) ]
[docs] def gen_tor_commands(tor_id, slices, port_to_ip, num_hosts, offset): """ Old implementation of loading direct routing table entries. Args: tor_id: ID of the ToR switch slices: List of time slices port_to_ip: Mapping of ports to IP addresses num_hosts: Number of hosts offset: IP address offset Returns: str: CLI commands for ToR switch configuration """ commands = "" # slice_size = int(max_slices / len(slices)) # commands += f"table_set_default static_config set_static_config {len(slices)} {slice_size} 0 10000 {tor_id}\n" # Non-optical network on port 2 commands += f"table_set_default time_flow_table to_calendar_q {len(slices)} 2\n" # For each time slice, register which port is connected to which, and # add an entry to the corresponding tor switch's forwarding table for send_time_slice, tor_pairs in enumerate(slices): connected_port = None for tor_pair in tor_pairs: if tor_id in tor_pair: if tor_id == tor_pair[0]: connected_port = tor_pair[1] else: connected_port = tor_pair[0] break if connected_port is None: break ips = port_to_ip[connected_port] # Optical network on port 1 for ip in ips: arrival_time_slice = 0 # tbf commands += f"table_add time_flow_table to_calendar_q {ip} {arrival_time_slice} => {send_time_slice} 1\n" for nodes in range(num_hosts): for send_time_slice, _ in enumerate(slices): commands += f"table_add ocs_schedule ocs_switch 10.0.0.{offset} => {send_time_slice} 0\n" offset += 1 return commands
# Traffic-aware related
[docs] def metric_to_matrix(metric: dict) -> dict: """Convert queue depth to traffic metric Args: metric: a dict of {sw_name["pq_depth"] : {(port, queue) : queue depth}} Return: a dict of traffic matrix {(node1, node2) : traffic} """ traffic_matrix = {} for sw_name, metric in metric.items(): src = int(sw_name[3:]) # tor0 queue_depth_dict = metric["pq_depth"] for (port, queue), depth in queue_depth_dict.items(): assert port == 0, "Only support single port for now" dst = queue # queue id is the dst id traffic_matrix[(src, dst)] = depth return traffic_matrix