blob: 5071c88c130add2de9a15d45804f89b797cf82ff [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 Cascone785fada2016-06-16 18:34:16 -07008from mininet.node import Switch
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 Casconeb7524272017-06-05 16:53:13 -040017
Carmelo Cascone785fada2016-06-16 18:34:16 -070018
19class ONOSBmv2Switch(Switch):
Carmelo Casconeb7524272017-06-05 16:53:13 -040020 """BMv2 software switch with gRPC server"""
Carmelo Cascone785fada2016-06-16 18:34:16 -070021
Carmelo Cascone785fada2016-06-16 18:34:16 -070022 deviceId = 0
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070023 instanceCount = 0
Carmelo Cascone785fada2016-06-16 18:34:16 -070024
Carmelo Casconefb76b042017-07-17 19:42:00 -040025 def __init__(self, name, json=None, debugger=False, loglevel="warn", elogger=False,
Carmelo Casconeaaf6d982017-07-29 19:52:58 -040026 persistent=False, grpcPort=None, thriftPort=None, netcfg=True, **kwargs):
Carmelo Cascone785fada2016-06-16 18:34:16 -070027 Switch.__init__(self, name, **kwargs)
Carmelo Casconeb7524272017-06-05 16:53:13 -040028 self.grpcPort = ONOSBmv2Switch.pickUnusedPort() if not grpcPort else grpcPort
Carmelo Casconeaaf6d982017-07-29 19:52:58 -040029 self.thriftPort = ONOSBmv2Switch.pickUnusedPort() if not thriftPort else thriftPort
Carmelo Cascone75e97992017-06-05 02:32:47 -040030 if self.dpid:
31 self.deviceId = int(self.dpid, 0 if 'x' in self.dpid else 16)
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070032 else:
Carmelo Cascone75e97992017-06-05 02:32:47 -040033 self.deviceId = ONOSBmv2Switch.deviceId
34 ONOSBmv2Switch.deviceId += 1
Carmelo Casconefb76b042017-07-17 19:42:00 -040035 self.json = json
Carmelo Cascone785fada2016-06-16 18:34:16 -070036 self.debugger = debugger
37 self.loglevel = loglevel
38 self.logfile = '/tmp/bmv2-%d.log' % self.deviceId
Carmelo Cascone785fada2016-06-16 18:34:16 -070039 self.elogger = elogger
40 self.persistent = persistent
Carmelo Casconeb7524272017-06-05 16:53:13 -040041 self.netcfg = netcfg
42 self.netcfgfile = '/tmp/bmv2-%d-netcfg.json' % self.deviceId
Carmelo Cascone785fada2016-06-16 18:34:16 -070043 if persistent:
44 self.exectoken = "/tmp/bmv2-%d-exec-token" % self.deviceId
45 self.cmd("touch %s" % self.exectoken)
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070046 # Store thrift port for future uses.
Carmelo Cascone75e97992017-06-05 02:32:47 -040047 self.cmd("echo %d > /tmp/bmv2-%d-grpc-port" % (self.grpcPort, self.deviceId))
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070048
49 @classmethod
50 def pickUnusedPort(cls):
51 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
52 s.bind(('localhost', 0))
53 addr, port = s.getsockname()
54 s.close()
55 return port
Carmelo Cascone785fada2016-06-16 18:34:16 -070056
Carmelo Casconeb7524272017-06-05 16:53:13 -040057 def getSourceIp(self, dstIP):
58 """
59 Queries the Linux routing table to get the source IP that can talk with dstIP, and vice
60 versa.
61 """
62 ipRouteOut = self.cmd('ip route get %s' % dstIP)
63 r = re.search(r"src (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", ipRouteOut)
64 return r.group(1) if r else None
65
66 def doOnosNetcfg(self, controllerIP):
67 """
68 Notifies ONOS about the new device via Netcfg.
69 """
70 srcIP = self.getSourceIp(controllerIP)
71 if not srcIP:
72 warn("WARN: unable to get device IP address, won't do onos-netcfg")
73 return
Carmelo Casconea11279b2017-06-22 04:30:08 -040074 onosDeviceId = "bmv2:%s" % self.deviceId
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +020075 portData = {}
76 portId = 1
77 for intfName in self.intfNames():
78 if intfName == 'lo':
79 continue
80 portData[str(portId)] = {
81 "number": portId,
82 "name": intfName,
83 "enabled": True,
84 "removed": False,
85 "type": "copper",
86 "speed": 10000
87 }
88 portId += 1
89
Carmelo Casconea11279b2017-06-22 04:30:08 -040090 cfgData = {
91 "devices": {
92 "device:%s" % onosDeviceId: {
93 "generalprovider": {
94 "p4runtime": {
95 "ip": srcIP,
96 "port": self.grpcPort,
Carmelo Cascone59f57de2017-07-11 19:55:09 -040097 "deviceId": self.deviceId,
Carmelo Casconea11279b2017-06-22 04:30:08 -040098 "deviceKeyId": "p4runtime:%s" % onosDeviceId
99 }
100 },
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400101 "piPipeconf": {
102 "piPipeconfId": ""
103 },
Carmelo Casconea11279b2017-06-22 04:30:08 -0400104 "basic": {
105 "driver": "bmv2"
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +0200106 },
107 "ports": portData
Carmelo Casconeb7524272017-06-05 16:53:13 -0400108 }
109 }
Carmelo Casconea11279b2017-06-22 04:30:08 -0400110 }
Carmelo Casconeb7524272017-06-05 16:53:13 -0400111 with open(self.netcfgfile, 'w') as fp:
112 json.dump(cfgData, fp, indent=4)
Brian O'Connor71167f92017-06-16 14:55:00 -0700113 # Build netcfg URL
114 url = 'http://%s:8181/onos/v1/network/configuration/' % controllerIP
115 # Instantiate password manager for HTTP auth
116 pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
117 pm.add_password(None, url, os.environ['ONOS_WEB_USER'], os.environ['ONOS_WEB_PASS'])
118 urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(pm)))
119 # Push config data to controller
Carmelo Casconea11279b2017-06-22 04:30:08 -0400120 req = urllib2.Request(url, json.dumps(cfgData), {'Content-Type': 'application/json'})
121 try:
122 f = urllib2.urlopen(req)
123 print f.read()
124 f.close()
125 except urllib2.URLError as e:
126 warn("WARN: unable to push config to ONOS (%s)" % e.reason)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400127
Carmelo Cascone785fada2016-06-16 18:34:16 -0700128 def start(self, controllers):
Carmelo Cascone75e97992017-06-05 02:32:47 -0400129 args = [BMV2_TARGET, '--device-id %s' % str(self.deviceId)]
Carmelo Cascone785fada2016-06-16 18:34:16 -0700130 for port, intf in self.intfs.items():
131 if not intf.IP():
132 args.append('-i %d@%s' % (port, intf.name))
Carmelo Cascone785fada2016-06-16 18:34:16 -0700133 if self.elogger:
134 nanomsg = 'ipc:///tmp/bmv2-%d-log.ipc' % self.deviceId
135 args.append('--nanolog %s' % nanomsg)
136 if self.debugger:
137 args.append('--debugger')
Carmelo Casconefb76b042017-07-17 19:42:00 -0400138 args.append('--log-console')
Carmelo Cascone9e6621f2017-06-27 16:06:33 -0400139 args.append('-L%s' % self.loglevel)
Carmelo Casconeaaf6d982017-07-29 19:52:58 -0400140 args.append('--thrift-port %d' % self.thriftPort)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400141 if not self.json:
142 args.append('--no-p4')
143 else:
144 args.append(self.json)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400145
146 # gRPC target-specific options.
Carmelo Cascone785fada2016-06-16 18:34:16 -0700147 args.append('--')
Carmelo Cascone569d4ad2017-07-10 16:09:00 -0400148 args.append('--cpu-port %d' % CPU_PORT)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400149 args.append('--grpc-server-addr 0.0.0.0:%d' % self.grpcPort)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700150
151 bmv2cmd = " ".join(args)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700152 info("\nStarting BMv2 target: %s\n" % bmv2cmd)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400153
Carmelo Cascone785fada2016-06-16 18:34:16 -0700154 if self.persistent:
Carmelo Casconeb7524272017-06-05 16:53:13 -0400155 # Bash loop to re-exec the switch if it crashes.
Carmelo Casconefb76b042017-07-17 19:42:00 -0400156 bmv2cmd = "(while [ -e {} ]; do {} ; sleep 1; done;)".format(self.exectoken, bmv2cmd)
157
158 cmdStr = "{} > {} 2>&1 &".format(bmv2cmd, self.logfile)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700159
Carmelo Casconeb7524272017-06-05 16:53:13 -0400160 # Starts the switch.
161 out = self.cmd(cmdStr)
162 if out:
163 print out
164
165 if self.netcfg:
166 try: # onos.py
167 clist = controllers[0].nodes()
168 except AttributeError:
169 clist = controllers
170 assert len(clist) > 0
171 cip = clist[0].IP()
172 self.doOnosNetcfg(cip)
173
174 def stop(self, deleteIntfs=True):
175 """Terminate switch."""
Carmelo Cascone977ae3f2016-06-23 19:28:28 -0700176 self.cmd("rm -f /tmp/bmv2-%d-*" % self.deviceId)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400177 self.cmd("rm -f /tmp/bmv2-%d.log*" % self.deviceId)
Carmelo Cascone75e97992017-06-05 02:32:47 -0400178 self.cmd('kill %' + BMV2_TARGET)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400179 Switch.stop(self, deleteIntfs)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700180
181
Carmelo Casconeb7524272017-06-05 16:53:13 -0400182# Exports for bin/mn
Carmelo Cascone785fada2016-06-16 18:34:16 -0700183switches = {'onosbmv2': ONOSBmv2Switch}