blob: f99aeaff99147acb95e61c1446e091ec7d44b2df [file] [log] [blame]
Carmelo Cascone499f3202019-02-08 22:54:33 -08001# coding=utf-8
2"""
3Copyright 2019-present Open Networking Foundation
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16"""
Carmelo Cascone3c216fa2018-06-22 14:52:15 +020017import multiprocessing
Carmelo Casconeb7524272017-06-05 16:53:13 -040018import os
Carmelo Cascone15693a22018-12-12 19:06:57 -080019
20import json
Carmelo Cascone44448a52018-06-25 23:36:57 +020021import random
Carmelo Casconeb7524272017-06-05 16:53:13 -040022import re
Carmelo Cascone44448a52018-06-25 23:36:57 +020023import socket
Carmelo Cascone499f3202019-02-08 22:54:33 -080024import sys
Carmelo Casconef11513d2018-01-16 00:31:14 -080025import threading
Carmelo Cascone15693a22018-12-12 19:06:57 -080026import time
Carmelo Cascone44448a52018-06-25 23:36:57 +020027import urllib2
Carmelo Casconef11513d2018-01-16 00:31:14 -080028from contextlib import closing
Carmelo Cascone499f3202019-02-08 22:54:33 -080029from mininet.log import info, warn, debug
Carmelo Cascone34433252017-08-25 20:27:18 +020030from mininet.node import Switch, Host
Carmelo Cascone785fada2016-06-16 18:34:16 -070031
Carmelo Casconef11513d2018-01-16 00:31:14 -080032SIMPLE_SWITCH_GRPC = 'simple_switch_grpc'
Carmelo Cascone34433252017-08-25 20:27:18 +020033PKT_BYTES_TO_DUMP = 80
Carmelo Cascone46d360b2017-08-29 20:20:32 +020034VALGRIND_PREFIX = 'valgrind --leak-check=yes'
Carmelo Cascone34d116c2019-03-13 18:58:41 -070035SWITCH_START_TIMEOUT = 10 # seconds
Carmelo Casconef11513d2018-01-16 00:31:14 -080036BMV2_LOG_LINES = 5
Carmelo Cascone03ae0ac2018-10-11 08:31:59 -070037BMV2_DEFAULT_DEVICE_ID = 1
Carmelo Cascone3977ea42019-02-28 13:43:42 -080038DEFAULT_PIPECONF = "org.onosproject.pipelines.basic"
Carmelo Casconef11513d2018-01-16 00:31:14 -080039
Carmelo Cascone499f3202019-02-08 22:54:33 -080040# Stratum paths relative to stratum repo root
41STRATUM_BMV2 = 'stratum_bmv2'
42STRATUM_BINARY = '/bazel-bin/stratum/hal/bin/bmv2/' + STRATUM_BMV2
43STRATUM_INIT_PIPELINE = '/stratum/hal/bin/bmv2/dummy.json'
44
45
Carmelo Casconeada7b5b2019-04-23 13:50:03 -070046def getEnvOrDefault(env, default):
47 try:
48 return os.environ[env]
49 except KeyError:
50 return default
51
52
53ONOS_WEB_USER = getEnvOrDefault('ONOS_WEB_USER', 'onos')
54ONOS_WEB_PASS = getEnvOrDefault('ONOS_WEB_PASS', 'rocks')
55
56
Carmelo Cascone499f3202019-02-08 22:54:33 -080057def getStratumRoot():
58 if 'STRATUM_ROOT' not in os.environ:
59 raise Exception("Env variable STRATUM_ROOT not set")
60 return os.environ['STRATUM_ROOT']
61
Carmelo Cascone46d360b2017-08-29 20:20:32 +020062
63def parseBoolean(value):
64 if value in ['1', 1, 'true', 'True']:
65 return True
66 else:
67 return False
Carmelo Cascone34433252017-08-25 20:27:18 +020068
69
Carmelo Casconef11513d2018-01-16 00:31:14 -080070def pickUnusedPort():
71 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
72 s.bind(('localhost', 0))
73 addr, port = s.getsockname()
74 s.close()
75 return port
76
77
78def writeToFile(path, value):
79 with open(path, "w") as f:
80 f.write(str(value))
81
82
83def watchDog(sw):
Carmelo Cascone499f3202019-02-08 22:54:33 -080084 try:
85 writeToFile(sw.keepaliveFile,
86 "Remove this file to terminate %s" % sw.name)
87 while True:
88 if ONOSBmv2Switch.mininet_exception == 1 \
89 or not os.path.isfile(sw.keepaliveFile):
90 sw.killBmv2(log=False)
Carmelo Casconef11513d2018-01-16 00:31:14 -080091 return
Carmelo Cascone499f3202019-02-08 22:54:33 -080092 if sw.stopped:
93 return
94 with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
Carmelo Cascone34d116c2019-03-13 18:58:41 -070095 port = sw.grpcPortInternal if sw.grpcPortInternal else sw.grpcPort
96 if s.connect_ex(('localhost', port)) == 0:
Carmelo Cascone499f3202019-02-08 22:54:33 -080097 time.sleep(1)
98 else:
99 warn("\n*** WARN: switch %s died ☠️ \n" % sw.name)
100 sw.printBmv2Log()
101 print ("-" * 80) + "\n"
102 return
103 except Exception as e:
104 warn("*** ERROR: " + e.message)
105 sw.killBmv2(log=True)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800106
107
Carmelo Cascone34433252017-08-25 20:27:18 +0200108class ONOSHost(Host):
109 def __init__(self, name, inNamespace=True, **params):
110 Host.__init__(self, name, inNamespace=inNamespace, **params)
111
112 def config(self, **params):
113 r = super(Host, self).config(**params)
114 for off in ["rx", "tx", "sg"]:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800115 cmd = "/sbin/ethtool --offload %s %s off" \
Carmelo Cascone6ec8f8f2017-11-22 14:27:06 -0800116 % (self.defaultIntf(), off)
Carmelo Cascone34433252017-08-25 20:27:18 +0200117 self.cmd(cmd)
118 # disable IPv6
119 self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
120 self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1")
121 self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1")
122 return r
Carmelo Casconeb7524272017-06-05 16:53:13 -0400123
Carmelo Cascone785fada2016-06-16 18:34:16 -0700124
125class ONOSBmv2Switch(Switch):
Carmelo Casconeb7524272017-06-05 16:53:13 -0400126 """BMv2 software switch with gRPC server"""
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200127 # Shared value used to notify to all instances of this class that a Mininet
128 # exception occurred. Mininet exception handling doesn't call the stop()
129 # method, so the mn process would hang after clean-up since Bmv2 would still
130 # be running.
131 mininet_exception = multiprocessing.Value('i', 0)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700132
Carmelo Casconef11513d2018-01-16 00:31:14 -0800133 def __init__(self, name, json=None, debugger=False, loglevel="warn",
Carmelo Cascone76e63862018-09-04 14:25:49 -0700134 elogger=False, grpcport=None, cpuport=255, notifications=False,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800135 thriftport=None, netcfg=True, dryrun=False,
136 pipeconf=DEFAULT_PIPECONF, pktdump=False, valgrind=False,
137 gnmi=False, portcfg=True, onosdevid=None, stratum=False,
138 **kwargs):
Carmelo Cascone785fada2016-06-16 18:34:16 -0700139 Switch.__init__(self, name, **kwargs)
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200140 self.grpcPort = grpcport
Carmelo Cascone34d116c2019-03-13 18:58:41 -0700141 self.grpcPortInternal = None # Needed for Stratum (local_hercules_url)
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200142 self.thriftPort = thriftport
Carmelo Casconeeaa8b1d2018-04-11 14:12:17 -0700143 self.cpuPort = cpuport
Carmelo Casconefb76b042017-07-17 19:42:00 -0400144 self.json = json
Carmelo Cascone499f3202019-02-08 22:54:33 -0800145 self.useStratum = parseBoolean(stratum)
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200146 self.debugger = parseBoolean(debugger)
Carmelo Cascone76e63862018-09-04 14:25:49 -0700147 self.notifications = parseBoolean(notifications)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700148 self.loglevel = loglevel
Carmelo Casconef11513d2018-01-16 00:31:14 -0800149 # Important: Mininet removes all /tmp/*.log files in case of exceptions.
150 # We want to be able to see the bmv2 log if anything goes wrong, hence
151 # avoid the .log extension.
Carmelo Casconec2821332018-05-14 18:15:33 -0700152 self.logfile = '/tmp/bmv2-%s-log' % self.name
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200153 self.elogger = parseBoolean(elogger)
154 self.pktdump = parseBoolean(pktdump)
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200155 self.netcfg = parseBoolean(netcfg)
156 self.dryrun = parseBoolean(dryrun)
157 self.valgrind = parseBoolean(valgrind)
Carmelo Casconec2821332018-05-14 18:15:33 -0700158 self.netcfgfile = '/tmp/bmv2-%s-netcfg.json' % self.name
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800159 self.chassisConfigFile = '/tmp/bmv2-%s-chassis-config.txt' % self.name
Carmelo Casconeeaa8b1d2018-04-11 14:12:17 -0700160 self.pipeconfId = pipeconf
161 self.injectPorts = parseBoolean(portcfg)
162 self.withGnmi = parseBoolean(gnmi)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800163 self.longitude = kwargs['longitude'] if 'longitude' in kwargs else None
164 self.latitude = kwargs['latitude'] if 'latitude' in kwargs else None
Carmelo Cascone55965c62018-05-17 18:13:16 -0700165 if onosdevid is not None and len(onosdevid) > 0:
166 self.onosDeviceId = onosdevid
167 else:
168 self.onosDeviceId = "device:bmv2:%s" % self.name
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800169 self.p4DeviceId = BMV2_DEFAULT_DEVICE_ID
Carmelo Casconef11513d2018-01-16 00:31:14 -0800170 self.logfd = None
171 self.bmv2popen = None
Carmelo Cascone499f3202019-02-08 22:54:33 -0800172 self.stopped = True
173 # In case of exceptions, mininet removes *.out files from /tmp. We use
174 # this as a signal to terminate the switch instance (if active).
175 self.keepaliveFile = '/tmp/bmv2-%s-watchdog.out' % self.name
176 self.targetName = STRATUM_BMV2 if self.useStratum else SIMPLE_SWITCH_GRPC
Yi Tseng7875cb72017-08-08 10:15:58 -0700177
Carmelo Casconef11513d2018-01-16 00:31:14 -0800178 # Remove files from previous executions
179 self.cleanupTmpFiles()
180
Carmelo Casconeb7524272017-06-05 16:53:13 -0400181 def getSourceIp(self, dstIP):
182 """
Carmelo Casconef11513d2018-01-16 00:31:14 -0800183 Queries the Linux routing table to get the source IP that can talk with
184 dstIP, and vice versa.
Carmelo Casconeb7524272017-06-05 16:53:13 -0400185 """
186 ipRouteOut = self.cmd('ip route get %s' % dstIP)
187 r = re.search(r"src (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", ipRouteOut)
188 return r.group(1) if r else None
189
Yi Tseng7875cb72017-08-08 10:15:58 -0700190 def getDeviceConfig(self, srcIP):
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +0200191
Yi Tseng7875cb72017-08-08 10:15:58 -0700192 basicCfg = {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800193 "managementAddress": "grpc://%s:%d?device_id=%d" % (
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800194 srcIP, self.grpcPort, self.p4DeviceId),
195 "driver": "stratum-bmv2" if self.useStratum else "bmv2",
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800196 "pipeconf": self.pipeconfId
Yi Tseng7875cb72017-08-08 10:15:58 -0700197 }
198
199 if self.longitude and self.latitude:
200 basicCfg["longitude"] = self.longitude
201 basicCfg["latitude"] = self.latitude
202
203 cfgData = {
Andrea Campanellabf9e5ce2017-12-06 14:26:36 +0100204 "basic": basicCfg
Yi Tseng7875cb72017-08-08 10:15:58 -0700205 }
206
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800207 if not self.useStratum and self.injectPorts:
Andrea Campanellabf9e5ce2017-12-06 14:26:36 +0100208 portData = {}
209 portId = 1
210 for intfName in self.intfNames():
211 if intfName == 'lo':
212 continue
213 portData[str(portId)] = {
214 "number": portId,
215 "name": intfName,
216 "enabled": True,
217 "removed": False,
218 "type": "copper",
219 "speed": 10000
220 }
221 portId += 1
222
223 cfgData['ports'] = portData
224
Yi Tseng7875cb72017-08-08 10:15:58 -0700225 return cfgData
226
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800227 def chassisConfig(self):
228 config = """description: "BMv2 simple_switch {name}"
229chassis {{
230 platform: PLT_P4_SOFT_SWITCH
231 name: "{name}"
232}}
233nodes {{
234 id: {nodeId}
235 name: "{name} node {nodeId}"
236 slot: 1
237 index: 1
238}}\n""".format(name=self.name, nodeId=self.p4DeviceId)
239
240 intfNumber = 1
241 for intfName in self.intfNames():
242 if intfName == 'lo':
243 continue
244 config = config + """singleton_ports {{
245 id: {intfNumber}
246 name: "{intfName}"
247 slot: 1
248 port: {intfNumber}
249 channel: 1
250 speed_bps: 10000000000
251 config_params {{
252 admin_state: ADMIN_STATE_ENABLED
253 }}
254 node: {nodeId}
255}}\n""".format(intfName=intfName, intfNumber=intfNumber,
256 nodeId=self.p4DeviceId)
257 intfNumber += 1
258
259 return config
260
Yi Tseng7875cb72017-08-08 10:15:58 -0700261 def doOnosNetcfg(self, controllerIP):
262 """
263 Notifies ONOS about the new device via Netcfg.
264 """
265 srcIP = self.getSourceIp(controllerIP)
266 if not srcIP:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800267 warn("*** WARN: unable to get switch IP address, won't do netcfg\n")
Yi Tseng7875cb72017-08-08 10:15:58 -0700268 return
269
Carmelo Casconea11279b2017-06-22 04:30:08 -0400270 cfgData = {
271 "devices": {
Yi Tseng7875cb72017-08-08 10:15:58 -0700272 self.onosDeviceId: self.getDeviceConfig(srcIP)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400273 }
Carmelo Casconea11279b2017-06-22 04:30:08 -0400274 }
Carmelo Casconeb7524272017-06-05 16:53:13 -0400275 with open(self.netcfgfile, 'w') as fp:
276 json.dump(cfgData, fp, indent=4)
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200277
278 if not self.netcfg:
279 # Do not push config to ONOS.
280 return
281
Brian O'Connor71167f92017-06-16 14:55:00 -0700282 # Build netcfg URL
283 url = 'http://%s:8181/onos/v1/network/configuration/' % controllerIP
284 # Instantiate password manager for HTTP auth
285 pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
Carmelo Casconeada7b5b2019-04-23 13:50:03 -0700286 pm.add_password(None, url, ONOS_WEB_USER, ONOS_WEB_PASS)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800287 urllib2.install_opener(urllib2.build_opener(
288 urllib2.HTTPBasicAuthHandler(pm)))
Brian O'Connor71167f92017-06-16 14:55:00 -0700289 # Push config data to controller
Carmelo Casconef11513d2018-01-16 00:31:14 -0800290 req = urllib2.Request(url, json.dumps(cfgData),
291 {'Content-Type': 'application/json'})
Carmelo Casconea11279b2017-06-22 04:30:08 -0400292 try:
293 f = urllib2.urlopen(req)
294 print f.read()
295 f.close()
296 except urllib2.URLError as e:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800297 warn("*** WARN: unable to push config to ONOS (%s)\n" % e.reason)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400298
Carmelo Cascone785fada2016-06-16 18:34:16 -0700299 def start(self, controllers):
Carmelo Casconef11513d2018-01-16 00:31:14 -0800300
Carmelo Cascone499f3202019-02-08 22:54:33 -0800301 if not self.stopped:
302 warn("*** %s is already running!\n" % self.name)
303 return
304
305 # Remove files from previous executions (if we are restarting)
306 self.cleanupTmpFiles()
307
308 if self.grpcPort is None:
309 self.grpcPort = pickUnusedPort()
310 writeToFile("/tmp/bmv2-%s-grpc-port" % self.name, self.grpcPort)
311
312 if self.useStratum:
313 config_dir = "/tmp/bmv2-%s-stratum" % self.name
314 os.mkdir(config_dir)
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800315 with open(self.chassisConfigFile, 'w') as fp:
316 fp.write(self.chassisConfig())
Carmelo Cascone34d116c2019-03-13 18:58:41 -0700317 if self.grpcPortInternal is None:
318 self.grpcPortInternal = pickUnusedPort()
Carmelo Cascone499f3202019-02-08 22:54:33 -0800319 cmdString = self.getStratumCmdString(config_dir)
320 else:
321 if self.thriftPort is None:
322 self.thriftPort = pickUnusedPort()
323 writeToFile("/tmp/bmv2-%s-thrift-port" % self.name, self.thriftPort)
324 cmdString = self.getBmv2CmdString()
Carmelo Casconef11513d2018-01-16 00:31:14 -0800325
326 if self.dryrun:
Carmelo Cascone499f3202019-02-08 22:54:33 -0800327 info("\n*** DRY RUN (not executing %s)\n" % self.targetName)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800328
Carmelo Cascone499f3202019-02-08 22:54:33 -0800329 debug("\n%s\n" % cmdString)
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200330
Carmelo Casconef11513d2018-01-16 00:31:14 -0800331 try:
332 if not self.dryrun:
333 # Start the switch
Carmelo Cascone499f3202019-02-08 22:54:33 -0800334 self.stopped = False
Carmelo Casconef11513d2018-01-16 00:31:14 -0800335 self.logfd = open(self.logfile, "w")
Carmelo Cascone34d116c2019-03-13 18:58:41 -0700336 self.logfd.write(cmdString + "\n\n" + "-" * 80 + "\n\n")
337 self.logfd.flush()
Carmelo Casconef11513d2018-01-16 00:31:14 -0800338 self.bmv2popen = self.popen(cmdString,
339 stdout=self.logfd,
340 stderr=self.logfd)
341 self.waitBmv2Start()
Carmelo Cascone499f3202019-02-08 22:54:33 -0800342 # We want to be notified if BMv2/Stratum dies...
Carmelo Casconef11513d2018-01-16 00:31:14 -0800343 threading.Thread(target=watchDog, args=[self]).start()
344
345 self.doOnosNetcfg(self.controllerIp(controllers))
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200346 except Exception:
347 ONOSBmv2Switch.mininet_exception = 1
Carmelo Casconef11513d2018-01-16 00:31:14 -0800348 self.killBmv2()
349 self.printBmv2Log()
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200350 raise
Carmelo Casconef11513d2018-01-16 00:31:14 -0800351
Carmelo Cascone499f3202019-02-08 22:54:33 -0800352 def getBmv2CmdString(self):
353 bmv2Args = [SIMPLE_SWITCH_GRPC] + self.bmv2Args()
354 if self.valgrind:
355 bmv2Args = VALGRIND_PREFIX.split() + bmv2Args
356 return " ".join(bmv2Args)
357
358 def getStratumCmdString(self, config_dir):
359 stratumRoot = getStratumRoot()
360 args = [
361 stratumRoot + STRATUM_BINARY,
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800362 '-device_id=%d' % self.p4DeviceId,
363 '-chassis_config_file=%s' % self.chassisConfigFile,
Carmelo Cascone34d116c2019-03-13 18:58:41 -0700364 '-forwarding_pipeline_configs_file=%s/pipeline_config.txt' % config_dir,
Carmelo Cascone499f3202019-02-08 22:54:33 -0800365 '-persistent_config_dir=' + config_dir,
366 '-initial_pipeline=' + stratumRoot + STRATUM_INIT_PIPELINE,
367 '-cpu_port=%s' % self.cpuPort,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800368 '-external_hercules_urls=0.0.0.0:%d' % self.grpcPort,
Carmelo Cascone34d116c2019-03-13 18:58:41 -0700369 '-local_hercules_url=localhost:%d' % self.grpcPortInternal,
Carmelo Casconeb07f2b52019-07-12 14:01:39 -0700370 '-max_num_controllers_per_node=10',
371 '-logtosyslog=false',
372 '-logtostderr=true'
Carmelo Cascone499f3202019-02-08 22:54:33 -0800373 ]
Carmelo Cascone499f3202019-02-08 22:54:33 -0800374 return " ".join(args)
375
376 def bmv2Args(self):
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800377 args = ['--device-id %s' % str(self.p4DeviceId)]
Carmelo Cascone785fada2016-06-16 18:34:16 -0700378 for port, intf in self.intfs.items():
379 if not intf.IP():
380 args.append('-i %d@%s' % (port, intf.name))
Carmelo Casconec2821332018-05-14 18:15:33 -0700381 args.append('--thrift-port %s' % self.thriftPort)
Carmelo Cascone76e63862018-09-04 14:25:49 -0700382 if self.notifications:
383 ntfaddr = 'ipc:///tmp/bmv2-%s-notifications.ipc' % self.name
384 args.append('--notifications-addr %s' % ntfaddr)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700385 if self.elogger:
Carmelo Casconec2821332018-05-14 18:15:33 -0700386 nanologaddr = 'ipc:///tmp/bmv2-%s-nanolog.ipc' % self.name
387 args.append('--nanolog %s' % nanologaddr)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700388 if self.debugger:
Carmelo Casconec2821332018-05-14 18:15:33 -0700389 dbgaddr = 'ipc:///tmp/bmv2-%s-debug.ipc' % self.name
390 args.append('--debugger-addr %s' % dbgaddr)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400391 args.append('--log-console')
Carmelo Cascone34433252017-08-25 20:27:18 +0200392 if self.pktdump:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800393 args.append('--pcap --dump-packet-data %s' % PKT_BYTES_TO_DUMP)
Carmelo Cascone9e6621f2017-06-27 16:06:33 -0400394 args.append('-L%s' % self.loglevel)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400395 if not self.json:
396 args.append('--no-p4')
397 else:
398 args.append(self.json)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800399 # gRPC target-specific options
Carmelo Cascone785fada2016-06-16 18:34:16 -0700400 args.append('--')
Carmelo Casconef11513d2018-01-16 00:31:14 -0800401 args.append('--cpu-port %s' % self.cpuPort)
402 args.append('--grpc-server-addr 0.0.0.0:%s' % self.grpcPort)
403 return args
Carmelo Cascone785fada2016-06-16 18:34:16 -0700404
Carmelo Casconef11513d2018-01-16 00:31:14 -0800405 def waitBmv2Start(self):
406 # Wait for switch to open gRPC port, before sending ONOS the netcfg.
407 # Include time-out just in case something hangs.
408 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
409 endtime = time.time() + SWITCH_START_TIMEOUT
410 while True:
Carmelo Cascone34d116c2019-03-13 18:58:41 -0700411 port = self.grpcPortInternal if self.grpcPortInternal else self.grpcPort
412 result = sock.connect_ex(('localhost', port))
Carmelo Casconef11513d2018-01-16 00:31:14 -0800413 if result == 0:
Carmelo Cascone499f3202019-02-08 22:54:33 -0800414 # No new line
415 sys.stdout.write("⚡️ %s @ %d" % (self.targetName, self.bmv2popen.pid))
416 sys.stdout.flush()
Carmelo Casconef11513d2018-01-16 00:31:14 -0800417 # The port is open. Let's go! (Close socket first)
418 sock.close()
419 break
420 # Port is not open yet. If there is time, we wait a bit.
421 if endtime > time.time():
Carmelo Cascone499f3202019-02-08 22:54:33 -0800422 sys.stdout.write('.')
423 sys.stdout.flush()
424 time.sleep(0.05)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800425 else:
426 # Time's up.
427 raise Exception("Switch did not start before timeout")
Carmelo Casconefb76b042017-07-17 19:42:00 -0400428
Carmelo Casconef11513d2018-01-16 00:31:14 -0800429 def printBmv2Log(self):
430 if os.path.isfile(self.logfile):
431 print "-" * 80
Carmelo Casconec2821332018-05-14 18:15:33 -0700432 print "%s log (from %s):" % (self.name, self.logfile)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800433 with open(self.logfile, 'r') as f:
434 lines = f.readlines()
435 if len(lines) > BMV2_LOG_LINES:
436 print "..."
437 for line in lines[-BMV2_LOG_LINES:]:
438 print line.rstrip()
Carmelo Casconefb76b042017-07-17 19:42:00 -0400439
Carmelo Casconef11513d2018-01-16 00:31:14 -0800440 @staticmethod
441 def controllerIp(controllers):
442 try:
443 # onos.py
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200444 clist = controllers[0].nodes()
445 except AttributeError:
446 clist = controllers
447 assert len(clist) > 0
Carmelo Cascone44448a52018-06-25 23:36:57 +0200448 return random.choice(clist).IP()
Keesjan Karsten8539f082018-01-04 17:03:31 +0100449
Carmelo Casconef11513d2018-01-16 00:31:14 -0800450 def killBmv2(self, log=False):
Carmelo Cascone499f3202019-02-08 22:54:33 -0800451 self.stopped = True
Carmelo Casconef11513d2018-01-16 00:31:14 -0800452 if self.bmv2popen is not None:
Carmelo Cascone499f3202019-02-08 22:54:33 -0800453 self.bmv2popen.terminate()
454 self.bmv2popen.wait()
455 self.bmv2popen = None
Carmelo Casconef11513d2018-01-16 00:31:14 -0800456 if self.logfd is not None:
457 if log:
458 self.logfd.write("*** PROCESS TERMINATED BY MININET ***\n")
459 self.logfd.close()
Carmelo Cascone499f3202019-02-08 22:54:33 -0800460 self.logfd = None
Keesjan Karsten8539f082018-01-04 17:03:31 +0100461
Carmelo Casconef11513d2018-01-16 00:31:14 -0800462 def cleanupTmpFiles(self):
Carmelo Cascone499f3202019-02-08 22:54:33 -0800463 self.cmd("rm -rf /tmp/bmv2-%s-*" % self.name)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400464
465 def stop(self, deleteIntfs=True):
466 """Terminate switch."""
Carmelo Casconef11513d2018-01-16 00:31:14 -0800467 self.killBmv2(log=True)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400468 Switch.stop(self, deleteIntfs)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700469
470
Carmelo Cascone499f3202019-02-08 22:54:33 -0800471class ONOSStratumSwitch(ONOSBmv2Switch):
472 def __init__(self, name, **kwargs):
473 kwargs["stratum"] = True
474 super(ONOSStratumSwitch, self).__init__(name, **kwargs)
475
476
Carmelo Casconeb7524272017-06-05 16:53:13 -0400477# Exports for bin/mn
Carmelo Cascone499f3202019-02-08 22:54:33 -0800478switches = {
479 'onosbmv2': ONOSBmv2Switch,
480 'stratum': ONOSStratumSwitch,
481}
Carmelo Cascone34433252017-08-25 20:27:18 +0200482hosts = {'onoshost': ONOSHost}