blob: ae2470c5bb8dda896ef073e4683b879113afadf2 [file] [log] [blame]
Charles Chan9c5169f2018-08-29 12:44:44 -07001#!/usr/bin/python
Carmelo Casconee0eb9a02018-09-04 13:26:42 -07002import argparse
Charles Chan9c5169f2018-08-29 12:44:44 -07003import sys
Carmelo Cascone9a80e942018-11-26 00:08:06 -08004import os
5import json
6import random
7import urllib2
Charles Chan9c5169f2018-08-29 12:44:44 -07008sys.path.append('..')
9from mininet.topo import Topo
10from mininet.cli import CLI
11from mininet.log import setLogLevel
Carmelo Casconee0eb9a02018-09-04 13:26:42 -070012from mininet.node import OVSBridge, OVSSwitch
Charles Chan9c5169f2018-08-29 12:44:44 -070013from ipaddress import ip_network
14from routinglib import BgpRouter
Charles Chan4bcd9562019-12-15 00:02:32 -080015from routinglib import RoutedHost, UserNAT
Carmelo Casconee0eb9a02018-09-04 13:26:42 -070016from trellislib import DhcpClient, DhcpServer
Charles Chan9c5169f2018-08-29 12:44:44 -070017from trellislib import DualHomedDhcpClient
Carmelo Casconee0eb9a02018-09-04 13:26:42 -070018from trellislib import get_mininet, set_up_zebra_config
Charles Chan9c5169f2018-08-29 12:44:44 -070019from functools import partial
Charles Chan9c5169f2018-08-29 12:44:44 -070020
21PIPECONF_ID = 'org.onosproject.pipelines.fabric'
22
Carmelo Casconee0eb9a02018-09-04 13:26:42 -070023
24class Trellis(Topo):
Charles Chan9c5169f2018-08-29 12:44:44 -070025 """Trellis HAG topology with both OVS and BMV2 switches"""
26
Carmelo Casconee0eb9a02018-09-04 13:26:42 -070027 p4_cls = None
28
29 def get_p4_switch_args(self, name):
30 assert Trellis.p4_cls is not None
31 return dict(
32 name=name,
33 cls=Trellis.p4_cls,
34 pipeconf=PIPECONF_ID,
35 portcfg=True,
36 onosdevid="device:" + name)
37
38 def __init__(self, *args, **kwargs):
39 Topo.__init__(self, *args, **kwargs)
Charles Chan9c5169f2018-08-29 12:44:44 -070040
41 # Spines
Carmelo Cascone9a80e942018-11-26 00:08:06 -080042 s226 = self.addSwitch(latitude="39", longitude="-105",
43 **self.get_p4_switch_args('bmv2-s226'))
44 s227 = self.addSwitch('ovs-s227', dpid='227', latitude="39",longitude="-95")
Charles Chan9c5169f2018-08-29 12:44:44 -070045
46 # Leaves
Carmelo Cascone9a80e942018-11-26 00:08:06 -080047 s203 = self.addSwitch('ovs-s203', dpid='203', latitude="35",longitude="-110")
48 s204 = self.addSwitch(latitude="35", longitude="-105",
49 **self.get_p4_switch_args('bmv2-s204'))
50 s205 = self.addSwitch('ovs-s205', dpid='205', latitude="35",longitude="-95")
51 s206 = self.addSwitch('ovs-s206', dpid='206', latitude="35",longitude="-90")
Charles Chan9c5169f2018-08-29 12:44:44 -070052
53 # Leaf-Spine Links
54 self.addLink(s226, s203)
55 self.addLink(s226, s203)
56 self.addLink(s226, s204)
57 self.addLink(s226, s204)
58 self.addLink(s226, s205)
59 self.addLink(s226, s205)
60 self.addLink(s226, s206)
61 self.addLink(s226, s206)
62 self.addLink(s227, s203)
63 self.addLink(s227, s203)
64 self.addLink(s227, s204)
65 self.addLink(s227, s204)
66 self.addLink(s227, s205)
67 self.addLink(s227, s205)
68 self.addLink(s227, s206)
69 self.addLink(s227, s206)
70
71 # Leaf-Leaf Links
72 self.addLink(s203, s204)
73 self.addLink(s205, s206)
74
75 # NOTE avoid using 10.0.1.0/24 which is the default subnet of quaggas
76 # NOTE avoid using 00:00:00:00:00:xx which is the default mac of host behind upstream router
77 # IPv4 Hosts
Charles Chan9c5169f2018-08-29 12:44:44 -070078 h3 = self.addHost('h3', cls=DhcpClient, mac='00:aa:00:00:00:03')
79 h4 = self.addHost('h4', cls=DhcpClient, mac='00:aa:00:00:00:04')
80 h5 = self.addHost('h5', cls=DhcpClient, mac='00:aa:00:00:00:05')
Carmelo Cascone9a80e942018-11-26 00:08:06 -080081 h6 = self.addHost('h6', cls=DhcpClient, mac='00:aa:00:00:00:06')
82 self.addLink(h3, s203)
83 self.addLink(h4, s204)
84 self.addLink(h5, s205)
85 self.addLink(h6, s205)
Charles Chan9c5169f2018-08-29 12:44:44 -070086
Carmelo Cascone9a80e942018-11-26 00:08:06 -080087 # Dual-homed IPv4 Host on 203-204
88 dh1 = self.addHost('dh1', cls=DualHomedDhcpClient, mac='00:cc:00:00:00:01')
Charles Chan9c5169f2018-08-29 12:44:44 -070089 self.addLink(dh1, s204)
90 self.addLink(dh1, s203)
91
Carmelo Cascone9a80e942018-11-26 00:08:06 -080092 # Dual-homed IPv4 Host for 205-206
93 dh2 = self.addHost('dh2', cls=DualHomedDhcpClient, mac='00:cc:00:00:00:02')
94 self.addLink(dh2, s205)
95 self.addLink(dh2, s206)
Charles Chan9c5169f2018-08-29 12:44:44 -070096
Carmelo Cascone9a80e942018-11-26 00:08:06 -080097 # DHCP server
98 dhcp = self.addHost('dhcp', cls=DhcpServer, mac='00:99:00:00:00:01',
99 ips=['10.0.3.253/24'], gateway='10.0.3.254',
100 configFile='./dhcpd_hybrid_v4.conf')
Charles Chan9c5169f2018-08-29 12:44:44 -0700101
102 # Dataplane L2 plane switch (for DHCP servers)
Charles Chana60fbd12019-10-22 21:56:35 -0700103 cs1 = self.addSwitch('cs1', cls=OVSBridge, datapath='user')
Charles Chan9c5169f2018-08-29 12:44:44 -0700104 self.addLink(cs1, s205)
105 self.addLink(dhcp, cs1)
Charles Chan9c5169f2018-08-29 12:44:44 -0700106
107 # Control plane switch (for quagga fpm)
Charles Chana60fbd12019-10-22 21:56:35 -0700108 cs0 = self.addSwitch('cs0', cls=OVSBridge, datapath='user')
Charles Chan9c5169f2018-08-29 12:44:44 -0700109
110 # Control plane NAT (for quagga fpm)
Charles Chan4bcd9562019-12-15 00:02:32 -0800111 nat = self.addHost('nat', cls=UserNAT,
Charles Chan9c5169f2018-08-29 12:44:44 -0700112 ip='172.16.0.1/24',
113 subnet=str(ip_network(u'172.16.0.0/24')), inNamespace=False)
114 self.addLink(cs0, nat)
115
116 # Internal Quagga bgp1
117 """
118 intfs = {'bgp1-eth0': [{'ipAddrs': ['10.0.1.2/24', '2000::102/120'], 'mac': '00:88:00:00:00:03', 'vlan': '110'},
119 {'ipAddrs': ['10.0.7.2/24', '2000::702/120'], 'mac': '00:88:00:00:00:03', 'vlan': '170'}],
120 'bgp1-eth1': {'ipAddrs': ['172.16.0.3/24']}}
121 """
122 intfs = {'bgp1-eth0': {'ipAddrs': ['10.0.1.2/24'], 'mac': '00:88:00:00:00:03', 'vlan': '110'},
123 'bgp1-eth1': {'ipAddrs': ['172.16.0.3/24']}}
124 bgp1 = self.addHost('bgp1', cls=BgpRouter,
125 interfaces=intfs,
126 quaggaConfFile='./bgpdbgp1.conf',
127 zebraConfFile='./zebradbgp1.conf')
128 self.addLink(bgp1, s205)
129 self.addLink(bgp1, cs0)
130
131 # Internal Quagga bgp2
132 """
133 intfs = {'bgp2-eth0': [{'ipAddrs': ['10.0.5.2/24', '2000::502/120'], 'mac': '00:88:00:00:00:04', 'vlan': '150'},
134 {'ipAddrs': ['10.0.6.2/24', '2000::602/120'], 'mac': '00:88:00:00:00:04', 'vlan': '160'}],
135 'bgp2-eth1': {'ipAddrs': ['172.16.0.4/24']}}
136 """
137 intfs = {'bgp2-eth0': {'ipAddrs': ['10.0.6.2/24'], 'mac': '00:88:00:00:00:04', 'vlan': '160'},
138 'bgp2-eth1': {'ipAddrs': ['172.16.0.4/24']}}
139 bgp2 = self.addHost('bgp2', cls=BgpRouter,
140 interfaces=intfs,
141 quaggaConfFile='./bgpdbgp2.conf',
142 zebraConfFile='./zebradbgp2.conf')
143 self.addLink(bgp2, s206)
144 self.addLink(bgp2, cs0)
145
146 # External Quagga r1
147 intfs = {'r1-eth0': {'ipAddrs': ['10.0.1.1/24'], 'mac': '00:88:00:00:00:01'},
148 'r1-eth1': {'ipAddrs': ['10.0.99.1/16']}}
149 r1 = self.addHost('r1', cls=BgpRouter,
150 interfaces=intfs,
151 quaggaConfFile='./bgpdr1.conf')
152 self.addLink(r1, s205)
153 #self.addLink(r1, s206)
154
155 # External IPv4 Host behind r1
156 rh1 = self.addHost('rh1', cls=RoutedHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
157 self.addLink(r1, rh1)
158
Charles Chan9c5169f2018-08-29 12:44:44 -0700159 # External Quagga r2
160 intfs = {'r2-eth0': {'ipAddrs': ['10.0.6.1/24'], 'mac': '00:88:00:00:00:02'},
161 'r2-eth1': {'ipAddrs': ['10.0.99.1/16']}}
162 r2 = self.addHost('r2', cls=BgpRouter,
163 interfaces=intfs,
164 quaggaConfFile='./bgpdr2.conf')
165 self.addLink(r2, s206)
166 #self.addLink(r2, s205)
167
168 # External IPv4 Host behind r2
169 rh2 = self.addHost('rh2', cls=RoutedHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
170 self.addLink(r2, rh2)
171
Charles Chan9c5169f2018-08-29 12:44:44 -0700172 # ----- Secondary fabric -----
173
174 # Spines(HAG)
Carmelo Cascone9a80e942018-11-26 00:08:06 -0800175 s246 = self.addSwitch(latitude="44", longitude="-105",
176 **self.get_p4_switch_args('bmv2-s246'))
177 s247 = self.addSwitch(latitude="44", longitude="-95",
178 **self.get_p4_switch_args('bmv2-s247'))
Charles Chan9c5169f2018-08-29 12:44:44 -0700179
180 # Leaves(DAAS)
Carmelo Cascone9a80e942018-11-26 00:08:06 -0800181 s207 = self.addSwitch('ovs-s207', dpid='207', latitude="47", longitude="-105")
182 s208 = self.addSwitch(latitude="47", longitude="-95",
183 **self.get_p4_switch_args('bmv2-s208'))
Charles Chan9c5169f2018-08-29 12:44:44 -0700184
185 # HAG-DAAS Links
186 self.addLink(s246, s207)
187 self.addLink(s246, s208)
188 self.addLink(s247, s207)
189 self.addLink(s247, s208)
190
191 # HAG - Spine Links
192 self.addLink(s246, s226)
193 self.addLink(s247, s227)
194
195 # IPv4 Hosts - RPDs
Carmelo Cascone9a80e942018-11-26 00:08:06 -0800196 h1 = self.addHost('h1', cls=DhcpClient, mac='00:dd:00:00:00:01')
197 h2 = self.addHost('h2', cls=DhcpClient, mac='00:dd:00:00:00:02')
198 self.addLink(h1, s207)
199 self.addLink(h2, s208)
Charles Chan9c5169f2018-08-29 12:44:44 -0700200
201
Charles Chan9c5169f2018-08-29 12:44:44 -0700202topos = { 'trellis' : Trellis }
203
Carmelo Cascone9a80e942018-11-26 00:08:06 -0800204class ONOSOVSSwitch( OVSSwitch ):
205 """OVSSwitch that generates and pushes config to ONOS"""
206
207 def __init__(self, name, netcfg=True, **kwargs):
208 OVSSwitch.__init__(self, name, **kwargs)
209 self.netcfg = netcfg in (True, '1', 'true', 'True')
210 self.netcfgfile = '/tmp/ovs-%s-netcfg.json' % self.name
211 self.onosDeviceId = 'of:%s' % self.dpid
212 self.longitude = kwargs['longitude'] if 'longitude' in kwargs else None
213 self.latitude = kwargs['latitude'] if 'latitude' in kwargs else None
214
215 @staticmethod
216 def controllerIp(controllers):
217 try:
218 clist = controllers[0].nodes()
219 except AttributeError:
220 clist = controllers
221 assert len(clist) > 0
222 return random.choice(clist).IP()
223
224 def start(self, controllers):
225 """
226 Starts the switch, then notifies ONOS about the new device via Netcfg.
227 """
228 OVSSwitch.start(self, controllers)
229
230 if not self.netcfg:
231 # Do not push config to ONOS.
232 return
233
234 controllerIP = self.controllerIp(controllers)
235
236 basicCfg = {
237 "name": self.name,
238 "driver": "ofdpa-ovs"
239 }
240
241 if self.longitude and self.latitude:
242 basicCfg["longitude"] = self.longitude
243 basicCfg["latitude"] = self.latitude
244
245 cfgData = {
246 "devices": {
247 self.onosDeviceId: { "basic": basicCfg }
248 }
249 }
250 with open(self.netcfgfile, 'w') as fp:
251 json.dump(cfgData, fp, indent=4)
252
253 # Build netcfg URL
254 url = 'http://%s:8181/onos/v1/network/configuration/' % controllerIP
255 # Instantiate password manager for HTTP auth
256 pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
257 user = os.environ['ONOS_WEB_USER'] if 'ONOS_WEB_USER' in os.environ else 'onos'
258 password = os.environ['ONOS_WEB_PASS'] if 'ONOS_WEB_PASS' in os.environ else 'rocks'
259 pm.add_password(None, url, user, password)
260 urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(pm)))
261 try:
262 # Push config data to controller
263 req = urllib2.Request(url, json.dumps(cfgData),
264 {'Content-Type': 'application/json'})
265 f = urllib2.urlopen(req)
266 print f.read()
267 f.close()
268 except urllib2.URLError as e:
269 warn("*** WARN: unable to push config to ONOS (%s)\n" % e.reason)
270
Charles Chan9c5169f2018-08-29 12:44:44 -0700271if __name__ == "__main__":
Carmelo Cascone9a80e942018-11-26 00:08:06 -0800272 setLogLevel('info')
Charles Chan9c5169f2018-08-29 12:44:44 -0700273
Carmelo Casconee0eb9a02018-09-04 13:26:42 -0700274 parser = argparse.ArgumentParser(description="Trellis Arguments")
275 parser.add_argument("-c", "--controllers", help = "Comma Separated List of ONOS controllers",
276 required = True, default = "")
277 parser.add_argument("-a", "--p4runtime-agent", help = "P4Runtime agent to use on Bmv2 devices (pi or stratum)",
278 required = False, default = "pi")
279 arguments = parser.parse_args()
280 agent = arguments.p4runtime_agent
281
282 if agent == "stratum":
283 from stratum import ONOSStratumBmv2Switch
284 Trellis.p4_cls = ONOSStratumBmv2Switch
285 else:
286 from bmv2 import ONOSBmv2Switch
287 Trellis.p4_cls = ONOSBmv2Switch
288
289 set_up_zebra_config(arguments.controllers)
290
Charles Chan9c5169f2018-08-29 12:44:44 -0700291 topo = Trellis()
Charles Chana60fbd12019-10-22 21:56:35 -0700292 switch = partial(ONOSOVSSwitch, protocols='OpenFlow13', datapath='user')
Charles Chan9c5169f2018-08-29 12:44:44 -0700293 net = get_mininet(arguments, topo, switch)
294
295 net.start()
296 CLI(net)
297 net.stop()