From 5a8bb489510da602fe7712378ae618f6c87ec0d6 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Tue, 20 Mar 2012 00:17:30 -0700 Subject: [PATCH] Attempt at revised/simplified topo class: - keys are strings - metadata is simply a dict - buildFromTopo greatly simplified --- mininet/net.py | 98 +++++------ mininet/topo.py | 431 ++++++++++++--------------------------------- mininet/topolib.py | 29 ++- mininet/util.py | 23 ++- 4 files changed, 188 insertions(+), 393 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index be93247..344f777 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -97,7 +97,7 @@ from mininet.log import info, error, debug, output from mininet.node import Host, OVSKernelSwitch, Controller from mininet.link import Link from mininet.util import quietRun, fixLimits -from mininet.util import macColonHex, ipStr, ipParse, netParse +from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd from mininet.term import cleanUpScreens, makeTerms class Mininet( object ): @@ -131,6 +131,8 @@ class Mininet( object ): self.link = link self.intf = intf self.ipBase = ipBase + self.ipBaseNum, self.prefixLen = netParse( self.ipBase ) + self.nextIP = 1 # start for address allocation self.inNamespace = inNamespace self.xterms = xterms self.cleanup = cleanup @@ -143,7 +145,6 @@ class Mininet( object ): self.controllers = [] self.nameToNode = {} # name to Node (Host/Switch) objects - self.idToNode = {} # dpid to Node (Host/Switch) objects self.terms = [] # list of spawned xterm processes @@ -153,35 +154,39 @@ class Mininet( object ): if topo and build: self.build() - # BL Note: - # The specific items for host/switch/etc. should probably be - # handled in the node classes rather than here!! - - def addHost( self, name, host=None, **params ): + def addHost( self, name, cls=None, **params ): """Add host. name: name of host to add - host: custom host constructor (optional) + cls: custom host class/constructor (optional) params: parameters for host returns: added host""" - if not host: - host = self.host - h = host( name, **params) + # Default IP and MAC addresses + defaults = { 'ip': ipAdd( self.nextIP, + ipBaseNum=self.ipBaseNum, + prefixLen=self.prefixLen ) } + if self.autoSetMacs: + defaults[ 'mac'] = macColonHex( self.nextIP ) + self.nextIP += 1 + defaults.update( params ) + if not cls: + cls = self.host + h = cls( name, **defaults ) self.hosts.append( h ) self.nameToNode[ name ] = h return h - def addSwitch( self, name, switch=None, **params ): + def addSwitch( self, name, cls=None, **params ): """Add switch. name: name of switch to add - switch: custom switch constructor (optional) + cls: custom switch class/constructor (optional) returns: added switch side effect: increments listenPort ivar .""" defaults = { 'listenPort': self.listenPort, 'inNamespace': self.inNamespace } defaults.update( params ) - if not switch: - switch = self.switch - sw = self.switch( name, **defaults ) + if not cls: + cls = self.switch + sw = cls( name, **defaults ) if not self.inNamespace and self.listenPort: self.listenPort += 1 self.switches.append( sw ) @@ -199,6 +204,15 @@ class Mininet( object ): self.nameToNode[ name ] = controller_new return controller_new + def addLink( self, src, dst, srcPort=None, dstPort=None, + cls=None, **params ): + "Add a link from topo" + if self.intf and not 'intf' in params: + params[ 'intf' ] = self.intf + if not cls: + cls = self.link + return cls( src, dst, srcPort, dstPort, **params ) + def configHosts( self ): "Configure a set of hosts." for host in self.hosts: @@ -215,40 +229,6 @@ class Mininet( object ): At the end of this function, everything should be connected and up.""" - ipBaseNum, prefixLen = netParse( self.ipBase ) - - if not topo: - topo = self.topo() - - def addNode( prefix, addMethod, nodeId ): - "Add a host or a switch from topo" - name = prefix + topo.name( nodeId ) - ni = topo.nodeInfo( nodeId ) - # Default IP and MAC addresses - defaults = { 'ip': topo.ip( nodeId, - ipBaseNum=ipBaseNum, - prefixLen=prefixLen ) } - if self.autoSetMacs: - defaults[ 'mac'] = macColonHex( nodeId ) - defaults.update( ni.params ) - node = addMethod( name, cls=ni.cls, **defaults ) - self.idToNode[ nodeId ] = node - info( name + ' ' ) - - def addLink( srcId, dstId, link=None ): - "Add a link from topo" - src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ] - srcPort, dstPort = topo.port( srcId, dstId ) - ei = topo.edgeInfo( srcId, dstId ) - link = getattr( ei, 'cls', link ) - params = ei.params - if self.intf and not 'intf' in params: - params[ 'intf' ] = self.intf - if not link: - link = self.link - info( '(%s, %s) ' % ( src.name, dst.name ) ) - link( src, dst, srcPort, dstPort, **params ) - # Possibly we should clean up here and/or validate # the topo if self.cleanup: @@ -262,16 +242,22 @@ class Mininet( object ): self.addController( 'c0' ) info( '*** Adding hosts:\n' ) - for hostId in sorted( topo.hosts() ): - addNode( 'h', self.addHost, hostId ) + for hostName in topo.hosts(): + self.addHost( hostName, **topo.nodeInfo( hostName ) ) + info( hostName + ' ' ) info( '\n*** Adding switches:\n' ) - for switchId in sorted( topo.switches() ): - addNode( 's', self.addSwitch, switchId ) + for switchName in topo.switches(): + self.addSwitch( switchName, **topo.nodeInfo( switchName) ) + info( switchName + ' ' ) info( '\n*** Adding links:\n' ) - for srcId, dstId in sorted( topo.edges() ): - addLink( srcId, dstId ) + for srcName, dstName in topo.links(sort=True): + src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ] + srcPort, dstPort = topo.port( srcName, dstName ) + self.addLink( src, dst, srcPort, dstPort, + **topo.linkInfo( srcName, dstName ) ) + info( '(%s, %s) ' % ( src.name, dst.name ) ) info( '\n' ) diff --git a/mininet/topo.py b/mininet/topo.py index 3de788c..6ef0276 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -16,146 +16,73 @@ setup for testing, and can even be emulated with the Mininet package. # from networkx.classes.graph import Graph from networkx import Graph -from mininet.util import netParse, ipStr - -class NodeID(object): - '''Topo node identifier.''' - - def __init__(self, dpid = None): - '''Init. - - @param dpid dpid - ''' - # DPID-compatible hashable identifier: opaque 64-bit unsigned int - self.dpid = dpid - - def __str__(self): - '''String conversion. - - @return str dpid as string - ''' - return str(self.dpid) - - def name_str(self): - '''Name conversion. - - @return name name as string - ''' - return str(self.dpid) - - def ip_str(self, ipBase=None, prefixLen=8, ipBaseNum=0x0a000000): - '''Name conversion. - ipBase: optional base IP address string - prefixLen: optional IP prefix length - ipBaseNum: option base IP address as int - @return ip ip as string - ''' - if ipBase: - ipnum, prefixLen = netParse( ipBase ) - else: - ipBaseNum = ipBaseNum - # Ugly but functional - assert self.dpid < ( 1 << ( 32 - prefixLen ) ) - mask = 0xffffffff ^ ( ( 1 << prefixLen ) - 1 ) - ipnum = self.dpid + ( ipBaseNum & mask ) - return ipStr( ipnum ) - - -class Node( object ): - '''Node-specific vertex metadata for a Topo object.''' - - def __init__(self, connected=False, admin_on=True, - power_on=True, fault=False, is_switch=True, - cls=None, **params ): - '''Init. - - @param connected actively connected to controller - @param admin_on administratively on or off - @param power_on powered on or off - @param fault fault seen on node - @param is_switch switch or host - @param cls node class (e.g. Host, Switch) - @param params node parameters - ''' - self.connected = connected - self.admin_on = admin_on - self.power_on = power_on - self.fault = fault - self.is_switch = is_switch - # BL: Above should mostly be deleted and replaced by the following - # is_switch is a bit annoying if we are already specifying - # a switch class!! Except that if cls is not specified, - # then Mininet() knows whether to create a switch or a host - # node and can call its own constructors... - self.cls = cls - self.params = params - - -class Edge(object): - '''Edge-specific metadata for a StructuredTopo graph.''' - - def __init__(self, admin_on=True, power_on=True, fault=False, - cls=None, **params): - '''Init. - - @param admin_on administratively on or off; defaults to True - @param power_on powered on or off; defaults to True - @param fault fault seen on edge; defaults to False - ''' - self.admin_on = admin_on - self.power_on = power_on - self.fault = fault - # Above should be deleted and replaced by the following - self.cls = cls - self.params = params - +from mininet.util import netParse, ipStr, irange, natural, naturalSeq class Topo(object): - """Data center network representation for structured multi-trees. - Note that the order of precedence is: - per-node/link classes and parameters - per-topo classes - per-network classes""" + "Data center network representation for structured multi-trees." - def __init__(self, node=None, switch=None, link=None ): - """Create Topo object. - node: default node/host class (optional) - switch: default switch class (optional) - link: default link class (optional) - ipBase: default IP address base (optional)""" + def __init__(self, hopts=None, sopts=None, lopts=None): + """Topo object: + hinfo: default host options + sopts: default switch options + lopts: default link options""" self.g = Graph() - self.node_info = {} # dpids hash to Node objects - self.edge_info = {} # (src_dpid, dst_dpid) tuples hash to Edge objects + self.node_info = {} + self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects + self.hopts = {} if hopts is None else hopts + self.sopts = {} if sopts is None else lopts + self.lopts = {} if lopts is None else lopts self.ports = {} # ports[src][dst] is port on src that connects to dst - self.id_gen = NodeID # class used to generate dpid - self.node = node - self.switch = switch - self.link = link - def add_node(self, dpid, node=None): - '''Add Node to graph. + def add_node(self, name, *args, **opts): + """Add Node to graph. + add_node('name', dict) add_node('name', **opts) + name: name + args: dict of node options + opts: node options""" + self.g.add_node(name) + if args and type(args[0]) is dict: + opts = args[0] + self.node_info[name] = opts + return name - @param dpid dpid - @param node Node object - ''' - self.g.add_node(dpid) - if not node: - node = Node( link=self.link ) - self.node_info[dpid] = node + def add_host(self, name, *args, **opts): + """Convenience method: Add host to graph. + add_host('name', dict) add_host('name', **opts) + name: name + args: dict of node options + opts: node options""" + if not opts and self.hopts: + opts = self.hopts + return self.add_node(name, *args, **opts) - def add_edge(self, src, dst, edge=None): - '''Add edge (Node, Node) to graph. + def add_switch(self, name, **opts): + """Convenience method: Add switch to graph. + add_switch('name', dict) add_switch('name', **opts) + name: name + args: dict of node options + opts: node options""" + if not opts and self.sopts: + opts = self.sopts + result = self.add_node(name, is_switch=True, **opts) + return result - @param src src dpid - @param dst dst dpid - @param edge Edge object - ''' - src, dst = tuple(sorted([src, dst])) + def add_link(self, src, dst, *args, **opts): + """Add link (Node, Node) to topo. + add_link(src, dst, dict) add_link(src, dst, **opts) + src: src name + dst: dst name + args: dict of node options + params: link parameters""" + src, dst = sorted([src, dst], key=naturalSeq) self.g.add_edge(src, dst) - if not edge: - edge = Edge( cls=self.link ) - self.edge_info[(src, dst)] = edge + if args and type(args[0]) is dict: + opts = args[0] + if not opts and self.sopts: + opts = self.sopts + self.link_info[(src, dst)] = opts self.add_port(src, dst) + return src, dst def add_port(self, src, dst): '''Generate port mapping for new edge. @@ -175,131 +102,48 @@ class Topo(object): if src not in self.ports[dst]: # num outlinks self.ports[dst][src] = len(self.ports[dst]) + dst_base - - def node_enabled(self, dpid): - '''Is node connected, admin on, powered on, and fault-free? - - @param dpid dpid - - @return bool node is enabled - ''' - ni = self.node_info[dpid] - return ni.connected and ni.admin_on and ni.power_on and not ni.fault - - def nodes_enabled(self, dpids, enabled = True): - '''Return subset of enabled nodes - - @param dpids list of dpids - @param enabled only return enabled nodes? - - @return dpids filtered list of dpids - ''' - if enabled: - return [n for n in dpids if self.node_enabled(n)] + + def nodes(self, sort=True): + "Return nodes in graph" + if sort: + return sorted( self.g.nodes(), key=natural ) else: - return dpids - - def nodes(self, enabled = True): - '''Return graph nodes. - - @param enabled only return enabled nodes? - - @return dpids list of dpids - ''' - return self.nodes_enabled(self.g.nodes(), enabled) - - def nodes_str(self, dpids): - '''Return string of custom-encoded nodes. - - @param dpids list of dpids - - @return str string - ''' - return [str(self.id_gen(dpid = dpid)) for dpid in dpids] + return self.g.nodes() def is_switch(self, n): '''Returns true if node is a switch.''' - return self.node_info[n].is_switch + info = self.node_info[n] + return info and info['is_switch'] - def switches(self, enabled = True): + def switches(self, sort=True): '''Return switches. - - @param enabled only return enabled nodes? - + sort: sort switches alphabetically @return dpids list of dpids ''' - nodes = [n for n in self.g.nodes() if self.is_switch(n)] - return self.nodes_enabled(nodes, enabled) + return [n for n in self.nodes(sort) if self.is_switch(n)] - def hosts(self, enabled = True): + def hosts(self, sort=True): '''Return hosts. - - @param enabled only return enabled nodes? - + sort: sort hosts alphabetically @return dpids list of dpids ''' + return [n for n in self.nodes(sort) if not self.is_switch(n)] - def is_host(n): - '''Returns true if node is a host.''' - return not self.node_info[n].is_switch - - nodes = [n for n in self.g.nodes() if is_host(n)] - return self.nodes_enabled(nodes, enabled) - - def edge_enabled(self, edge): - '''Is edge admin on, powered on, and fault-free? - - @param edge (src, dst) dpid tuple - - @return bool edge is enabled + def links(self, sort=True): + '''Return links. + sort: sort links alphabetically + @return links list of name pairs ''' - src, dst = edge - src, dst = tuple(sorted([src, dst])) - ei = self.edge_info[tuple(sorted([src, dst]))] - return ei.admin_on and ei.power_on and not ei.fault - - def edges_enabled(self, edges, enabled = True): - '''Return subset of enabled edges - - @param edges list of edges - @param enabled only return enabled edges? - - @return edges filtered list of edges - ''' - if enabled: - return [e for e in edges if self.edge_enabled(e)] + if not sort: + return self.g.edges() else: - return edges - - def edges(self, enabled = True): - '''Return edges. - - @param enabled only return enabled edges? - - @return edges list of dpid pairs - ''' - return self.edges_enabled(self.g.edges(), enabled) - - def edges_str(self, dpid_pairs): - '''Return string of custom-encoded node pairs. - - @param dpid_pairs list of dpid pairs (src, dst) - - @return str string - ''' - edges = [] - for pair in dpid_pairs: - src, dst = pair - src = str(self.id_gen(dpid = src)) - dst = str(self.id_gen(dpid = dst)) - edges.append((src, dst)) - return edges + return sorted( self.g.edges(), key=naturalSeq ) def port(self, src, dst): '''Get port number. - @param src source switch DPID - @param dst destination switch DPID + @param src source switch name + @param dst destination switch name @return tuple (src_port, dst_port): src_port: port on source switch leading to the destination switch dst_port: port on destination switch leading to the source switch @@ -308,84 +152,41 @@ class Topo(object): assert dst in self.ports and src in self.ports[dst] return (self.ports[src][dst], self.ports[dst][src]) - def edgeInfo( self, src, dst ): - "Return edge metadata" - # BL: Perhaps this should be rethought or we should just use the - # dicts... - return self.edge_info[ ( src, dst ) ] + def linkInfo( self, src, dst ): + "Return link metadata" + src, dst = sorted((src, dst), key=naturalSeq) + return self.link_info[(src, dst)] - def enable_edges(self): - '''Enable all edges in the network graph. + def nodeInfo( self, name ): + "Return metadata (dict) for node" + info = self.node_info[ name ] + return info if info is not None else {} - Set admin on, power on, and fault off. - ''' - for e in self.g.edges(): - src, dst = e - ei = self.edge_info[tuple(sorted([src, dst]))] - ei.admin_on = True - ei.power_on = True - ei.fault = False - - def enable_nodes(self): - '''Enable all nodes in the network graph. - - Set connected on, admin on, power on, and fault off. - ''' - for node in self.g.nodes(): - ni = self.node_info[node] - ni.connected = True - ni.admin_on = True - ni.power_on = True - ni.fault = False - - def enable_all(self): - '''Enable all nodes and edges in the network graph.''' - self.enable_nodes() - self.enable_edges() - - def name(self, dpid): - '''Get string name of node ID. - - @param dpid DPID of host or switch - @return name_str string name with no dashes - ''' - return self.id_gen(dpid = dpid).name_str() - - def ip(self, dpid, **params): - '''Get IP dotted-decimal string of node ID. - @param dpid DPID of host or switch - @param params: params to pass to ip_str - @return ip_str - ''' - return self.id_gen(dpid = dpid).ip_str(**params) - - def nodeInfo( self, dpid ): - "Return metadata for node" - # BL: may wish to rethink this or just use dicts.. - return self.node_info[ dpid ] + def setNodeInfo( self, name, info ): + self.node_info[ name ] = info + @staticmethod + def sorted( items ): + "Items sorted in natural (i.e. alphabetical) order" + return sorted(items, key=natural) class SingleSwitchTopo(Topo): '''Single switch connected to k hosts.''' - def __init__(self, k = 2, enable_all = True): + def __init__(self, k=2, **opts): '''Init. @param k number of hosts @param enable_all enables all nodes and switches? ''' - super(SingleSwitchTopo, self).__init__() + super(SingleSwitchTopo, self).__init__(**opts) self.k = k - self.add_node(1, Node()) - hosts = range(2, k + 2) - for h in hosts: - self.add_node(h, Node(is_switch = False)) - self.add_edge(h, 1, Edge()) - - if enable_all: - self.enable_all() + switch = self.add_switch('s1') + for h in irange(1, k): + host = self.add_host('h%s' % h) + self.add_link(host, switch) class SingleSwitchReversedTopo(SingleSwitchTopo): @@ -422,27 +223,23 @@ class SingleSwitchReversedTopo(SingleSwitchTopo): class LinearTopo(Topo): - '''Linear topology of k switches, with one host per switch.''' + "Linear topology of k switches, with one host per switch." - def __init__(self, k = 2, enable_all = True): - '''Init. + def __init__(self, k=2, **opts): + """Init. + k: number of switches (and hosts) + hconf: host configuration options + lconf: link configuration options""" - @param k number of switches (and hosts too) - @param enable_all enables all nodes and switches? - ''' - super(LinearTopo, self).__init__() + super(LinearTopo, self).__init__(**opts) self.k = k - switches = range(1, k + 1) - for s in switches: - h = s + k - self.add_node(s, Node()) - self.add_node(h, Node(is_switch = False)) - self.add_edge(s, h, Edge()) - for s in switches: - if s != k: - self.add_edge(s, s + 1, Edge()) - - if enable_all: - self.enable_all() + lastSwitch = None + for i in irange(1, k): + host = self.add_host('h%s' % i) + switch = self.add_switch('s%s' % i) + self.add_link( host, switch) + if lastSwitch: + self.add_link( switch, lastSwitch) + lastSwitch = switch diff --git a/mininet/topolib.py b/mininet/topolib.py index d42a42a..a8de9d8 100644 --- a/mininet/topolib.py +++ b/mininet/topolib.py @@ -1,6 +1,6 @@ "Library of potentially useful topologies for Mininet" -from mininet.topo import Topo, Node +from mininet.topo import Topo from mininet.net import Mininet class TreeTopo( Topo ): @@ -8,36 +8,27 @@ class TreeTopo( Topo ): def __init__( self, depth=1, fanout=2 ): super( TreeTopo, self ).__init__() - # Numbering: h1..N, sN+1..M - hostCount = fanout ** depth + # Numbering: h1..N, s1..M self.hostNum = 1 - self.switchNum = hostCount + 1 + self.switchNum = 1 # Build topology self.addTree( depth, fanout ) - # Consider all switches and hosts 'on' - self.enable_all() - - # It is OK that i is "unused" in the for loop. - # pylint: disable-msg=W0612 def addTree( self, depth, fanout ): """Add a subtree starting with node n. returns: last node added""" isSwitch = depth > 0 if isSwitch: - num = self.switchNum + node = self.add_switch( 's%s' % self.switchNum ) self.switchNum += 1 - else: - num = self.hostNum - self.hostNum += 1 - self.add_node( num, Node( is_switch=isSwitch ) ) - if isSwitch: - for i in range( 0, fanout ): + for _ in range( fanout ): child = self.addTree( depth - 1, fanout ) - self.add_edge( num, child ) - return num + self.add_link( node, child ) + else: + node = self.add_host( 'h%s' % self.hostNum ) + self.hostNum += 1 + return node - # pylint: enable-msg=W0612 def TreeNet( depth=1, fanout=2, **kwargs ): "Convenience function for creating tree networks." diff --git a/mininet/util.py b/mininet/util.py index 450472a..11a30a1 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -224,6 +224,18 @@ def ipNum( w, x, y, z ): returns: w << 24 | x << 16 | y << 8 | z""" return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z +def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ): + """Return IP address string from ints + i: int to be added to ipbase + prefixLen: optional IP prefix length + ipBaseNum: option base IP address as int + returns IP address as string""" + # Ugly but functional + assert i < ( 1 << ( 32 - prefixLen ) ) + mask = 0xffffffff ^ ( ( 1 << prefixLen ) - 1 ) + ipnum = i + ( ipBaseNum & mask ) + return ipStr( ipnum ) + def ipParse( ip ): "Parse an IP address and return an unsigned int." args = [ int( arg ) for arg in ip.split( '.' ) ] @@ -275,9 +287,13 @@ def natural( text ): "To sort sanely/alphabetically: sorted( l, key=natural )" def num( s ): "Convert text segment to int if necessary" - return int( s ) if s.isdigit() else text + return int( s ) if s.isdigit() else s return [ num( s ) for s in re.split( r'(\d+)', text ) ] +def naturalSeq( t ): + "Natural sort key function for sequences" + return [ natural( x ) for x in t ] + def numCores(): "Returns number of CPU cores based on /proc/cpuinfo" if hasattr( numCores, 'ncores' ): @@ -288,6 +304,11 @@ def numCores(): return 0 return numCores.ncores +def irange(start, end): + """Inclusive range from start to end (vs. Python insanity.) + irange(1,5) -> 1, 2, 3, 4, 5""" + return range( start, end + 1 ) + def custom( cls, **params ): "Returns customized constructor for class cls." def customized( *args, **kwargs):