blob: 6a7285f088cecea587ae6a11fb0ed00de7f4e651 [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
46def getStratumRoot():
47 if 'STRATUM_ROOT' not in os.environ:
48 raise Exception("Env variable STRATUM_ROOT not set")
49 return os.environ['STRATUM_ROOT']
50
Carmelo Cascone46d360b2017-08-29 20:20:32 +020051
52def parseBoolean(value):
53 if value in ['1', 1, 'true', 'True']:
54 return True
55 else:
56 return False
Carmelo Cascone34433252017-08-25 20:27:18 +020057
58
Carmelo Casconef11513d2018-01-16 00:31:14 -080059def pickUnusedPort():
60 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
61 s.bind(('localhost', 0))
62 addr, port = s.getsockname()
63 s.close()
64 return port
65
66
67def writeToFile(path, value):
68 with open(path, "w") as f:
69 f.write(str(value))
70
71
72def watchDog(sw):
Carmelo Cascone499f3202019-02-08 22:54:33 -080073 try:
74 writeToFile(sw.keepaliveFile,
75 "Remove this file to terminate %s" % sw.name)
76 while True:
77 if ONOSBmv2Switch.mininet_exception == 1 \
78 or not os.path.isfile(sw.keepaliveFile):
79 sw.killBmv2(log=False)
Carmelo Casconef11513d2018-01-16 00:31:14 -080080 return
Carmelo Cascone499f3202019-02-08 22:54:33 -080081 if sw.stopped:
82 return
83 with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
Carmelo Cascone34d116c2019-03-13 18:58:41 -070084 port = sw.grpcPortInternal if sw.grpcPortInternal else sw.grpcPort
85 if s.connect_ex(('localhost', port)) == 0:
Carmelo Cascone499f3202019-02-08 22:54:33 -080086 time.sleep(1)
87 else:
88 warn("\n*** WARN: switch %s died ☠️ \n" % sw.name)
89 sw.printBmv2Log()
90 print ("-" * 80) + "\n"
91 return
92 except Exception as e:
93 warn("*** ERROR: " + e.message)
94 sw.killBmv2(log=True)
Carmelo Casconef11513d2018-01-16 00:31:14 -080095
96
Carmelo Cascone34433252017-08-25 20:27:18 +020097class ONOSHost(Host):
98 def __init__(self, name, inNamespace=True, **params):
99 Host.__init__(self, name, inNamespace=inNamespace, **params)
100
101 def config(self, **params):
102 r = super(Host, self).config(**params)
103 for off in ["rx", "tx", "sg"]:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800104 cmd = "/sbin/ethtool --offload %s %s off" \
Carmelo Cascone6ec8f8f2017-11-22 14:27:06 -0800105 % (self.defaultIntf(), off)
Carmelo Cascone34433252017-08-25 20:27:18 +0200106 self.cmd(cmd)
107 # disable IPv6
108 self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
109 self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1")
110 self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1")
111 return r
Carmelo Casconeb7524272017-06-05 16:53:13 -0400112
Carmelo Cascone785fada2016-06-16 18:34:16 -0700113
114class ONOSBmv2Switch(Switch):
Carmelo Casconeb7524272017-06-05 16:53:13 -0400115 """BMv2 software switch with gRPC server"""
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200116 # Shared value used to notify to all instances of this class that a Mininet
117 # exception occurred. Mininet exception handling doesn't call the stop()
118 # method, so the mn process would hang after clean-up since Bmv2 would still
119 # be running.
120 mininet_exception = multiprocessing.Value('i', 0)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700121
Carmelo Casconef11513d2018-01-16 00:31:14 -0800122 def __init__(self, name, json=None, debugger=False, loglevel="warn",
Carmelo Cascone76e63862018-09-04 14:25:49 -0700123 elogger=False, grpcport=None, cpuport=255, notifications=False,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800124 thriftport=None, netcfg=True, dryrun=False,
125 pipeconf=DEFAULT_PIPECONF, pktdump=False, valgrind=False,
126 gnmi=False, portcfg=True, onosdevid=None, stratum=False,
127 **kwargs):
Carmelo Cascone785fada2016-06-16 18:34:16 -0700128 Switch.__init__(self, name, **kwargs)
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200129 self.grpcPort = grpcport
Carmelo Cascone34d116c2019-03-13 18:58:41 -0700130 self.grpcPortInternal = None # Needed for Stratum (local_hercules_url)
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200131 self.thriftPort = thriftport
Carmelo Casconeeaa8b1d2018-04-11 14:12:17 -0700132 self.cpuPort = cpuport
Carmelo Casconefb76b042017-07-17 19:42:00 -0400133 self.json = json
Carmelo Cascone499f3202019-02-08 22:54:33 -0800134 self.useStratum = parseBoolean(stratum)
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200135 self.debugger = parseBoolean(debugger)
Carmelo Cascone76e63862018-09-04 14:25:49 -0700136 self.notifications = parseBoolean(notifications)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700137 self.loglevel = loglevel
Carmelo Casconef11513d2018-01-16 00:31:14 -0800138 # Important: Mininet removes all /tmp/*.log files in case of exceptions.
139 # We want to be able to see the bmv2 log if anything goes wrong, hence
140 # avoid the .log extension.
Carmelo Casconec2821332018-05-14 18:15:33 -0700141 self.logfile = '/tmp/bmv2-%s-log' % self.name
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200142 self.elogger = parseBoolean(elogger)
143 self.pktdump = parseBoolean(pktdump)
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200144 self.netcfg = parseBoolean(netcfg)
145 self.dryrun = parseBoolean(dryrun)
146 self.valgrind = parseBoolean(valgrind)
Carmelo Casconec2821332018-05-14 18:15:33 -0700147 self.netcfgfile = '/tmp/bmv2-%s-netcfg.json' % self.name
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800148 self.chassisConfigFile = '/tmp/bmv2-%s-chassis-config.txt' % self.name
Carmelo Casconeeaa8b1d2018-04-11 14:12:17 -0700149 self.pipeconfId = pipeconf
150 self.injectPorts = parseBoolean(portcfg)
151 self.withGnmi = parseBoolean(gnmi)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800152 self.longitude = kwargs['longitude'] if 'longitude' in kwargs else None
153 self.latitude = kwargs['latitude'] if 'latitude' in kwargs else None
Carmelo Cascone55965c62018-05-17 18:13:16 -0700154 if onosdevid is not None and len(onosdevid) > 0:
155 self.onosDeviceId = onosdevid
156 else:
157 self.onosDeviceId = "device:bmv2:%s" % self.name
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800158 self.p4DeviceId = BMV2_DEFAULT_DEVICE_ID
Carmelo Casconef11513d2018-01-16 00:31:14 -0800159 self.logfd = None
160 self.bmv2popen = None
Carmelo Cascone499f3202019-02-08 22:54:33 -0800161 self.stopped = True
162 # In case of exceptions, mininet removes *.out files from /tmp. We use
163 # this as a signal to terminate the switch instance (if active).
164 self.keepaliveFile = '/tmp/bmv2-%s-watchdog.out' % self.name
165 self.targetName = STRATUM_BMV2 if self.useStratum else SIMPLE_SWITCH_GRPC
Yi Tseng7875cb72017-08-08 10:15:58 -0700166
Carmelo Casconef11513d2018-01-16 00:31:14 -0800167 # Remove files from previous executions
168 self.cleanupTmpFiles()
169
Carmelo Casconeb7524272017-06-05 16:53:13 -0400170 def getSourceIp(self, dstIP):
171 """
Carmelo Casconef11513d2018-01-16 00:31:14 -0800172 Queries the Linux routing table to get the source IP that can talk with
173 dstIP, and vice versa.
Carmelo Casconeb7524272017-06-05 16:53:13 -0400174 """
175 ipRouteOut = self.cmd('ip route get %s' % dstIP)
176 r = re.search(r"src (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", ipRouteOut)
177 return r.group(1) if r else None
178
Yi Tseng7875cb72017-08-08 10:15:58 -0700179 def getDeviceConfig(self, srcIP):
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +0200180
Yi Tseng7875cb72017-08-08 10:15:58 -0700181 basicCfg = {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800182 "managementAddress": "grpc://%s:%d?device_id=%d" % (
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800183 srcIP, self.grpcPort, self.p4DeviceId),
184 "driver": "stratum-bmv2" if self.useStratum else "bmv2",
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800185 "pipeconf": self.pipeconfId
Yi Tseng7875cb72017-08-08 10:15:58 -0700186 }
187
188 if self.longitude and self.latitude:
189 basicCfg["longitude"] = self.longitude
190 basicCfg["latitude"] = self.latitude
191
192 cfgData = {
Andrea Campanellabf9e5ce2017-12-06 14:26:36 +0100193 "basic": basicCfg
Yi Tseng7875cb72017-08-08 10:15:58 -0700194 }
195
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800196 if not self.useStratum and self.injectPorts:
Andrea Campanellabf9e5ce2017-12-06 14:26:36 +0100197 portData = {}
198 portId = 1
199 for intfName in self.intfNames():
200 if intfName == 'lo':
201 continue
202 portData[str(portId)] = {
203 "number": portId,
204 "name": intfName,
205 "enabled": True,
206 "removed": False,
207 "type": "copper",
208 "speed": 10000
209 }
210 portId += 1
211
212 cfgData['ports'] = portData
213
Yi Tseng7875cb72017-08-08 10:15:58 -0700214 return cfgData
215
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800216 def chassisConfig(self):
217 config = """description: "BMv2 simple_switch {name}"
218chassis {{
219 platform: PLT_P4_SOFT_SWITCH
220 name: "{name}"
221}}
222nodes {{
223 id: {nodeId}
224 name: "{name} node {nodeId}"
225 slot: 1
226 index: 1
227}}\n""".format(name=self.name, nodeId=self.p4DeviceId)
228
229 intfNumber = 1
230 for intfName in self.intfNames():
231 if intfName == 'lo':
232 continue
233 config = config + """singleton_ports {{
234 id: {intfNumber}
235 name: "{intfName}"
236 slot: 1
237 port: {intfNumber}
238 channel: 1
239 speed_bps: 10000000000
240 config_params {{
241 admin_state: ADMIN_STATE_ENABLED
242 }}
243 node: {nodeId}
244}}\n""".format(intfName=intfName, intfNumber=intfNumber,
245 nodeId=self.p4DeviceId)
246 intfNumber += 1
247
248 return config
249
Yi Tseng7875cb72017-08-08 10:15:58 -0700250 def doOnosNetcfg(self, controllerIP):
251 """
252 Notifies ONOS about the new device via Netcfg.
253 """
254 srcIP = self.getSourceIp(controllerIP)
255 if not srcIP:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800256 warn("*** WARN: unable to get switch IP address, won't do netcfg\n")
Yi Tseng7875cb72017-08-08 10:15:58 -0700257 return
258
Carmelo Casconea11279b2017-06-22 04:30:08 -0400259 cfgData = {
260 "devices": {
Yi Tseng7875cb72017-08-08 10:15:58 -0700261 self.onosDeviceId: self.getDeviceConfig(srcIP)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400262 }
Carmelo Casconea11279b2017-06-22 04:30:08 -0400263 }
Carmelo Casconeb7524272017-06-05 16:53:13 -0400264 with open(self.netcfgfile, 'w') as fp:
265 json.dump(cfgData, fp, indent=4)
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200266
267 if not self.netcfg:
268 # Do not push config to ONOS.
269 return
270
Brian O'Connor71167f92017-06-16 14:55:00 -0700271 # Build netcfg URL
272 url = 'http://%s:8181/onos/v1/network/configuration/' % controllerIP
273 # Instantiate password manager for HTTP auth
274 pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
Carmelo Casconef11513d2018-01-16 00:31:14 -0800275 pm.add_password(None, url,
276 os.environ['ONOS_WEB_USER'],
277 os.environ['ONOS_WEB_PASS'])
278 urllib2.install_opener(urllib2.build_opener(
279 urllib2.HTTPBasicAuthHandler(pm)))
Brian O'Connor71167f92017-06-16 14:55:00 -0700280 # Push config data to controller
Carmelo Casconef11513d2018-01-16 00:31:14 -0800281 req = urllib2.Request(url, json.dumps(cfgData),
282 {'Content-Type': 'application/json'})
Carmelo Casconea11279b2017-06-22 04:30:08 -0400283 try:
284 f = urllib2.urlopen(req)
285 print f.read()
286 f.close()
287 except urllib2.URLError as e:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800288 warn("*** WARN: unable to push config to ONOS (%s)\n" % e.reason)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400289
Carmelo Cascone785fada2016-06-16 18:34:16 -0700290 def start(self, controllers):
Carmelo Casconef11513d2018-01-16 00:31:14 -0800291
Carmelo Cascone499f3202019-02-08 22:54:33 -0800292 if not self.stopped:
293 warn("*** %s is already running!\n" % self.name)
294 return
295
296 # Remove files from previous executions (if we are restarting)
297 self.cleanupTmpFiles()
298
299 if self.grpcPort is None:
300 self.grpcPort = pickUnusedPort()
301 writeToFile("/tmp/bmv2-%s-grpc-port" % self.name, self.grpcPort)
302
303 if self.useStratum:
304 config_dir = "/tmp/bmv2-%s-stratum" % self.name
305 os.mkdir(config_dir)
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800306 with open(self.chassisConfigFile, 'w') as fp:
307 fp.write(self.chassisConfig())
Carmelo Cascone34d116c2019-03-13 18:58:41 -0700308 if self.grpcPortInternal is None:
309 self.grpcPortInternal = pickUnusedPort()
Carmelo Cascone499f3202019-02-08 22:54:33 -0800310 cmdString = self.getStratumCmdString(config_dir)
311 else:
312 if self.thriftPort is None:
313 self.thriftPort = pickUnusedPort()
314 writeToFile("/tmp/bmv2-%s-thrift-port" % self.name, self.thriftPort)
315 cmdString = self.getBmv2CmdString()
Carmelo Casconef11513d2018-01-16 00:31:14 -0800316
317 if self.dryrun:
Carmelo Cascone499f3202019-02-08 22:54:33 -0800318 info("\n*** DRY RUN (not executing %s)\n" % self.targetName)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800319
Carmelo Cascone499f3202019-02-08 22:54:33 -0800320 debug("\n%s\n" % cmdString)
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200321
Carmelo Casconef11513d2018-01-16 00:31:14 -0800322 try:
323 if not self.dryrun:
324 # Start the switch
Carmelo Cascone499f3202019-02-08 22:54:33 -0800325 self.stopped = False
Carmelo Casconef11513d2018-01-16 00:31:14 -0800326 self.logfd = open(self.logfile, "w")
Carmelo Cascone34d116c2019-03-13 18:58:41 -0700327 self.logfd.write(cmdString + "\n\n" + "-" * 80 + "\n\n")
328 self.logfd.flush()
Carmelo Casconef11513d2018-01-16 00:31:14 -0800329 self.bmv2popen = self.popen(cmdString,
330 stdout=self.logfd,
331 stderr=self.logfd)
332 self.waitBmv2Start()
Carmelo Cascone499f3202019-02-08 22:54:33 -0800333 # We want to be notified if BMv2/Stratum dies...
Carmelo Casconef11513d2018-01-16 00:31:14 -0800334 threading.Thread(target=watchDog, args=[self]).start()
335
336 self.doOnosNetcfg(self.controllerIp(controllers))
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200337 except Exception:
338 ONOSBmv2Switch.mininet_exception = 1
Carmelo Casconef11513d2018-01-16 00:31:14 -0800339 self.killBmv2()
340 self.printBmv2Log()
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200341 raise
Carmelo Casconef11513d2018-01-16 00:31:14 -0800342
Carmelo Cascone499f3202019-02-08 22:54:33 -0800343 def getBmv2CmdString(self):
344 bmv2Args = [SIMPLE_SWITCH_GRPC] + self.bmv2Args()
345 if self.valgrind:
346 bmv2Args = VALGRIND_PREFIX.split() + bmv2Args
347 return " ".join(bmv2Args)
348
349 def getStratumCmdString(self, config_dir):
350 stratumRoot = getStratumRoot()
351 args = [
352 stratumRoot + STRATUM_BINARY,
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800353 '-device_id=%d' % self.p4DeviceId,
354 '-chassis_config_file=%s' % self.chassisConfigFile,
Carmelo Cascone34d116c2019-03-13 18:58:41 -0700355 '-forwarding_pipeline_configs_file=%s/pipeline_config.txt' % config_dir,
Carmelo Cascone499f3202019-02-08 22:54:33 -0800356 '-persistent_config_dir=' + config_dir,
357 '-initial_pipeline=' + stratumRoot + STRATUM_INIT_PIPELINE,
358 '-cpu_port=%s' % self.cpuPort,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800359 '-external_hercules_urls=0.0.0.0:%d' % self.grpcPort,
Carmelo Cascone34d116c2019-03-13 18:58:41 -0700360 '-local_hercules_url=localhost:%d' % self.grpcPortInternal,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800361 '-max_num_controllers_per_node=10'
Carmelo Cascone499f3202019-02-08 22:54:33 -0800362 ]
Carmelo Cascone499f3202019-02-08 22:54:33 -0800363 return " ".join(args)
364
365 def bmv2Args(self):
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800366 args = ['--device-id %s' % str(self.p4DeviceId)]
Carmelo Cascone785fada2016-06-16 18:34:16 -0700367 for port, intf in self.intfs.items():
368 if not intf.IP():
369 args.append('-i %d@%s' % (port, intf.name))
Carmelo Casconec2821332018-05-14 18:15:33 -0700370 args.append('--thrift-port %s' % self.thriftPort)
Carmelo Cascone76e63862018-09-04 14:25:49 -0700371 if self.notifications:
372 ntfaddr = 'ipc:///tmp/bmv2-%s-notifications.ipc' % self.name
373 args.append('--notifications-addr %s' % ntfaddr)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700374 if self.elogger:
Carmelo Casconec2821332018-05-14 18:15:33 -0700375 nanologaddr = 'ipc:///tmp/bmv2-%s-nanolog.ipc' % self.name
376 args.append('--nanolog %s' % nanologaddr)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700377 if self.debugger:
Carmelo Casconec2821332018-05-14 18:15:33 -0700378 dbgaddr = 'ipc:///tmp/bmv2-%s-debug.ipc' % self.name
379 args.append('--debugger-addr %s' % dbgaddr)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400380 args.append('--log-console')
Carmelo Cascone34433252017-08-25 20:27:18 +0200381 if self.pktdump:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800382 args.append('--pcap --dump-packet-data %s' % PKT_BYTES_TO_DUMP)
Carmelo Cascone9e6621f2017-06-27 16:06:33 -0400383 args.append('-L%s' % self.loglevel)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400384 if not self.json:
385 args.append('--no-p4')
386 else:
387 args.append(self.json)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800388 # gRPC target-specific options
Carmelo Cascone785fada2016-06-16 18:34:16 -0700389 args.append('--')
Carmelo Casconef11513d2018-01-16 00:31:14 -0800390 args.append('--cpu-port %s' % self.cpuPort)
391 args.append('--grpc-server-addr 0.0.0.0:%s' % self.grpcPort)
392 return args
Carmelo Cascone785fada2016-06-16 18:34:16 -0700393
Carmelo Casconef11513d2018-01-16 00:31:14 -0800394 def waitBmv2Start(self):
395 # Wait for switch to open gRPC port, before sending ONOS the netcfg.
396 # Include time-out just in case something hangs.
397 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
398 endtime = time.time() + SWITCH_START_TIMEOUT
399 while True:
Carmelo Cascone34d116c2019-03-13 18:58:41 -0700400 port = self.grpcPortInternal if self.grpcPortInternal else self.grpcPort
401 result = sock.connect_ex(('localhost', port))
Carmelo Casconef11513d2018-01-16 00:31:14 -0800402 if result == 0:
Carmelo Cascone499f3202019-02-08 22:54:33 -0800403 # No new line
404 sys.stdout.write("⚡️ %s @ %d" % (self.targetName, self.bmv2popen.pid))
405 sys.stdout.flush()
Carmelo Casconef11513d2018-01-16 00:31:14 -0800406 # The port is open. Let's go! (Close socket first)
407 sock.close()
408 break
409 # Port is not open yet. If there is time, we wait a bit.
410 if endtime > time.time():
Carmelo Cascone499f3202019-02-08 22:54:33 -0800411 sys.stdout.write('.')
412 sys.stdout.flush()
413 time.sleep(0.05)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800414 else:
415 # Time's up.
416 raise Exception("Switch did not start before timeout")
Carmelo Casconefb76b042017-07-17 19:42:00 -0400417
Carmelo Casconef11513d2018-01-16 00:31:14 -0800418 def printBmv2Log(self):
419 if os.path.isfile(self.logfile):
420 print "-" * 80
Carmelo Casconec2821332018-05-14 18:15:33 -0700421 print "%s log (from %s):" % (self.name, self.logfile)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800422 with open(self.logfile, 'r') as f:
423 lines = f.readlines()
424 if len(lines) > BMV2_LOG_LINES:
425 print "..."
426 for line in lines[-BMV2_LOG_LINES:]:
427 print line.rstrip()
Carmelo Casconefb76b042017-07-17 19:42:00 -0400428
Carmelo Casconef11513d2018-01-16 00:31:14 -0800429 @staticmethod
430 def controllerIp(controllers):
431 try:
432 # onos.py
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200433 clist = controllers[0].nodes()
434 except AttributeError:
435 clist = controllers
436 assert len(clist) > 0
Carmelo Cascone44448a52018-06-25 23:36:57 +0200437 return random.choice(clist).IP()
Keesjan Karsten8539f082018-01-04 17:03:31 +0100438
Carmelo Casconef11513d2018-01-16 00:31:14 -0800439 def killBmv2(self, log=False):
Carmelo Cascone499f3202019-02-08 22:54:33 -0800440 self.stopped = True
Carmelo Casconef11513d2018-01-16 00:31:14 -0800441 if self.bmv2popen is not None:
Carmelo Cascone499f3202019-02-08 22:54:33 -0800442 self.bmv2popen.terminate()
443 self.bmv2popen.wait()
444 self.bmv2popen = None
Carmelo Casconef11513d2018-01-16 00:31:14 -0800445 if self.logfd is not None:
446 if log:
447 self.logfd.write("*** PROCESS TERMINATED BY MININET ***\n")
448 self.logfd.close()
Carmelo Cascone499f3202019-02-08 22:54:33 -0800449 self.logfd = None
Keesjan Karsten8539f082018-01-04 17:03:31 +0100450
Carmelo Casconef11513d2018-01-16 00:31:14 -0800451 def cleanupTmpFiles(self):
Carmelo Cascone499f3202019-02-08 22:54:33 -0800452 self.cmd("rm -rf /tmp/bmv2-%s-*" % self.name)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400453
454 def stop(self, deleteIntfs=True):
455 """Terminate switch."""
Carmelo Casconef11513d2018-01-16 00:31:14 -0800456 self.killBmv2(log=True)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400457 Switch.stop(self, deleteIntfs)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700458
459
Carmelo Cascone499f3202019-02-08 22:54:33 -0800460class ONOSStratumSwitch(ONOSBmv2Switch):
461 def __init__(self, name, **kwargs):
462 kwargs["stratum"] = True
463 super(ONOSStratumSwitch, self).__init__(name, **kwargs)
464
465
Carmelo Casconeb7524272017-06-05 16:53:13 -0400466# Exports for bin/mn
Carmelo Cascone499f3202019-02-08 22:54:33 -0800467switches = {
468 'onosbmv2': ONOSBmv2Switch,
469 'stratum': ONOSStratumSwitch,
470}
Carmelo Cascone34433252017-08-25 20:27:18 +0200471hosts = {'onoshost': ONOSHost}