carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | '''@package topo |
| 3 | |
| 4 | Network topology creation. |
| 5 | |
| 6 | @author Brandon Heller (brandonh@stanford.edu) |
| 7 | |
| 8 | This package includes code to represent network topologies. |
| 9 | |
| 10 | A Topo object can be a topology database for NOX, can represent a physical |
| 11 | setup for testing, and can even be emulated with the Mininet package. |
| 12 | ''' |
| 13 | |
| 14 | # BL: we may have to fix compatibility here. |
| 15 | # networkx is also a fairly heavyweight dependency |
| 16 | # from networkx.classes.graph import Graph |
| 17 | |
| 18 | from networkx import Graph |
| 19 | from mininet.util import irange, natural, naturalSeq |
Caio | 188d2f3 | 2015-01-22 23:35:08 -0200 | [diff] [blame^] | 20 | import pdb |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 21 | |
| 22 | class Topo(object): |
| 23 | "Data center network representation for structured multi-trees." |
| 24 | |
Caio | 188d2f3 | 2015-01-22 23:35:08 -0200 | [diff] [blame^] | 25 | def __init__(self, hopts=None, sopts=None, lopts=None, ropts=None): |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 26 | """Topo object: |
| 27 | hinfo: default host options |
| 28 | sopts: default switch options |
| 29 | lopts: default link options""" |
| 30 | self.g = Graph() |
| 31 | self.node_info = {} |
| 32 | self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects |
| 33 | self.hopts = {} if hopts is None else hopts |
Caio | 188d2f3 | 2015-01-22 23:35:08 -0200 | [diff] [blame^] | 34 | self.ropts = {} if ropts is None else ropts |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 35 | self.sopts = {} if sopts is None else sopts |
| 36 | self.lopts = {} if lopts is None else lopts |
| 37 | self.ports = {} # ports[src][dst] is port on src that connects to dst |
| 38 | |
| 39 | def addNode(self, name, **opts): |
| 40 | """Add Node to graph. |
| 41 | name: name |
| 42 | opts: node options |
| 43 | returns: node name""" |
| 44 | self.g.add_node(name) |
| 45 | self.node_info[name] = opts |
| 46 | return name |
| 47 | |
| 48 | def addHost(self, name, **opts): |
| 49 | """Convenience method: Add host to graph. |
| 50 | name: host name |
| 51 | opts: host options |
| 52 | returns: host name""" |
Caio | 188d2f3 | 2015-01-22 23:35:08 -0200 | [diff] [blame^] | 53 | #pdb.set_trace() |
| 54 | if not opts: |
| 55 | if self.hopts: |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 56 | opts = self.hopts |
Caio | 188d2f3 | 2015-01-22 23:35:08 -0200 | [diff] [blame^] | 57 | elif self.ropts: |
| 58 | opts = self.ropts |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 59 | return self.addNode(name, **opts) |
| 60 | |
| 61 | def addSwitch(self, name, **opts): |
| 62 | """Convenience method: Add switch to graph. |
| 63 | name: switch name |
| 64 | opts: switch options |
| 65 | returns: switch name""" |
| 66 | if not opts and self.sopts: |
| 67 | opts = self.sopts |
| 68 | result = self.addNode(name, isSwitch=True, **opts) |
| 69 | return result |
| 70 | |
| 71 | def addLink(self, node1, node2, port1=None, port2=None, |
| 72 | **opts): |
| 73 | """node1, node2: nodes to link together |
| 74 | port1, port2: ports (optional) |
| 75 | opts: link options (optional) |
| 76 | returns: link info key""" |
| 77 | if not opts and self.lopts: |
| 78 | opts = self.lopts |
| 79 | self.addPort(node1, node2, port1, port2) |
| 80 | key = tuple(self.sorted([node1, node2])) |
| 81 | self.link_info[key] = opts |
| 82 | self.g.add_edge(*key) |
| 83 | return key |
| 84 | |
| 85 | def addPort(self, src, dst, sport=None, dport=None): |
| 86 | '''Generate port mapping for new edge. |
| 87 | @param src source switch name |
| 88 | @param dst destination switch name |
| 89 | ''' |
| 90 | self.ports.setdefault(src, {}) |
| 91 | self.ports.setdefault(dst, {}) |
| 92 | # New port: number of outlinks + base |
| 93 | src_base = 1 if self.isSwitch(src) else 0 |
| 94 | dst_base = 1 if self.isSwitch(dst) else 0 |
| 95 | if sport is None: |
| 96 | sport = len(self.ports[src]) + src_base |
| 97 | if dport is None: |
| 98 | dport = len(self.ports[dst]) + dst_base |
| 99 | self.ports[src][dst] = sport |
| 100 | self.ports[dst][src] = dport |
| 101 | |
| 102 | def nodes(self, sort=True): |
| 103 | "Return nodes in graph" |
| 104 | if sort: |
| 105 | return self.sorted( self.g.nodes() ) |
| 106 | else: |
| 107 | return self.g.nodes() |
| 108 | |
| 109 | def isSwitch(self, n): |
| 110 | '''Returns true if node is a switch.''' |
Caio | 188d2f3 | 2015-01-22 23:35:08 -0200 | [diff] [blame^] | 111 | #pdb.set_trace() |
carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 112 | info = self.node_info[n] |
| 113 | return info and info.get('isSwitch', False) |
| 114 | |
| 115 | def switches(self, sort=True): |
| 116 | '''Return switches. |
| 117 | sort: sort switches alphabetically |
| 118 | @return dpids list of dpids |
| 119 | ''' |
| 120 | return [n for n in self.nodes(sort) if self.isSwitch(n)] |
| 121 | |
| 122 | def hosts(self, sort=True): |
| 123 | '''Return hosts. |
| 124 | sort: sort hosts alphabetically |
| 125 | @return dpids list of dpids |
| 126 | ''' |
| 127 | return [n for n in self.nodes(sort) if not self.isSwitch(n)] |
| 128 | |
| 129 | def links(self, sort=True): |
| 130 | '''Return links. |
| 131 | sort: sort links alphabetically |
| 132 | @return links list of name pairs |
| 133 | ''' |
| 134 | if not sort: |
| 135 | return self.g.edges() |
| 136 | else: |
| 137 | links = [tuple(self.sorted(e)) for e in self.g.edges()] |
| 138 | return sorted( links, key=naturalSeq ) |
| 139 | |
| 140 | def port(self, src, dst): |
| 141 | '''Get port number. |
| 142 | |
| 143 | @param src source switch name |
| 144 | @param dst destination switch name |
| 145 | @return tuple (src_port, dst_port): |
| 146 | src_port: port on source switch leading to the destination switch |
| 147 | dst_port: port on destination switch leading to the source switch |
| 148 | ''' |
| 149 | if src in self.ports and dst in self.ports[src]: |
| 150 | assert dst in self.ports and src in self.ports[dst] |
| 151 | return (self.ports[src][dst], self.ports[dst][src]) |
| 152 | |
| 153 | def linkInfo( self, src, dst ): |
| 154 | "Return link metadata" |
| 155 | src, dst = self.sorted([src, dst]) |
| 156 | return self.link_info[(src, dst)] |
| 157 | |
| 158 | def setlinkInfo( self, src, dst, info ): |
| 159 | "Set link metadata" |
| 160 | src, dst = self.sorted([src, dst]) |
| 161 | self.link_info[(src, dst)] = info |
| 162 | |
| 163 | def nodeInfo( self, name ): |
| 164 | "Return metadata (dict) for node" |
| 165 | info = self.node_info[ name ] |
| 166 | return info if info is not None else {} |
| 167 | |
| 168 | def setNodeInfo( self, name, info ): |
| 169 | "Set metadata (dict) for node" |
| 170 | self.node_info[ name ] = info |
| 171 | |
| 172 | @staticmethod |
| 173 | def sorted( items ): |
| 174 | "Items sorted in natural (i.e. alphabetical) order" |
| 175 | return sorted(items, key=natural) |
| 176 | |
| 177 | class SingleSwitchTopo(Topo): |
| 178 | '''Single switch connected to k hosts.''' |
| 179 | |
| 180 | def __init__(self, k=2, **opts): |
| 181 | '''Init. |
| 182 | |
| 183 | @param k number of hosts |
| 184 | @param enable_all enables all nodes and switches? |
| 185 | ''' |
| 186 | super(SingleSwitchTopo, self).__init__(**opts) |
| 187 | |
| 188 | self.k = k |
| 189 | |
| 190 | switch = self.addSwitch('s1') |
| 191 | for h in irange(1, k): |
| 192 | host = self.addHost('h%s' % h) |
| 193 | self.addLink(host, switch) |
| 194 | |
| 195 | |
| 196 | class SingleSwitchReversedTopo(Topo): |
| 197 | '''Single switch connected to k hosts, with reversed ports. |
| 198 | |
| 199 | The lowest-numbered host is connected to the highest-numbered port. |
| 200 | |
| 201 | Useful to verify that Mininet properly handles custom port numberings. |
| 202 | ''' |
| 203 | def __init__(self, k=2, **opts): |
| 204 | '''Init. |
| 205 | |
| 206 | @param k number of hosts |
| 207 | @param enable_all enables all nodes and switches? |
| 208 | ''' |
| 209 | super(SingleSwitchReversedTopo, self).__init__(**opts) |
| 210 | self.k = k |
| 211 | switch = self.addSwitch('s1') |
| 212 | for h in irange(1, k): |
| 213 | host = self.addHost('h%s' % h) |
| 214 | self.addLink(host, switch, |
| 215 | port1=0, port2=(k - h + 1)) |
| 216 | |
| 217 | class LinearTopo(Topo): |
| 218 | "Linear topology of k switches, with one host per switch." |
| 219 | |
| 220 | def __init__(self, k=2, **opts): |
| 221 | """Init. |
| 222 | k: number of switches (and hosts) |
| 223 | hconf: host configuration options |
| 224 | lconf: link configuration options""" |
| 225 | |
| 226 | super(LinearTopo, self).__init__(**opts) |
| 227 | |
| 228 | self.k = k |
| 229 | |
| 230 | lastSwitch = None |
| 231 | for i in irange(1, k): |
| 232 | host = self.addHost('h%s' % i) |
| 233 | switch = self.addSwitch('s%s' % i) |
| 234 | self.addLink( host, switch) |
| 235 | if lastSwitch: |
| 236 | self.addLink( switch, lastSwitch) |
| 237 | lastSwitch = switch |