blob: b1824a3e7661343774c16ce7ff538a871208817d [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
Brian O'Connor71167f92017-06-16 14:55:00 -07005import urllib2
Carmelo Cascone977ae3f2016-06-23 19:28:28 -07006
Carmelo Cascone46d360b2017-08-29 20:20:32 +02007import time
8
Carmelo Casconeb7524272017-06-05 16:53:13 -04009from mininet.log import info, warn, error
Carmelo Cascone34433252017-08-25 20:27:18 +020010from mininet.node import Switch, Host
Carmelo Cascone785fada2016-06-16 18:34:16 -070011
Carmelo Casconeb7524272017-06-05 16:53:13 -040012if 'ONOS_ROOT' not in os.environ:
13 error("ERROR: environment var $ONOS_ROOT not set")
14 exit()
15
Carmelo Cascone75e97992017-06-05 02:32:47 -040016BMV2_TARGET = 'simple_switch_grpc'
Carmelo Casconeb7524272017-06-05 16:53:13 -040017ONOS_ROOT = os.environ["ONOS_ROOT"]
Carmelo Cascone569d4ad2017-07-10 16:09:00 -040018CPU_PORT = 255
Carmelo Cascone34433252017-08-25 20:27:18 +020019PKT_BYTES_TO_DUMP = 80
Carmelo Cascone46d360b2017-08-29 20:20:32 +020020VALGRIND_PREFIX = 'valgrind --leak-check=yes'
Keesjan Karsten8539f082018-01-04 17:03:31 +010021SWITCH_START_TIMEOUT = 5 #seconds
Carmelo Cascone46d360b2017-08-29 20:20:32 +020022
23def parseBoolean(value):
24 if value in ['1', 1, 'true', 'True']:
25 return True
26 else:
27 return False
Carmelo Cascone34433252017-08-25 20:27:18 +020028
29
30class ONOSHost(Host):
31 def __init__(self, name, inNamespace=True, **params):
32 Host.__init__(self, name, inNamespace=inNamespace, **params)
33
34 def config(self, **params):
35 r = super(Host, self).config(**params)
36 for off in ["rx", "tx", "sg"]:
Carmelo Cascone6ec8f8f2017-11-22 14:27:06 -080037 cmd = "/sbin/ethtool --offload %s %s off"\
38 % (self.defaultIntf(), off)
Carmelo Cascone34433252017-08-25 20:27:18 +020039 self.cmd(cmd)
40 # disable IPv6
41 self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
42 self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1")
43 self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1")
44 return r
Carmelo Casconeb7524272017-06-05 16:53:13 -040045
Carmelo Cascone785fada2016-06-16 18:34:16 -070046
47class ONOSBmv2Switch(Switch):
Carmelo Casconeb7524272017-06-05 16:53:13 -040048 """BMv2 software switch with gRPC server"""
Carmelo Cascone785fada2016-06-16 18:34:16 -070049
Carmelo Cascone785fada2016-06-16 18:34:16 -070050 deviceId = 0
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070051 instanceCount = 0
Carmelo Cascone785fada2016-06-16 18:34:16 -070052
Carmelo Casconefb76b042017-07-17 19:42:00 -040053 def __init__(self, name, json=None, debugger=False, loglevel="warn", elogger=False,
Carmelo Cascone46d360b2017-08-29 20:20:32 +020054 persistent=False, grpcPort=None, thriftPort=None, netcfg=True, dryrun=False,
Carmelo Cascone6ec8f8f2017-11-22 14:27:06 -080055 pipeconfId="", pktdump=False, valgrind=False, netcfgDelay=0,
56 **kwargs):
Carmelo Cascone785fada2016-06-16 18:34:16 -070057 Switch.__init__(self, name, **kwargs)
Carmelo Casconeb7524272017-06-05 16:53:13 -040058 self.grpcPort = ONOSBmv2Switch.pickUnusedPort() if not grpcPort else grpcPort
Carmelo Casconeaaf6d982017-07-29 19:52:58 -040059 self.thriftPort = ONOSBmv2Switch.pickUnusedPort() if not thriftPort else thriftPort
Carmelo Cascone75e97992017-06-05 02:32:47 -040060 if self.dpid:
61 self.deviceId = int(self.dpid, 0 if 'x' in self.dpid else 16)
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070062 else:
Carmelo Cascone75e97992017-06-05 02:32:47 -040063 self.deviceId = ONOSBmv2Switch.deviceId
64 ONOSBmv2Switch.deviceId += 1
Carmelo Casconefb76b042017-07-17 19:42:00 -040065 self.json = json
Carmelo Cascone46d360b2017-08-29 20:20:32 +020066 self.debugger = parseBoolean(debugger)
Carmelo Cascone785fada2016-06-16 18:34:16 -070067 self.loglevel = loglevel
68 self.logfile = '/tmp/bmv2-%d.log' % self.deviceId
Carmelo Cascone46d360b2017-08-29 20:20:32 +020069 self.elogger = parseBoolean(elogger)
70 self.pktdump = parseBoolean(pktdump)
71 self.persistent = parseBoolean(persistent)
72 self.netcfg = parseBoolean(netcfg)
73 self.dryrun = parseBoolean(dryrun)
74 self.valgrind = parseBoolean(valgrind)
Carmelo Cascone6ec8f8f2017-11-22 14:27:06 -080075 self.netcfgDelay = netcfgDelay
Carmelo Casconeb7524272017-06-05 16:53:13 -040076 self.netcfgfile = '/tmp/bmv2-%d-netcfg.json' % self.deviceId
Yi Tseng7875cb72017-08-08 10:15:58 -070077 self.pipeconfId = pipeconfId
Carmelo Cascone785fada2016-06-16 18:34:16 -070078 if persistent:
79 self.exectoken = "/tmp/bmv2-%d-exec-token" % self.deviceId
80 self.cmd("touch %s" % self.exectoken)
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070081 # Store thrift port for future uses.
Carmelo Cascone75e97992017-06-05 02:32:47 -040082 self.cmd("echo %d > /tmp/bmv2-%d-grpc-port" % (self.grpcPort, self.deviceId))
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070083
Yi Tseng7875cb72017-08-08 10:15:58 -070084 if 'longitude' in kwargs:
85 self.longitude = kwargs['longitude']
86 else:
87 self.longitude = None
88
89 if 'latitude' in kwargs:
90 self.latitude = kwargs['latitude']
91 else:
92 self.latitude = None
93
94 self.onosDeviceId = "device:bmv2:%d" % self.deviceId
95
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070096 @classmethod
97 def pickUnusedPort(cls):
98 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
99 s.bind(('localhost', 0))
100 addr, port = s.getsockname()
101 s.close()
102 return port
Carmelo Cascone785fada2016-06-16 18:34:16 -0700103
Carmelo Casconeb7524272017-06-05 16:53:13 -0400104 def getSourceIp(self, dstIP):
105 """
106 Queries the Linux routing table to get the source IP that can talk with dstIP, and vice
107 versa.
108 """
109 ipRouteOut = self.cmd('ip route get %s' % dstIP)
110 r = re.search(r"src (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", ipRouteOut)
111 return r.group(1) if r else None
112
Yi Tseng7875cb72017-08-08 10:15:58 -0700113 def getDeviceConfig(self, srcIP):
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +0200114 portData = {}
115 portId = 1
116 for intfName in self.intfNames():
117 if intfName == 'lo':
118 continue
119 portData[str(portId)] = {
120 "number": portId,
121 "name": intfName,
122 "enabled": True,
123 "removed": False,
124 "type": "copper",
125 "speed": 10000
126 }
127 portId += 1
128
Yi Tseng7875cb72017-08-08 10:15:58 -0700129 basicCfg = {
130 "driver": "bmv2"
131 }
132
133 if self.longitude and self.latitude:
134 basicCfg["longitude"] = self.longitude
135 basicCfg["latitude"] = self.latitude
136
137 cfgData = {
Carmelo Cascone34433252017-08-25 20:27:18 +0200138 "generalprovider": {
139 "p4runtime": {
140 "ip": srcIP,
141 "port": self.grpcPort,
142 "deviceId": self.deviceId,
143 "deviceKeyId": "p4runtime:%s" % self.onosDeviceId
144 }
145 },
146 "piPipeconf": {
147 "piPipeconfId": self.pipeconfId
148 },
149 "basic": basicCfg,
150 "ports": portData
Yi Tseng7875cb72017-08-08 10:15:58 -0700151 }
152
153 return cfgData
154
155 def doOnosNetcfg(self, controllerIP):
156 """
157 Notifies ONOS about the new device via Netcfg.
158 """
159 srcIP = self.getSourceIp(controllerIP)
160 if not srcIP:
161 warn("WARN: unable to get device IP address, won't do onos-netcfg")
162 return
163
Carmelo Casconea11279b2017-06-22 04:30:08 -0400164 cfgData = {
165 "devices": {
Yi Tseng7875cb72017-08-08 10:15:58 -0700166 self.onosDeviceId: self.getDeviceConfig(srcIP)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400167 }
Carmelo Casconea11279b2017-06-22 04:30:08 -0400168 }
Carmelo Casconeb7524272017-06-05 16:53:13 -0400169 with open(self.netcfgfile, 'w') as fp:
170 json.dump(cfgData, fp, indent=4)
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200171
172 if not self.netcfg:
173 # Do not push config to ONOS.
174 return
175
Brian O'Connor71167f92017-06-16 14:55:00 -0700176 # Build netcfg URL
177 url = 'http://%s:8181/onos/v1/network/configuration/' % controllerIP
178 # Instantiate password manager for HTTP auth
179 pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
180 pm.add_password(None, url, os.environ['ONOS_WEB_USER'], os.environ['ONOS_WEB_PASS'])
181 urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(pm)))
182 # Push config data to controller
Carmelo Casconea11279b2017-06-22 04:30:08 -0400183 req = urllib2.Request(url, json.dumps(cfgData), {'Content-Type': 'application/json'})
184 try:
185 f = urllib2.urlopen(req)
186 print f.read()
187 f.close()
188 except urllib2.URLError as e:
189 warn("WARN: unable to push config to ONOS (%s)" % e.reason)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400190
Carmelo Cascone785fada2016-06-16 18:34:16 -0700191 def start(self, controllers):
Carmelo Cascone75e97992017-06-05 02:32:47 -0400192 args = [BMV2_TARGET, '--device-id %s' % str(self.deviceId)]
Carmelo Cascone785fada2016-06-16 18:34:16 -0700193 for port, intf in self.intfs.items():
194 if not intf.IP():
195 args.append('-i %d@%s' % (port, intf.name))
Carmelo Cascone785fada2016-06-16 18:34:16 -0700196 if self.elogger:
197 nanomsg = 'ipc:///tmp/bmv2-%d-log.ipc' % self.deviceId
198 args.append('--nanolog %s' % nanomsg)
199 if self.debugger:
200 args.append('--debugger')
Carmelo Casconefb76b042017-07-17 19:42:00 -0400201 args.append('--log-console')
Carmelo Cascone34433252017-08-25 20:27:18 +0200202 if self.pktdump:
203 args.append('--pcap --dump-packet-data %d' % PKT_BYTES_TO_DUMP)
Carmelo Cascone9e6621f2017-06-27 16:06:33 -0400204 args.append('-L%s' % self.loglevel)
Carmelo Casconeaaf6d982017-07-29 19:52:58 -0400205 args.append('--thrift-port %d' % self.thriftPort)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400206 if not self.json:
207 args.append('--no-p4')
208 else:
209 args.append(self.json)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400210
211 # gRPC target-specific options.
Carmelo Cascone785fada2016-06-16 18:34:16 -0700212 args.append('--')
Carmelo Cascone569d4ad2017-07-10 16:09:00 -0400213 args.append('--cpu-port %d' % CPU_PORT)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400214 args.append('--grpc-server-addr 0.0.0.0:%d' % self.grpcPort)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700215
216 bmv2cmd = " ".join(args)
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200217 if self.valgrind:
218 bmv2cmd = "%s %s" % (VALGRIND_PREFIX, bmv2cmd)
219 if self.dryrun:
220 info("\n*** DRY RUN (not executing bmv2)")
Carmelo Cascone6e854042017-09-11 21:37:53 +0200221 info("\nStarting BMv2 target: %s\n" % bmv2cmd)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400222
Carmelo Cascone785fada2016-06-16 18:34:16 -0700223 if self.persistent:
Carmelo Casconeb7524272017-06-05 16:53:13 -0400224 # Bash loop to re-exec the switch if it crashes.
Carmelo Casconefb76b042017-07-17 19:42:00 -0400225 bmv2cmd = "(while [ -e {} ]; do {} ; sleep 1; done;)".format(self.exectoken, bmv2cmd)
226
227 cmdStr = "{} > {} 2>&1 &".format(bmv2cmd, self.logfile)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700228
Carmelo Casconeb7524272017-06-05 16:53:13 -0400229 # Starts the switch.
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200230 if not self.dryrun:
231 out = self.cmd(cmdStr)
232 if out:
233 print out
Carmelo Cascone6ec8f8f2017-11-22 14:27:06 -0800234 if self.netcfg:
Carmelo Cascone6ec8f8f2017-11-22 14:27:06 -0800235 time.sleep(self.netcfgDelay)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400236
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200237 try: # onos.py
238 clist = controllers[0].nodes()
239 except AttributeError:
240 clist = controllers
241 assert len(clist) > 0
242 cip = clist[0].IP()
Keesjan Karsten8539f082018-01-04 17:03:31 +0100243
244 # Wait for switch to open gRPC port, before sending ONOS the netcfg.json.
245 # Include time-out just in case something hangs.
246 if not self.dryrun:
247 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
248 endtime = time.time() + SWITCH_START_TIMEOUT
249 while True:
250 result = sock.connect_ex(('127.0.0.1', self.grpcPort))
251 if result == 0:
252 # The port is open. Let's go! (Close socket first)
253 sock.close()
254 break
255 # Port is not open yet. If there is time, we wait a bit.
256 if endtime > time.time():
257 time.sleep(0.2)
258 else:
259 # Time's up.
260 raise Exception("Switch did not start before {} second timeout. Exiting.\n"
261 .format(SWITCH_START_TIMEOUT))
262 exit()
263
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200264 self.doOnosNetcfg(cip)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400265
266 def stop(self, deleteIntfs=True):
267 """Terminate switch."""
Carmelo Cascone977ae3f2016-06-23 19:28:28 -0700268 self.cmd("rm -f /tmp/bmv2-%d-*" % self.deviceId)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400269 self.cmd("rm -f /tmp/bmv2-%d.log*" % self.deviceId)
Carmelo Cascone75e97992017-06-05 02:32:47 -0400270 self.cmd('kill %' + BMV2_TARGET)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400271 Switch.stop(self, deleteIntfs)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700272
273
Carmelo Casconeb7524272017-06-05 16:53:13 -0400274# Exports for bin/mn
Carmelo Cascone785fada2016-06-16 18:34:16 -0700275switches = {'onosbmv2': ONOSBmv2Switch}
Carmelo Cascone34433252017-08-25 20:27:18 +0200276hosts = {'onoshost': ONOSHost}