mininet / mininet / net.py @ 824afb84
History | View | Annotate | Download (30.3 KB)
1 | 281f6e59 | Bob Lantz | """
|
---|---|---|---|
2 |
|
||
3 | a6bcad8f | Bob Lantz | Mininet: A simple networking testbed for OpenFlow/SDN!
|
4 | 281f6e59 | Bob Lantz |
|
5 | author: Bob Lantz (rlantz@cs.stanford.edu)
|
||
6 | author: Brandon Heller (brandonh@stanford.edu)
|
||
7 | 98d4f189 | Bob Lantz |
|
8 | 08cef003 | Bob Lantz | Mininet creates scalable OpenFlow test networks by using
|
9 | 723d068c | Brandon Heller | process-based virtualization and network namespaces.
|
10 | 98d4f189 | Bob Lantz |
|
11 | eddef947 | Bob Lantz | Simulated hosts are created as processes in separate network
|
12 | namespaces. This allows a complete OpenFlow network to be simulated on
|
||
13 | top of a single Linux kernel.
|
||
14 | 98d4f189 | Bob Lantz |
|
15 | Each host has:
|
||
16 | 7d4b7b7f | Bob Lantz |
|
17 | 281f6e59 | Bob Lantz | A virtual console (pipes to a shell)
|
18 | A virtual interfaces (half of a veth pair)
|
||
19 | A parent shell (and possibly some child processes) in a namespace
|
||
20 | 723d068c | Brandon Heller |
|
21 | eddef947 | Bob Lantz | Hosts have a network interface which is configured via ifconfig/ip
|
22 | 8f20b95d | Brandon Heller | link/etc.
|
23 | 98d4f189 | Bob Lantz |
|
24 | 55dd9368 | Bob Lantz | This version supports both the kernel and user space datapaths
|
25 | 1bda2d21 | Bob Lantz | from the OpenFlow reference implementation (openflowswitch.org)
|
26 | as well as OpenVSwitch (openvswitch.org.)
|
||
27 | 08cef003 | Bob Lantz |
|
28 | 98d4f189 | Bob Lantz | In kernel datapath mode, the controller and switches are simply
|
29 | processes in the root namespace.
|
||
30 |
|
||
31 | 281f6e59 | Bob Lantz | Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
|
32 | 55dd9368 | Bob Lantz | attached to the one side of a veth pair; the other side resides in the
|
33 | host namespace. In this mode, switch processes can simply connect to the
|
||
34 | eddef947 | Bob Lantz | controller via the loopback interface.
|
35 | 98d4f189 | Bob Lantz |
|
36 | 31b43002 | Bob Lantz | In user datapath mode, the controller and switches can be full-service
|
37 | eddef947 | Bob Lantz | nodes that live in their own network namespaces and have management
|
38 | bbe5f8a3 | Bob Lantz | interfaces and IP addresses on a control network (e.g. 192.168.123.1,
|
39 | 281f6e59 | Bob Lantz | currently routed although it could be bridged.)
|
40 | 98d4f189 | Bob Lantz |
|
41 | In addition to a management interface, user mode switches also have
|
||
42 | 55dd9368 | Bob Lantz | several switch interfaces, halves of veth pairs whose other halves
|
43 | reside in the host nodes that the switches are connected to.
|
||
44 | 98d4f189 | Bob Lantz |
|
45 | 31b43002 | Bob Lantz | Consistent, straightforward naming is important in order to easily
|
46 | identify hosts, switches and controllers, both from the CLI and
|
||
47 | from program code. Interfaces are named to make it easy to identify
|
||
48 | which interfaces belong to which node.
|
||
49 |
|
||
50 | The basic naming scheme is as follows:
|
||
51 | 281f6e59 | Bob Lantz |
|
52 | 80be5642 | Bob Lantz | Host nodes are named h1-hN
|
53 | Switch nodes are named s1-sN
|
||
54 | 31b43002 | Bob Lantz | Controller nodes are named c0-cN
|
55 | Interfaces are named {nodename}-eth0 .. {nodename}-ethN
|
||
56 |
|
||
57 | 80be5642 | Bob Lantz | Note: If the network topology is created using mininet.topo, then
|
58 | node numbers are unique among hosts and switches (e.g. we have
|
||
59 | h1..hN and SN..SN+M) and also correspond to their default IP addresses
|
||
60 | of 10.x.y.z/8 where x.y.z is the base-256 representation of N for
|
||
61 | hN. This mapping allows easy determination of a node's IP
|
||
62 | address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1.
|
||
63 |
|
||
64 | bbe5f8a3 | Bob Lantz | Note also that 10.0.0.1 can often be written as 10.1 for short, e.g.
|
65 | "ping 10.1" is equivalent to "ping 10.0.0.1".
|
||
66 |
|
||
67 | 31b43002 | Bob Lantz | Currently we wrap the entire network in a 'mininet' object, which
|
68 | constructs a simulated network based on a network topology created
|
||
69 | 80be5642 | Bob Lantz | using a topology object (e.g. LinearTopo) from mininet.topo or
|
70 | mininet.topolib, and a Controller which the switches will connect
|
||
71 | to. Several configuration options are provided for functions such as
|
||
72 | 31b43002 | Bob Lantz | automatically setting MAC addresses, populating the ARP table, or
|
73 | 15b482e3 | Brandon Heller | even running a set of terminals to allow direct interaction with nodes.
|
74 | 31b43002 | Bob Lantz |
|
75 | 80be5642 | Bob Lantz | After the network is created, it can be started using start(), and a
|
76 | variety of useful tasks maybe performed, including basic connectivity
|
||
77 | and bandwidth tests and running the mininet CLI.
|
||
78 | 31b43002 | Bob Lantz |
|
79 | Once the network is up and running, test code can easily get access
|
||
80 | 80be5642 | Bob Lantz | to host and switch objects which can then be used for arbitrary
|
81 | experiments, typically involving running a series of commands on the
|
||
82 | hosts.
|
||
83 | 31b43002 | Bob Lantz |
|
84 | After all desired tests or activities have been completed, the stop()
|
||
85 | method may be called to shut down the network.
|
||
86 | 281f6e59 | Bob Lantz |
|
87 | """
|
||
88 | |||
89 | 8b5062a3 | Brandon Heller | import os |
90 | import re |
||
91 | ec7b211c | Bob Lantz | import select |
92 | 8a034f4f | Brandon Heller | import signal |
93 | 98d4f189 | Bob Lantz | from time import sleep |
94 | bd558875 | Bob Lantz | from itertools import chain |
95 | 98d4f189 | Bob Lantz | |
96 | 496b5f9e | Bob Lantz | from mininet.cli import CLI |
97 | cdeaca86 | Brandon Heller | from mininet.log import info, error, debug, output |
98 | 84a91a14 | Bob Lantz | from mininet.node import Host, OVSKernelSwitch, Controller |
99 | e8146dd1 | Bob Lantz | from mininet.link import Link, Intf |
100 | bcfb3009 | Brandon Heller | from mininet.util import quietRun, fixLimits, numCores, ensureRoot |
101 | 5a8bb489 | Bob Lantz | from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd |
102 | 15b482e3 | Brandon Heller | from mininet.term import cleanUpScreens, makeTerms |
103 | 8b5062a3 | Brandon Heller | |
104 | 39128f8c | Bob Lantz | # Mininet version: should be consistent with README and LICENSE
|
105 | 96952b92 | Bob Lantz | VERSION = "2.1.0+"
|
106 | 39128f8c | Bob Lantz | |
107 | 80a8fa62 | Bob Lantz | class Mininet( object ): |
108 | "Network emulation with hosts spawned in network namespaces."
|
||
109 | |||
110 | eaf5888a | Bob Lantz | def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, |
111 | edf60032 | Brandon Heller | controller=Controller, link=Link, intf=Intf, |
112 | build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8', |
||
113 | inNamespace=False,
|
||
114 | autoSetMacs=False, autoStaticArp=False, autoPinCpus=False, |
||
115 | listenPort=None ):
|
||
116 | 80a8fa62 | Bob Lantz | """Create Mininet object.
|
117 | 019bff82 | Bob Lantz | topo: Topo (topology) object or None
|
118 | a6bcad8f | Bob Lantz | switch: default Switch class
|
119 | host: default Host class/constructor
|
||
120 | controller: default Controller class/constructor
|
||
121 | link: default Link class/constructor
|
||
122 | 216a4b7c | Bob Lantz | intf: default Intf class/constructor
|
123 | 84a91a14 | Bob Lantz | ipBase: base IP address for hosts,
|
124 | 956bf4f2 | Brandon Heller | build: build now from topo?
|
125 | 80a8fa62 | Bob Lantz | xterms: if build now, spawn xterms?
|
126 | cleanup: if build now, cleanup before creating?
|
||
127 | inNamespace: spawn switches and controller in net namespaces?
|
||
128 | 197b083f | Bob Lantz | autoSetMacs: set MAC addrs automatically like IP addresses?
|
129 | ccca871a | Brandon Heller | autoStaticArp: set all-pairs static MAC addrs?
|
130 | 197b083f | Bob Lantz | autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
|
131 | ccca871a | Brandon Heller | listenPort: base listening port to open; will be incremented for
|
132 | each additional switch in the net if inNamespace=False"""
|
||
133 | 216a4b7c | Bob Lantz | self.topo = topo
|
134 | 8b5062a3 | Brandon Heller | self.switch = switch
|
135 | self.host = host
|
||
136 | self.controller = controller
|
||
137 | a6bcad8f | Bob Lantz | self.link = link
|
138 | 216a4b7c | Bob Lantz | self.intf = intf
|
139 | 82f483f5 | Bob Lantz | self.ipBase = ipBase
|
140 | 5a8bb489 | Bob Lantz | self.ipBaseNum, self.prefixLen = netParse( self.ipBase ) |
141 | self.nextIP = 1 # start for address allocation |
||
142 | 80a8fa62 | Bob Lantz | self.inNamespace = inNamespace
|
143 | 376bcba4 | Brandon Heller | self.xterms = xterms
|
144 | self.cleanup = cleanup
|
||
145 | 80a8fa62 | Bob Lantz | self.autoSetMacs = autoSetMacs
|
146 | self.autoStaticArp = autoStaticArp
|
||
147 | 197b083f | Bob Lantz | self.autoPinCpus = autoPinCpus
|
148 | self.numCores = numCores()
|
||
149 | self.nextCore = 0 # next core for pinning hosts to CPUs |
||
150 | ccca871a | Brandon Heller | self.listenPort = listenPort
|
151 | 8b5062a3 | Brandon Heller | |
152 | 019bff82 | Bob Lantz | self.hosts = []
|
153 | self.switches = []
|
||
154 | self.controllers = []
|
||
155 | 84a91a14 | Bob Lantz | |
156 | cf6f6704 | Bob Lantz | self.nameToNode = {} # name to Node (Host/Switch) objects |
157 | 84a91a14 | Bob Lantz | |
158 | cf6f6704 | Bob Lantz | self.terms = [] # list of spawned xterm processes |
159 | 8a034f4f | Brandon Heller | |
160 | 8e3699ec | Bob Lantz | Mininet.init() # Initialize Mininet if necessary
|
161 | b055728f | Brandon Heller | |
162 | 99c035d9 | Bob Lantz | self.built = False |
163 | if topo and build: |
||
164 | d44a5843 | Bob Lantz | self.build()
|
165 | 8b5062a3 | Brandon Heller | |
166 | 5a8bb489 | Bob Lantz | def addHost( self, name, cls=None, **params ): |
167 | 80a8fa62 | Bob Lantz | """Add host.
|
168 | 019bff82 | Bob Lantz | name: name of host to add
|
169 | 5a8bb489 | Bob Lantz | cls: custom host class/constructor (optional)
|
170 | 84a91a14 | Bob Lantz | params: parameters for host
|
171 | 019bff82 | Bob Lantz | returns: added host"""
|
172 | 5a8bb489 | Bob Lantz | # Default IP and MAC addresses
|
173 | defaults = { 'ip': ipAdd( self.nextIP, |
||
174 | ipBaseNum=self.ipBaseNum,
|
||
175 | 0f832c92 | Bob Lantz | prefixLen=self.prefixLen ) +
|
176 | '/%s' % self.prefixLen } |
||
177 | 5a8bb489 | Bob Lantz | if self.autoSetMacs: |
178 | defaults[ 'mac'] = macColonHex( self.nextIP ) |
||
179 | 197b083f | Bob Lantz | if self.autoPinCpus: |
180 | defaults[ 'cores' ] = self.nextCore |
||
181 | self.nextCore = ( self.nextCore + 1 ) % self.numCores |
||
182 | 5a8bb489 | Bob Lantz | self.nextIP += 1 |
183 | defaults.update( params ) |
||
184 | if not cls: |
||
185 | 9005ce32 | Bob Lantz | cls = self.host
|
186 | 5a8bb489 | Bob Lantz | h = cls( name, **defaults ) |
187 | a6bcad8f | Bob Lantz | self.hosts.append( h )
|
188 | self.nameToNode[ name ] = h
|
||
189 | return h
|
||
190 | |||
191 | 5a8bb489 | Bob Lantz | def addSwitch( self, name, cls=None, **params ): |
192 | 80a8fa62 | Bob Lantz | """Add switch.
|
193 | 019bff82 | Bob Lantz | name: name of switch to add
|
194 | 5a8bb489 | Bob Lantz | cls: custom switch class/constructor (optional)
|
195 | ccca871a | Brandon Heller | returns: added switch
|
196 | 84a91a14 | Bob Lantz | side effect: increments listenPort ivar ."""
|
197 | 14ff3ad3 | Bob Lantz | defaults = { 'listenPort': self.listenPort, |
198 | a6bcad8f | Bob Lantz | 'inNamespace': self.inNamespace } |
199 | 84a91a14 | Bob Lantz | defaults.update( params ) |
200 | 5a8bb489 | Bob Lantz | if not cls: |
201 | cls = self.switch
|
||
202 | sw = cls( name, **defaults ) |
||
203 | 0a9358c9 | Brandon Heller | if not self.inNamespace and self.listenPort: |
204 | ccca871a | Brandon Heller | self.listenPort += 1 |
205 | 019bff82 | Bob Lantz | self.switches.append( sw )
|
206 | self.nameToNode[ name ] = sw
|
||
207 | return sw
|
||
208 | 80a8fa62 | Bob Lantz | |
209 | a6bcad8f | Bob Lantz | def addController( self, name='c0', controller=None, **params ): |
210 | 80a8fa62 | Bob Lantz | """Add controller.
|
211 | e30f2c99 | Bob Lantz | controller: Controller class"""
|
212 | 35029978 | Bob Lantz | # Get controller class
|
213 | e30f2c99 | Bob Lantz | if not controller: |
214 | controller = self.controller
|
||
215 | 35029978 | Bob Lantz | # Construct new controller if one is not given
|
216 | 15146d90 | Brian O'Connor | if isinstance(name, Controller): |
217 | controller_new = name |
||
218 | 35029978 | Bob Lantz | # Pylint thinks controller is a str()
|
219 | # pylint: disable=E1103
|
||
220 | 15146d90 | Brian O'Connor | name = controller_new.name |
221 | 35029978 | Bob Lantz | # pylint: enable=E1103
|
222 | 15146d90 | Brian O'Connor | else:
|
223 | controller_new = controller( name, **params ) |
||
224 | 35029978 | Bob Lantz | # Add new controller to net
|
225 | f32a5468 | Brandon Heller | if controller_new: # allow controller-less setups |
226 | self.controllers.append( controller_new )
|
||
227 | 82b72072 | Bob Lantz | self.nameToNode[ name ] = controller_new
|
228 | eaf5888a | Bob Lantz | return controller_new
|
229 | e8146dd1 | Bob Lantz | |
230 | bd558875 | Bob Lantz | # BL: We now have four ways to look up nodes
|
231 | # This may (should?) be cleaned up in the future.
|
||
232 | 548580d8 | Bob Lantz | def getNodeByName( self, *args ): |
233 | "Return node(s) with given name(s)"
|
||
234 | if len( args ) == 1: |
||
235 | return self.nameToNode[ args[ 0 ] ] |
||
236 | return [ self.nameToNode[ n ] for n in args ] |
||
237 | 8b5062a3 | Brandon Heller | |
238 | 089e8130 | Bob Lantz | def get( self, *args ): |
239 | "Convenience alias for getNodeByName"
|
||
240 | return self.getNodeByName( *args ) |
||
241 | |||
242 | bd558875 | Bob Lantz | # Even more convenient syntax for node lookup and iteration
|
243 | 15146d90 | Brian O'Connor | def __getitem__( self, key ): |
244 | bd558875 | Bob Lantz | """net [ name ] operator: Return node(s) with given name(s)"""
|
245 | 15146d90 | Brian O'Connor | return self.nameToNode[ key ] |
246 | bd558875 | Bob Lantz | |
247 | def __iter__( self ): |
||
248 | 09b12391 | Brian O'Connor | "return iterator over node names"
|
249 | 9281719d | Brian O'Connor | for node in chain( self.hosts, self.switches, self.controllers ): |
250 | yield node.name
|
||
251 | bd558875 | Bob Lantz | |
252 | 8e04a9f8 | Brian O'Connor | def __len__( self ): |
253 | 9281719d | Brian O'Connor | "returns number of nodes in net"
|
254 | 35029978 | Bob Lantz | return ( len( self.hosts ) + len( self.switches ) + |
255 | len( self.controllers ) ) |
||
256 | 8e04a9f8 | Brian O'Connor | |
257 | def __contains__( self, item ): |
||
258 | 9281719d | Brian O'Connor | "returns True if net contains named node"
|
259 | 15146d90 | Brian O'Connor | return item in self.nameToNode |
260 | 8e04a9f8 | Brian O'Connor | |
261 | def keys( self ): |
||
262 | 9281719d | Brian O'Connor | "return a list of all node names or net's keys"
|
263 | 15146d90 | Brian O'Connor | return list( self ) |
264 | 8e04a9f8 | Brian O'Connor | |
265 | def values( self ): |
||
266 | 9281719d | Brian O'Connor | "return a list of all nodes or net's values"
|
267 | 15146d90 | Brian O'Connor | return [ self[name] for name in self ] |
268 | 8e04a9f8 | Brian O'Connor | |
269 | def items( self ): |
||
270 | 9281719d | Brian O'Connor | "return (key,value) tuple list for every node in net"
|
271 | 8e04a9f8 | Brian O'Connor | return zip( self.keys(), self.values() ) |
272 | |||
273 | e8146dd1 | Bob Lantz | def addLink( self, node1, node2, port1=None, port2=None, |
274 | 5a8bb489 | Bob Lantz | cls=None, **params ):
|
275 | e8146dd1 | Bob Lantz | """"Add a link from node1 to node2
|
276 | node1: source node
|
||
277 | node2: dest node
|
||
278 | port1: source port
|
||
279 | port2: dest port
|
||
280 | returns: link object"""
|
||
281 | defaults = { 'port1': port1,
|
||
282 | 'port2': port2,
|
||
283 | 'intf': self.intf } |
||
284 | defaults.update( params ) |
||
285 | 5a8bb489 | Bob Lantz | if not cls: |
286 | cls = self.link
|
||
287 | e8146dd1 | Bob Lantz | return cls( node1, node2, **defaults )
|
288 | 5a8bb489 | Bob Lantz | |
289 | 80be5642 | Bob Lantz | def configHosts( self ): |
290 | 80a8fa62 | Bob Lantz | "Configure a set of hosts."
|
291 | 019bff82 | Bob Lantz | for host in self.hosts: |
292 | 216a4b7c | Bob Lantz | info( host.name + ' ' )
|
293 | e1ca7196 | Bob Lantz | intf = host.defaultIntf() |
294 | if intf:
|
||
295 | dd21df3c | Bob Lantz | host.configDefault() |
296 | e1ca7196 | Bob Lantz | else:
|
297 | # Don't configure nonexistent intf
|
||
298 | host.configDefault( ip=None, mac=None ) |
||
299 | 8b5062a3 | Brandon Heller | # You're low priority, dude!
|
300 | 84a91a14 | Bob Lantz | # BL: do we want to do this here or not?
|
301 | # May not make sense if we have CPU lmiting...
|
||
302 | # quietRun( 'renice +18 -p ' + repr( host.pid ) )
|
||
303 | a9c28885 | Bob Lantz | # This may not be the right place to do this, but
|
304 | # it needs to be done somewhere.
|
||
305 | host.cmd( 'ifconfig lo up' )
|
||
306 | d5886525 | Bob Lantz | info( '\n' )
|
307 | 80a8fa62 | Bob Lantz | |
308 | 84a91a14 | Bob Lantz | def buildFromTopo( self, topo=None ): |
309 | 019bff82 | Bob Lantz | """Build mininet from a topology object
|
310 | 80a8fa62 | Bob Lantz | At the end of this function, everything should be connected
|
311 | and up."""
|
||
312 | d44a5843 | Bob Lantz | |
313 | # Possibly we should clean up here and/or validate
|
||
314 | # the topo
|
||
315 | 376bcba4 | Brandon Heller | if self.cleanup: |
316 | d44a5843 | Bob Lantz | pass
|
317 | |||
318 | d5886525 | Bob Lantz | info( '*** Creating network\n' )
|
319 | 84a91a14 | Bob Lantz | |
320 | 15146d90 | Brian O'Connor | if not self.controllers and self.controller: |
321 | 84a91a14 | Bob Lantz | # Add a default controller
|
322 | info( '*** Adding controller\n' )
|
||
323 | f58f83c0 | Bob Lantz | classes = self.controller
|
324 | if type( classes ) is not list: |
||
325 | classes = [ classes ] |
||
326 | for i, cls in enumerate( classes ): |
||
327 | self.addController( 'c%d' % i, cls ) |
||
328 | 84a91a14 | Bob Lantz | |
329 | d5886525 | Bob Lantz | info( '*** Adding hosts:\n' )
|
330 | 5a8bb489 | Bob Lantz | for hostName in topo.hosts(): |
331 | self.addHost( hostName, **topo.nodeInfo( hostName ) )
|
||
332 | info( hostName + ' ' )
|
||
333 | 84a91a14 | Bob Lantz | |
334 | d5886525 | Bob Lantz | info( '\n*** Adding switches:\n' )
|
335 | 5a8bb489 | Bob Lantz | for switchName in topo.switches(): |
336 | self.addSwitch( switchName, **topo.nodeInfo( switchName) )
|
||
337 | info( switchName + ' ' )
|
||
338 | 84a91a14 | Bob Lantz | |
339 | 724f1144 | Bob Lantz | info( '\n*** Adding links:\n' )
|
340 | 5a8bb489 | Bob Lantz | for srcName, dstName in topo.links(sort=True): |
341 | src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ] |
||
342 | e8146dd1 | Bob Lantz | params = topo.linkInfo( srcName, dstName ) |
343 | 5a8bb489 | Bob Lantz | srcPort, dstPort = topo.port( srcName, dstName ) |
344 | e1246c37 | Bob Lantz | self.addLink( src, dst, srcPort, dstPort, **params )
|
345 | 5a8bb489 | Bob Lantz | info( '(%s, %s) ' % ( src.name, dst.name ) )
|
346 | 84a91a14 | Bob Lantz | |
347 | d5886525 | Bob Lantz | info( '\n' )
|
348 | 80a8fa62 | Bob Lantz | |
349 | 84a91a14 | Bob Lantz | def configureControlNetwork( self ): |
350 | 14ff3ad3 | Bob Lantz | "Control net config hook: override in subclass"
|
351 | raise Exception( 'configureControlNetwork: ' |
||
352 | edf60032 | Brandon Heller | 'should be overriden in subclass', self ) |
353 | 84a91a14 | Bob Lantz | |
354 | d44a5843 | Bob Lantz | def build( self ): |
355 | "Build mininet."
|
||
356 | if self.topo: |
||
357 | self.buildFromTopo( self.topo ) |
||
358 | 824afb84 | Rémy Léone | if self.inNamespace: |
359 | d44a5843 | Bob Lantz | self.configureControlNetwork()
|
360 | d5886525 | Bob Lantz | info( '*** Configuring hosts\n' )
|
361 | 80be5642 | Bob Lantz | self.configHosts()
|
362 | 376bcba4 | Brandon Heller | if self.xterms: |
363 | 15b482e3 | Brandon Heller | self.startTerms()
|
364 | 80a8fa62 | Bob Lantz | if self.autoStaticArp: |
365 | self.staticArp()
|
||
366 | 99c035d9 | Bob Lantz | self.built = True |
367 | 80a8fa62 | Bob Lantz | |
368 | 15b482e3 | Brandon Heller | def startTerms( self ): |
369 | "Start a terminal for each node."
|
||
370 | 4316be95 | Brian O'Connor | if 'DISPLAY' not in os.environ: |
371 | error( "Error starting terms: Cannot connect to display\n" )
|
||
372 | return
|
||
373 | 15b482e3 | Brandon Heller | info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] ) |
374 | 8a034f4f | Brandon Heller | cleanUpScreens() |
375 | 15b482e3 | Brandon Heller | self.terms += makeTerms( self.controllers, 'controller' ) |
376 | self.terms += makeTerms( self.switches, 'switch' ) |
||
377 | self.terms += makeTerms( self.hosts, 'host' ) |
||
378 | 8a034f4f | Brandon Heller | |
379 | 80a8fa62 | Bob Lantz | def stopXterms( self ): |
380 | "Kill each xterm."
|
||
381 | 8a034f4f | Brandon Heller | for term in self.terms: |
382 | 80a8fa62 | Bob Lantz | os.kill( term.pid, signal.SIGKILL ) |
383 | 8a034f4f | Brandon Heller | cleanUpScreens() |
384 | 8b5062a3 | Brandon Heller | |
385 | 80a8fa62 | Bob Lantz | def staticArp( self ): |
386 | "Add all-pairs ARP entries to remove the need to handle broadcast."
|
||
387 | 019bff82 | Bob Lantz | for src in self.hosts: |
388 | for dst in self.hosts: |
||
389 | 376bcba4 | Brandon Heller | if src != dst:
|
390 | 1bda2d21 | Bob Lantz | src.setARP( ip=dst.IP(), mac=dst.MAC() ) |
391 | 376bcba4 | Brandon Heller | |
392 | 80a8fa62 | Bob Lantz | def start( self ): |
393 | 99c035d9 | Bob Lantz | "Start controller and switches."
|
394 | if not self.built: |
||
395 | self.build()
|
||
396 | d5886525 | Bob Lantz | info( '*** Starting controller\n' )
|
397 | 019bff82 | Bob Lantz | for controller in self.controllers: |
398 | controller.start() |
||
399 | info( '*** Starting %s switches\n' % len( self.switches ) ) |
||
400 | for switch in self.switches: |
||
401 | efc9a01c | Bob Lantz | info( switch.name + ' ')
|
402 | 80a8fa62 | Bob Lantz | switch.start( self.controllers )
|
403 | d5886525 | Bob Lantz | info( '\n' )
|
404 | 80a8fa62 | Bob Lantz | |
405 | def stop( self ): |
||
406 | d5886525 | Bob Lantz | "Stop the controller(s), switches and hosts"
|
407 | 8a034f4f | Brandon Heller | if self.terms: |
408 | d5886525 | Bob Lantz | info( '*** Stopping %i terms\n' % len( self.terms ) ) |
409 | 80a8fa62 | Bob Lantz | self.stopXterms()
|
410 | 019bff82 | Bob Lantz | info( '*** Stopping %i switches\n' % len( self.switches ) ) |
411 | for switch in self.switches: |
||
412 | 84a91a14 | Bob Lantz | info( switch.name + ' ' )
|
413 | 8b5062a3 | Brandon Heller | switch.stop() |
414 | d5886525 | Bob Lantz | info( '\n' )
|
415 | 10be691b | Bob Lantz | info( '*** Stopping %i hosts\n' % len( self.hosts ) ) |
416 | for host in self.hosts: |
||
417 | info( host.name + ' ' )
|
||
418 | host.terminate() |
||
419 | info( '\n' )
|
||
420 | 019bff82 | Bob Lantz | info( '*** Stopping %i controllers\n' % len( self.controllers ) ) |
421 | for controller in self.controllers: |
||
422 | 84a91a14 | Bob Lantz | info( controller.name + ' ' )
|
423 | 019bff82 | Bob Lantz | controller.stop() |
424 | 84a91a14 | Bob Lantz | info( '\n*** Done\n' )
|
425 | 8b5062a3 | Brandon Heller | |
426 | 67516aa4 | Bob Lantz | def run( self, test, *args, **kwargs ): |
427 | 80a8fa62 | Bob Lantz | "Perform a complete start/test/stop cycle."
|
428 | 8b5062a3 | Brandon Heller | self.start()
|
429 | d5886525 | Bob Lantz | info( '*** Running test\n' )
|
430 | 67516aa4 | Bob Lantz | result = test( *args, **kwargs ) |
431 | 8b5062a3 | Brandon Heller | self.stop()
|
432 | return result
|
||
433 | |||
434 | 259d7133 | Bob Lantz | def monitor( self, hosts=None, timeoutms=-1 ): |
435 | ec7b211c | Bob Lantz | """Monitor a set of hosts (or all hosts by default),
|
436 | and return their output, a line at a time.
|
||
437 | 259d7133 | Bob Lantz | hosts: (optional) set of hosts to monitor
|
438 | timeoutms: (optional) timeout value in ms
|
||
439 | returns: iterator which returns host, line"""
|
||
440 | ec7b211c | Bob Lantz | if hosts is None: |
441 | hosts = self.hosts
|
||
442 | poller = select.poll() |
||
443 | cf6f6704 | Bob Lantz | Node = hosts[ 0 ] # so we can call class method fdToNode |
444 | ec7b211c | Bob Lantz | for host in hosts: |
445 | poller.register( host.stdout ) |
||
446 | while True: |
||
447 | 259d7133 | Bob Lantz | ready = poller.poll( timeoutms ) |
448 | ec7b211c | Bob Lantz | for fd, event in ready: |
449 | host = Node.fdToNode( fd ) |
||
450 | if event & select.POLLIN:
|
||
451 | line = host.readline() |
||
452 | 259d7133 | Bob Lantz | if line is not None: |
453 | ec7b211c | Bob Lantz | yield host, line
|
454 | 259d7133 | Bob Lantz | # Return if non-blocking
|
455 | if not ready and timeoutms >= 0: |
||
456 | yield None, None |
||
457 | ec7b211c | Bob Lantz | |
458 | 84a91a14 | Bob Lantz | # XXX These test methods should be moved out of this class.
|
459 | # Probably we should create a tests.py for them
|
||
460 | |||
461 | 8b5062a3 | Brandon Heller | @staticmethod
|
462 | 80a8fa62 | Bob Lantz | def _parsePing( pingOutput ): |
463 | "Parse ping output and return packets sent, received."
|
||
464 | 3fac5a43 | Brandon Heller | # Check for downed link
|
465 | if 'connect: Network is unreachable' in pingOutput: |
||
466 | 824afb84 | Rémy Léone | return 1, 0 |
467 | 8b5062a3 | Brandon Heller | r = r'(\d+) packets transmitted, (\d+) received'
|
468 | 80a8fa62 | Bob Lantz | m = re.search( r, pingOutput ) |
469 | 7a506047 | Brandon Heller | if m is None: |
470 | d5886525 | Bob Lantz | error( '*** Error: could not parse ping output: %s\n' %
|
471 | 2e089b5e | Brandon Heller | pingOutput ) |
472 | 824afb84 | Rémy Léone | return 1, 0 |
473 | 80a8fa62 | Bob Lantz | sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) |
474 | 8b5062a3 | Brandon Heller | return sent, received
|
475 | |||
476 | 1f1d590c | Brandon Heller | def ping( self, hosts=None, timeout=None ): |
477 | 80a8fa62 | Bob Lantz | """Ping between all specified hosts.
|
478 | 019bff82 | Bob Lantz | hosts: list of hosts
|
479 | 1f1d590c | Brandon Heller | timeout: time to wait for a response, as string
|
480 | 80a8fa62 | Bob Lantz | returns: ploss packet loss percentage"""
|
481 | d44a5843 | Bob Lantz | # should we check if running?
|
482 | 8b5062a3 | Brandon Heller | packets = 0
|
483 | lost = 0
|
||
484 | ploss = None
|
||
485 | if not hosts: |
||
486 | 019bff82 | Bob Lantz | hosts = self.hosts
|
487 | cdeaca86 | Brandon Heller | output( '*** Ping: testing ping reachability\n' )
|
488 | 019bff82 | Bob Lantz | for node in hosts: |
489 | cdeaca86 | Brandon Heller | output( '%s -> ' % node.name )
|
490 | 019bff82 | Bob Lantz | for dest in hosts: |
491 | 8b5062a3 | Brandon Heller | if node != dest:
|
492 | 1f1d590c | Brandon Heller | opts = ''
|
493 | if timeout:
|
||
494 | opts = '-W %s' % timeout
|
||
495 | result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
|
||
496 | 80a8fa62 | Bob Lantz | sent, received = self._parsePing( result )
|
497 | 8b5062a3 | Brandon Heller | packets += sent |
498 | if received > sent:
|
||
499 | d5886525 | Bob Lantz | error( '*** Error: received too many packets' )
|
500 | error( '%s' % result )
|
||
501 | 80a8fa62 | Bob Lantz | node.cmdPrint( 'route' )
|
502 | exit( 1 ) |
||
503 | 8b5062a3 | Brandon Heller | lost += sent - received |
504 | cdeaca86 | Brandon Heller | output( ( '%s ' % dest.name ) if received else 'X ' ) |
505 | output( '\n' )
|
||
506 | c188bee3 | Brian O'Connor | if packets > 0: |
507 | 8b5062a3 | Brandon Heller | ploss = 100 * lost / packets
|
508 | fec98e27 | Brian O'Connor | received = packets - lost |
509 | output( "*** Results: %i%% dropped (%d/%d received)\n" %
|
||
510 | ( ploss, received, packets ) ) |
||
511 | c188bee3 | Brian O'Connor | else:
|
512 | ploss = 0
|
||
513 | output( "*** Warning: No packets sent\n" )
|
||
514 | 8b5062a3 | Brandon Heller | return ploss
|
515 | |||
516 | 1f1d590c | Brandon Heller | @staticmethod
|
517 | def _parsePingFull( pingOutput ): |
||
518 | "Parse ping output and return all data."
|
||
519 | 1ecc63df | Brian O'Connor | errorTuple = (1, 0, 0, 0, 0, 0) |
520 | 1f1d590c | Brandon Heller | # Check for downed link
|
521 | 1ecc63df | Brian O'Connor | r = r'[uU]nreachable'
|
522 | m = re.search( r, pingOutput ) |
||
523 | if m is not None: |
||
524 | return errorTuple
|
||
525 | 1f1d590c | Brandon Heller | r = r'(\d+) packets transmitted, (\d+) received'
|
526 | m = re.search( r, pingOutput ) |
||
527 | if m is None: |
||
528 | error( '*** Error: could not parse ping output: %s\n' %
|
||
529 | pingOutput ) |
||
530 | 1ecc63df | Brian O'Connor | return errorTuple
|
531 | 1f1d590c | Brandon Heller | sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) |
532 | r = r'rtt min/avg/max/mdev = '
|
||
533 | r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
|
||
534 | m = re.search( r, pingOutput ) |
||
535 | 1ecc63df | Brian O'Connor | if m is None: |
536 | error( '*** Error: could not parse ping output: %s\n' %
|
||
537 | pingOutput ) |
||
538 | return errorTuple
|
||
539 | 1f1d590c | Brandon Heller | rttmin = float( m.group( 1 ) ) |
540 | rttavg = float( m.group( 2 ) ) |
||
541 | rttmax = float( m.group( 3 ) ) |
||
542 | rttdev = float( m.group( 4 ) ) |
||
543 | return sent, received, rttmin, rttavg, rttmax, rttdev
|
||
544 | |||
545 | def pingFull( self, hosts=None, timeout=None ): |
||
546 | """Ping between all specified hosts and return all data.
|
||
547 | hosts: list of hosts
|
||
548 | timeout: time to wait for a response, as string
|
||
549 | returns: all ping data; see function body."""
|
||
550 | # should we check if running?
|
||
551 | # Each value is a tuple: (src, dsd, [all ping outputs])
|
||
552 | all_outputs = [] |
||
553 | if not hosts: |
||
554 | hosts = self.hosts
|
||
555 | output( '*** Ping: testing ping reachability\n' )
|
||
556 | for node in hosts: |
||
557 | output( '%s -> ' % node.name )
|
||
558 | for dest in hosts: |
||
559 | if node != dest:
|
||
560 | opts = ''
|
||
561 | if timeout:
|
||
562 | opts = '-W %s' % timeout
|
||
563 | result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
|
||
564 | outputs = self._parsePingFull( result )
|
||
565 | sent, received, rttmin, rttavg, rttmax, rttdev = outputs |
||
566 | all_outputs.append( (node, dest, outputs) ) |
||
567 | output( ( '%s ' % dest.name ) if received else 'X ' ) |
||
568 | output( '\n' )
|
||
569 | output( "*** Results: \n" )
|
||
570 | for outputs in all_outputs: |
||
571 | src, dest, ping_outputs = outputs |
||
572 | sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs |
||
573 | output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
|
||
574 | output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
|
||
575 | (rttmin, rttavg, rttmax, rttdev) ) |
||
576 | return all_outputs
|
||
577 | |||
578 | 80a8fa62 | Bob Lantz | def pingAll( self ): |
579 | """Ping between all hosts.
|
||
580 | returns: ploss packet loss percentage"""
|
||
581 | 1bb4412f | Brandon Heller | return self.ping() |
582 | eeb9cb3c | Brandon Heller | |
583 | 80a8fa62 | Bob Lantz | def pingPair( self ): |
584 | """Ping between first two hosts, useful for testing.
|
||
585 | returns: ploss packet loss percentage"""
|
||
586 | 019bff82 | Bob Lantz | hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ] |
587 | 80a8fa62 | Bob Lantz | return self.ping( hosts=hosts ) |
588 | eeb9cb3c | Brandon Heller | |
589 | 1f1d590c | Brandon Heller | def pingAllFull( self ): |
590 | """Ping between all hosts.
|
||
591 | returns: ploss packet loss percentage"""
|
||
592 | return self.pingFull() |
||
593 | |||
594 | def pingPairFull( self ): |
||
595 | """Ping between first two hosts, useful for testing.
|
||
596 | returns: ploss packet loss percentage"""
|
||
597 | hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ] |
||
598 | return self.pingFull( hosts=hosts ) |
||
599 | |||
600 | eeb9cb3c | Brandon Heller | @staticmethod
|
601 | 80a8fa62 | Bob Lantz | def _parseIperf( iperfOutput ): |
602 | """Parse iperf output and return bandwidth.
|
||
603 | iperfOutput: string
|
||
604 | returns: result string"""
|
||
605 | eeb9cb3c | Brandon Heller | r = r'([\d\.]+ \w+/sec)'
|
606 | ad2fda25 | Bob Lantz | m = re.findall( r, iperfOutput ) |
607 | eeb9cb3c | Brandon Heller | if m:
|
608 | ad2fda25 | Bob Lantz | return m[-1] |
609 | eeb9cb3c | Brandon Heller | else:
|
610 | ad2fda25 | Bob Lantz | # was: raise Exception(...)
|
611 | error( 'could not parse iperf output: ' + iperfOutput )
|
||
612 | return '' |
||
613 | 80a8fa62 | Bob Lantz | |
614 | 216a4b7c | Bob Lantz | # XXX This should be cleaned up
|
615 | |||
616 | 1a40cd04 | Brandon Heller | def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ): |
617 | 80a8fa62 | Bob Lantz | """Run iperf between two hosts.
|
618 | 019bff82 | Bob Lantz | hosts: list of hosts; if None, uses opposite hosts
|
619 | 80a8fa62 | Bob Lantz | l4Type: string, one of [ TCP, UDP ]
|
620 | returns: results two-element array of server and client speeds"""
|
||
621 | ad2fda25 | Bob Lantz | if not quietRun( 'which telnet' ): |
622 | error( 'Cannot find telnet in $PATH - required for iperf test' )
|
||
623 | return
|
||
624 | eeb9cb3c | Brandon Heller | if not hosts: |
625 | 019bff82 | Bob Lantz | hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ] |
626 | eeb9cb3c | Brandon Heller | else:
|
627 | 80a8fa62 | Bob Lantz | assert len( hosts ) == 2 |
628 | ec7b211c | Bob Lantz | client, server = hosts |
629 | cdeaca86 | Brandon Heller | output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' ) |
630 | ec7b211c | Bob Lantz | output( "%s and %s\n" % ( client.name, server.name ) )
|
631 | server.cmd( 'killall -9 iperf' )
|
||
632 | 80a8fa62 | Bob Lantz | iperfArgs = 'iperf '
|
633 | bwArgs = ''
|
||
634 | if l4Type == 'UDP': |
||
635 | iperfArgs += '-u '
|
||
636 | bwArgs = '-b ' + udpBw + ' ' |
||
637 | elif l4Type != 'TCP': |
||
638 | raise Exception( 'Unexpected l4 type: %s' % l4Type ) |
||
639 | ec7b211c | Bob Lantz | server.sendCmd( iperfArgs + '-s', printPid=True ) |
640 | servout = ''
|
||
641 | while server.lastPid is None: |
||
642 | servout += server.monitor() |
||
643 | 8856d284 | Bob Lantz | if l4Type == 'TCP': |
644 | while 'Connected' not in client.cmd( |
||
645 | 615ebb7a | Brandon Heller | 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
|
646 | 8856d284 | Bob Lantz | output('waiting for iperf to start up...')
|
647 | sleep(.5)
|
||
648 | ec7b211c | Bob Lantz | cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' + |
649 | edf60032 | Brandon Heller | bwArgs ) |
650 | ec7b211c | Bob Lantz | debug( 'Client output: %s\n' % cliout )
|
651 | server.sendInt() |
||
652 | servout += server.waitOutput() |
||
653 | debug( 'Server output: %s\n' % servout )
|
||
654 | result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ] |
||
655 | 80a8fa62 | Bob Lantz | if l4Type == 'UDP': |
656 | result.insert( 0, udpBw )
|
||
657 | cdeaca86 | Brandon Heller | output( '*** Results: %s\n' % result )
|
658 | eeb9cb3c | Brandon Heller | return result
|
659 | |||
660 | fcd01592 | Brandon Heller | def runCpuLimitTest( self, cpu, duration=5 ): |
661 | """run CPU limit test with 'while true' processes.
|
||
662 | cpu: desired CPU fraction of each host
|
||
663 | duration: test duration in seconds
|
||
664 | returns a single list of measured CPU fractions as floats.
|
||
665 | """
|
||
666 | pct = cpu * 100
|
||
667 | info('*** Testing CPU %.0f%% bandwidth limit\n' % pct)
|
||
668 | hosts = self.hosts
|
||
669 | for h in hosts: |
||
670 | h.cmd( 'while true; do a=1; done &' )
|
||
671 | pids = [h.cmd( 'echo $!' ).strip() for h in hosts] |
||
672 | pids_str = ",".join(["%s" % pid for pid in pids]) |
||
673 | cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str
|
||
674 | # It's a shame that this is what pylint prefers
|
||
675 | outputs = [] |
||
676 | for _ in range( duration ): |
||
677 | sleep( 1 )
|
||
678 | outputs.append( quietRun( cmd ).strip() ) |
||
679 | for h in hosts: |
||
680 | h.cmd( 'kill %1' )
|
||
681 | cpu_fractions = [] |
||
682 | for test_output in outputs: |
||
683 | # Split by line. Ignore first line, which looks like this:
|
||
684 | # PID %CPU COMMAND\n
|
||
685 | for line in test_output.split('\n')[1:]: |
||
686 | 03c3123b | Brandon Heller | r = r'\d+\s*(\d+\.\d+)'
|
687 | fcd01592 | Brandon Heller | m = re.search( r, line ) |
688 | if m is None: |
||
689 | error( '*** Error: could not extract CPU fraction: %s\n' %
|
||
690 | line ) |
||
691 | return None |
||
692 | cpu_fractions.append( float( m.group( 1 ) ) ) |
||
693 | output( '*** Results: %s\n' % cpu_fractions )
|
||
694 | return cpu_fractions
|
||
695 | |||
696 | 84a91a14 | Bob Lantz | # BL: I think this can be rewritten now that we have
|
697 | # a real link class.
|
||
698 | c70aab0a | Bob Lantz | def configLinkStatus( self, src, dst, status ): |
699 | """Change status of src <-> dst links.
|
||
700 | src: node name
|
||
701 | dst: node name
|
||
702 | status: string {up, down}"""
|
||
703 | 8d3c2859 | Brandon Heller | if src not in self.nameToNode: |
704 | error( 'src not in network: %s\n' % src )
|
||
705 | elif dst not in self.nameToNode: |
||
706 | error( 'dst not in network: %s\n' % dst )
|
||
707 | else:
|
||
708 | 8856d284 | Bob Lantz | if type( src ) is str: |
709 | src = self.nameToNode[ src ]
|
||
710 | if type( dst ) is str: |
||
711 | dst = self.nameToNode[ dst ]
|
||
712 | connections = src.connectionsTo( dst ) |
||
713 | fb2f6523 | Bob Lantz | if len( connections ) == 0: |
714 | 8d3c2859 | Brandon Heller | error( 'src and dst not connected: %s %s\n' % ( src, dst) )
|
715 | fb2f6523 | Bob Lantz | for srcIntf, dstIntf in connections: |
716 | 8856d284 | Bob Lantz | result = srcIntf.ifconfig( status ) |
717 | 8d3c2859 | Brandon Heller | if result:
|
718 | error( 'link src status change failed: %s\n' % result )
|
||
719 | 8856d284 | Bob Lantz | result = dstIntf.ifconfig( status ) |
720 | 8d3c2859 | Brandon Heller | if result:
|
721 | error( 'link dst status change failed: %s\n' % result )
|
||
722 | |||
723 | 80a8fa62 | Bob Lantz | def interact( self ): |
724 | "Start network and run our simple CLI."
|
||
725 | eeb9cb3c | Brandon Heller | self.start()
|
726 | 496b5f9e | Bob Lantz | result = CLI( self )
|
727 | eeb9cb3c | Brandon Heller | self.stop()
|
728 | return result
|
||
729 | 82b72072 | Bob Lantz | |
730 | 8e3699ec | Bob Lantz | inited = False
|
731 | 14ff3ad3 | Bob Lantz | |
732 | 8e3699ec | Bob Lantz | @classmethod
|
733 | def init( cls ): |
||
734 | "Initialize Mininet"
|
||
735 | if cls.inited:
|
||
736 | return
|
||
737 | bcfb3009 | Brandon Heller | ensureRoot() |
738 | 8e3699ec | Bob Lantz | fixLimits() |
739 | cls.inited = True
|
||
740 | |||
741 | 82b72072 | Bob Lantz | |
742 | 84a91a14 | Bob Lantz | class MininetWithControlNet( Mininet ): |
743 | |||
744 | """Control network support:
|
||
745 |
|
||
746 | Create an explicit control network. Currently this is only
|
||
747 | used/usable with the user datapath.
|
||
748 |
|
||
749 | Notes:
|
||
750 |
|
||
751 | 1. If the controller and switches are in the same (e.g. root)
|
||
752 | namespace, they can just use the loopback connection.
|
||
753 |
|
||
754 | 2. If we can get unix domain sockets to work, we can use them
|
||
755 | instead of an explicit control network.
|
||
756 |
|
||
757 | 3. Instead of routing, we could bridge or use 'in-band' control.
|
||
758 |
|
||
759 | 4. Even if we dispense with this in general, it could still be
|
||
760 | useful for people who wish to simulate a separate control
|
||
761 | network (since real networks may need one!)
|
||
762 |
|
||
763 | 5. Basically nobody ever used this code, so it has been moved
|
||
764 | 14ff3ad3 | Bob Lantz | into its own class.
|
765 |
|
||
766 | 6. Ultimately we may wish to extend this to allow us to create a
|
||
767 | control network which every node's control interface is
|
||
768 | attached to."""
|
||
769 | 84a91a14 | Bob Lantz | |
770 | def configureControlNetwork( self ): |
||
771 | "Configure control network."
|
||
772 | self.configureRoutedControlNetwork()
|
||
773 | |||
774 | # We still need to figure out the right way to pass
|
||
775 | # in the control network location.
|
||
776 | |||
777 | def configureRoutedControlNetwork( self, ip='192.168.123.1', |
||
778 | edf60032 | Brandon Heller | prefixLen=16 ):
|
779 | 84a91a14 | Bob Lantz | """Configure a routed control network on controller and switches.
|
780 | For use with the user datapath only right now."""
|
||
781 | controller = self.controllers[ 0 ] |
||
782 | info( controller.name + ' <->' )
|
||
783 | cip = ip |
||
784 | snum = ipParse( ip ) |
||
785 | for switch in self.switches: |
||
786 | info( ' ' + switch.name )
|
||
787 | 14ff3ad3 | Bob Lantz | link = self.link( switch, controller, port1=0 ) |
788 | sintf, cintf = link.intf1, link.intf2 |
||
789 | switch.controlIntf = sintf |
||
790 | 84a91a14 | Bob Lantz | snum += 1
|
791 | while snum & 0xff in [ 0, 255 ]: |
||
792 | snum += 1
|
||
793 | sip = ipStr( snum ) |
||
794 | 14ff3ad3 | Bob Lantz | cintf.setIP( cip, prefixLen ) |
795 | sintf.setIP( sip, prefixLen ) |
||
796 | 84a91a14 | Bob Lantz | controller.setHostRoute( sip, cintf ) |
797 | switch.setHostRoute( cip, sintf ) |
||
798 | info( '\n' )
|
||
799 | info( '*** Testing control network\n' )
|
||
800 | 14ff3ad3 | Bob Lantz | while not cintf.isUp(): |
801 | 84a91a14 | Bob Lantz | info( '*** Waiting for', cintf, 'to come up\n' ) |
802 | sleep( 1 )
|
||
803 | for switch in self.switches: |
||
804 | 14ff3ad3 | Bob Lantz | while not sintf.isUp(): |
805 | 84a91a14 | Bob Lantz | info( '*** Waiting for', sintf, 'to come up\n' ) |
806 | sleep( 1 )
|
||
807 | if self.ping( hosts=[ switch, controller ] ) != 0: |
||
808 | error( '*** Error: control network test failed\n' )
|
||
809 | exit( 1 ) |
||
810 | info( '\n' ) |