mininet / mininet / net.py @ a4e93368
History | View | Annotate | Download (35.8 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 | eba13f0c | cody burkard | import random |
94 | 50774e40 | Bob Lantz | |
95 | 98d4f189 | Bob Lantz | from time import sleep |
96 | 876e66e5 | Rich Lane | from itertools import chain, groupby |
97 | ce781a18 | cody burkard | from math import ceil |
98 | 98d4f189 | Bob Lantz | |
99 | 496b5f9e | Bob Lantz | from mininet.cli import CLI |
100 | 84ea8d7f | Cody Burkard | from mininet.log import info, error, debug, output, warn |
101 | b1ec912d | Bob Lantz | from mininet.node import ( Node, Host, OVSKernelSwitch, DefaultController, |
102 | 7a3159c9 | Bob Lantz | Controller ) |
103 | 4015e066 | Brian O'Connor | from mininet.nodelib import NAT |
104 | e8146dd1 | Bob Lantz | from mininet.link import Link, Intf |
105 | bcfb3009 | Brandon Heller | from mininet.util import quietRun, fixLimits, numCores, ensureRoot |
106 | 5a8bb489 | Bob Lantz | from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd |
107 | 15b482e3 | Brandon Heller | from mininet.term import cleanUpScreens, makeTerms |
108 | 8b5062a3 | Brandon Heller | |
109 | 39128f8c | Bob Lantz | # Mininet version: should be consistent with README and LICENSE
|
110 | f7b29333 | Bob Lantz | VERSION = "2.2.0+"
|
111 | 39128f8c | Bob Lantz | |
112 | 80a8fa62 | Bob Lantz | class Mininet( object ): |
113 | "Network emulation with hosts spawned in network namespaces."
|
||
114 | |||
115 | eaf5888a | Bob Lantz | def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, |
116 | a19cc915 | Cody Burkard | controller=DefaultController, link=Link, intf=Intf, |
117 | edf60032 | Brandon Heller | build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8', |
118 | inNamespace=False,
|
||
119 | autoSetMacs=False, autoStaticArp=False, autoPinCpus=False, |
||
120 | 84ea8d7f | Cody Burkard | listenPort=None, waitConnected=False ): |
121 | 80a8fa62 | Bob Lantz | """Create Mininet object.
|
122 | 019bff82 | Bob Lantz | topo: Topo (topology) object or None
|
123 | a6bcad8f | Bob Lantz | switch: default Switch class
|
124 | host: default Host class/constructor
|
||
125 | controller: default Controller class/constructor
|
||
126 | link: default Link class/constructor
|
||
127 | 216a4b7c | Bob Lantz | intf: default Intf class/constructor
|
128 | 84a91a14 | Bob Lantz | ipBase: base IP address for hosts,
|
129 | 956bf4f2 | Brandon Heller | build: build now from topo?
|
130 | 80a8fa62 | Bob Lantz | xterms: if build now, spawn xterms?
|
131 | cleanup: if build now, cleanup before creating?
|
||
132 | inNamespace: spawn switches and controller in net namespaces?
|
||
133 | 197b083f | Bob Lantz | autoSetMacs: set MAC addrs automatically like IP addresses?
|
134 | ccca871a | Brandon Heller | autoStaticArp: set all-pairs static MAC addrs?
|
135 | 197b083f | Bob Lantz | autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
|
136 | ccca871a | Brandon Heller | listenPort: base listening port to open; will be incremented for
|
137 | 3f2355a3 | Brian O'Connor | each additional switch in the net if inNamespace=False"""
|
138 | 216a4b7c | Bob Lantz | self.topo = topo
|
139 | 8b5062a3 | Brandon Heller | self.switch = switch
|
140 | self.host = host
|
||
141 | self.controller = controller
|
||
142 | a6bcad8f | Bob Lantz | self.link = link
|
143 | 216a4b7c | Bob Lantz | self.intf = intf
|
144 | 82f483f5 | Bob Lantz | self.ipBase = ipBase
|
145 | 5a8bb489 | Bob Lantz | self.ipBaseNum, self.prefixLen = netParse( self.ipBase ) |
146 | self.nextIP = 1 # start for address allocation |
||
147 | 80a8fa62 | Bob Lantz | self.inNamespace = inNamespace
|
148 | 376bcba4 | Brandon Heller | self.xterms = xterms
|
149 | self.cleanup = cleanup
|
||
150 | 80a8fa62 | Bob Lantz | self.autoSetMacs = autoSetMacs
|
151 | self.autoStaticArp = autoStaticArp
|
||
152 | 197b083f | Bob Lantz | self.autoPinCpus = autoPinCpus
|
153 | self.numCores = numCores()
|
||
154 | self.nextCore = 0 # next core for pinning hosts to CPUs |
||
155 | ccca871a | Brandon Heller | self.listenPort = listenPort
|
156 | c23c992f | Cody Burkard | self.waitConn = waitConnected
|
157 | 8b5062a3 | Brandon Heller | |
158 | 019bff82 | Bob Lantz | self.hosts = []
|
159 | self.switches = []
|
||
160 | self.controllers = []
|
||
161 | c265deed | Bob Lantz | self.links = []
|
162 | 84a91a14 | Bob Lantz | |
163 | cf6f6704 | Bob Lantz | self.nameToNode = {} # name to Node (Host/Switch) objects |
164 | 84a91a14 | Bob Lantz | |
165 | cf6f6704 | Bob Lantz | self.terms = [] # list of spawned xterm processes |
166 | 8a034f4f | Brandon Heller | |
167 | 8e3699ec | Bob Lantz | Mininet.init() # Initialize Mininet if necessary
|
168 | b055728f | Brandon Heller | |
169 | 99c035d9 | Bob Lantz | self.built = False |
170 | if topo and build: |
||
171 | d44a5843 | Bob Lantz | self.build()
|
172 | 8b5062a3 | Brandon Heller | |
173 | 4794871a | Bob Lantz | def waitConnected( self, timeout=None, delay=.5 ): |
174 | 84ea8d7f | Cody Burkard | """wait for each switch to connect to a controller,
|
175 | up to 5 seconds
|
||
176 | 4797b420 | Cody Burkard | timeout: time to wait, or None to wait indefinitely
|
177 | 4794871a | Bob Lantz | delay: seconds to sleep per iteration
|
178 | 84ea8d7f | Cody Burkard | returns: True if all switches are connected"""
|
179 | 13d25b41 | Bob Lantz | info( '*** Waiting for switches to connect\n' )
|
180 | 84ea8d7f | Cody Burkard | time = 0
|
181 | 708b1843 | Bob Lantz | remaining = list( self.switches ) |
182 | 4794871a | Bob Lantz | while True: |
183 | 708b1843 | Bob Lantz | for switch in tuple( remaining ): |
184 | 4794871a | Bob Lantz | if switch.connected():
|
185 | info( '%s ' % switch )
|
||
186 | 8e2443ad | Cody Burkard | remaining.remove( switch ) |
187 | 4794871a | Bob Lantz | if not remaining: |
188 | info( '\n' )
|
||
189 | return True |
||
190 | if time > timeout and timeout is not None: |
||
191 | 84ea8d7f | Cody Burkard | break
|
192 | 4794871a | Bob Lantz | sleep( delay ) |
193 | time += delay |
||
194 | warn( 'Timed out after %d seconds\n' % time )
|
||
195 | for switch in remaining: |
||
196 | if not switch.connected(): |
||
197 | warn( 'Warning: %s is not connected to a controller\n'
|
||
198 | % switch.name ) |
||
199 | else:
|
||
200 | remaining.remove( switch ) |
||
201 | return not remaining |
||
202 | 84ea8d7f | Cody Burkard | |
203 | 5a8bb489 | Bob Lantz | def addHost( self, name, cls=None, **params ): |
204 | 80a8fa62 | Bob Lantz | """Add host.
|
205 | 019bff82 | Bob Lantz | name: name of host to add
|
206 | 5a8bb489 | Bob Lantz | cls: custom host class/constructor (optional)
|
207 | 84a91a14 | Bob Lantz | params: parameters for host
|
208 | 019bff82 | Bob Lantz | returns: added host"""
|
209 | 5a8bb489 | Bob Lantz | # Default IP and MAC addresses
|
210 | defaults = { 'ip': ipAdd( self.nextIP, |
||
211 | ipBaseNum=self.ipBaseNum,
|
||
212 | 0f832c92 | Bob Lantz | prefixLen=self.prefixLen ) +
|
213 | '/%s' % self.prefixLen } |
||
214 | 5a8bb489 | Bob Lantz | if self.autoSetMacs: |
215 | 47b9466f | Brian O'Connor | defaults[ 'mac' ] = macColonHex( self.nextIP ) |
216 | 197b083f | Bob Lantz | if self.autoPinCpus: |
217 | defaults[ 'cores' ] = self.nextCore |
||
218 | self.nextCore = ( self.nextCore + 1 ) % self.numCores |
||
219 | 5a8bb489 | Bob Lantz | self.nextIP += 1 |
220 | defaults.update( params ) |
||
221 | if not cls: |
||
222 | 9005ce32 | Bob Lantz | cls = self.host
|
223 | 5a8bb489 | Bob Lantz | h = cls( name, **defaults ) |
224 | a6bcad8f | Bob Lantz | self.hosts.append( h )
|
225 | self.nameToNode[ name ] = h
|
||
226 | return h
|
||
227 | |||
228 | 5a8bb489 | Bob Lantz | def addSwitch( self, name, cls=None, **params ): |
229 | 80a8fa62 | Bob Lantz | """Add switch.
|
230 | 019bff82 | Bob Lantz | name: name of switch to add
|
231 | 5a8bb489 | Bob Lantz | cls: custom switch class/constructor (optional)
|
232 | ccca871a | Brandon Heller | returns: added switch
|
233 | 84a91a14 | Bob Lantz | side effect: increments listenPort ivar ."""
|
234 | 14ff3ad3 | Bob Lantz | defaults = { 'listenPort': self.listenPort, |
235 | a6bcad8f | Bob Lantz | 'inNamespace': self.inNamespace } |
236 | 84a91a14 | Bob Lantz | defaults.update( params ) |
237 | 5a8bb489 | Bob Lantz | if not cls: |
238 | cls = self.switch
|
||
239 | sw = cls( name, **defaults ) |
||
240 | 0a9358c9 | Brandon Heller | if not self.inNamespace and self.listenPort: |
241 | ccca871a | Brandon Heller | self.listenPort += 1 |
242 | 019bff82 | Bob Lantz | self.switches.append( sw )
|
243 | self.nameToNode[ name ] = sw
|
||
244 | return sw
|
||
245 | 80a8fa62 | Bob Lantz | |
246 | a6bcad8f | Bob Lantz | def addController( self, name='c0', controller=None, **params ): |
247 | 80a8fa62 | Bob Lantz | """Add controller.
|
248 | e30f2c99 | Bob Lantz | controller: Controller class"""
|
249 | 35029978 | Bob Lantz | # Get controller class
|
250 | e30f2c99 | Bob Lantz | if not controller: |
251 | controller = self.controller
|
||
252 | 35029978 | Bob Lantz | # Construct new controller if one is not given
|
253 | e183e699 | Bob Lantz | if isinstance( name, Controller ): |
254 | 15146d90 | Brian O'Connor | controller_new = name |
255 | 35029978 | Bob Lantz | # Pylint thinks controller is a str()
|
256 | 061598f0 | Bob Lantz | # pylint: disable=maybe-no-member
|
257 | 15146d90 | Brian O'Connor | name = controller_new.name |
258 | 061598f0 | Bob Lantz | # pylint: enable=maybe-no-member
|
259 | 15146d90 | Brian O'Connor | else:
|
260 | controller_new = controller( name, **params ) |
||
261 | 35029978 | Bob Lantz | # Add new controller to net
|
262 | 7a3159c9 | Bob Lantz | if controller_new: # allow controller-less setups |
263 | f32a5468 | Brandon Heller | self.controllers.append( controller_new )
|
264 | 82b72072 | Bob Lantz | self.nameToNode[ name ] = controller_new
|
265 | eaf5888a | Bob Lantz | return controller_new
|
266 | e8146dd1 | Bob Lantz | |
267 | b1ec912d | Bob Lantz | def addNAT( self, name='nat0', connect=True, inNamespace=False, |
268 | **params): |
||
269 | """Add a NAT to the Mininet network
|
||
270 | name: name of NAT node
|
||
271 | connect: switch to connect to | True (s1) | None
|
||
272 | inNamespace: create in a network namespace
|
||
273 | params: other NAT node params, notably:
|
||
274 | ip: used as default gateway address"""
|
||
275 | aabbf295 | Bob Lantz | nat = self.addHost( name, cls=NAT, inNamespace=inNamespace,
|
276 | ffeb16eb | Brian O'Connor | subnet=self.ipBase, **params )
|
277 | 47b9466f | Brian O'Connor | # find first switch and create link
|
278 | if connect:
|
||
279 | b1ec912d | Bob Lantz | if not isinstance( connect, Node ): |
280 | # Use first switch if not specified
|
||
281 | connect = self.switches[ 0 ] |
||
282 | # Connect the nat to the switch
|
||
283 | 47b9466f | Brian O'Connor | self.addLink( nat, self.switches[ 0 ] ) |
284 | b1ec912d | Bob Lantz | # Set the default route on hosts
|
285 | ffeb16eb | Brian O'Connor | natIP = nat.params[ 'ip' ].split('/')[ 0 ] |
286 | for host in self.hosts: |
||
287 | if host.inNamespace:
|
||
288 | host.setDefaultRoute( 'via %s' % natIP )
|
||
289 | 47b9466f | Brian O'Connor | return nat
|
290 | |||
291 | bd558875 | Bob Lantz | # BL: We now have four ways to look up nodes
|
292 | # This may (should?) be cleaned up in the future.
|
||
293 | 548580d8 | Bob Lantz | def getNodeByName( self, *args ): |
294 | "Return node(s) with given name(s)"
|
||
295 | if len( args ) == 1: |
||
296 | return self.nameToNode[ args[ 0 ] ] |
||
297 | return [ self.nameToNode[ n ] for n in args ] |
||
298 | 8b5062a3 | Brandon Heller | |
299 | 089e8130 | Bob Lantz | def get( self, *args ): |
300 | "Convenience alias for getNodeByName"
|
||
301 | return self.getNodeByName( *args ) |
||
302 | |||
303 | bd558875 | Bob Lantz | # Even more convenient syntax for node lookup and iteration
|
304 | 15146d90 | Brian O'Connor | def __getitem__( self, key ): |
305 | bd558875 | Bob Lantz | """net [ name ] operator: Return node(s) with given name(s)"""
|
306 | 15146d90 | Brian O'Connor | return self.nameToNode[ key ] |
307 | bd558875 | Bob Lantz | |
308 | def __iter__( self ): |
||
309 | 09b12391 | Brian O'Connor | "return iterator over node names"
|
310 | 9281719d | Brian O'Connor | for node in chain( self.hosts, self.switches, self.controllers ): |
311 | yield node.name
|
||
312 | bd558875 | Bob Lantz | |
313 | 8e04a9f8 | Brian O'Connor | def __len__( self ): |
314 | 9281719d | Brian O'Connor | "returns number of nodes in net"
|
315 | 35029978 | Bob Lantz | return ( len( self.hosts ) + len( self.switches ) + |
316 | len( self.controllers ) ) |
||
317 | 8e04a9f8 | Brian O'Connor | |
318 | def __contains__( self, item ): |
||
319 | 9281719d | Brian O'Connor | "returns True if net contains named node"
|
320 | 15146d90 | Brian O'Connor | return item in self.nameToNode |
321 | 8e04a9f8 | Brian O'Connor | |
322 | def keys( self ): |
||
323 | 9281719d | Brian O'Connor | "return a list of all node names or net's keys"
|
324 | 15146d90 | Brian O'Connor | return list( self ) |
325 | 8e04a9f8 | Brian O'Connor | |
326 | def values( self ): |
||
327 | 9281719d | Brian O'Connor | "return a list of all nodes or net's values"
|
328 | 15146d90 | Brian O'Connor | return [ self[name] for name in self ] |
329 | 8e04a9f8 | Brian O'Connor | |
330 | def items( self ): |
||
331 | 9281719d | Brian O'Connor | "return (key,value) tuple list for every node in net"
|
332 | 8e04a9f8 | Brian O'Connor | return zip( self.keys(), self.values() ) |
333 | |||
334 | 89fb0819 | Bob Lantz | @staticmethod
|
335 | def randMac(): |
||
336 | "Return a random, non-multicast MAC address"
|
||
337 | 7a3159c9 | Bob Lantz | return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff | |
338 | 2a2d6050 | Bob Lantz | 0x020000000000 )
|
339 | aabbf295 | Bob Lantz | |
340 | e8146dd1 | Bob Lantz | def addLink( self, node1, node2, port1=None, port2=None, |
341 | 2a2d6050 | Bob Lantz | cls=None, **params ):
|
342 | e8146dd1 | Bob Lantz | """"Add a link from node1 to node2
|
343 | 2a2d6050 | Bob Lantz | node1: source node (or name)
|
344 | node2: dest node (or name)
|
||
345 | 89fb0819 | Bob Lantz | port1: source port (optional)
|
346 | port2: dest port (optional)
|
||
347 | cls: link class (optional)
|
||
348 | params: additional link params (optional)
|
||
349 | e8146dd1 | Bob Lantz | returns: link object"""
|
350 | c92c4efb | Bob Lantz | # Accept node objects or names
|
351 | 9a8bdfd7 | Bob Lantz | node1 = node1 if not isinstance( node1, basestring ) else self[ node1 ] |
352 | node2 = node2 if not isinstance( node2, basestring ) else self[ node2 ] |
||
353 | 2a2d6050 | Bob Lantz | options = dict( params )
|
354 | c92c4efb | Bob Lantz | # Port is optional
|
355 | 39203484 | Bob Lantz | if port1 is not None: |
356 | options.setdefault( 'port1', port1 )
|
||
357 | if port2 is not None: |
||
358 | options.setdefault( 'port2', port2 )
|
||
359 | c92c4efb | Bob Lantz | # Set default MAC - this should probably be in Link
|
360 | options.setdefault( 'addr1', self.randMac() ) |
||
361 | options.setdefault( 'addr2', self.randMac() ) |
||
362 | 89fb0819 | Bob Lantz | cls = self.link if cls is None else cls |
363 | 2a2d6050 | Bob Lantz | link = cls( node1, node2, **options ) |
364 | c265deed | Bob Lantz | self.links.append( link )
|
365 | return link
|
||
366 | 5a8bb489 | Bob Lantz | |
367 | 80be5642 | Bob Lantz | def configHosts( self ): |
368 | 80a8fa62 | Bob Lantz | "Configure a set of hosts."
|
369 | 019bff82 | Bob Lantz | for host in self.hosts: |
370 | 216a4b7c | Bob Lantz | info( host.name + ' ' )
|
371 | e1ca7196 | Bob Lantz | intf = host.defaultIntf() |
372 | if intf:
|
||
373 | dd21df3c | Bob Lantz | host.configDefault() |
374 | e1ca7196 | Bob Lantz | else:
|
375 | # Don't configure nonexistent intf
|
||
376 | host.configDefault( ip=None, mac=None ) |
||
377 | 8b5062a3 | Brandon Heller | # You're low priority, dude!
|
378 | 84a91a14 | Bob Lantz | # BL: do we want to do this here or not?
|
379 | # May not make sense if we have CPU lmiting...
|
||
380 | # quietRun( 'renice +18 -p ' + repr( host.pid ) )
|
||
381 | a9c28885 | Bob Lantz | # This may not be the right place to do this, but
|
382 | # it needs to be done somewhere.
|
||
383 | d5886525 | Bob Lantz | info( '\n' )
|
384 | 80a8fa62 | Bob Lantz | |
385 | 84a91a14 | Bob Lantz | def buildFromTopo( self, topo=None ): |
386 | 019bff82 | Bob Lantz | """Build mininet from a topology object
|
387 | 80a8fa62 | Bob Lantz | At the end of this function, everything should be connected
|
388 | and up."""
|
||
389 | d44a5843 | Bob Lantz | |
390 | # Possibly we should clean up here and/or validate
|
||
391 | # the topo
|
||
392 | 376bcba4 | Brandon Heller | if self.cleanup: |
393 | d44a5843 | Bob Lantz | pass
|
394 | |||
395 | d5886525 | Bob Lantz | info( '*** Creating network\n' )
|
396 | 84a91a14 | Bob Lantz | |
397 | 15146d90 | Brian O'Connor | if not self.controllers and self.controller: |
398 | 84a91a14 | Bob Lantz | # Add a default controller
|
399 | aabbf295 | Bob Lantz | info( '*** Adding controller\n' )
|
400 | f58f83c0 | Bob Lantz | classes = self.controller
|
401 | 9a8bdfd7 | Bob Lantz | if not isinstance( classes, list ): |
402 | f58f83c0 | Bob Lantz | classes = [ classes ] |
403 | for i, cls in enumerate( classes ): |
||
404 | b7268856 | Bob Lantz | # Allow Controller objects because nobody understands currying
|
405 | e183e699 | Bob Lantz | if isinstance( cls, Controller ): |
406 | b7268856 | Bob Lantz | self.addController( cls )
|
407 | else:
|
||
408 | self.addController( 'c%d' % i, cls ) |
||
409 | 84a91a14 | Bob Lantz | |
410 | d5886525 | Bob Lantz | info( '*** Adding hosts:\n' )
|
411 | 5a8bb489 | Bob Lantz | for hostName in topo.hosts(): |
412 | self.addHost( hostName, **topo.nodeInfo( hostName ) )
|
||
413 | info( hostName + ' ' )
|
||
414 | 84a91a14 | Bob Lantz | |
415 | d5886525 | Bob Lantz | info( '\n*** Adding switches:\n' )
|
416 | 5a8bb489 | Bob Lantz | for switchName in topo.switches(): |
417 | a4e93368 | Bob Lantz | # A bit ugly: add batch parameter if appropriate
|
418 | params = topo.nodeInfo( switchName) |
||
419 | cls = params.get( 'cls', self.switch ) |
||
420 | if hasattr( cls, 'batchStartup' ): |
||
421 | params.setdefault( 'batch', True ) |
||
422 | self.addSwitch( switchName, **params )
|
||
423 | 5a8bb489 | Bob Lantz | info( switchName + ' ' )
|
424 | 84a91a14 | Bob Lantz | |
425 | 724f1144 | Bob Lantz | info( '\n*** Adding links:\n' )
|
426 | 38ce329e | Bob Lantz | for srcName, dstName, params in topo.links( |
427 | sort=True, withInfo=True ): |
||
428 | 2a2d6050 | Bob Lantz | self.addLink( **params )
|
429 | 38ce329e | Bob Lantz | info( '(%s, %s) ' % ( srcName, dstName ) )
|
430 | 84a91a14 | Bob Lantz | |
431 | d5886525 | Bob Lantz | info( '\n' )
|
432 | 80a8fa62 | Bob Lantz | |
433 | 84a91a14 | Bob Lantz | def configureControlNetwork( self ): |
434 | 14ff3ad3 | Bob Lantz | "Control net config hook: override in subclass"
|
435 | raise Exception( 'configureControlNetwork: ' |
||
436 | edf60032 | Brandon Heller | 'should be overriden in subclass', self ) |
437 | 84a91a14 | Bob Lantz | |
438 | d44a5843 | Bob Lantz | def build( self ): |
439 | "Build mininet."
|
||
440 | if self.topo: |
||
441 | self.buildFromTopo( self.topo ) |
||
442 | 824afb84 | Rémy Léone | if self.inNamespace: |
443 | d44a5843 | Bob Lantz | self.configureControlNetwork()
|
444 | d5886525 | Bob Lantz | info( '*** Configuring hosts\n' )
|
445 | 80be5642 | Bob Lantz | self.configHosts()
|
446 | 376bcba4 | Brandon Heller | if self.xterms: |
447 | 15b482e3 | Brandon Heller | self.startTerms()
|
448 | 80a8fa62 | Bob Lantz | if self.autoStaticArp: |
449 | self.staticArp()
|
||
450 | 99c035d9 | Bob Lantz | self.built = True |
451 | 80a8fa62 | Bob Lantz | |
452 | 15b482e3 | Brandon Heller | def startTerms( self ): |
453 | "Start a terminal for each node."
|
||
454 | 4316be95 | Brian O'Connor | if 'DISPLAY' not in os.environ: |
455 | error( "Error starting terms: Cannot connect to display\n" )
|
||
456 | return
|
||
457 | 15b482e3 | Brandon Heller | info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] ) |
458 | 8a034f4f | Brandon Heller | cleanUpScreens() |
459 | 15b482e3 | Brandon Heller | self.terms += makeTerms( self.controllers, 'controller' ) |
460 | self.terms += makeTerms( self.switches, 'switch' ) |
||
461 | self.terms += makeTerms( self.hosts, 'host' ) |
||
462 | 8a034f4f | Brandon Heller | |
463 | 80a8fa62 | Bob Lantz | def stopXterms( self ): |
464 | "Kill each xterm."
|
||
465 | 8a034f4f | Brandon Heller | for term in self.terms: |
466 | 80a8fa62 | Bob Lantz | os.kill( term.pid, signal.SIGKILL ) |
467 | 8a034f4f | Brandon Heller | cleanUpScreens() |
468 | 8b5062a3 | Brandon Heller | |
469 | 80a8fa62 | Bob Lantz | def staticArp( self ): |
470 | "Add all-pairs ARP entries to remove the need to handle broadcast."
|
||
471 | 019bff82 | Bob Lantz | for src in self.hosts: |
472 | for dst in self.hosts: |
||
473 | 376bcba4 | Brandon Heller | if src != dst:
|
474 | 1bda2d21 | Bob Lantz | src.setARP( ip=dst.IP(), mac=dst.MAC() ) |
475 | 376bcba4 | Brandon Heller | |
476 | 80a8fa62 | Bob Lantz | def start( self ): |
477 | 99c035d9 | Bob Lantz | "Start controller and switches."
|
478 | if not self.built: |
||
479 | self.build()
|
||
480 | d5886525 | Bob Lantz | info( '*** Starting controller\n' )
|
481 | 019bff82 | Bob Lantz | for controller in self.controllers: |
482 | 6721f065 | backb1 | info( controller.name + ' ')
|
483 | 019bff82 | Bob Lantz | controller.start() |
484 | 6721f065 | backb1 | info( '\n' )
|
485 | 019bff82 | Bob Lantz | info( '*** Starting %s switches\n' % len( self.switches ) ) |
486 | for switch in self.switches: |
||
487 | efc9a01c | Bob Lantz | info( switch.name + ' ')
|
488 | 80a8fa62 | Bob Lantz | switch.start( self.controllers )
|
489 | 9bda9848 | Bob Lantz | started = {} |
490 | for swclass, switches in groupby( |
||
491 | sorted( self.switches, key=type ), type ): |
||
492 | switches = tuple( switches )
|
||
493 | bdad3e8c | Bob Lantz | if hasattr( swclass, 'batchStartup' ): |
494 | success = swclass.batchStartup( switches ) |
||
495 | started.update( { s: s for s in success } ) |
||
496 | d5886525 | Bob Lantz | info( '\n' )
|
497 | c23c992f | Cody Burkard | if self.waitConn: |
498 | 5a9c74be | Cody Burkard | self.waitConnected()
|
499 | 80a8fa62 | Bob Lantz | |
500 | def stop( self ): |
||
501 | d5886525 | Bob Lantz | "Stop the controller(s), switches and hosts"
|
502 | b7a112cb | Cody Burkard | info( '*** Stopping %i controllers\n' % len( self.controllers ) ) |
503 | for controller in self.controllers: |
||
504 | info( controller.name + ' ' )
|
||
505 | controller.stop() |
||
506 | info( '\n' )
|
||
507 | 8a034f4f | Brandon Heller | if self.terms: |
508 | d5886525 | Bob Lantz | info( '*** Stopping %i terms\n' % len( self.terms ) ) |
509 | 80a8fa62 | Bob Lantz | self.stopXterms()
|
510 | d66b9626 | Bob Lantz | info( '*** Stopping %i links\n' % len( self.links ) ) |
511 | for link in self.links: |
||
512 | info( '.' )
|
||
513 | link.stop() |
||
514 | info( '\n' )
|
||
515 | 019bff82 | Bob Lantz | info( '*** Stopping %i switches\n' % len( self.switches ) ) |
516 | d66b9626 | Bob Lantz | stopped = {} |
517 | b1ec912d | Bob Lantz | for swclass, switches in groupby( |
518 | sorted( self.switches, key=type ), type ): |
||
519 | d66b9626 | Bob Lantz | switches = tuple( switches )
|
520 | bdad3e8c | Bob Lantz | if hasattr( swclass, 'batchShutdown' ): |
521 | success = swclass.batchShutdown( switches ) |
||
522 | stopped.update( { s: s for s in success } ) |
||
523 | 019bff82 | Bob Lantz | for switch in self.switches: |
524 | 84a91a14 | Bob Lantz | info( switch.name + ' ' )
|
525 | d66b9626 | Bob Lantz | if switch not in stopped: |
526 | switch.stop() |
||
527 | c265deed | Bob Lantz | switch.terminate() |
528 | info( '\n' )
|
||
529 | 10be691b | Bob Lantz | info( '*** Stopping %i hosts\n' % len( self.hosts ) ) |
530 | for host in self.hosts: |
||
531 | info( host.name + ' ' )
|
||
532 | host.terminate() |
||
533 | 84a91a14 | Bob Lantz | info( '\n*** Done\n' )
|
534 | 8b5062a3 | Brandon Heller | |
535 | 67516aa4 | Bob Lantz | def run( self, test, *args, **kwargs ): |
536 | 80a8fa62 | Bob Lantz | "Perform a complete start/test/stop cycle."
|
537 | 8b5062a3 | Brandon Heller | self.start()
|
538 | d5886525 | Bob Lantz | info( '*** Running test\n' )
|
539 | 67516aa4 | Bob Lantz | result = test( *args, **kwargs ) |
540 | 8b5062a3 | Brandon Heller | self.stop()
|
541 | return result
|
||
542 | |||
543 | 259d7133 | Bob Lantz | def monitor( self, hosts=None, timeoutms=-1 ): |
544 | ec7b211c | Bob Lantz | """Monitor a set of hosts (or all hosts by default),
|
545 | and return their output, a line at a time.
|
||
546 | 259d7133 | Bob Lantz | hosts: (optional) set of hosts to monitor
|
547 | timeoutms: (optional) timeout value in ms
|
||
548 | returns: iterator which returns host, line"""
|
||
549 | ec7b211c | Bob Lantz | if hosts is None: |
550 | hosts = self.hosts
|
||
551 | poller = select.poll() |
||
552 | b1ec912d | Bob Lantz | h1 = hosts[ 0 ] # so we can call class method fdToNode |
553 | ec7b211c | Bob Lantz | for host in hosts: |
554 | poller.register( host.stdout ) |
||
555 | while True: |
||
556 | 259d7133 | Bob Lantz | ready = poller.poll( timeoutms ) |
557 | ec7b211c | Bob Lantz | for fd, event in ready: |
558 | b1ec912d | Bob Lantz | host = h1.fdToNode( fd ) |
559 | ec7b211c | Bob Lantz | if event & select.POLLIN:
|
560 | line = host.readline() |
||
561 | 259d7133 | Bob Lantz | if line is not None: |
562 | ec7b211c | Bob Lantz | yield host, line
|
563 | 259d7133 | Bob Lantz | # Return if non-blocking
|
564 | if not ready and timeoutms >= 0: |
||
565 | yield None, None |
||
566 | ec7b211c | Bob Lantz | |
567 | 84a91a14 | Bob Lantz | # XXX These test methods should be moved out of this class.
|
568 | # Probably we should create a tests.py for them
|
||
569 | |||
570 | 8b5062a3 | Brandon Heller | @staticmethod
|
571 | 80a8fa62 | Bob Lantz | def _parsePing( pingOutput ): |
572 | "Parse ping output and return packets sent, received."
|
||
573 | 3fac5a43 | Brandon Heller | # Check for downed link
|
574 | if 'connect: Network is unreachable' in pingOutput: |
||
575 | 824afb84 | Rémy Léone | return 1, 0 |
576 | 8b5062a3 | Brandon Heller | r = r'(\d+) packets transmitted, (\d+) received'
|
577 | 80a8fa62 | Bob Lantz | m = re.search( r, pingOutput ) |
578 | 7a506047 | Brandon Heller | if m is None: |
579 | d5886525 | Bob Lantz | error( '*** Error: could not parse ping output: %s\n' %
|
580 | 2e089b5e | Brandon Heller | pingOutput ) |
581 | 824afb84 | Rémy Léone | return 1, 0 |
582 | 80a8fa62 | Bob Lantz | sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) |
583 | 8b5062a3 | Brandon Heller | return sent, received
|
584 | |||
585 | 1f1d590c | Brandon Heller | def ping( self, hosts=None, timeout=None ): |
586 | 80a8fa62 | Bob Lantz | """Ping between all specified hosts.
|
587 | 019bff82 | Bob Lantz | hosts: list of hosts
|
588 | 1f1d590c | Brandon Heller | timeout: time to wait for a response, as string
|
589 | 80a8fa62 | Bob Lantz | returns: ploss packet loss percentage"""
|
590 | d44a5843 | Bob Lantz | # should we check if running?
|
591 | 8b5062a3 | Brandon Heller | packets = 0
|
592 | lost = 0
|
||
593 | ploss = None
|
||
594 | if not hosts: |
||
595 | 019bff82 | Bob Lantz | hosts = self.hosts
|
596 | cdeaca86 | Brandon Heller | output( '*** Ping: testing ping reachability\n' )
|
597 | 019bff82 | Bob Lantz | for node in hosts: |
598 | cdeaca86 | Brandon Heller | output( '%s -> ' % node.name )
|
599 | 019bff82 | Bob Lantz | for dest in hosts: |
600 | 8b5062a3 | Brandon Heller | if node != dest:
|
601 | 1f1d590c | Brandon Heller | opts = ''
|
602 | if timeout:
|
||
603 | opts = '-W %s' % timeout
|
||
604 | 778267aa | cody burkard | if dest.intfs:
|
605 | b1ec912d | Bob Lantz | result = node.cmd( 'ping -c1 %s %s' %
|
606 | (opts, dest.IP()) ) |
||
607 | 778267aa | cody burkard | sent, received = self._parsePing( result )
|
608 | else:
|
||
609 | sent, received = 0, 0 |
||
610 | 8b5062a3 | Brandon Heller | packets += sent |
611 | if received > sent:
|
||
612 | d5886525 | Bob Lantz | error( '*** Error: received too many packets' )
|
613 | error( '%s' % result )
|
||
614 | 80a8fa62 | Bob Lantz | node.cmdPrint( 'route' )
|
615 | exit( 1 ) |
||
616 | 8b5062a3 | Brandon Heller | lost += sent - received |
617 | cdeaca86 | Brandon Heller | output( ( '%s ' % dest.name ) if received else 'X ' ) |
618 | output( '\n' )
|
||
619 | c188bee3 | Brian O'Connor | if packets > 0: |
620 | 92a28881 | lantz | ploss = 100.0 * lost / packets
|
621 | fec98e27 | Brian O'Connor | received = packets - lost |
622 | output( "*** Results: %i%% dropped (%d/%d received)\n" %
|
||
623 | ( ploss, received, packets ) ) |
||
624 | c188bee3 | Brian O'Connor | else:
|
625 | ploss = 0
|
||
626 | output( "*** Warning: No packets sent\n" )
|
||
627 | 8b5062a3 | Brandon Heller | return ploss
|
628 | |||
629 | 1f1d590c | Brandon Heller | @staticmethod
|
630 | def _parsePingFull( pingOutput ): |
||
631 | "Parse ping output and return all data."
|
||
632 | 1ecc63df | Brian O'Connor | errorTuple = (1, 0, 0, 0, 0, 0) |
633 | 1f1d590c | Brandon Heller | # Check for downed link
|
634 | 1ecc63df | Brian O'Connor | r = r'[uU]nreachable'
|
635 | m = re.search( r, pingOutput ) |
||
636 | if m is not None: |
||
637 | return errorTuple
|
||
638 | 1f1d590c | Brandon Heller | r = r'(\d+) packets transmitted, (\d+) received'
|
639 | m = re.search( r, pingOutput ) |
||
640 | if m is None: |
||
641 | error( '*** Error: could not parse ping output: %s\n' %
|
||
642 | pingOutput ) |
||
643 | 1ecc63df | Brian O'Connor | return errorTuple
|
644 | 1f1d590c | Brandon Heller | sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) |
645 | r = r'rtt min/avg/max/mdev = '
|
||
646 | r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
|
||
647 | m = re.search( r, pingOutput ) |
||
648 | 1ecc63df | Brian O'Connor | if m is None: |
649 | 00cbb348 | cody burkard | if received == 0: |
650 | return errorTuple
|
||
651 | 1ecc63df | Brian O'Connor | error( '*** Error: could not parse ping output: %s\n' %
|
652 | pingOutput ) |
||
653 | return errorTuple
|
||
654 | 1f1d590c | Brandon Heller | rttmin = float( m.group( 1 ) ) |
655 | rttavg = float( m.group( 2 ) ) |
||
656 | rttmax = float( m.group( 3 ) ) |
||
657 | rttdev = float( m.group( 4 ) ) |
||
658 | return sent, received, rttmin, rttavg, rttmax, rttdev
|
||
659 | |||
660 | def pingFull( self, hosts=None, timeout=None ): |
||
661 | """Ping between all specified hosts and return all data.
|
||
662 | hosts: list of hosts
|
||
663 | timeout: time to wait for a response, as string
|
||
664 | returns: all ping data; see function body."""
|
||
665 | # should we check if running?
|
||
666 | # Each value is a tuple: (src, dsd, [all ping outputs])
|
||
667 | all_outputs = [] |
||
668 | if not hosts: |
||
669 | hosts = self.hosts
|
||
670 | output( '*** Ping: testing ping reachability\n' )
|
||
671 | for node in hosts: |
||
672 | output( '%s -> ' % node.name )
|
||
673 | for dest in hosts: |
||
674 | if node != dest:
|
||
675 | opts = ''
|
||
676 | if timeout:
|
||
677 | opts = '-W %s' % timeout
|
||
678 | result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
|
||
679 | outputs = self._parsePingFull( result )
|
||
680 | sent, received, rttmin, rttavg, rttmax, rttdev = outputs |
||
681 | all_outputs.append( (node, dest, outputs) ) |
||
682 | output( ( '%s ' % dest.name ) if received else 'X ' ) |
||
683 | output( '\n' )
|
||
684 | output( "*** Results: \n" )
|
||
685 | for outputs in all_outputs: |
||
686 | src, dest, ping_outputs = outputs |
||
687 | sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs |
||
688 | output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
|
||
689 | output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
|
||
690 | (rttmin, rttavg, rttmax, rttdev) ) |
||
691 | return all_outputs
|
||
692 | |||
693 | 4d1a9cdc | Jon Hall | def pingAll( self, timeout=None ): |
694 | 80a8fa62 | Bob Lantz | """Ping between all hosts.
|
695 | returns: ploss packet loss percentage"""
|
||
696 | 4d1a9cdc | Jon Hall | return self.ping( timeout=timeout ) |
697 | eeb9cb3c | Brandon Heller | |
698 | 80a8fa62 | Bob Lantz | def pingPair( self ): |
699 | """Ping between first two hosts, useful for testing.
|
||
700 | returns: ploss packet loss percentage"""
|
||
701 | 019bff82 | Bob Lantz | hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ] |
702 | 80a8fa62 | Bob Lantz | return self.ping( hosts=hosts ) |
703 | eeb9cb3c | Brandon Heller | |
704 | 1f1d590c | Brandon Heller | def pingAllFull( self ): |
705 | """Ping between all hosts.
|
||
706 | returns: ploss packet loss percentage"""
|
||
707 | return self.pingFull() |
||
708 | |||
709 | def pingPairFull( self ): |
||
710 | """Ping between first two hosts, useful for testing.
|
||
711 | returns: ploss packet loss percentage"""
|
||
712 | hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ] |
||
713 | return self.pingFull( hosts=hosts ) |
||
714 | |||
715 | eeb9cb3c | Brandon Heller | @staticmethod
|
716 | 80a8fa62 | Bob Lantz | def _parseIperf( iperfOutput ): |
717 | """Parse iperf output and return bandwidth.
|
||
718 | iperfOutput: string
|
||
719 | returns: result string"""
|
||
720 | eeb9cb3c | Brandon Heller | r = r'([\d\.]+ \w+/sec)'
|
721 | ad2fda25 | Bob Lantz | m = re.findall( r, iperfOutput ) |
722 | eeb9cb3c | Brandon Heller | if m:
|
723 | ad2fda25 | Bob Lantz | return m[-1] |
724 | eeb9cb3c | Brandon Heller | else:
|
725 | ad2fda25 | Bob Lantz | # was: raise Exception(...)
|
726 | error( 'could not parse iperf output: ' + iperfOutput )
|
||
727 | return '' |
||
728 | 80a8fa62 | Bob Lantz | |
729 | 216a4b7c | Bob Lantz | # XXX This should be cleaned up
|
730 | |||
731 | b1ec912d | Bob Lantz | def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', fmt=None, |
732 | d3377bf9 | Bob Lantz | seconds=5):
|
733 | 80a8fa62 | Bob Lantz | """Run iperf between two hosts.
|
734 | b1ec912d | Bob Lantz | hosts: list of hosts; if None, uses first and last hosts
|
735 | 80a8fa62 | Bob Lantz | l4Type: string, one of [ TCP, UDP ]
|
736 | d3377bf9 | Bob Lantz | udpBw: bandwidth target for UDP test
|
737 | b1ec912d | Bob Lantz | fmt: iperf format argument if any
|
738 | d3377bf9 | Bob Lantz | seconds: iperf time to transmit
|
739 | returns: two-element array of [ server, client ] speeds
|
||
740 | e1711f35 | Bob Lantz | note: send() is buffered, so client rate can be much higher than
|
741 | the actual transmission rate; on an unloaded system, server
|
||
742 | rate should be much closer to the actual receive rate"""
|
||
743 | ad2fda25 | Bob Lantz | if not quietRun( 'which telnet' ): |
744 | error( 'Cannot find telnet in $PATH - required for iperf test' )
|
||
745 | return
|
||
746 | 18aab5b7 | Bob Lantz | hosts = hosts or [ self.hosts[ 0 ], self.hosts[ -1 ] ] |
747 | assert len( hosts ) == 2 |
||
748 | ec7b211c | Bob Lantz | client, server = hosts |
749 | cdeaca86 | Brandon Heller | output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' ) |
750 | ec7b211c | Bob Lantz | output( "%s and %s\n" % ( client.name, server.name ) )
|
751 | server.cmd( 'killall -9 iperf' )
|
||
752 | 80a8fa62 | Bob Lantz | iperfArgs = 'iperf '
|
753 | bwArgs = ''
|
||
754 | if l4Type == 'UDP': |
||
755 | iperfArgs += '-u '
|
||
756 | bwArgs = '-b ' + udpBw + ' ' |
||
757 | elif l4Type != 'TCP': |
||
758 | raise Exception( 'Unexpected l4 type: %s' % l4Type ) |
||
759 | b1ec912d | Bob Lantz | if fmt:
|
760 | iperfArgs += '-f %s ' % fmt
|
||
761 | ec7b211c | Bob Lantz | server.sendCmd( iperfArgs + '-s', printPid=True ) |
762 | servout = ''
|
||
763 | while server.lastPid is None: |
||
764 | servout += server.monitor() |
||
765 | 8856d284 | Bob Lantz | if l4Type == 'TCP': |
766 | while 'Connected' not in client.cmd( |
||
767 | 615ebb7a | Brandon Heller | 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
|
768 | 13d25b41 | Bob Lantz | info( 'Waiting for iperf to start up...' )
|
769 | 8856d284 | Bob Lantz | sleep(.5)
|
770 | d3377bf9 | Bob Lantz | cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
|
771 | server.IP() + ' ' + bwArgs )
|
||
772 | ec7b211c | Bob Lantz | debug( 'Client output: %s\n' % cliout )
|
773 | server.sendInt() |
||
774 | servout += server.waitOutput() |
||
775 | debug( 'Server output: %s\n' % servout )
|
||
776 | result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ] |
||
777 | 80a8fa62 | Bob Lantz | if l4Type == 'UDP': |
778 | result.insert( 0, udpBw )
|
||
779 | cdeaca86 | Brandon Heller | output( '*** Results: %s\n' % result )
|
780 | eeb9cb3c | Brandon Heller | return result
|
781 | |||
782 | fcd01592 | Brandon Heller | def runCpuLimitTest( self, cpu, duration=5 ): |
783 | """run CPU limit test with 'while true' processes.
|
||
784 | cpu: desired CPU fraction of each host
|
||
785 | b1ec912d | Bob Lantz | duration: test duration in seconds (integer)
|
786 | fcd01592 | Brandon Heller | returns a single list of measured CPU fractions as floats.
|
787 | """
|
||
788 | ce781a18 | cody burkard | cores = int( quietRun( 'nproc' ) ) |
789 | fcd01592 | Brandon Heller | pct = cpu * 100
|
790 | ce781a18 | cody burkard | info( '*** Testing CPU %.0f%% bandwidth limit\n' % pct )
|
791 | fcd01592 | Brandon Heller | hosts = self.hosts
|
792 | ce781a18 | cody burkard | cores = int( quietRun( 'nproc' ) ) |
793 | # number of processes to run a while loop on per host
|
||
794 | num_procs = int( ceil( cores * cpu ) )
|
||
795 | pids = {} |
||
796 | fcd01592 | Brandon Heller | for h in hosts: |
797 | ce781a18 | cody burkard | pids[ h ] = [] |
798 | for _core in range( num_procs ): |
||
799 | h.cmd( 'while true; do a=1; done &' )
|
||
800 | pids[ h ].append( h.cmd( 'echo $!' ).strip() )
|
||
801 | outputs = {} |
||
802 | time = {} |
||
803 | # get the initial cpu time for each host
|
||
804 | for host in hosts: |
||
805 | outputs[ host ] = [] |
||
806 | b1ec912d | Bob Lantz | with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % |
807 | host, 'r' ) as f: |
||
808 | ce781a18 | cody burkard | time[ host ] = float( f.read() )
|
809 | b1ec912d | Bob Lantz | for _ in range( duration ): |
810 | fcd01592 | Brandon Heller | sleep( 1 )
|
811 | ce781a18 | cody burkard | for host in hosts: |
812 | b1ec912d | Bob Lantz | with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % |
813 | host, 'r' ) as f: |
||
814 | ce781a18 | cody burkard | readTime = float( f.read() )
|
815 | outputs[ host ].append( ( ( readTime - time[ host ] ) |
||
816 | / 1000000000 ) / cores * 100 ) |
||
817 | time[ host ] = readTime |
||
818 | for h, pids in pids.items(): |
||
819 | for pid in pids: |
||
820 | h.cmd( 'kill -9 %s' % pid )
|
||
821 | fcd01592 | Brandon Heller | cpu_fractions = [] |
822 | ce781a18 | cody burkard | for _host, outputs in outputs.items(): |
823 | for pct in outputs: |
||
824 | cpu_fractions.append( pct ) |
||
825 | fcd01592 | Brandon Heller | output( '*** Results: %s\n' % cpu_fractions )
|
826 | return cpu_fractions
|
||
827 | |||
828 | 84a91a14 | Bob Lantz | # BL: I think this can be rewritten now that we have
|
829 | # a real link class.
|
||
830 | c70aab0a | Bob Lantz | def configLinkStatus( self, src, dst, status ): |
831 | """Change status of src <-> dst links.
|
||
832 | src: node name
|
||
833 | dst: node name
|
||
834 | status: string {up, down}"""
|
||
835 | 8d3c2859 | Brandon Heller | if src not in self.nameToNode: |
836 | error( 'src not in network: %s\n' % src )
|
||
837 | elif dst not in self.nameToNode: |
||
838 | error( 'dst not in network: %s\n' % dst )
|
||
839 | else:
|
||
840 | 9a8bdfd7 | Bob Lantz | if isinstance( src, basestring ): |
841 | 8856d284 | Bob Lantz | src = self.nameToNode[ src ]
|
842 | 9a8bdfd7 | Bob Lantz | if isinstance( dst, basestring ): |
843 | 8856d284 | Bob Lantz | dst = self.nameToNode[ dst ]
|
844 | connections = src.connectionsTo( dst ) |
||
845 | fb2f6523 | Bob Lantz | if len( connections ) == 0: |
846 | 8d3c2859 | Brandon Heller | error( 'src and dst not connected: %s %s\n' % ( src, dst) )
|
847 | fb2f6523 | Bob Lantz | for srcIntf, dstIntf in connections: |
848 | 8856d284 | Bob Lantz | result = srcIntf.ifconfig( status ) |
849 | 8d3c2859 | Brandon Heller | if result:
|
850 | error( 'link src status change failed: %s\n' % result )
|
||
851 | 8856d284 | Bob Lantz | result = dstIntf.ifconfig( status ) |
852 | 8d3c2859 | Brandon Heller | if result:
|
853 | error( 'link dst status change failed: %s\n' % result )
|
||
854 | |||
855 | 80a8fa62 | Bob Lantz | def interact( self ): |
856 | "Start network and run our simple CLI."
|
||
857 | eeb9cb3c | Brandon Heller | self.start()
|
858 | 496b5f9e | Bob Lantz | result = CLI( self )
|
859 | eeb9cb3c | Brandon Heller | self.stop()
|
860 | return result
|
||
861 | 82b72072 | Bob Lantz | |
862 | 8e3699ec | Bob Lantz | inited = False
|
863 | 14ff3ad3 | Bob Lantz | |
864 | 8e3699ec | Bob Lantz | @classmethod
|
865 | def init( cls ): |
||
866 | "Initialize Mininet"
|
||
867 | if cls.inited:
|
||
868 | return
|
||
869 | bcfb3009 | Brandon Heller | ensureRoot() |
870 | 8e3699ec | Bob Lantz | fixLimits() |
871 | cls.inited = True
|
||
872 | |||
873 | 82b72072 | Bob Lantz | |
874 | 84a91a14 | Bob Lantz | class MininetWithControlNet( Mininet ): |
875 | |||
876 | """Control network support:
|
||
877 |
|
||
878 | Create an explicit control network. Currently this is only
|
||
879 | used/usable with the user datapath.
|
||
880 |
|
||
881 | Notes:
|
||
882 |
|
||
883 | 1. If the controller and switches are in the same (e.g. root)
|
||
884 | namespace, they can just use the loopback connection.
|
||
885 |
|
||
886 | 2. If we can get unix domain sockets to work, we can use them
|
||
887 | instead of an explicit control network.
|
||
888 |
|
||
889 | 3. Instead of routing, we could bridge or use 'in-band' control.
|
||
890 |
|
||
891 | 4. Even if we dispense with this in general, it could still be
|
||
892 | useful for people who wish to simulate a separate control
|
||
893 | network (since real networks may need one!)
|
||
894 |
|
||
895 | 5. Basically nobody ever used this code, so it has been moved
|
||
896 | 14ff3ad3 | Bob Lantz | into its own class.
|
897 |
|
||
898 | 6. Ultimately we may wish to extend this to allow us to create a
|
||
899 | control network which every node's control interface is
|
||
900 | attached to."""
|
||
901 | 84a91a14 | Bob Lantz | |
902 | def configureControlNetwork( self ): |
||
903 | "Configure control network."
|
||
904 | self.configureRoutedControlNetwork()
|
||
905 | |||
906 | # We still need to figure out the right way to pass
|
||
907 | # in the control network location.
|
||
908 | |||
909 | def configureRoutedControlNetwork( self, ip='192.168.123.1', |
||
910 | edf60032 | Brandon Heller | prefixLen=16 ):
|
911 | 84a91a14 | Bob Lantz | """Configure a routed control network on controller and switches.
|
912 | For use with the user datapath only right now."""
|
||
913 | controller = self.controllers[ 0 ] |
||
914 | info( controller.name + ' <->' )
|
||
915 | cip = ip |
||
916 | snum = ipParse( ip ) |
||
917 | for switch in self.switches: |
||
918 | info( ' ' + switch.name )
|
||
919 | 14ff3ad3 | Bob Lantz | link = self.link( switch, controller, port1=0 ) |
920 | sintf, cintf = link.intf1, link.intf2 |
||
921 | switch.controlIntf = sintf |
||
922 | 84a91a14 | Bob Lantz | snum += 1
|
923 | while snum & 0xff in [ 0, 255 ]: |
||
924 | snum += 1
|
||
925 | sip = ipStr( snum ) |
||
926 | 14ff3ad3 | Bob Lantz | cintf.setIP( cip, prefixLen ) |
927 | sintf.setIP( sip, prefixLen ) |
||
928 | 84a91a14 | Bob Lantz | controller.setHostRoute( sip, cintf ) |
929 | switch.setHostRoute( cip, sintf ) |
||
930 | info( '\n' )
|
||
931 | info( '*** Testing control network\n' )
|
||
932 | 14ff3ad3 | Bob Lantz | while not cintf.isUp(): |
933 | 84a91a14 | Bob Lantz | info( '*** Waiting for', cintf, 'to come up\n' ) |
934 | sleep( 1 )
|
||
935 | for switch in self.switches: |
||
936 | 14ff3ad3 | Bob Lantz | while not sintf.isUp(): |
937 | 84a91a14 | Bob Lantz | info( '*** Waiting for', sintf, 'to come up\n' ) |
938 | sleep( 1 )
|
||
939 | if self.ping( hosts=[ switch, controller ] ) != 0: |
||
940 | error( '*** Error: control network test failed\n' )
|
||
941 | exit( 1 ) |
||
942 | info( '\n' ) |