blob: b0b26a15088b783ea7021d6d2d8f3c921bce3a91 [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
You Wang5102af12018-02-08 12:30:12 -080015from routinglib import BgpRouter
16from trellislib import TrellisHost
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -070017
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),
You Wang5102af12018-02-08 12:30:12 -0800127 cls=TrellisHost,
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -0700128 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),
You Wang5102af12018-02-08 12:30:12 -0800131 vlan=vlan_id[ dual_ls*fanout + f ],
132 dualHomed=True
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -0700133 )
134 else:
135 host = self.addHost(
136 name='h%s' % (dual_ls * fanout + f + 1),
You Wang5102af12018-02-08 12:30:12 -0800137 cls=TrellisHost,
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -0700138 ips=['10.0.%d.%d/%d' % (dual_ls+2, f+1, IP4_SUBNET_CLASS)],
139 gateway='10.0.%d.254' % (dual_ls+2),
You Wang5102af12018-02-08 12:30:12 -0800140 mac='00:aa:00:00:00:%02x' % (dual_ls * fanout + f + 1),
141 dualHomed=True
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -0700142 )
143 self.addLink(host, leafs[ls], **linkopts)
144 self.addLink(host, leafs[ls-1], **linkopts)
145
146 last_ls = leafs[leaf-2]
147 last_paired_ls = leafs[leaf-1]
148 # Create common components
149 # DHCP server
You Wang5102af12018-02-08 12:30:12 -0800150 dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01', ips=['10.0.3.253/24'],
151 gateway='10.0.3.254', dhcpServer=True)
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -0700152
153 # Control plane switch (for DHCP servers)
154 cs1 = self.addSwitch('cs1', cls=OVSBridge)
155 self.addLink(cs1, last_ls)
156 self.addLink(dhcp, cs1)
157
158 # Control plane switch (for quagga fpm)
159 cs0 = self.addSwitch('cs0', cls=OVSBridge)
160
161 # Control plane NAT (for quagga fpm)
162 nat = self.addHost('nat', cls=NAT,
163 ip='172.16.0.1/12',
164 subnet=str(ip_network(u'172.16.0.0/12')), inNamespace=False)
165 self.addLink(cs0, nat)
166
167 # Internal Quagga bgp1
168 intfs = {'bgp1-eth0': {'ipAddrs': ['10.0.1.2/24', '2000::102/120'], 'mac': '00:88:00:00:00:02'},
169 'bgp1-eth1': {'ipAddrs': ['172.16.0.2/12']}}
170 bgp1 = self.addHost('bgp1', cls=BgpRouter,
171 interfaces=intfs,
172 quaggaConfFile='conf/bgpdbgp1.conf',
173 zebraConfFile='conf/zebradbgp1.conf')
174 self.addLink(bgp1, last_ls)
175 self.addLink(bgp1, cs0)
176
177 # Internal Quagga bgp2
178 intfs = {'bgp2-eth0': [{'ipAddrs': ['10.0.5.2/24', '2000::502/120'], 'mac': '00:88:00:00:00:04', 'vlan': '150'},
179 {'ipAddrs': ['10.0.6.2/24', '2000::602/120'], 'mac': '00:88:00:00:00:04', 'vlan': '160'}],
180 'bgp2-eth1': {'ipAddrs': ['172.16.0.4/12']}}
181 bgp2 = self.addHost('bgp2', cls=BgpRouter,
182 interfaces=intfs,
183 quaggaConfFile='conf/bgpdbgp2.conf',
184 zebraConfFile='conf/zebradbgp2.conf')
185 self.addLink(bgp2, last_paired_ls)
186 self.addLink(bgp2, cs0)
187
188 # External Quagga r1
189 intfs = {'r1-eth0': {'ipAddrs': ['10.0.1.1/24', '2000::101/120'], 'mac': '00:88:00:00:00:01'},
190 'r1-eth1': {'ipAddrs': ['10.0.5.1/24', '2000::501/120'], 'mac': '00:88:00:00:00:11'},
191 'r1-eth2': {'ipAddrs': ['10.0.99.1/16']}}
192 r1 = self.addHost('r1', cls=BgpRouter,
193 interfaces=intfs,
194 quaggaConfFile='conf/bgpdr1.conf')
195 self.addLink(r1, last_ls)
196 self.addLink(r1, last_paired_ls)
197
198 # External IPv4 Host behind r1
You Wang5102af12018-02-08 12:30:12 -0800199 rh1 = self.addHost('rh1', cls=TrellisHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -0700200 self.addLink(r1, rh1)
201
202 # External Quagga r2
203 intfs = {'r2-eth0': {'ipAddrs': ['10.0.6.1/24', '2000::601/120'], 'mac': '00:88:00:00:00:02'},
204 'r2-eth1': {'ipAddrs': ['10.0.7.1/24', '2000::701/120'], 'mac': '00:88:00:00:00:22'},
205 'r2-eth2': {'ipAddrs': ['10.0.99.1/16']}}
206 r2 = self.addHost('r2', cls=BgpRouter,
207 interfaces=intfs,
208 quaggaConfFile='conf/bgpdr2.conf')
209 self.addLink(r2, last_ls)
210 self.addLink(r2, last_paired_ls)
211
212 # External IPv4 Host behind r2
You Wang5102af12018-02-08 12:30:12 -0800213 rh2 = self.addHost('rh2', cls=TrellisHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -0700214 self.addLink(r2, rh2)
215
216class LeafSpineFabric (Topo) :
217 def __init__(self, spine = 2, leaf = 2, fanout = 2, vlan_id = [], **opts):
218 Topo.__init__(self, **opts)
219 spines = dict()
220 leafs = dict()
221
222 # TODO: support IPv6 hosts
223 linkopts = dict( bw=100 )
224
225 # Create spine switches
226 for s in range(spine):
227 spines[s] = self.addSwitch('spine10%s' % (s + 1), dpid = "00000000010%s" % (s + 1) )
228
229 # Create leaf switches
230 for ls in range(leaf):
231 leafs[ls] = self.addSwitch('leaf%s' % (ls + 1), dpid = "00000000000%s" % ( ls + 1) )
232
233 # Connect leaf to all spines
234 for s in range( spine ):
235 switch = spines[ s ]
236 self.addLink( leafs[ ls ], switch, **linkopts )
237
238 # If dual-homed ToR, add hosts only when adding second switch at each edge-pair
239 # When the number of leaf switches is odd, leave the last switch as a single ToR
240
241 # Add hosts
242 for f in range(fanout):
243 if vlan_id[ls * fanout + f] != 0:
244 host = self.addHost(
245 name='h%s' % (ls * fanout + f + 1),
You Wang5102af12018-02-08 12:30:12 -0800246 cls=TrellisHost,
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -0700247 ips=['10.0.%d.%d/%d' % (ls+2, f+1, IP4_SUBNET_CLASS)],
248 gateway='10.0.%d.254' % (ls+2),
249 mac='00:aa:00:00:00:%02x' % (ls * fanout + f + 1),
250 vlan=vlan_id[ ls*fanout + f ]
251 )
252 else:
253 host = self.addHost(
254 name='h%s' % (ls * fanout + f + 1),
You Wang5102af12018-02-08 12:30:12 -0800255 cls=TrellisHost,
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -0700256 ips=['10.0.%d.%d/%d' % (ls+2, f+1, IP4_SUBNET_CLASS)],
257 gateway='10.0.%d.254' % (ls+2),
258 mac='00:aa:00:00:00:%02x' % (ls * fanout + f + 1)
259 )
260 self.addLink(host, leafs[ls], **linkopts)
261
262 last_ls = leafs[leaf-1]
263 # Create common components
264 # DHCP server
You Wang5102af12018-02-08 12:30:12 -0800265 dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01', ips=['10.0.3.253/24'],
266 gateway='10.0.3.254', dhcpServer=True)
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -0700267
268 # Control plane switch (for DHCP servers)
269 cs1 = self.addSwitch('cs1', cls=OVSBridge)
270 self.addLink(cs1, last_ls)
271 self.addLink(dhcp, cs1)
272
273 # Control plane switch (for quagga fpm)
274 cs0 = self.addSwitch('cs0', cls=OVSBridge)
275
276 # Control plane NAT (for quagga fpm)
277 nat = self.addHost('nat', cls=NAT,
278 ip='172.16.0.1/12',
279 subnet=str(ip_network(u'172.16.0.0/12')), inNamespace=False)
280 self.addLink(cs0, nat)
281
282 # Internal Quagga bgp1
283 intfs = {'bgp1-eth0': {'ipAddrs': ['10.0.1.2/24', '2000::102/120'], 'mac': '00:88:00:00:00:02'},
284 'bgp1-eth1': {'ipAddrs': ['172.16.0.2/12']}}
285 bgp1 = self.addHost('bgp1', cls=BgpRouter,
286 interfaces=intfs,
287 quaggaConfFile='conf/bgpdbgp1.conf',
288 zebraConfFile='conf/zebradbgp1.conf')
289 self.addLink(bgp1, last_ls)
290 self.addLink(bgp1, cs0)
291
292 # External Quagga r1
293 intfs = {'r1-eth0': {'ipAddrs': ['10.0.1.1/24', '2000::101/120'], 'mac': '00:88:00:00:00:01'},
294 'r1-eth1': {'ipAddrs': ['10.0.99.1/16']},
295 'r1-eth2': {'ipAddrs': ['2000::9901/120']}}
296 r1 = self.addHost('r1', cls=BgpRouter,
297 interfaces=intfs,
298 quaggaConfFile='conf/bgpdr1.conf')
299 self.addLink(r1, last_ls)
300
301 # External IPv4 Host behind r1
You Wang5102af12018-02-08 12:30:12 -0800302 rh1 = self.addHost('rh1', cls=TrellisHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -0700303 self.addLink(r1, rh1)
304
Jonghwan Hyun3731d6a2017-10-19 11:59:31 -0700305def config( opts ):
306 spine = opts.spine
307 leaf = opts.leaf
308 fanout = opts.fanout
309 ipv6 = opts.ipv6
310 dualhomed = opts.dualhomed
311 if opts.vlan == '':
312 vlan = [0] * (((leaf / 2) if dualhomed else leaf) * fanout)
313 else:
314 vlan = [int(vlan_id) if vlan_id != '' else 0 for vlan_id in opts.vlan.split(',')]
315
316 if opts.onosIp != '':
317 controllers = opts.onosIp.split( ',' )
318 else:
319 controllers = ['127.0.0.1']
320
321 if len(vlan) != ((leaf / 2) if dualhomed else leaf ) * fanout:
322 print "Invalid vlan configuration is given."
323 return
324
325 if not ipv6:
326 if dualhomed:
327 if leaf % 2 == 1 or leaf == 0:
328 print "Even number of leaf switches (at least two) are needed to build dual-homed topology."
329 return
330 else:
331 topo = DualHomedLeafSpineFabric(spine=spine, leaf=leaf, fanout=fanout, vlan_id=vlan)
332 else:
333 topo = LeafSpineFabric(spine=spine, leaf=leaf, fanout=fanout, vlan_id=vlan)
334 else:
335 print "IPv6 hosts are not supported yet."
336 return
337
338 net = Mininet( topo=topo, link=TCLink, build=False,
339 controller=None, autoSetMacs=True )
340 i = 0
341 for ip in controllers:
342 net.addController( "c%s" % ( i ), controller=RemoteController, ip=ip )
343 i += 1
344 net.build()
345 net.start()
346 CLI( net )
347 net.stop()
348
349if __name__ == '__main__':
350 setLogLevel('info')
351 config(opts)
352 os.system('sudo mn -c')