blob: 6998008b7020629e4280a5dd6b20b6ddcc03dee2 [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 Cascone977ae3f2016-06-23 19:28:28 -07005
Carmelo Casconeb7524272017-06-05 16:53:13 -04006from mininet.log import info, warn, error
Carmelo Cascone785fada2016-06-16 18:34:16 -07007from mininet.node import Switch
Carmelo Cascone785fada2016-06-16 18:34:16 -07008
Carmelo Casconeb7524272017-06-05 16:53:13 -04009if 'ONOS_ROOT' not in os.environ:
10 error("ERROR: environment var $ONOS_ROOT not set")
11 exit()
12
Carmelo Cascone75e97992017-06-05 02:32:47 -040013BMV2_TARGET = 'simple_switch_grpc'
Carmelo Casconeb7524272017-06-05 16:53:13 -040014ONOS_ROOT = os.environ["ONOS_ROOT"]
15INIT_BMV2_JSON = '%s/tools/test/p4src/p4c-out/empty.json' % ONOS_ROOT
16
Carmelo Cascone785fada2016-06-16 18:34:16 -070017
18class ONOSBmv2Switch(Switch):
Carmelo Casconeb7524272017-06-05 16:53:13 -040019 """BMv2 software switch with gRPC server"""
Carmelo Cascone785fada2016-06-16 18:34:16 -070020
Carmelo Cascone785fada2016-06-16 18:34:16 -070021 deviceId = 0
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070022 instanceCount = 0
Carmelo Cascone785fada2016-06-16 18:34:16 -070023
Carmelo Cascone75e97992017-06-05 02:32:47 -040024 def __init__(self, name, debugger=False, loglevel="warn", elogger=False, persistent=False,
Carmelo Casconeb7524272017-06-05 16:53:13 -040025 logflush=False, thriftPort=None, grpcPort=None, netcfg=True, **kwargs):
Carmelo Cascone785fada2016-06-16 18:34:16 -070026 Switch.__init__(self, name, **kwargs)
Carmelo Casconeb7524272017-06-05 16:53:13 -040027 self.thriftPort = ONOSBmv2Switch.pickUnusedPort() if not thriftPort else thriftPort
28 self.grpcPort = ONOSBmv2Switch.pickUnusedPort() if not grpcPort else grpcPort
Carmelo Cascone75e97992017-06-05 02:32:47 -040029 if self.dpid:
30 self.deviceId = int(self.dpid, 0 if 'x' in self.dpid else 16)
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070031 else:
Carmelo Cascone75e97992017-06-05 02:32:47 -040032 self.deviceId = ONOSBmv2Switch.deviceId
33 ONOSBmv2Switch.deviceId += 1
Carmelo Cascone785fada2016-06-16 18:34:16 -070034 self.debugger = debugger
35 self.loglevel = loglevel
36 self.logfile = '/tmp/bmv2-%d.log' % self.deviceId
Carmelo Cascone785fada2016-06-16 18:34:16 -070037 self.elogger = elogger
38 self.persistent = persistent
Carmelo Cascone75e97992017-06-05 02:32:47 -040039 self.logflush = logflush
Carmelo Casconeb7524272017-06-05 16:53:13 -040040 self.netcfg = netcfg
41 self.netcfgfile = '/tmp/bmv2-%d-netcfg.json' % self.deviceId
Carmelo Cascone785fada2016-06-16 18:34:16 -070042 if persistent:
43 self.exectoken = "/tmp/bmv2-%d-exec-token" % self.deviceId
44 self.cmd("touch %s" % self.exectoken)
Carmelo Cascone977ae3f2016-06-23 19:28:28 -070045 # Store thrift port for future uses.
46 self.cmd("echo %d > /tmp/bmv2-%d-thrift-port" % (self.thriftPort, self.deviceId))
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
74 onosDeviceId = "bmv2:%s:%s#%s" % (srcIP, self.grpcPort, self.deviceId)
75 cfgData = {"devices": {
76 onosDeviceId: {
77 "basic": {
78 "name": "bmv2:%s" % self.deviceId
79 }
80 }
81 }}
82 with open(self.netcfgfile, 'w') as fp:
83 json.dump(cfgData, fp, indent=4)
84 out = self.cmd("%s/tools/test/bin/onos-netcfg %s %s"
85 % (ONOS_ROOT, controllerIP, self.netcfgfile))
86 if out:
87 print out
88
Carmelo Cascone785fada2016-06-16 18:34:16 -070089 def start(self, controllers):
Carmelo Cascone75e97992017-06-05 02:32:47 -040090 args = [BMV2_TARGET, '--device-id %s' % str(self.deviceId)]
Carmelo Cascone785fada2016-06-16 18:34:16 -070091 for port, intf in self.intfs.items():
92 if not intf.IP():
93 args.append('-i %d@%s' % (port, intf.name))
94 if self.thriftPort:
95 args.append('--thrift-port %d' % self.thriftPort)
96 if self.elogger:
97 nanomsg = 'ipc:///tmp/bmv2-%d-log.ipc' % self.deviceId
98 args.append('--nanolog %s' % nanomsg)
99 if self.debugger:
100 args.append('--debugger')
Carmelo Cascone75e97992017-06-05 02:32:47 -0400101 args.append('--log-file %s -L%s' % (self.logfile, self.loglevel))
102 if self.logflush:
103 args.append('--log-flush')
Carmelo Casconeb7524272017-06-05 16:53:13 -0400104
105 args.append(INIT_BMV2_JSON)
106
107 # gRPC target-specific options.
Carmelo Cascone785fada2016-06-16 18:34:16 -0700108 args.append('--')
Carmelo Cascone75e97992017-06-05 02:32:47 -0400109 args.append('--enable-swap')
Carmelo Casconeb7524272017-06-05 16:53:13 -0400110 args.append('--grpc-server-addr 0.0.0.0:%d' % self.grpcPort)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700111
112 bmv2cmd = " ".join(args)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700113 info("\nStarting BMv2 target: %s\n" % bmv2cmd)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700114 if self.persistent:
Carmelo Casconeb7524272017-06-05 16:53:13 -0400115 # Bash loop to re-exec the switch if it crashes.
Carmelo Cascone785fada2016-06-16 18:34:16 -0700116 cmdStr = "(while [ -e {} ]; " \
117 "do {} ; " \
118 "sleep 1; " \
Carmelo Cascone75e97992017-06-05 02:32:47 -0400119 "done;) &".format(self.exectoken, bmv2cmd)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700120 else:
Carmelo Cascone75e97992017-06-05 02:32:47 -0400121 cmdStr = "{} &".format(bmv2cmd)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700122
Carmelo Casconeb7524272017-06-05 16:53:13 -0400123 # Starts the switch.
124 out = self.cmd(cmdStr)
125 if out:
126 print out
127
128 if self.netcfg:
129 try: # onos.py
130 clist = controllers[0].nodes()
131 except AttributeError:
132 clist = controllers
133 assert len(clist) > 0
134 cip = clist[0].IP()
135 self.doOnosNetcfg(cip)
136
137 def stop(self, deleteIntfs=True):
138 """Terminate switch."""
Carmelo Cascone977ae3f2016-06-23 19:28:28 -0700139 self.cmd("rm -f /tmp/bmv2-%d-*" % self.deviceId)
Carmelo Cascone75e97992017-06-05 02:32:47 -0400140 # wildcard end as BMv2 might create log files with .txt extension
141 self.cmd("rm -f /tmp/bmv2-%d.log*" % self.deviceId)
142 self.cmd('kill %' + BMV2_TARGET)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400143 Switch.stop(self, deleteIntfs)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700144
145
Carmelo Casconeb7524272017-06-05 16:53:13 -0400146# Exports for bin/mn
Carmelo Cascone785fada2016-06-16 18:34:16 -0700147switches = {'onosbmv2': ONOSBmv2Switch}