blob: 98b531656d6fcd37dcf2bae456a6b9546af533bb [file] [log] [blame]
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -07001#!/usr/bin/python
2import os
3import re
4from optparse import OptionParser
5
6from ipaddress import ip_network
7from mininet.node import RemoteController, OVSBridge, Host
8from mininet.link import TCLink
9from mininet.log import setLogLevel
10from mininet.net import Mininet
11from mininet.topo import Topo
12from mininet.nodelib import NAT
13from mininet.cli import CLI
14
15from routinglib import BgpRouter, RoutedHost
16from trellislib import DhcpServer, TaggedRoutedHost, DualHomedRoutedHost, DualHomedTaggedRoutedHost
17
18# Parse command line options and dump results
19def parseOptions():
20 "Parse command line options"
21 parser = OptionParser()
22 parser.add_option( '--spine', dest='spine', type='int', default=2,
23 help='number of spine switches, default=2' )
24 parser.add_option( '--leaf', dest='leaf', type='int', default=2,
25 help='number of leaf switches, default=2' )
26 parser.add_option( '--fanout', dest='fanout', type='int', default=2,
27 help='number of hosts per leaf switch, default=2' )
28 parser.add_option( '--onos-ip', dest='onosIp', type='str', default='',
29 help='IP address list of ONOS instances, separated by comma(,). Overrides --onos option' )
30 parser.add_option( '--ipv6', action="store_true", dest='ipv6',
31 help='hosts are capable to use also ipv6' )
32 parser.add_option( '--dual-homed', action="store_true", dest='dualhomed', default=False,
33 help='True if the topology is dual-homed, default=False' )
34 parser.add_option( '--vlan', dest='vlan', type='str', default='',
35 help='list of vlan id for hosts, separated by comma(,).'
36 'Empty or id with 0 will be unconfigured.' )
37 ( options, args ) = parser.parse_args()
38 return options, args
39
40
41opts, args = parseOptions()
42
43IP6_SUBNET_CLASS = 120
44IP4_SUBNET_CLASS = 24
45
46# TODO: DHCP support
47class IpHost( Host ):
48
49 def __init__( self, name, *args, **kwargs ):
50 super( IpHost, self ).__init__( name, *args, **kwargs )
51 gateway = re.split( '\.|/', kwargs[ 'ip' ] )
52 gateway[ 3 ] = '254'
53 self.gateway = '.'.join( gateway[ 0:4 ] )
54
55 def config( self, **kwargs ):
56 Host.config( self, **kwargs )
57 mtu = "ifconfig " + self.name + "-eth0 mtu 1490"
58 self.cmd( mtu )
59 self.cmd( 'ip route add default via %s' % self.gateway )
60
61class DualHomedIpHost(IpHost):
62 def __init__(self, name, *args, **kwargs):
63 super(DualHomedIpHost, self).__init__(name, **kwargs)
64 self.bond0 = None
65
66 def config(self, **kwargs):
67 super(DualHomedIpHost, self).config(**kwargs)
68 intf0 = self.intfs[0].name
69 intf1 = self.intfs[1].name
70 self.bond0 = "%s-bond0" % self.name
71 self.cmd('modprobe bonding')
72 self.cmd('ip link add %s type bond' % self.bond0)
73 self.cmd('ip link set %s down' % intf0)
74 self.cmd('ip link set %s down' % intf1)
75 self.cmd('ip link set %s master %s' % (intf0, self.bond0))
76 self.cmd('ip link set %s master %s' % (intf1, self.bond0))
77 self.cmd('ip addr flush dev %s' % intf0)
78 self.cmd('ip addr flush dev %s' % intf1)
79 self.cmd('ip link set %s up' % self.bond0)
80
81 def terminate(self, **kwargs):
82 self.cmd('ip link set %s down' % self.bond0)
83 self.cmd('ip link delete %s' % self.bond0)
84 self.cmd('kill -9 `cat %s`' % self.pidFile)
85 self.cmd('rm -rf %s' % self.pidFile)
86 super(DualHomedIpHost, self).terminate()
87
88
89# TODO: Implement IPv6 support
90class DualHomedLeafSpineFabric (Topo) :
91 def __init__(self, spine = 2, leaf = 4, fanout = 2, vlan_id = [], **opts):
92 Topo.__init__(self, **opts)
93 spines = dict()
94 leafs = dict()
95
96 # leaf should be 2 or 4
97
98 # calculate the subnets to use and set options
99 linkopts = dict( bw=100 )
100 # Create spine switches
101 for s in range(spine):
102 spines[s] = self.addSwitch('spine10%s' % (s + 1), dpid = "00000000010%s" % (s + 1) )
103
104 # Create leaf switches
105 for ls in range(leaf):
106 leafs[ls] = self.addSwitch('leaf%s' % (ls + 1), dpid = "00000000000%s" % ( ls + 1) )
107
108 # Connect leaf to all spines with dual link
109 for s in range( spine ):
110 switch = spines[ s ]
111 self.addLink(leafs[ls], switch, **linkopts)
112 self.addLink(leafs[ls], switch, **linkopts)
113
114 # Add hosts after paired ToR switches are added.
115 if ls % 2 == 0:
116 continue
117
118 # Add leaf-leaf link
119 self.addLink(leafs[ls], leafs[ls-1])
120
121 dual_ls = ls / 2
122 # Add hosts
123 for f in range(fanout):
124 if vlan_id[ dual_ls * fanout + f] != 0:
125 host = self.addHost(
126 name='h%s' % ( dual_ls * fanout + f + 1),
127 cls=DualHomedTaggedRoutedHost,
128 ips=['10.0.%d.%d/%d' % ( dual_ls + 2, f + 1, IP4_SUBNET_CLASS)],
129 gateway='10.0.%d.254' % ( dual_ls + 2),
130 mac='00:aa:00:00:00:%02x' % (dual_ls * fanout + f + 1),
131 vlan=vlan_id[ dual_ls*fanout + f ]
132 )
133 else:
134 host = self.addHost(
135 name='h%s' % (dual_ls * fanout + f + 1),
136 cls= DualHomedRoutedHost,
137 ips=['10.0.%d.%d/%d' % (dual_ls+2, f+1, IP4_SUBNET_CLASS)],
138 gateway='10.0.%d.254' % (dual_ls+2),
139 mac='00:aa:00:00:00:%02x' % (dual_ls * fanout + f + 1)
140 )
141 self.addLink(host, leafs[ls], **linkopts)
142 self.addLink(host, leafs[ls-1], **linkopts)
143
144 last_ls = leafs[leaf-2]
145 last_paired_ls = leafs[leaf-1]
146 # Create common components
147 # DHCP server
148 dhcp = self.addHost('dhcp', cls=DhcpServer, mac='00:99:00:00:00:01', ips=['10.0.3.253/24'],
149 gateway='10.0.3.254')
150
151 # Control plane switch (for DHCP servers)
152 cs1 = self.addSwitch('cs1', cls=OVSBridge)
153 self.addLink(cs1, last_ls)
154 self.addLink(dhcp, cs1)
155
156 # Control plane switch (for quagga fpm)
157 cs0 = self.addSwitch('cs0', cls=OVSBridge)
158
159 # Control plane NAT (for quagga fpm)
160 nat = self.addHost('nat', cls=NAT,
161 ip='172.16.0.1/12',
162 subnet=str(ip_network(u'172.16.0.0/12')), inNamespace=False)
163 self.addLink(cs0, nat)
164
165 # Internal Quagga bgp1
166 intfs = {'bgp1-eth0': {'ipAddrs': ['10.0.1.2/24', '2000::102/120'], 'mac': '00:88:00:00:00:02'},
167 'bgp1-eth1': {'ipAddrs': ['172.16.0.2/12']}}
168 bgp1 = self.addHost('bgp1', cls=BgpRouter,
169 interfaces=intfs,
170 quaggaConfFile='conf/bgpdbgp1.conf',
171 zebraConfFile='conf/zebradbgp1.conf')
172 self.addLink(bgp1, last_ls)
173 self.addLink(bgp1, cs0)
174
175 # Internal Quagga bgp2
176 intfs = {'bgp2-eth0': [{'ipAddrs': ['10.0.5.2/24', '2000::502/120'], 'mac': '00:88:00:00:00:04', 'vlan': '150'},
177 {'ipAddrs': ['10.0.6.2/24', '2000::602/120'], 'mac': '00:88:00:00:00:04', 'vlan': '160'}],
178 'bgp2-eth1': {'ipAddrs': ['172.16.0.4/12']}}
179 bgp2 = self.addHost('bgp2', cls=BgpRouter,
180 interfaces=intfs,
181 quaggaConfFile='conf/bgpdbgp2.conf',
182 zebraConfFile='conf/zebradbgp2.conf')
183 self.addLink(bgp2, last_paired_ls)
184 self.addLink(bgp2, cs0)
185
186 # External Quagga r1
187 intfs = {'r1-eth0': {'ipAddrs': ['10.0.1.1/24', '2000::101/120'], 'mac': '00:88:00:00:00:01'},
188 'r1-eth1': {'ipAddrs': ['10.0.5.1/24', '2000::501/120'], 'mac': '00:88:00:00:00:11'},
189 'r1-eth2': {'ipAddrs': ['10.0.99.1/16']}}
190 r1 = self.addHost('r1', cls=BgpRouter,
191 interfaces=intfs,
192 quaggaConfFile='conf/bgpdr1.conf')
193 self.addLink(r1, last_ls)
194 self.addLink(r1, last_paired_ls)
195
196 # External IPv4 Host behind r1
197 rh1 = self.addHost('rh1', cls=RoutedHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
198 self.addLink(r1, rh1)
199
200 # External Quagga r2
201 intfs = {'r2-eth0': {'ipAddrs': ['10.0.6.1/24', '2000::601/120'], 'mac': '00:88:00:00:00:02'},
202 'r2-eth1': {'ipAddrs': ['10.0.7.1/24', '2000::701/120'], 'mac': '00:88:00:00:00:22'},
203 'r2-eth2': {'ipAddrs': ['10.0.99.1/16']}}
204 r2 = self.addHost('r2', cls=BgpRouter,
205 interfaces=intfs,
206 quaggaConfFile='conf/bgpdr2.conf')
207 self.addLink(r2, last_ls)
208 self.addLink(r2, last_paired_ls)
209
210 # External IPv4 Host behind r2
211 rh2 = self.addHost('rh2', cls=RoutedHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
212 self.addLink(r2, rh2)
213
214class LeafSpineFabric (Topo) :
215 def __init__(self, spine = 2, leaf = 2, fanout = 2, vlan_id = [], **opts):
216 Topo.__init__(self, **opts)
217 spines = dict()
218 leafs = dict()
219
220 # TODO: support IPv6 hosts
221 linkopts = dict( bw=100 )
222
223 # Create spine switches
224 for s in range(spine):
225 spines[s] = self.addSwitch('spine10%s' % (s + 1), dpid = "00000000010%s" % (s + 1) )
226
227 # Create leaf switches
228 for ls in range(leaf):
229 leafs[ls] = self.addSwitch('leaf%s' % (ls + 1), dpid = "00000000000%s" % ( ls + 1) )
230
231 # Connect leaf to all spines
232 for s in range( spine ):
233 switch = spines[ s ]
234 self.addLink( leafs[ ls ], switch, **linkopts )
235
236 # If dual-homed ToR, add hosts only when adding second switch at each edge-pair
237 # When the number of leaf switches is odd, leave the last switch as a single ToR
238
239 # Add hosts
240 for f in range(fanout):
241 if vlan_id[ls * fanout + f] != 0:
242 host = self.addHost(
243 name='h%s' % (ls * fanout + f + 1),
244 cls=TaggedRoutedHost,
245 ips=['10.0.%d.%d/%d' % (ls+2, f+1, IP4_SUBNET_CLASS)],
246 gateway='10.0.%d.254' % (ls+2),
247 mac='00:aa:00:00:00:%02x' % (ls * fanout + f + 1),
248 vlan=vlan_id[ ls*fanout + f ]
249 )
250 else:
251 host = self.addHost(
252 name='h%s' % (ls * fanout + f + 1),
253 cls= RoutedHost,
254 ips=['10.0.%d.%d/%d' % (ls+2, f+1, IP4_SUBNET_CLASS)],
255 gateway='10.0.%d.254' % (ls+2),
256 mac='00:aa:00:00:00:%02x' % (ls * fanout + f + 1)
257 )
258 self.addLink(host, leafs[ls], **linkopts)
259
260 last_ls = leafs[leaf-1]
261 # Create common components
262 # DHCP server
263 dhcp = self.addHost('dhcp', cls=DhcpServer, mac='00:99:00:00:00:01', ips=['10.0.3.253/24'],
264 gateway='10.0.3.254')
265
266 # Control plane switch (for DHCP servers)
267 cs1 = self.addSwitch('cs1', cls=OVSBridge)
268 self.addLink(cs1, last_ls)
269 self.addLink(dhcp, cs1)
270
271 # Control plane switch (for quagga fpm)
272 cs0 = self.addSwitch('cs0', cls=OVSBridge)
273
274 # Control plane NAT (for quagga fpm)
275 nat = self.addHost('nat', cls=NAT,
276 ip='172.16.0.1/12',
277 subnet=str(ip_network(u'172.16.0.0/12')), inNamespace=False)
278 self.addLink(cs0, nat)
279
280 # Internal Quagga bgp1
281 intfs = {'bgp1-eth0': {'ipAddrs': ['10.0.1.2/24', '2000::102/120'], 'mac': '00:88:00:00:00:02'},
282 'bgp1-eth1': {'ipAddrs': ['172.16.0.2/12']}}
283 bgp1 = self.addHost('bgp1', cls=BgpRouter,
284 interfaces=intfs,
285 quaggaConfFile='conf/bgpdbgp1.conf',
286 zebraConfFile='conf/zebradbgp1.conf')
287 self.addLink(bgp1, last_ls)
288 self.addLink(bgp1, cs0)
289
290 # External Quagga r1
291 intfs = {'r1-eth0': {'ipAddrs': ['10.0.1.1/24', '2000::101/120'], 'mac': '00:88:00:00:00:01'},
292 'r1-eth1': {'ipAddrs': ['10.0.99.1/16']},
293 'r1-eth2': {'ipAddrs': ['2000::9901/120']}}
294 r1 = self.addHost('r1', cls=BgpRouter,
295 interfaces=intfs,
296 quaggaConfFile='conf/bgpdr1.conf')
297 self.addLink(r1, last_ls)
298
299 # External IPv4 Host behind r1
300 rh1 = self.addHost('rh1', cls=RoutedHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
301 self.addLink(r1, rh1)
302
303
304def config( opts ):
305 spine = opts.spine
306 leaf = opts.leaf
307 fanout = opts.fanout
308 ipv6 = opts.ipv6
309 dualhomed = opts.dualhomed
310 if opts.vlan == '':
311 vlan = [0] * (((leaf / 2) if dualhomed else leaf) * fanout)
312 else:
313 vlan = [int(vlan_id) if vlan_id != '' else 0 for vlan_id in opts.vlan.split(',')]
314
315 if opts.onosIp != '':
316 controllers = opts.onosIp.split( ',' )
317 else:
318 controllers = ['127.0.0.1']
319
320 if len(vlan) != ((leaf / 2) if dualhomed else leaf ) * fanout:
321 print "Invalid vlan configuration is given."
322 return
323
324 if not ipv6:
325 if dualhomed:
326 if leaf % 2 == 1 or leaf == 0:
327 print "Even number of leaf switches (at least two) are needed to build dual-homed topology."
328 return
329 else:
330 topo = DualHomedLeafSpineFabric(spine=spine, leaf=leaf, fanout=fanout, vlan_id=vlan)
331 else:
332 topo = LeafSpineFabric(spine=spine, leaf=leaf, fanout=fanout, vlan_id=vlan)
333 else:
334 print "IPv6 hosts are not supported yet."
335 return
336
337 net = Mininet( topo=topo, link=TCLink, build=False,
338 controller=None, autoSetMacs=True )
339 i = 0
340 for ip in controllers:
341 net.addController( "c%s" % ( i ), controller=RemoteController, ip=ip )
342 i += 1
343 net.build()
344 net.start()
345 CLI( net )
346 net.stop()
347
348if __name__ == '__main__':
349 setLogLevel('info')
350 config(opts)
351 os.system('sudo mn -c')