blob: 522a80c78cf69cb3d82f4ef09241791d56ef4ec0 [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 Casconef11513d2018-01-16 00:31:14 -080035SWITCH_START_TIMEOUT = 5 # seconds
36BMV2_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:
84 if s.connect_ex(('127.0.0.1', sw.grpcPort)) == 0:
85 time.sleep(1)
86 else:
87 warn("\n*** WARN: switch %s died ☠️ \n" % sw.name)
88 sw.printBmv2Log()
89 print ("-" * 80) + "\n"
90 return
91 except Exception as e:
92 warn("*** ERROR: " + e.message)
93 sw.killBmv2(log=True)
Carmelo Casconef11513d2018-01-16 00:31:14 -080094
95
Carmelo Cascone34433252017-08-25 20:27:18 +020096class ONOSHost(Host):
97 def __init__(self, name, inNamespace=True, **params):
98 Host.__init__(self, name, inNamespace=inNamespace, **params)
99
100 def config(self, **params):
101 r = super(Host, self).config(**params)
102 for off in ["rx", "tx", "sg"]:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800103 cmd = "/sbin/ethtool --offload %s %s off" \
Carmelo Cascone6ec8f8f2017-11-22 14:27:06 -0800104 % (self.defaultIntf(), off)
Carmelo Cascone34433252017-08-25 20:27:18 +0200105 self.cmd(cmd)
106 # disable IPv6
107 self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
108 self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1")
109 self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1")
110 return r
Carmelo Casconeb7524272017-06-05 16:53:13 -0400111
Carmelo Cascone785fada2016-06-16 18:34:16 -0700112
113class ONOSBmv2Switch(Switch):
Carmelo Casconeb7524272017-06-05 16:53:13 -0400114 """BMv2 software switch with gRPC server"""
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200115 # Shared value used to notify to all instances of this class that a Mininet
116 # exception occurred. Mininet exception handling doesn't call the stop()
117 # method, so the mn process would hang after clean-up since Bmv2 would still
118 # be running.
119 mininet_exception = multiprocessing.Value('i', 0)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700120
Carmelo Casconef11513d2018-01-16 00:31:14 -0800121 def __init__(self, name, json=None, debugger=False, loglevel="warn",
Carmelo Cascone76e63862018-09-04 14:25:49 -0700122 elogger=False, grpcport=None, cpuport=255, notifications=False,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800123 thriftport=None, netcfg=True, dryrun=False,
124 pipeconf=DEFAULT_PIPECONF, pktdump=False, valgrind=False,
125 gnmi=False, portcfg=True, onosdevid=None, stratum=False,
126 **kwargs):
Carmelo Cascone785fada2016-06-16 18:34:16 -0700127 Switch.__init__(self, name, **kwargs)
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200128 self.grpcPort = grpcport
129 self.thriftPort = thriftport
Carmelo Casconeeaa8b1d2018-04-11 14:12:17 -0700130 self.cpuPort = cpuport
Carmelo Casconefb76b042017-07-17 19:42:00 -0400131 self.json = json
Carmelo Cascone499f3202019-02-08 22:54:33 -0800132 self.useStratum = parseBoolean(stratum)
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200133 self.debugger = parseBoolean(debugger)
Carmelo Cascone76e63862018-09-04 14:25:49 -0700134 self.notifications = parseBoolean(notifications)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700135 self.loglevel = loglevel
Carmelo Casconef11513d2018-01-16 00:31:14 -0800136 # Important: Mininet removes all /tmp/*.log files in case of exceptions.
137 # We want to be able to see the bmv2 log if anything goes wrong, hence
138 # avoid the .log extension.
Carmelo Casconec2821332018-05-14 18:15:33 -0700139 self.logfile = '/tmp/bmv2-%s-log' % self.name
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200140 self.elogger = parseBoolean(elogger)
141 self.pktdump = parseBoolean(pktdump)
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200142 self.netcfg = parseBoolean(netcfg)
143 self.dryrun = parseBoolean(dryrun)
144 self.valgrind = parseBoolean(valgrind)
Carmelo Casconec2821332018-05-14 18:15:33 -0700145 self.netcfgfile = '/tmp/bmv2-%s-netcfg.json' % self.name
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800146 self.chassisConfigFile = '/tmp/bmv2-%s-chassis-config.txt' % self.name
Carmelo Casconeeaa8b1d2018-04-11 14:12:17 -0700147 self.pipeconfId = pipeconf
148 self.injectPorts = parseBoolean(portcfg)
149 self.withGnmi = parseBoolean(gnmi)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800150 self.longitude = kwargs['longitude'] if 'longitude' in kwargs else None
151 self.latitude = kwargs['latitude'] if 'latitude' in kwargs else None
Carmelo Cascone55965c62018-05-17 18:13:16 -0700152 if onosdevid is not None and len(onosdevid) > 0:
153 self.onosDeviceId = onosdevid
154 else:
155 self.onosDeviceId = "device:bmv2:%s" % self.name
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800156 self.p4DeviceId = BMV2_DEFAULT_DEVICE_ID
Carmelo Casconef11513d2018-01-16 00:31:14 -0800157 self.logfd = None
158 self.bmv2popen = None
Carmelo Cascone499f3202019-02-08 22:54:33 -0800159 self.stopped = True
160 # In case of exceptions, mininet removes *.out files from /tmp. We use
161 # this as a signal to terminate the switch instance (if active).
162 self.keepaliveFile = '/tmp/bmv2-%s-watchdog.out' % self.name
163 self.targetName = STRATUM_BMV2 if self.useStratum else SIMPLE_SWITCH_GRPC
Yi Tseng7875cb72017-08-08 10:15:58 -0700164
Carmelo Casconef11513d2018-01-16 00:31:14 -0800165 # Remove files from previous executions
166 self.cleanupTmpFiles()
167
Carmelo Casconeb7524272017-06-05 16:53:13 -0400168 def getSourceIp(self, dstIP):
169 """
Carmelo Casconef11513d2018-01-16 00:31:14 -0800170 Queries the Linux routing table to get the source IP that can talk with
171 dstIP, and vice versa.
Carmelo Casconeb7524272017-06-05 16:53:13 -0400172 """
173 ipRouteOut = self.cmd('ip route get %s' % dstIP)
174 r = re.search(r"src (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", ipRouteOut)
175 return r.group(1) if r else None
176
Yi Tseng7875cb72017-08-08 10:15:58 -0700177 def getDeviceConfig(self, srcIP):
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +0200178
Yi Tseng7875cb72017-08-08 10:15:58 -0700179 basicCfg = {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800180 "managementAddress": "grpc://%s:%d?device_id=%d" % (
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800181 srcIP, self.grpcPort, self.p4DeviceId),
182 "driver": "stratum-bmv2" if self.useStratum else "bmv2",
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800183 "pipeconf": self.pipeconfId
Yi Tseng7875cb72017-08-08 10:15:58 -0700184 }
185
186 if self.longitude and self.latitude:
187 basicCfg["longitude"] = self.longitude
188 basicCfg["latitude"] = self.latitude
189
190 cfgData = {
Andrea Campanellabf9e5ce2017-12-06 14:26:36 +0100191 "basic": basicCfg
Yi Tseng7875cb72017-08-08 10:15:58 -0700192 }
193
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800194 if not self.useStratum and self.injectPorts:
Andrea Campanellabf9e5ce2017-12-06 14:26:36 +0100195 portData = {}
196 portId = 1
197 for intfName in self.intfNames():
198 if intfName == 'lo':
199 continue
200 portData[str(portId)] = {
201 "number": portId,
202 "name": intfName,
203 "enabled": True,
204 "removed": False,
205 "type": "copper",
206 "speed": 10000
207 }
208 portId += 1
209
210 cfgData['ports'] = portData
211
Yi Tseng7875cb72017-08-08 10:15:58 -0700212 return cfgData
213
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800214 def chassisConfig(self):
215 config = """description: "BMv2 simple_switch {name}"
216chassis {{
217 platform: PLT_P4_SOFT_SWITCH
218 name: "{name}"
219}}
220nodes {{
221 id: {nodeId}
222 name: "{name} node {nodeId}"
223 slot: 1
224 index: 1
225}}\n""".format(name=self.name, nodeId=self.p4DeviceId)
226
227 intfNumber = 1
228 for intfName in self.intfNames():
229 if intfName == 'lo':
230 continue
231 config = config + """singleton_ports {{
232 id: {intfNumber}
233 name: "{intfName}"
234 slot: 1
235 port: {intfNumber}
236 channel: 1
237 speed_bps: 10000000000
238 config_params {{
239 admin_state: ADMIN_STATE_ENABLED
240 }}
241 node: {nodeId}
242}}\n""".format(intfName=intfName, intfNumber=intfNumber,
243 nodeId=self.p4DeviceId)
244 intfNumber += 1
245
246 return config
247
Yi Tseng7875cb72017-08-08 10:15:58 -0700248 def doOnosNetcfg(self, controllerIP):
249 """
250 Notifies ONOS about the new device via Netcfg.
251 """
252 srcIP = self.getSourceIp(controllerIP)
253 if not srcIP:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800254 warn("*** WARN: unable to get switch IP address, won't do netcfg\n")
Yi Tseng7875cb72017-08-08 10:15:58 -0700255 return
256
Carmelo Casconea11279b2017-06-22 04:30:08 -0400257 cfgData = {
258 "devices": {
Yi Tseng7875cb72017-08-08 10:15:58 -0700259 self.onosDeviceId: self.getDeviceConfig(srcIP)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400260 }
Carmelo Casconea11279b2017-06-22 04:30:08 -0400261 }
Carmelo Casconeb7524272017-06-05 16:53:13 -0400262 with open(self.netcfgfile, 'w') as fp:
263 json.dump(cfgData, fp, indent=4)
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200264
265 if not self.netcfg:
266 # Do not push config to ONOS.
267 return
268
Brian O'Connor71167f92017-06-16 14:55:00 -0700269 # Build netcfg URL
270 url = 'http://%s:8181/onos/v1/network/configuration/' % controllerIP
271 # Instantiate password manager for HTTP auth
272 pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
Carmelo Casconef11513d2018-01-16 00:31:14 -0800273 pm.add_password(None, url,
274 os.environ['ONOS_WEB_USER'],
275 os.environ['ONOS_WEB_PASS'])
276 urllib2.install_opener(urllib2.build_opener(
277 urllib2.HTTPBasicAuthHandler(pm)))
Brian O'Connor71167f92017-06-16 14:55:00 -0700278 # Push config data to controller
Carmelo Casconef11513d2018-01-16 00:31:14 -0800279 req = urllib2.Request(url, json.dumps(cfgData),
280 {'Content-Type': 'application/json'})
Carmelo Casconea11279b2017-06-22 04:30:08 -0400281 try:
282 f = urllib2.urlopen(req)
283 print f.read()
284 f.close()
285 except urllib2.URLError as e:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800286 warn("*** WARN: unable to push config to ONOS (%s)\n" % e.reason)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400287
Carmelo Cascone785fada2016-06-16 18:34:16 -0700288 def start(self, controllers):
Carmelo Casconef11513d2018-01-16 00:31:14 -0800289
Carmelo Cascone499f3202019-02-08 22:54:33 -0800290 if not self.stopped:
291 warn("*** %s is already running!\n" % self.name)
292 return
293
294 # Remove files from previous executions (if we are restarting)
295 self.cleanupTmpFiles()
296
297 if self.grpcPort is None:
298 self.grpcPort = pickUnusedPort()
299 writeToFile("/tmp/bmv2-%s-grpc-port" % self.name, self.grpcPort)
300
301 if self.useStratum:
302 config_dir = "/tmp/bmv2-%s-stratum" % self.name
303 os.mkdir(config_dir)
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800304 with open(self.chassisConfigFile, 'w') as fp:
305 fp.write(self.chassisConfig())
Carmelo Cascone499f3202019-02-08 22:54:33 -0800306 cmdString = self.getStratumCmdString(config_dir)
307 else:
308 if self.thriftPort is None:
309 self.thriftPort = pickUnusedPort()
310 writeToFile("/tmp/bmv2-%s-thrift-port" % self.name, self.thriftPort)
311 cmdString = self.getBmv2CmdString()
Carmelo Casconef11513d2018-01-16 00:31:14 -0800312
313 if self.dryrun:
Carmelo Cascone499f3202019-02-08 22:54:33 -0800314 info("\n*** DRY RUN (not executing %s)\n" % self.targetName)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800315
Carmelo Cascone499f3202019-02-08 22:54:33 -0800316 debug("\n%s\n" % cmdString)
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200317
Carmelo Casconef11513d2018-01-16 00:31:14 -0800318 try:
319 if not self.dryrun:
320 # Start the switch
Carmelo Cascone499f3202019-02-08 22:54:33 -0800321 self.stopped = False
Carmelo Casconef11513d2018-01-16 00:31:14 -0800322 self.logfd = open(self.logfile, "w")
323 self.bmv2popen = self.popen(cmdString,
324 stdout=self.logfd,
325 stderr=self.logfd)
326 self.waitBmv2Start()
Carmelo Cascone499f3202019-02-08 22:54:33 -0800327 # We want to be notified if BMv2/Stratum dies...
Carmelo Casconef11513d2018-01-16 00:31:14 -0800328 threading.Thread(target=watchDog, args=[self]).start()
329
330 self.doOnosNetcfg(self.controllerIp(controllers))
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200331 except Exception:
332 ONOSBmv2Switch.mininet_exception = 1
Carmelo Casconef11513d2018-01-16 00:31:14 -0800333 self.killBmv2()
334 self.printBmv2Log()
Carmelo Cascone3c216fa2018-06-22 14:52:15 +0200335 raise
Carmelo Casconef11513d2018-01-16 00:31:14 -0800336
Carmelo Cascone499f3202019-02-08 22:54:33 -0800337 def getBmv2CmdString(self):
338 bmv2Args = [SIMPLE_SWITCH_GRPC] + self.bmv2Args()
339 if self.valgrind:
340 bmv2Args = VALGRIND_PREFIX.split() + bmv2Args
341 return " ".join(bmv2Args)
342
343 def getStratumCmdString(self, config_dir):
344 stratumRoot = getStratumRoot()
345 args = [
346 stratumRoot + STRATUM_BINARY,
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800347 '-device_id=%d' % self.p4DeviceId,
348 '-chassis_config_file=%s' % self.chassisConfigFile,
Carmelo Cascone499f3202019-02-08 22:54:33 -0800349 '-forwarding_pipeline_configs_file=%s/config.txt' % config_dir,
350 '-persistent_config_dir=' + config_dir,
351 '-initial_pipeline=' + stratumRoot + STRATUM_INIT_PIPELINE,
352 '-cpu_port=%s' % self.cpuPort,
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800353 '-external_hercules_urls=0.0.0.0:%d' % self.grpcPort,
354 '-max_num_controllers_per_node=10'
Carmelo Cascone499f3202019-02-08 22:54:33 -0800355 ]
Carmelo Cascone499f3202019-02-08 22:54:33 -0800356 return " ".join(args)
357
358 def bmv2Args(self):
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800359 args = ['--device-id %s' % str(self.p4DeviceId)]
Carmelo Cascone785fada2016-06-16 18:34:16 -0700360 for port, intf in self.intfs.items():
361 if not intf.IP():
362 args.append('-i %d@%s' % (port, intf.name))
Carmelo Casconec2821332018-05-14 18:15:33 -0700363 args.append('--thrift-port %s' % self.thriftPort)
Carmelo Cascone76e63862018-09-04 14:25:49 -0700364 if self.notifications:
365 ntfaddr = 'ipc:///tmp/bmv2-%s-notifications.ipc' % self.name
366 args.append('--notifications-addr %s' % ntfaddr)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700367 if self.elogger:
Carmelo Casconec2821332018-05-14 18:15:33 -0700368 nanologaddr = 'ipc:///tmp/bmv2-%s-nanolog.ipc' % self.name
369 args.append('--nanolog %s' % nanologaddr)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700370 if self.debugger:
Carmelo Casconec2821332018-05-14 18:15:33 -0700371 dbgaddr = 'ipc:///tmp/bmv2-%s-debug.ipc' % self.name
372 args.append('--debugger-addr %s' % dbgaddr)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400373 args.append('--log-console')
Carmelo Cascone34433252017-08-25 20:27:18 +0200374 if self.pktdump:
Carmelo Casconef11513d2018-01-16 00:31:14 -0800375 args.append('--pcap --dump-packet-data %s' % PKT_BYTES_TO_DUMP)
Carmelo Cascone9e6621f2017-06-27 16:06:33 -0400376 args.append('-L%s' % self.loglevel)
Carmelo Casconefb76b042017-07-17 19:42:00 -0400377 if not self.json:
378 args.append('--no-p4')
379 else:
380 args.append(self.json)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800381 # gRPC target-specific options
Carmelo Cascone785fada2016-06-16 18:34:16 -0700382 args.append('--')
Carmelo Casconef11513d2018-01-16 00:31:14 -0800383 args.append('--cpu-port %s' % self.cpuPort)
384 args.append('--grpc-server-addr 0.0.0.0:%s' % self.grpcPort)
385 return args
Carmelo Cascone785fada2016-06-16 18:34:16 -0700386
Carmelo Casconef11513d2018-01-16 00:31:14 -0800387 def waitBmv2Start(self):
388 # Wait for switch to open gRPC port, before sending ONOS the netcfg.
389 # Include time-out just in case something hangs.
390 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
391 endtime = time.time() + SWITCH_START_TIMEOUT
392 while True:
393 result = sock.connect_ex(('127.0.0.1', self.grpcPort))
394 if result == 0:
Carmelo Cascone499f3202019-02-08 22:54:33 -0800395 # No new line
396 sys.stdout.write("⚡️ %s @ %d" % (self.targetName, self.bmv2popen.pid))
397 sys.stdout.flush()
Carmelo Casconef11513d2018-01-16 00:31:14 -0800398 # The port is open. Let's go! (Close socket first)
399 sock.close()
400 break
401 # Port is not open yet. If there is time, we wait a bit.
402 if endtime > time.time():
Carmelo Cascone499f3202019-02-08 22:54:33 -0800403 sys.stdout.write('.')
404 sys.stdout.flush()
405 time.sleep(0.05)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800406 else:
407 # Time's up.
408 raise Exception("Switch did not start before timeout")
Carmelo Casconefb76b042017-07-17 19:42:00 -0400409
Carmelo Casconef11513d2018-01-16 00:31:14 -0800410 def printBmv2Log(self):
411 if os.path.isfile(self.logfile):
412 print "-" * 80
Carmelo Casconec2821332018-05-14 18:15:33 -0700413 print "%s log (from %s):" % (self.name, self.logfile)
Carmelo Casconef11513d2018-01-16 00:31:14 -0800414 with open(self.logfile, 'r') as f:
415 lines = f.readlines()
416 if len(lines) > BMV2_LOG_LINES:
417 print "..."
418 for line in lines[-BMV2_LOG_LINES:]:
419 print line.rstrip()
Carmelo Casconefb76b042017-07-17 19:42:00 -0400420
Carmelo Casconef11513d2018-01-16 00:31:14 -0800421 @staticmethod
422 def controllerIp(controllers):
423 try:
424 # onos.py
Carmelo Cascone46d360b2017-08-29 20:20:32 +0200425 clist = controllers[0].nodes()
426 except AttributeError:
427 clist = controllers
428 assert len(clist) > 0
Carmelo Cascone44448a52018-06-25 23:36:57 +0200429 return random.choice(clist).IP()
Keesjan Karsten8539f082018-01-04 17:03:31 +0100430
Carmelo Casconef11513d2018-01-16 00:31:14 -0800431 def killBmv2(self, log=False):
Carmelo Cascone499f3202019-02-08 22:54:33 -0800432 self.stopped = True
Carmelo Casconef11513d2018-01-16 00:31:14 -0800433 if self.bmv2popen is not None:
Carmelo Cascone499f3202019-02-08 22:54:33 -0800434 self.bmv2popen.terminate()
435 self.bmv2popen.wait()
436 self.bmv2popen = None
Carmelo Casconef11513d2018-01-16 00:31:14 -0800437 if self.logfd is not None:
438 if log:
439 self.logfd.write("*** PROCESS TERMINATED BY MININET ***\n")
440 self.logfd.close()
Carmelo Cascone499f3202019-02-08 22:54:33 -0800441 self.logfd = None
Keesjan Karsten8539f082018-01-04 17:03:31 +0100442
Carmelo Casconef11513d2018-01-16 00:31:14 -0800443 def cleanupTmpFiles(self):
Carmelo Cascone499f3202019-02-08 22:54:33 -0800444 self.cmd("rm -rf /tmp/bmv2-%s-*" % self.name)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400445
446 def stop(self, deleteIntfs=True):
447 """Terminate switch."""
Carmelo Casconef11513d2018-01-16 00:31:14 -0800448 self.killBmv2(log=True)
Carmelo Casconeb7524272017-06-05 16:53:13 -0400449 Switch.stop(self, deleteIntfs)
Carmelo Cascone785fada2016-06-16 18:34:16 -0700450
451
Carmelo Cascone499f3202019-02-08 22:54:33 -0800452class ONOSStratumSwitch(ONOSBmv2Switch):
453 def __init__(self, name, **kwargs):
454 kwargs["stratum"] = True
455 super(ONOSStratumSwitch, self).__init__(name, **kwargs)
456
457
Carmelo Casconeb7524272017-06-05 16:53:13 -0400458# Exports for bin/mn
Carmelo Cascone499f3202019-02-08 22:54:33 -0800459switches = {
460 'onosbmv2': ONOSBmv2Switch,
461 'stratum': ONOSStratumSwitch,
462}
Carmelo Cascone34433252017-08-25 20:27:18 +0200463hosts = {'onoshost': ONOSHost}