blob: 1f450882d918de988040b99bd567cad77cd8da0c [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 mininet.nodelib import NAT
14from ipaddress import ip_network
15from routinglib import BgpRouter
Carmelo Casconee0eb9a02018-09-04 13:26:42 -070016from routinglib import RoutedHost
17from trellislib import DhcpClient, DhcpServer
Charles Chan9c5169f2018-08-29 12:44:44 -070018from trellislib import DualHomedDhcpClient
Carmelo Casconee0eb9a02018-09-04 13:26:42 -070019from trellislib import get_mininet, set_up_zebra_config
Charles Chan9c5169f2018-08-29 12:44:44 -070020from functools import partial
Charles Chan9c5169f2018-08-29 12:44:44 -070021
22PIPECONF_ID = 'org.onosproject.pipelines.fabric'
23
Carmelo Casconee0eb9a02018-09-04 13:26:42 -070024
25class Trellis(Topo):
Charles Chan9c5169f2018-08-29 12:44:44 -070026 """Trellis HAG topology with both OVS and BMV2 switches"""
27
Carmelo Casconee0eb9a02018-09-04 13:26:42 -070028 p4_cls = None
29
30 def get_p4_switch_args(self, name):
31 assert Trellis.p4_cls is not None
32 return dict(
33 name=name,
34 cls=Trellis.p4_cls,
35 pipeconf=PIPECONF_ID,
36 portcfg=True,
37 onosdevid="device:" + name)
38
39 def __init__(self, *args, **kwargs):
40 Topo.__init__(self, *args, **kwargs)
Charles Chan9c5169f2018-08-29 12:44:44 -070041
42 # Spines
Carmelo Cascone9a80e942018-11-26 00:08:06 -080043 s226 = self.addSwitch(latitude="39", longitude="-105",
44 **self.get_p4_switch_args('bmv2-s226'))
45 s227 = self.addSwitch('ovs-s227', dpid='227', latitude="39",longitude="-95")
Charles Chan9c5169f2018-08-29 12:44:44 -070046
47 # Leaves
Carmelo Cascone9a80e942018-11-26 00:08:06 -080048 s203 = self.addSwitch('ovs-s203', dpid='203', latitude="35",longitude="-110")
49 s204 = self.addSwitch(latitude="35", longitude="-105",
50 **self.get_p4_switch_args('bmv2-s204'))
51 s205 = self.addSwitch('ovs-s205', dpid='205', latitude="35",longitude="-95")
52 s206 = self.addSwitch('ovs-s206', dpid='206', latitude="35",longitude="-90")
Charles Chan9c5169f2018-08-29 12:44:44 -070053
54 # Leaf-Spine Links
55 self.addLink(s226, s203)
56 self.addLink(s226, s203)
57 self.addLink(s226, s204)
58 self.addLink(s226, s204)
59 self.addLink(s226, s205)
60 self.addLink(s226, s205)
61 self.addLink(s226, s206)
62 self.addLink(s226, s206)
63 self.addLink(s227, s203)
64 self.addLink(s227, s203)
65 self.addLink(s227, s204)
66 self.addLink(s227, s204)
67 self.addLink(s227, s205)
68 self.addLink(s227, s205)
69 self.addLink(s227, s206)
70 self.addLink(s227, s206)
71
72 # Leaf-Leaf Links
73 self.addLink(s203, s204)
74 self.addLink(s205, s206)
75
76 # NOTE avoid using 10.0.1.0/24 which is the default subnet of quaggas
77 # NOTE avoid using 00:00:00:00:00:xx which is the default mac of host behind upstream router
78 # IPv4 Hosts
Charles Chan9c5169f2018-08-29 12:44:44 -070079 h3 = self.addHost('h3', cls=DhcpClient, mac='00:aa:00:00:00:03')
80 h4 = self.addHost('h4', cls=DhcpClient, mac='00:aa:00:00:00:04')
81 h5 = self.addHost('h5', cls=DhcpClient, mac='00:aa:00:00:00:05')
Carmelo Cascone9a80e942018-11-26 00:08:06 -080082 h6 = self.addHost('h6', cls=DhcpClient, mac='00:aa:00:00:00:06')
83 self.addLink(h3, s203)
84 self.addLink(h4, s204)
85 self.addLink(h5, s205)
86 self.addLink(h6, s205)
Charles Chan9c5169f2018-08-29 12:44:44 -070087
Carmelo Cascone9a80e942018-11-26 00:08:06 -080088 # Dual-homed IPv4 Host on 203-204
89 dh1 = self.addHost('dh1', cls=DualHomedDhcpClient, mac='00:cc:00:00:00:01')
Charles Chan9c5169f2018-08-29 12:44:44 -070090 self.addLink(dh1, s204)
91 self.addLink(dh1, s203)
92
Carmelo Cascone9a80e942018-11-26 00:08:06 -080093 # Dual-homed IPv4 Host for 205-206
94 dh2 = self.addHost('dh2', cls=DualHomedDhcpClient, mac='00:cc:00:00:00:02')
95 self.addLink(dh2, s205)
96 self.addLink(dh2, s206)
Charles Chan9c5169f2018-08-29 12:44:44 -070097
Carmelo Cascone9a80e942018-11-26 00:08:06 -080098 # DHCP server
99 dhcp = self.addHost('dhcp', cls=DhcpServer, mac='00:99:00:00:00:01',
100 ips=['10.0.3.253/24'], gateway='10.0.3.254',
101 configFile='./dhcpd_hybrid_v4.conf')
Charles Chan9c5169f2018-08-29 12:44:44 -0700102
103 # Dataplane L2 plane switch (for DHCP servers)
104 cs1 = self.addSwitch('cs1', cls=OVSBridge)
105 self.addLink(cs1, s205)
106 self.addLink(dhcp, cs1)
Charles Chan9c5169f2018-08-29 12:44:44 -0700107
108 # Control plane switch (for quagga fpm)
109 cs0 = self.addSwitch('cs0', cls=OVSBridge)
110
111 # Control plane NAT (for quagga fpm)
112 nat = self.addHost('nat', cls=NAT,
113 ip='172.16.0.1/24',
114 subnet=str(ip_network(u'172.16.0.0/24')), inNamespace=False)
115 self.addLink(cs0, nat)
116
117 # Internal Quagga bgp1
118 """
119 intfs = {'bgp1-eth0': [{'ipAddrs': ['10.0.1.2/24', '2000::102/120'], 'mac': '00:88:00:00:00:03', 'vlan': '110'},
120 {'ipAddrs': ['10.0.7.2/24', '2000::702/120'], 'mac': '00:88:00:00:00:03', 'vlan': '170'}],
121 'bgp1-eth1': {'ipAddrs': ['172.16.0.3/24']}}
122 """
123 intfs = {'bgp1-eth0': {'ipAddrs': ['10.0.1.2/24'], 'mac': '00:88:00:00:00:03', 'vlan': '110'},
124 'bgp1-eth1': {'ipAddrs': ['172.16.0.3/24']}}
125 bgp1 = self.addHost('bgp1', cls=BgpRouter,
126 interfaces=intfs,
127 quaggaConfFile='./bgpdbgp1.conf',
128 zebraConfFile='./zebradbgp1.conf')
129 self.addLink(bgp1, s205)
130 self.addLink(bgp1, cs0)
131
132 # Internal Quagga bgp2
133 """
134 intfs = {'bgp2-eth0': [{'ipAddrs': ['10.0.5.2/24', '2000::502/120'], 'mac': '00:88:00:00:00:04', 'vlan': '150'},
135 {'ipAddrs': ['10.0.6.2/24', '2000::602/120'], 'mac': '00:88:00:00:00:04', 'vlan': '160'}],
136 'bgp2-eth1': {'ipAddrs': ['172.16.0.4/24']}}
137 """
138 intfs = {'bgp2-eth0': {'ipAddrs': ['10.0.6.2/24'], 'mac': '00:88:00:00:00:04', 'vlan': '160'},
139 'bgp2-eth1': {'ipAddrs': ['172.16.0.4/24']}}
140 bgp2 = self.addHost('bgp2', cls=BgpRouter,
141 interfaces=intfs,
142 quaggaConfFile='./bgpdbgp2.conf',
143 zebraConfFile='./zebradbgp2.conf')
144 self.addLink(bgp2, s206)
145 self.addLink(bgp2, cs0)
146
147 # External Quagga r1
148 intfs = {'r1-eth0': {'ipAddrs': ['10.0.1.1/24'], 'mac': '00:88:00:00:00:01'},
149 'r1-eth1': {'ipAddrs': ['10.0.99.1/16']}}
150 r1 = self.addHost('r1', cls=BgpRouter,
151 interfaces=intfs,
152 quaggaConfFile='./bgpdr1.conf')
153 self.addLink(r1, s205)
154 #self.addLink(r1, s206)
155
156 # External IPv4 Host behind r1
157 rh1 = self.addHost('rh1', cls=RoutedHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
158 self.addLink(r1, rh1)
159
Charles Chan9c5169f2018-08-29 12:44:44 -0700160 # External Quagga r2
161 intfs = {'r2-eth0': {'ipAddrs': ['10.0.6.1/24'], 'mac': '00:88:00:00:00:02'},
162 'r2-eth1': {'ipAddrs': ['10.0.99.1/16']}}
163 r2 = self.addHost('r2', cls=BgpRouter,
164 interfaces=intfs,
165 quaggaConfFile='./bgpdr2.conf')
166 self.addLink(r2, s206)
167 #self.addLink(r2, s205)
168
169 # External IPv4 Host behind r2
170 rh2 = self.addHost('rh2', cls=RoutedHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
171 self.addLink(r2, rh2)
172
Charles Chan9c5169f2018-08-29 12:44:44 -0700173 # ----- Secondary fabric -----
174
175 # Spines(HAG)
Carmelo Cascone9a80e942018-11-26 00:08:06 -0800176 s246 = self.addSwitch(latitude="44", longitude="-105",
177 **self.get_p4_switch_args('bmv2-s246'))
178 s247 = self.addSwitch(latitude="44", longitude="-95",
179 **self.get_p4_switch_args('bmv2-s247'))
Charles Chan9c5169f2018-08-29 12:44:44 -0700180
181 # Leaves(DAAS)
Carmelo Cascone9a80e942018-11-26 00:08:06 -0800182 s207 = self.addSwitch('ovs-s207', dpid='207', latitude="47", longitude="-105")
183 s208 = self.addSwitch(latitude="47", longitude="-95",
184 **self.get_p4_switch_args('bmv2-s208'))
Charles Chan9c5169f2018-08-29 12:44:44 -0700185
186 # HAG-DAAS Links
187 self.addLink(s246, s207)
188 self.addLink(s246, s208)
189 self.addLink(s247, s207)
190 self.addLink(s247, s208)
191
192 # HAG - Spine Links
193 self.addLink(s246, s226)
194 self.addLink(s247, s227)
195
196 # IPv4 Hosts - RPDs
Carmelo Cascone9a80e942018-11-26 00:08:06 -0800197 h1 = self.addHost('h1', cls=DhcpClient, mac='00:dd:00:00:00:01')
198 h2 = self.addHost('h2', cls=DhcpClient, mac='00:dd:00:00:00:02')
199 self.addLink(h1, s207)
200 self.addLink(h2, s208)
Charles Chan9c5169f2018-08-29 12:44:44 -0700201
202
Charles Chan9c5169f2018-08-29 12:44:44 -0700203topos = { 'trellis' : Trellis }
204
Carmelo Cascone9a80e942018-11-26 00:08:06 -0800205class ONOSOVSSwitch( OVSSwitch ):
206 """OVSSwitch that generates and pushes config to ONOS"""
207
208 def __init__(self, name, netcfg=True, **kwargs):
209 OVSSwitch.__init__(self, name, **kwargs)
210 self.netcfg = netcfg in (True, '1', 'true', 'True')
211 self.netcfgfile = '/tmp/ovs-%s-netcfg.json' % self.name
212 self.onosDeviceId = 'of:%s' % self.dpid
213 self.longitude = kwargs['longitude'] if 'longitude' in kwargs else None
214 self.latitude = kwargs['latitude'] if 'latitude' in kwargs else None
215
216 @staticmethod
217 def controllerIp(controllers):
218 try:
219 clist = controllers[0].nodes()
220 except AttributeError:
221 clist = controllers
222 assert len(clist) > 0
223 return random.choice(clist).IP()
224
225 def start(self, controllers):
226 """
227 Starts the switch, then notifies ONOS about the new device via Netcfg.
228 """
229 OVSSwitch.start(self, controllers)
230
231 if not self.netcfg:
232 # Do not push config to ONOS.
233 return
234
235 controllerIP = self.controllerIp(controllers)
236
237 basicCfg = {
238 "name": self.name,
239 "driver": "ofdpa-ovs"
240 }
241
242 if self.longitude and self.latitude:
243 basicCfg["longitude"] = self.longitude
244 basicCfg["latitude"] = self.latitude
245
246 cfgData = {
247 "devices": {
248 self.onosDeviceId: { "basic": basicCfg }
249 }
250 }
251 with open(self.netcfgfile, 'w') as fp:
252 json.dump(cfgData, fp, indent=4)
253
254 # Build netcfg URL
255 url = 'http://%s:8181/onos/v1/network/configuration/' % controllerIP
256 # Instantiate password manager for HTTP auth
257 pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
258 user = os.environ['ONOS_WEB_USER'] if 'ONOS_WEB_USER' in os.environ else 'onos'
259 password = os.environ['ONOS_WEB_PASS'] if 'ONOS_WEB_PASS' in os.environ else 'rocks'
260 pm.add_password(None, url, user, password)
261 urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(pm)))
262 try:
263 # Push config data to controller
264 req = urllib2.Request(url, json.dumps(cfgData),
265 {'Content-Type': 'application/json'})
266 f = urllib2.urlopen(req)
267 print f.read()
268 f.close()
269 except urllib2.URLError as e:
270 warn("*** WARN: unable to push config to ONOS (%s)\n" % e.reason)
271
Charles Chan9c5169f2018-08-29 12:44:44 -0700272if __name__ == "__main__":
Carmelo Cascone9a80e942018-11-26 00:08:06 -0800273 setLogLevel('info')
Charles Chan9c5169f2018-08-29 12:44:44 -0700274
Carmelo Casconee0eb9a02018-09-04 13:26:42 -0700275 parser = argparse.ArgumentParser(description="Trellis Arguments")
276 parser.add_argument("-c", "--controllers", help = "Comma Separated List of ONOS controllers",
277 required = True, default = "")
278 parser.add_argument("-a", "--p4runtime-agent", help = "P4Runtime agent to use on Bmv2 devices (pi or stratum)",
279 required = False, default = "pi")
280 arguments = parser.parse_args()
281 agent = arguments.p4runtime_agent
282
283 if agent == "stratum":
284 from stratum import ONOSStratumBmv2Switch
285 Trellis.p4_cls = ONOSStratumBmv2Switch
286 else:
287 from bmv2 import ONOSBmv2Switch
288 Trellis.p4_cls = ONOSBmv2Switch
289
290 set_up_zebra_config(arguments.controllers)
291
Charles Chan9c5169f2018-08-29 12:44:44 -0700292 topo = Trellis()
Carmelo Cascone9a80e942018-11-26 00:08:06 -0800293 switch = partial(ONOSOVSSwitch, protocols='OpenFlow13')
Charles Chan9c5169f2018-08-29 12:44:44 -0700294 net = get_mininet(arguments, topo, switch)
295
296 net.start()
297 CLI(net)
298 net.stop()