blob: 5a4de21b4004ec4514a908268d9293d0887232a5 [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 Casconeb7524272017-06-05 16:53:13 -04007from mininet.log import info, warn, error
Carmelo Cascone34433252017-08-25 20:27:18 +02008from mininet.node import Switch, Host
Carmelo Cascone785fada2016-06-16 18:34:16 -07009
Carmelo Casconeb7524272017-06-05 16:53:13 -040010if 'ONOS_ROOT' not in os.environ:
11 error("ERROR: environment var $ONOS_ROOT not set")
12 exit()
13
Carmelo Cascone75e97992017-06-05 02:32:47 -040014BMV2_TARGET = 'simple_switch_grpc'
Carmelo Casconeb7524272017-06-05 16:53:13 -040015ONOS_ROOT = os.environ["ONOS_ROOT"]
Carmelo Cascone569d4ad2017-07-10 16:09:00 -040016CPU_PORT = 255
Carmelo Cascone34433252017-08-25 20:27:18 +020017PKT_BYTES_TO_DUMP = 80
18
19
20class ONOSHost(Host):
21 def __init__(self, name, inNamespace=True, **params):
22 Host.__init__(self, name, inNamespace=inNamespace, **params)
23
24 def config(self, **params):
25 r = super(Host, self).config(**params)
26 for off in ["rx", "tx", "sg"]:
27 cmd = "/sbin/ethtool --offload %s %s off" % (self.defaultIntf(), off)
28 self.cmd(cmd)
29 # disable IPv6
30 self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
31 self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1")
32 self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1")
33 return r
Carmelo Casconeb7524272017-06-05 16:53:13 -040034
Carmelo Cascone785fada2016-06-16 18:34:16 -070035
36class ONOSBmv2Switch(Switch):
Carmelo Casconeb7524272017-06-05 16:53:13 -040037 """BMv2 software switch with gRPC server"""
Carmelo Cascone785fada2016-06-16 18:34:16 -070038
Carmelo Cascone785fada2016-06-16 18:34:16 -070039 deviceId = 0
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070040 instanceCount = 0
Carmelo Cascone785fada2016-06-16 18:34:16 -070041
Carmelo Casconefb76b042017-07-17 19:42:00 -040042 def __init__(self, name, json=None, debugger=False, loglevel="warn", elogger=False,
Yi Tseng7875cb72017-08-08 10:15:58 -070043 persistent=False, grpcPort=None, thriftPort=None, netcfg=True,
Carmelo Cascone34433252017-08-25 20:27:18 +020044 pipeconfId="", pktdump=False, **kwargs):
Carmelo Cascone785fada2016-06-16 18:34:16 -070045 Switch.__init__(self, name, **kwargs)
Carmelo Casconeb7524272017-06-05 16:53:13 -040046 self.grpcPort = ONOSBmv2Switch.pickUnusedPort() if not grpcPort else grpcPort
Carmelo Casconeaaf6d982017-07-29 19:52:58 -040047 self.thriftPort = ONOSBmv2Switch.pickUnusedPort() if not thriftPort else thriftPort
Carmelo Cascone75e97992017-06-05 02:32:47 -040048 if self.dpid:
49 self.deviceId = int(self.dpid, 0 if 'x' in self.dpid else 16)
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070050 else:
Carmelo Cascone75e97992017-06-05 02:32:47 -040051 self.deviceId = ONOSBmv2Switch.deviceId
52 ONOSBmv2Switch.deviceId += 1
Carmelo Casconefb76b042017-07-17 19:42:00 -040053 self.json = json
Carmelo Cascone785fada2016-06-16 18:34:16 -070054 self.debugger = debugger
55 self.loglevel = loglevel
56 self.logfile = '/tmp/bmv2-%d.log' % self.deviceId
Carmelo Cascone785fada2016-06-16 18:34:16 -070057 self.elogger = elogger
Carmelo Cascone34433252017-08-25 20:27:18 +020058 self.pktdump = pktdump
Carmelo Cascone785fada2016-06-16 18:34:16 -070059 self.persistent = persistent
Carmelo Casconeb7524272017-06-05 16:53:13 -040060 self.netcfg = netcfg
61 self.netcfgfile = '/tmp/bmv2-%d-netcfg.json' % self.deviceId
Yi Tseng7875cb72017-08-08 10:15:58 -070062 self.pipeconfId = pipeconfId
Carmelo Cascone785fada2016-06-16 18:34:16 -070063 if persistent:
64 self.exectoken = "/tmp/bmv2-%d-exec-token" % self.deviceId
65 self.cmd("touch %s" % self.exectoken)
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070066 # Store thrift port for future uses.
Carmelo Cascone75e97992017-06-05 02:32:47 -040067 self.cmd("echo %d > /tmp/bmv2-%d-grpc-port" % (self.grpcPort, self.deviceId))
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070068
Yi Tseng7875cb72017-08-08 10:15:58 -070069 if 'longitude' in kwargs:
70 self.longitude = kwargs['longitude']
71 else:
72 self.longitude = None
73
74 if 'latitude' in kwargs:
75 self.latitude = kwargs['latitude']
76 else:
77 self.latitude = None
78
79 self.onosDeviceId = "device:bmv2:%d" % self.deviceId
80
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070081 @classmethod
82 def pickUnusedPort(cls):
83 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
84 s.bind(('localhost', 0))
85 addr, port = s.getsockname()
86 s.close()
87 return port
Carmelo Cascone785fada2016-06-16 18:34:16 -070088
Carmelo Casconeb7524272017-06-05 16:53:13 -040089 def getSourceIp(self, dstIP):
90 """
91 Queries the Linux routing table to get the source IP that can talk with dstIP, and vice
92 versa.
93 """
94 ipRouteOut = self.cmd('ip route get %s' % dstIP)
95 r = re.search(r"src (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", ipRouteOut)
96 return r.group(1) if r else None
97
Yi Tseng7875cb72017-08-08 10:15:58 -070098 def getDeviceConfig(self, srcIP):
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +020099 portData = {}
100 portId = 1
101 for intfName in self.intfNames():
102 if intfName == 'lo':
103 continue
104 portData[str(portId)] = {
105 "number": portId,
106 "name": intfName,
107 "enabled": True,
108 "removed": False,
109 "type": "copper",
110 "speed": 10000
111 }
112 portId += 1
113
Yi Tseng7875cb72017-08-08 10:15:58 -0700114 basicCfg = {
115 "driver": "bmv2"
116 }
117
118 if self.longitude and self.latitude:
119 basicCfg["longitude"] = self.longitude
120 basicCfg["latitude"] = self.latitude
121
122 cfgData = {
Carmelo Cascone34433252017-08-25 20:27:18 +0200123 "generalprovider": {
124 "p4runtime": {
125 "ip": srcIP,
126 "port": self.grpcPort,
127 "deviceId": self.deviceId,
128 "deviceKeyId": "p4runtime:%s" % self.onosDeviceId
129 }
130 },
131 "piPipeconf": {
132 "piPipeconfId": self.pipeconfId
133 },
134 "basic": basicCfg,
135 "ports": portData
Yi Tseng7875cb72017-08-08 10:15:58 -0700136 }
137
138 return cfgData
139
140 def doOnosNetcfg(self, controllerIP):
141 """
142 Notifies ONOS about the new device via Netcfg.
143 """
144 srcIP = self.getSourceIp(controllerIP)
145 if not srcIP:
146 warn("WARN: unable to get device IP address, won't do onos-netcfg")
147 return
148
Carmelo Casconea11279b2017-06-22 04:30:08 -0400149 cfgData = {
150 "devices": {
Yi Tseng7875cb72017-08-08 10:15:58 -0700151 self.onosDeviceId: self.getDeviceConfig(srcIP)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400152 }
Carmelo Casconea11279b2017-06-22 04:30:08 -0400153 }
Carmelo Casconeb7524272017-06-05 16:53:13 -0400154 with open(self.netcfgfile, 'w') as fp:
155 json.dump(cfgData, fp, indent=4)
Brian O'Connor71167f92017-06-16 14:55:00 -0700156 # Build netcfg URL
157 url = 'http://%s:8181/onos/v1/network/configuration/' % controllerIP
158 # Instantiate password manager for HTTP auth
159 pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
160 pm.add_password(None, url, os.environ['ONOS_WEB_USER'], os.environ['ONOS_WEB_PASS'])
161 urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(pm)))
162 # Push config data to controller
Carmelo Casconea11279b2017-06-22 04:30:08 -0400163 req = urllib2.Request(url, json.dumps(cfgData), {'Content-Type': 'application/json'})
164 try:
165 f = urllib2.urlopen(req)
166 print f.read()
167 f.close()
168 except urllib2.URLError as e:
169 warn("WARN: unable to push config to ONOS (%s)" % e.reason)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400170
Carmelo Cascone785fada2016-06-16 18:34:16 -0700171 def start(self, controllers):
Carmelo Cascone75e97992017-06-05 02:32:47 -0400172 args = [BMV2_TARGET, '--device-id %s' % str(self.deviceId)]
Carmelo Cascone785fada2016-06-16 18:34:16 -0700173 for port, intf in self.intfs.items():
174 if not intf.IP():
175 args.append('-i %d@%s' % (port, intf.name))
Carmelo Cascone785fada2016-06-16 18:34:16 -0700176 if self.elogger:
177 nanomsg = 'ipc:///tmp/bmv2-%d-log.ipc' % self.deviceId
178 args.append('--nanolog %s' % nanomsg)
179 if self.debugger:
180 args.append('--debugger')
Carmelo Casconefb76b042017-07-17 19:42:00 -0400181 args.append('--log-console')
Carmelo Cascone34433252017-08-25 20:27:18 +0200182 if self.pktdump:
183 args.append('--pcap --dump-packet-data %d' % PKT_BYTES_TO_DUMP)
Carmelo Cascone9e6621f2017-06-27 16:06:33 -0400184 args.append('-L%s' % self.loglevel)
Carmelo Casconeaaf6d982017-07-29 19:52:58 -0400185 args.append('--thrift-port %d' % self.thriftPort)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400186 if not self.json:
187 args.append('--no-p4')
188 else:
189 args.append(self.json)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400190
191 # gRPC target-specific options.
Carmelo Cascone785fada2016-06-16 18:34:16 -0700192 args.append('--')
Carmelo Cascone569d4ad2017-07-10 16:09:00 -0400193 args.append('--cpu-port %d' % CPU_PORT)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400194 args.append('--grpc-server-addr 0.0.0.0:%d' % self.grpcPort)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700195
196 bmv2cmd = " ".join(args)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700197 info("\nStarting BMv2 target: %s\n" % bmv2cmd)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400198
Carmelo Cascone785fada2016-06-16 18:34:16 -0700199 if self.persistent:
Carmelo Casconeb7524272017-06-05 16:53:13 -0400200 # Bash loop to re-exec the switch if it crashes.
Carmelo Casconefb76b042017-07-17 19:42:00 -0400201 bmv2cmd = "(while [ -e {} ]; do {} ; sleep 1; done;)".format(self.exectoken, bmv2cmd)
202
203 cmdStr = "{} > {} 2>&1 &".format(bmv2cmd, self.logfile)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700204
Carmelo Casconeb7524272017-06-05 16:53:13 -0400205 # Starts the switch.
206 out = self.cmd(cmdStr)
207 if out:
208 print out
209
210 if self.netcfg:
211 try: # onos.py
212 clist = controllers[0].nodes()
213 except AttributeError:
214 clist = controllers
215 assert len(clist) > 0
216 cip = clist[0].IP()
217 self.doOnosNetcfg(cip)
218
219 def stop(self, deleteIntfs=True):
220 """Terminate switch."""
Carmelo Cascone977ae3f2016-06-23 19:28:28 -0700221 self.cmd("rm -f /tmp/bmv2-%d-*" % self.deviceId)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400222 self.cmd("rm -f /tmp/bmv2-%d.log*" % self.deviceId)
Carmelo Cascone75e97992017-06-05 02:32:47 -0400223 self.cmd('kill %' + BMV2_TARGET)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400224 Switch.stop(self, deleteIntfs)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700225
226
Carmelo Casconeb7524272017-06-05 16:53:13 -0400227# Exports for bin/mn
Carmelo Cascone785fada2016-06-16 18:34:16 -0700228switches = {'onosbmv2': ONOSBmv2Switch}
Carmelo Cascone34433252017-08-25 20:27:18 +0200229hosts = {'onoshost': ONOSHost}