Attempt at revised/simplified topo class:

- keys are strings
- metadata is simply a dict
- buildFromTopo greatly simplified
This commit is contained in:
Bob Lantz
2012-03-20 00:17:30 -07:00
parent 318ae55e35
commit 5a8bb48951
4 changed files with 188 additions and 393 deletions
+42 -56
View File
@@ -97,7 +97,7 @@ from mininet.log import info, error, debug, output
from mininet.node import Host, OVSKernelSwitch, Controller from mininet.node import Host, OVSKernelSwitch, Controller
from mininet.link import Link from mininet.link import Link
from mininet.util import quietRun, fixLimits 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 from mininet.term import cleanUpScreens, makeTerms
class Mininet( object ): class Mininet( object ):
@@ -131,6 +131,8 @@ class Mininet( object ):
self.link = link self.link = link
self.intf = intf self.intf = intf
self.ipBase = ipBase self.ipBase = ipBase
self.ipBaseNum, self.prefixLen = netParse( self.ipBase )
self.nextIP = 1 # start for address allocation
self.inNamespace = inNamespace self.inNamespace = inNamespace
self.xterms = xterms self.xterms = xterms
self.cleanup = cleanup self.cleanup = cleanup
@@ -143,7 +145,6 @@ class Mininet( object ):
self.controllers = [] self.controllers = []
self.nameToNode = {} # name to Node (Host/Switch) objects self.nameToNode = {} # name to Node (Host/Switch) objects
self.idToNode = {} # dpid to Node (Host/Switch) objects
self.terms = [] # list of spawned xterm processes self.terms = [] # list of spawned xterm processes
@@ -153,35 +154,39 @@ class Mininet( object ):
if topo and build: if topo and build:
self.build() self.build()
# BL Note: def addHost( self, name, cls=None, **params ):
# 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 ):
"""Add host. """Add host.
name: name of host to add name: name of host to add
host: custom host constructor (optional) cls: custom host class/constructor (optional)
params: parameters for host params: parameters for host
returns: added host""" returns: added host"""
if not host: # Default IP and MAC addresses
host = self.host defaults = { 'ip': ipAdd( self.nextIP,
h = host( name, **params) 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.hosts.append( h )
self.nameToNode[ name ] = h self.nameToNode[ name ] = h
return h return h
def addSwitch( self, name, switch=None, **params ): def addSwitch( self, name, cls=None, **params ):
"""Add switch. """Add switch.
name: name of switch to add name: name of switch to add
switch: custom switch constructor (optional) cls: custom switch class/constructor (optional)
returns: added switch returns: added switch
side effect: increments listenPort ivar .""" side effect: increments listenPort ivar ."""
defaults = { 'listenPort': self.listenPort, defaults = { 'listenPort': self.listenPort,
'inNamespace': self.inNamespace } 'inNamespace': self.inNamespace }
defaults.update( params ) defaults.update( params )
if not switch: if not cls:
switch = self.switch cls = self.switch
sw = self.switch( name, **defaults ) sw = cls( name, **defaults )
if not self.inNamespace and self.listenPort: if not self.inNamespace and self.listenPort:
self.listenPort += 1 self.listenPort += 1
self.switches.append( sw ) self.switches.append( sw )
@@ -199,6 +204,15 @@ class Mininet( object ):
self.nameToNode[ name ] = controller_new self.nameToNode[ name ] = controller_new
return 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 ): def configHosts( self ):
"Configure a set of hosts." "Configure a set of hosts."
for host in self.hosts: for host in self.hosts:
@@ -215,40 +229,6 @@ class Mininet( object ):
At the end of this function, everything should be connected At the end of this function, everything should be connected
and up.""" 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 # Possibly we should clean up here and/or validate
# the topo # the topo
if self.cleanup: if self.cleanup:
@@ -262,16 +242,22 @@ class Mininet( object ):
self.addController( 'c0' ) self.addController( 'c0' )
info( '*** Adding hosts:\n' ) info( '*** Adding hosts:\n' )
for hostId in sorted( topo.hosts() ): for hostName in topo.hosts():
addNode( 'h', self.addHost, hostId ) self.addHost( hostName, **topo.nodeInfo( hostName ) )
info( hostName + ' ' )
info( '\n*** Adding switches:\n' ) info( '\n*** Adding switches:\n' )
for switchId in sorted( topo.switches() ): for switchName in topo.switches():
addNode( 's', self.addSwitch, switchId ) self.addSwitch( switchName, **topo.nodeInfo( switchName) )
info( switchName + ' ' )
info( '\n*** Adding links:\n' ) info( '\n*** Adding links:\n' )
for srcId, dstId in sorted( topo.edges() ): for srcName, dstName in topo.links(sort=True):
addLink( srcId, dstId ) 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' ) info( '\n' )
+114 -317
View File
@@ -16,146 +16,73 @@ setup for testing, and can even be emulated with the Mininet package.
# from networkx.classes.graph import Graph # from networkx.classes.graph import Graph
from networkx import Graph from networkx import Graph
from mininet.util import netParse, ipStr from mininet.util import netParse, ipStr, irange, natural, naturalSeq
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
class Topo(object): class Topo(object):
"""Data center network representation for structured multi-trees. "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"""
def __init__(self, node=None, switch=None, link=None ): def __init__(self, hopts=None, sopts=None, lopts=None):
"""Create Topo object. """Topo object:
node: default node/host class (optional) hinfo: default host options
switch: default switch class (optional) sopts: default switch options
link: default link class (optional) lopts: default link options"""
ipBase: default IP address base (optional)"""
self.g = Graph() self.g = Graph()
self.node_info = {} # dpids hash to Node objects self.node_info = {}
self.edge_info = {} # (src_dpid, dst_dpid) tuples hash to Edge objects 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.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): def add_node(self, name, *args, **opts):
'''Add Node to graph. """Add Node to graph.
add_node('name', dict) <or> 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 def add_host(self, name, *args, **opts):
@param node Node object """Convenience method: Add host to graph.
''' add_host('name', dict) <or> add_host('name', **opts)
self.g.add_node(dpid) name: name
if not node: args: dict of node options
node = Node( link=self.link ) opts: node options"""
self.node_info[dpid] = node if not opts and self.hopts:
opts = self.hopts
return self.add_node(name, *args, **opts)
def add_edge(self, src, dst, edge=None): def add_switch(self, name, **opts):
'''Add edge (Node, Node) to graph. """Convenience method: Add switch to graph.
add_switch('name', dict) <or> 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 def add_link(self, src, dst, *args, **opts):
@param dst dst dpid """Add link (Node, Node) to topo.
@param edge Edge object add_link(src, dst, dict) <or> add_link(src, dst, **opts)
''' src: src name
src, dst = tuple(sorted([src, dst])) dst: dst name
args: dict of node options
params: link parameters"""
src, dst = sorted([src, dst], key=naturalSeq)
self.g.add_edge(src, dst) self.g.add_edge(src, dst)
if not edge: if args and type(args[0]) is dict:
edge = Edge( cls=self.link ) opts = args[0]
self.edge_info[(src, dst)] = edge if not opts and self.sopts:
opts = self.sopts
self.link_info[(src, dst)] = opts
self.add_port(src, dst) self.add_port(src, dst)
return src, dst
def add_port(self, src, dst): def add_port(self, src, dst):
'''Generate port mapping for new edge. '''Generate port mapping for new edge.
@@ -175,131 +102,48 @@ class Topo(object):
if src not in self.ports[dst]: if src not in self.ports[dst]:
# num outlinks # num outlinks
self.ports[dst][src] = len(self.ports[dst]) + dst_base self.ports[dst][src] = len(self.ports[dst]) + dst_base
def node_enabled(self, dpid): def nodes(self, sort=True):
'''Is node connected, admin on, powered on, and fault-free? "Return nodes in graph"
if sort:
@param dpid dpid return sorted( self.g.nodes(), key=natural )
@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)]
else: else:
return dpids return self.g.nodes()
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]
def is_switch(self, n): def is_switch(self, n):
'''Returns true if node is a switch.''' '''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. '''Return switches.
sort: sort switches alphabetically
@param enabled only return enabled nodes?
@return dpids list of dpids @return dpids list of dpids
''' '''
nodes = [n for n in self.g.nodes() if self.is_switch(n)] return [n for n in self.nodes(sort) if self.is_switch(n)]
return self.nodes_enabled(nodes, enabled)
def hosts(self, enabled = True): def hosts(self, sort=True):
'''Return hosts. '''Return hosts.
sort: sort hosts alphabetically
@param enabled only return enabled nodes?
@return dpids list of dpids @return dpids list of dpids
''' '''
return [n for n in self.nodes(sort) if not self.is_switch(n)]
def is_host(n): def links(self, sort=True):
'''Returns true if node is a host.''' '''Return links.
return not self.node_info[n].is_switch sort: sort links alphabetically
@return links list of name pairs
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
''' '''
src, dst = edge if not sort:
src, dst = tuple(sorted([src, dst])) return self.g.edges()
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)]
else: else:
return edges return sorted( self.g.edges(), key=naturalSeq )
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
def port(self, src, dst): def port(self, src, dst):
'''Get port number. '''Get port number.
@param src source switch DPID @param src source switch name
@param dst destination switch DPID @param dst destination switch name
@return tuple (src_port, dst_port): @return tuple (src_port, dst_port):
src_port: port on source switch leading to the destination switch src_port: port on source switch leading to the destination switch
dst_port: port on destination switch leading to the source 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] assert dst in self.ports and src in self.ports[dst]
return (self.ports[src][dst], self.ports[dst][src]) return (self.ports[src][dst], self.ports[dst][src])
def edgeInfo( self, src, dst ): def linkInfo( self, src, dst ):
"Return edge metadata" "Return link metadata"
# BL: Perhaps this should be rethought or we should just use the src, dst = sorted((src, dst), key=naturalSeq)
# dicts... return self.link_info[(src, dst)]
return self.edge_info[ ( src, dst ) ]
def enable_edges(self): def nodeInfo( self, name ):
'''Enable all edges in the network graph. "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. def setNodeInfo( self, name, info ):
''' self.node_info[ name ] = info
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 ]
@staticmethod
def sorted( items ):
"Items sorted in natural (i.e. alphabetical) order"
return sorted(items, key=natural)
class SingleSwitchTopo(Topo): class SingleSwitchTopo(Topo):
'''Single switch connected to k hosts.''' '''Single switch connected to k hosts.'''
def __init__(self, k = 2, enable_all = True): def __init__(self, k=2, **opts):
'''Init. '''Init.
@param k number of hosts @param k number of hosts
@param enable_all enables all nodes and switches? @param enable_all enables all nodes and switches?
''' '''
super(SingleSwitchTopo, self).__init__() super(SingleSwitchTopo, self).__init__(**opts)
self.k = k self.k = k
self.add_node(1, Node()) switch = self.add_switch('s1')
hosts = range(2, k + 2) for h in irange(1, k):
for h in hosts: host = self.add_host('h%s' % h)
self.add_node(h, Node(is_switch = False)) self.add_link(host, switch)
self.add_edge(h, 1, Edge())
if enable_all:
self.enable_all()
class SingleSwitchReversedTopo(SingleSwitchTopo): class SingleSwitchReversedTopo(SingleSwitchTopo):
@@ -422,27 +223,23 @@ class SingleSwitchReversedTopo(SingleSwitchTopo):
class LinearTopo(Topo): 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): def __init__(self, k=2, **opts):
'''Init. """Init.
k: number of switches (and hosts)
hconf: host configuration options
lconf: link configuration options"""
@param k number of switches (and hosts too) super(LinearTopo, self).__init__(**opts)
@param enable_all enables all nodes and switches?
'''
super(LinearTopo, self).__init__()
self.k = k self.k = k
switches = range(1, k + 1) lastSwitch = None
for s in switches: for i in irange(1, k):
h = s + k host = self.add_host('h%s' % i)
self.add_node(s, Node()) switch = self.add_switch('s%s' % i)
self.add_node(h, Node(is_switch = False)) self.add_link( host, switch)
self.add_edge(s, h, Edge()) if lastSwitch:
for s in switches: self.add_link( switch, lastSwitch)
if s != k: lastSwitch = switch
self.add_edge(s, s + 1, Edge())
if enable_all:
self.enable_all()
+10 -19
View File
@@ -1,6 +1,6 @@
"Library of potentially useful topologies for Mininet" "Library of potentially useful topologies for Mininet"
from mininet.topo import Topo, Node from mininet.topo import Topo
from mininet.net import Mininet from mininet.net import Mininet
class TreeTopo( Topo ): class TreeTopo( Topo ):
@@ -8,36 +8,27 @@ class TreeTopo( Topo ):
def __init__( self, depth=1, fanout=2 ): def __init__( self, depth=1, fanout=2 ):
super( TreeTopo, self ).__init__() super( TreeTopo, self ).__init__()
# Numbering: h1..N, sN+1..M # Numbering: h1..N, s1..M
hostCount = fanout ** depth
self.hostNum = 1 self.hostNum = 1
self.switchNum = hostCount + 1 self.switchNum = 1
# Build topology # Build topology
self.addTree( depth, fanout ) 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 ): def addTree( self, depth, fanout ):
"""Add a subtree starting with node n. """Add a subtree starting with node n.
returns: last node added""" returns: last node added"""
isSwitch = depth > 0 isSwitch = depth > 0
if isSwitch: if isSwitch:
num = self.switchNum node = self.add_switch( 's%s' % self.switchNum )
self.switchNum += 1 self.switchNum += 1
else: for _ in range( fanout ):
num = self.hostNum
self.hostNum += 1
self.add_node( num, Node( is_switch=isSwitch ) )
if isSwitch:
for i in range( 0, fanout ):
child = self.addTree( depth - 1, fanout ) child = self.addTree( depth - 1, fanout )
self.add_edge( num, child ) self.add_link( node, child )
return num 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 ): def TreeNet( depth=1, fanout=2, **kwargs ):
"Convenience function for creating tree networks." "Convenience function for creating tree networks."
+22 -1
View File
@@ -224,6 +224,18 @@ def ipNum( w, x, y, z ):
returns: w << 24 | x << 16 | y << 8 | z""" returns: w << 24 | x << 16 | y << 8 | z"""
return ( 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 ): def ipParse( ip ):
"Parse an IP address and return an unsigned int." "Parse an IP address and return an unsigned int."
args = [ int( arg ) for arg in ip.split( '.' ) ] args = [ int( arg ) for arg in ip.split( '.' ) ]
@@ -275,9 +287,13 @@ def natural( text ):
"To sort sanely/alphabetically: sorted( l, key=natural )" "To sort sanely/alphabetically: sorted( l, key=natural )"
def num( s ): def num( s ):
"Convert text segment to int if necessary" "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 ) ] 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(): def numCores():
"Returns number of CPU cores based on /proc/cpuinfo" "Returns number of CPU cores based on /proc/cpuinfo"
if hasattr( numCores, 'ncores' ): if hasattr( numCores, 'ncores' ):
@@ -288,6 +304,11 @@ def numCores():
return 0 return 0
return numCores.ncores 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 ): def custom( cls, **params ):
"Returns customized constructor for class cls." "Returns customized constructor for class cls."
def customized( *args, **kwargs): def customized( *args, **kwargs):