blob: cdc26e1967499573300cc461c234b70945d8635c [file] [log] [blame]
Carmelo Casconeb7524272017-06-05 16:53:13 -04001import os
Carmelo Cascone977ae3f2016-06-23 19:28:28 -07002import socket
Carmelo Casconeb7524272017-06-05 16:53:13 -04003import re
4import json
Carmelo Casconef11513d2018-01-16 00:31:14 -08005import threading
Brian O'Connor71167f92017-06-16 14:55:00 -07006import urllib2
Carmelo Cascone977ae3f2016-06-23 19:28:28 -07007
Carmelo Cascone46d360b2017-08-29 20:20:32 +02008import time
Carmelo Casconef11513d2018-01-16 00:31:14 -08009from contextlib import closing
Carmelo Cascone46d360b2017-08-29 20:20:32 +020010
Carmelo Casconeb7524272017-06-05 16:53:13 -040011from mininet.log import info, warn, error
Carmelo Cascone34433252017-08-25 20:27:18 +020012from mininet.node import Switch, Host
Carmelo Cascone785fada2016-06-16 18:34:16 -070013
Carmelo Casconeb7524272017-06-05 16:53:13 -040014if 'ONOS_ROOT' not in os.environ:
15 error("ERROR: environment var $ONOS_ROOT not set")
16 exit()
17
Carmelo Casconef11513d2018-01-16 00:31:14 -080018SIMPLE_SWITCH_GRPC = 'simple_switch_grpc'
Carmelo Casconeb7524272017-06-05 16:53:13 -040019ONOS_ROOT = os.environ["ONOS_ROOT"]
Carmelo Cascone34433252017-08-25 20:27:18 +020020PKT_BYTES_TO_DUMP = 80
Carmelo Cascone46d360b2017-08-29 20:20:32 +020021VALGRIND_PREFIX = 'valgrind --leak-check=yes'
Carmelo Casconef11513d2018-01-16 00:31:14 -080022SWITCH_START_TIMEOUT = 5 # seconds
23BMV2_LOG_LINES = 5
24
Carmelo Cascone46d360b2017-08-29 20:20:32 +020025
26def parseBoolean(value):
27 if value in ['1', 1, 'true', 'True']:
28 return True
29 else:
30 return False
Carmelo Cascone34433252017-08-25 20:27:18 +020031
32
Carmelo Casconef11513d2018-01-16 00:31:14 -080033def pickUnusedPort():
34 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
35 s.bind(('localhost', 0))
36 addr, port = s.getsockname()
37 s.close()
38 return port
39
40
41def writeToFile(path, value):
42 with open(path, "w") as f:
43 f.write(str(value))
44
45
46def watchDog(sw):
47 while True:
48 if sw.stopped:
49 return
50 with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
51 if s.connect_ex(('127.0.0.1', sw.grpcPort)) == 0:
52 time.sleep(1)
53 else:
54 warn("\n*** WARN: BMv2 instance %s (%s) died!\n"
55 % (sw.deviceId, sw.name))
56 sw.printBmv2Log()
57 print ("-" * 80) + "\n"
58 return
59
60
Carmelo Cascone34433252017-08-25 20:27:18 +020061class ONOSHost(Host):
62 def __init__(self, name, inNamespace=True, **params):
63 Host.__init__(self, name, inNamespace=inNamespace, **params)
64
65 def config(self, **params):
66 r = super(Host, self).config(**params)
67 for off in ["rx", "tx", "sg"]:
Carmelo Casconef11513d2018-01-16 00:31:14 -080068 cmd = "/sbin/ethtool --offload %s %s off" \
Carmelo Cascone6ec8f8f2017-11-22 14:27:06 -080069 % (self.defaultIntf(), off)
Carmelo Cascone34433252017-08-25 20:27:18 +020070 self.cmd(cmd)
71 # disable IPv6
72 self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
73 self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1")
74 self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1")
75 return r
Carmelo Casconeb7524272017-06-05 16:53:13 -040076
Carmelo Cascone785fada2016-06-16 18:34:16 -070077
78class ONOSBmv2Switch(Switch):
Carmelo Casconeb7524272017-06-05 16:53:13 -040079 """BMv2 software switch with gRPC server"""
Carmelo Cascone785fada2016-06-16 18:34:16 -070080
Carmelo Cascone785fada2016-06-16 18:34:16 -070081 deviceId = 0
82
Carmelo Casconef11513d2018-01-16 00:31:14 -080083 def __init__(self, name, json=None, debugger=False, loglevel="warn",
84 elogger=False, grpcPort=None, cpuPort=255,
85 thriftPort=None, netcfg=True, dryrun=False, pipeconfId="",
86 pktdump=False, valgrind=False, **kwargs):
Carmelo Cascone785fada2016-06-16 18:34:16 -070087 Switch.__init__(self, name, **kwargs)
Carmelo Casconef11513d2018-01-16 00:31:14 -080088 self.grpcPort = pickUnusedPort() if not grpcPort else grpcPort
89 self.thriftPort = pickUnusedPort() if not thriftPort else thriftPort
Carmelo Cascone75e97992017-06-05 02:32:47 -040090 if self.dpid:
91 self.deviceId = int(self.dpid, 0 if 'x' in self.dpid else 16)
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070092 else:
Carmelo Cascone75e97992017-06-05 02:32:47 -040093 self.deviceId = ONOSBmv2Switch.deviceId
94 ONOSBmv2Switch.deviceId += 1
Carmelo Casconef11513d2018-01-16 00:31:14 -080095 self.cpuPort = cpuPort
Carmelo Casconefb76b042017-07-17 19:42:00 -040096 self.json = json
Carmelo Cascone46d360b2017-08-29 20:20:32 +020097 self.debugger = parseBoolean(debugger)
Carmelo Cascone785fada2016-06-16 18:34:16 -070098 self.loglevel = loglevel
Carmelo Casconef11513d2018-01-16 00:31:14 -080099 # Important: Mininet removes all /tmp/*.log files in case of exceptions.
100 # We want to be able to see the bmv2 log if anything goes wrong, hence
101 # avoid the .log extension.
102 self.logfile = '/tmp/bmv2-%d-log' % self.deviceId
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200103 self.elogger = parseBoolean(elogger)
104 self.pktdump = parseBoolean(pktdump)
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200105 self.netcfg = parseBoolean(netcfg)
106 self.dryrun = parseBoolean(dryrun)
107 self.valgrind = parseBoolean(valgrind)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400108 self.netcfgfile = '/tmp/bmv2-%d-netcfg.json' % self.deviceId
Yi Tseng7875cb72017-08-08 10:15:58 -0700109 self.pipeconfId = pipeconfId
Carmelo Casconef11513d2018-01-16 00:31:14 -0800110 self.longitude = kwargs['longitude'] if 'longitude' in kwargs else None
111 self.latitude = kwargs['latitude'] if 'latitude' in kwargs else None
Yi Tseng7875cb72017-08-08 10:15:58 -0700112 self.onosDeviceId = "device:bmv2:%d" % self.deviceId
Carmelo Casconef11513d2018-01-16 00:31:14 -0800113 self.logfd = None
114 self.bmv2popen = None
115 self.stopped = False
Yi Tseng7875cb72017-08-08 10:15:58 -0700116
Carmelo Casconef11513d2018-01-16 00:31:14 -0800117 # Remove files from previous executions
118 self.cleanupTmpFiles()
119
120 writeToFile("/tmp/bmv2-%d-grpc-port" % self.deviceId, self.grpcPort)
121 writeToFile("/tmp/bmv2-%d-thrift-port" % self.deviceId, self.thriftPort)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700122
Carmelo Casconeb7524272017-06-05 16:53:13 -0400123 def getSourceIp(self, dstIP):
124 """
Carmelo Casconef11513d2018-01-16 00:31:14 -0800125 Queries the Linux routing table to get the source IP that can talk with
126 dstIP, and vice versa.
Carmelo Casconeb7524272017-06-05 16:53:13 -0400127 """
128 ipRouteOut = self.cmd('ip route get %s' % dstIP)
129 r = re.search(r"src (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", ipRouteOut)
130 return r.group(1) if r else None
131
Yi Tseng7875cb72017-08-08 10:15:58 -0700132 def getDeviceConfig(self, srcIP):
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +0200133 portData = {}
134 portId = 1
135 for intfName in self.intfNames():
136 if intfName == 'lo':
137 continue
138 portData[str(portId)] = {
139 "number": portId,
140 "name": intfName,
141 "enabled": True,
142 "removed": False,
143 "type": "copper",
144 "speed": 10000
145 }
146 portId += 1
147
Yi Tseng7875cb72017-08-08 10:15:58 -0700148 basicCfg = {
149 "driver": "bmv2"
150 }
151
152 if self.longitude and self.latitude:
153 basicCfg["longitude"] = self.longitude
154 basicCfg["latitude"] = self.latitude
155
156 cfgData = {
Carmelo Cascone34433252017-08-25 20:27:18 +0200157 "generalprovider": {
158 "p4runtime": {
159 "ip": srcIP,
160 "port": self.grpcPort,
161 "deviceId": self.deviceId,
162 "deviceKeyId": "p4runtime:%s" % self.onosDeviceId
163 }
164 },
165 "piPipeconf": {
166 "piPipeconfId": self.pipeconfId
167 },
168 "basic": basicCfg,
169 "ports": portData
Yi Tseng7875cb72017-08-08 10:15:58 -0700170 }
171
172 return cfgData
173
174 def doOnosNetcfg(self, controllerIP):
175 """
176 Notifies ONOS about the new device via Netcfg.
177 """
178 srcIP = self.getSourceIp(controllerIP)
179 if not srcIP:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800180 warn("*** WARN: unable to get switch IP address, won't do netcfg\n")
Yi Tseng7875cb72017-08-08 10:15:58 -0700181 return
182
Carmelo Casconea11279b2017-06-22 04:30:08 -0400183 cfgData = {
184 "devices": {
Yi Tseng7875cb72017-08-08 10:15:58 -0700185 self.onosDeviceId: self.getDeviceConfig(srcIP)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400186 }
Carmelo Casconea11279b2017-06-22 04:30:08 -0400187 }
Carmelo Casconeb7524272017-06-05 16:53:13 -0400188 with open(self.netcfgfile, 'w') as fp:
189 json.dump(cfgData, fp, indent=4)
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200190
191 if not self.netcfg:
192 # Do not push config to ONOS.
193 return
194
Brian O'Connor71167f92017-06-16 14:55:00 -0700195 # Build netcfg URL
196 url = 'http://%s:8181/onos/v1/network/configuration/' % controllerIP
197 # Instantiate password manager for HTTP auth
198 pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
Carmelo Casconef11513d2018-01-16 00:31:14 -0800199 pm.add_password(None, url,
200 os.environ['ONOS_WEB_USER'],
201 os.environ['ONOS_WEB_PASS'])
202 urllib2.install_opener(urllib2.build_opener(
203 urllib2.HTTPBasicAuthHandler(pm)))
Brian O'Connor71167f92017-06-16 14:55:00 -0700204 # Push config data to controller
Carmelo Casconef11513d2018-01-16 00:31:14 -0800205 req = urllib2.Request(url, json.dumps(cfgData),
206 {'Content-Type': 'application/json'})
Carmelo Casconea11279b2017-06-22 04:30:08 -0400207 try:
208 f = urllib2.urlopen(req)
209 print f.read()
210 f.close()
211 except urllib2.URLError as e:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800212 warn("*** WARN: unable to push config to ONOS (%s)\n" % e.reason)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400213
Carmelo Cascone785fada2016-06-16 18:34:16 -0700214 def start(self, controllers):
Carmelo Casconef11513d2018-01-16 00:31:14 -0800215 bmv2Args = [SIMPLE_SWITCH_GRPC] + self.grpcTargetArgs()
216 if self.valgrind:
217 bmv2Args = VALGRIND_PREFIX.split() + bmv2Args
218
219 cmdString = " ".join(bmv2Args)
220
221 if self.dryrun:
222 info("\n*** DRY RUN (not executing bmv2)")
223
224 info("\nStarting BMv2 target: %s\n" % cmdString)
225
226 try:
227 if not self.dryrun:
228 # Start the switch
229 self.logfd = open(self.logfile, "w")
230 self.bmv2popen = self.popen(cmdString,
231 stdout=self.logfd,
232 stderr=self.logfd)
233 self.waitBmv2Start()
234 # We want to be notified if BMv2 dies...
235 threading.Thread(target=watchDog, args=[self]).start()
236
237 self.doOnosNetcfg(self.controllerIp(controllers))
238 except Exception as ex:
239 self.killBmv2()
240 self.printBmv2Log()
241 raise ex
242
243 def grpcTargetArgs(self):
244 args = ['--device-id %s' % str(self.deviceId)]
Carmelo Cascone785fada2016-06-16 18:34:16 -0700245 for port, intf in self.intfs.items():
246 if not intf.IP():
247 args.append('-i %d@%s' % (port, intf.name))
Carmelo Cascone785fada2016-06-16 18:34:16 -0700248 if self.elogger:
249 nanomsg = 'ipc:///tmp/bmv2-%d-log.ipc' % self.deviceId
250 args.append('--nanolog %s' % nanomsg)
251 if self.debugger:
252 args.append('--debugger')
Carmelo Casconefb76b042017-07-17 19:42:00 -0400253 args.append('--log-console')
Carmelo Cascone34433252017-08-25 20:27:18 +0200254 if self.pktdump:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800255 args.append('--pcap --dump-packet-data %s' % PKT_BYTES_TO_DUMP)
Carmelo Cascone9e6621f2017-06-27 16:06:33 -0400256 args.append('-L%s' % self.loglevel)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800257 args.append('--thrift-port %s' % self.thriftPort)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400258 if not self.json:
259 args.append('--no-p4')
260 else:
261 args.append(self.json)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800262 # gRPC target-specific options
Carmelo Cascone785fada2016-06-16 18:34:16 -0700263 args.append('--')
Carmelo Casconef11513d2018-01-16 00:31:14 -0800264 args.append('--cpu-port %s' % self.cpuPort)
265 args.append('--grpc-server-addr 0.0.0.0:%s' % self.grpcPort)
266 return args
Carmelo Cascone785fada2016-06-16 18:34:16 -0700267
Carmelo Casconef11513d2018-01-16 00:31:14 -0800268 def waitBmv2Start(self):
269 # Wait for switch to open gRPC port, before sending ONOS the netcfg.
270 # Include time-out just in case something hangs.
271 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
272 endtime = time.time() + SWITCH_START_TIMEOUT
273 while True:
274 result = sock.connect_ex(('127.0.0.1', self.grpcPort))
275 if result == 0:
276 # The port is open. Let's go! (Close socket first)
277 sock.close()
278 break
279 # Port is not open yet. If there is time, we wait a bit.
280 if endtime > time.time():
281 time.sleep(0.2)
282 else:
283 # Time's up.
284 raise Exception("Switch did not start before timeout")
Carmelo Casconefb76b042017-07-17 19:42:00 -0400285
Carmelo Casconef11513d2018-01-16 00:31:14 -0800286 def printBmv2Log(self):
287 if os.path.isfile(self.logfile):
288 print "-" * 80
289 print "BMv2 %d log (from %s):" % (self.deviceId, self.logfile)
290 with open(self.logfile, 'r') as f:
291 lines = f.readlines()
292 if len(lines) > BMV2_LOG_LINES:
293 print "..."
294 for line in lines[-BMV2_LOG_LINES:]:
295 print line.rstrip()
Carmelo Casconefb76b042017-07-17 19:42:00 -0400296
Carmelo Casconef11513d2018-01-16 00:31:14 -0800297 @staticmethod
298 def controllerIp(controllers):
299 try:
300 # onos.py
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200301 clist = controllers[0].nodes()
302 except AttributeError:
303 clist = controllers
304 assert len(clist) > 0
Carmelo Casconef11513d2018-01-16 00:31:14 -0800305 return clist[0].IP()
Keesjan Karsten8539f082018-01-04 17:03:31 +0100306
Carmelo Casconef11513d2018-01-16 00:31:14 -0800307 def killBmv2(self, log=False):
308 if self.bmv2popen is not None:
309 self.bmv2popen.kill()
310 if self.logfd is not None:
311 if log:
312 self.logfd.write("*** PROCESS TERMINATED BY MININET ***\n")
313 self.logfd.close()
Keesjan Karsten8539f082018-01-04 17:03:31 +0100314
Carmelo Casconef11513d2018-01-16 00:31:14 -0800315 def cleanupTmpFiles(self):
316 self.cmd("rm -f /tmp/bmv2-%d-*" % self.deviceId)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400317
318 def stop(self, deleteIntfs=True):
319 """Terminate switch."""
Carmelo Casconef11513d2018-01-16 00:31:14 -0800320 self.stopped = True
321 self.killBmv2(log=True)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400322 Switch.stop(self, deleteIntfs)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700323
324
Carmelo Casconeb7524272017-06-05 16:53:13 -0400325# Exports for bin/mn
Carmelo Cascone785fada2016-06-16 18:34:16 -0700326switches = {'onosbmv2': ONOSBmv2Switch}
Carmelo Cascone34433252017-08-25 20:27:18 +0200327hosts = {'onoshost': ONOSHost}