Update bmv2.py to run stratum_bmv2 plus various improvements

Also added alias to quickly run mininet with stratum in cell machines
and p4vm

Change-Id: Id10bf8f3de4fe14d77b5efe47b6129a8a28b5a89
This commit is contained in:
Carmelo Cascone 2019-02-08 22:54:33 -08:00
parent 79705aab23
commit 499f320249
2 changed files with 127 additions and 37 deletions

View File

@ -343,4 +343,5 @@ alias atttopo='onos-netcfg $OCI $ONOS_ROOT/tools/test/topos/attmpls-cfg.json'
alias uktopo='onos-netcfg $OCI $ONOS_ROOT/tools/test/topos/uk-cfg.json' alias uktopo='onos-netcfg $OCI $ONOS_ROOT/tools/test/topos/uk-cfg.json'
# Mininet command that uses BMv2 instead of OVS # Mininet command that uses BMv2 instead of OVS
alias mn-p4='sudo -E mn --custom $BMV2_MN_PY --switch onosbmv2 --controller remote,ip=$OC1' alias mn-bmv2='sudo -E mn --custom $BMV2_MN_PY --switch onosbmv2 --controller remote,ip=$OC1'
alias mn-stratum='sudo -E mn --custom $BMV2_MN_PY --switch stratum --controller remote,ip=$OC1'

View File

@ -1,3 +1,19 @@
# coding=utf-8
"""
Copyright 2019-present Open Networking Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import multiprocessing import multiprocessing
import os import os
@ -5,11 +21,12 @@ import json
import random import random
import re import re
import socket import socket
import sys
import threading import threading
import time import time
import urllib2 import urllib2
from contextlib import closing from contextlib import closing
from mininet.log import info, warn from mininet.log import info, warn, debug
from mininet.node import Switch, Host from mininet.node import Switch, Host
SIMPLE_SWITCH_GRPC = 'simple_switch_grpc' SIMPLE_SWITCH_GRPC = 'simple_switch_grpc'
@ -19,6 +36,17 @@ SWITCH_START_TIMEOUT = 5 # seconds
BMV2_LOG_LINES = 5 BMV2_LOG_LINES = 5
BMV2_DEFAULT_DEVICE_ID = 1 BMV2_DEFAULT_DEVICE_ID = 1
# Stratum paths relative to stratum repo root
STRATUM_BMV2 = 'stratum_bmv2'
STRATUM_BINARY = '/bazel-bin/stratum/hal/bin/bmv2/' + STRATUM_BMV2
STRATUM_INIT_PIPELINE = '/stratum/hal/bin/bmv2/dummy.json'
def getStratumRoot():
if 'STRATUM_ROOT' not in os.environ:
raise Exception("Env variable STRATUM_ROOT not set")
return os.environ['STRATUM_ROOT']
def parseBoolean(value): def parseBoolean(value):
if value in ['1', 1, 'true', 'True']: if value in ['1', 1, 'true', 'True']:
@ -41,20 +69,27 @@ def writeToFile(path, value):
def watchDog(sw): def watchDog(sw):
while True: try:
if ONOSBmv2Switch.mininet_exception == 1: writeToFile(sw.keepaliveFile,
sw.killBmv2(log=False) "Remove this file to terminate %s" % sw.name)
return while True:
if sw.stopped: if ONOSBmv2Switch.mininet_exception == 1 \
return or not os.path.isfile(sw.keepaliveFile):
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: sw.killBmv2(log=False)
if s.connect_ex(('127.0.0.1', sw.grpcPort)) == 0:
time.sleep(1)
else:
warn("\n*** WARN: BMv2 instance %s died!\n" % sw.name)
sw.printBmv2Log()
print ("-" * 80) + "\n"
return return
if sw.stopped:
return
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
if s.connect_ex(('127.0.0.1', sw.grpcPort)) == 0:
time.sleep(1)
else:
warn("\n*** WARN: switch %s died ☠️ \n" % sw.name)
sw.printBmv2Log()
print ("-" * 80) + "\n"
return
except Exception as e:
warn("*** ERROR: " + e.message)
sw.killBmv2(log=True)
class ONOSHost(Host): class ONOSHost(Host):
@ -86,12 +121,13 @@ class ONOSBmv2Switch(Switch):
elogger=False, grpcport=None, cpuport=255, notifications=False, elogger=False, grpcport=None, cpuport=255, notifications=False,
thriftport=None, netcfg=True, dryrun=False, pipeconf="", thriftport=None, netcfg=True, dryrun=False, pipeconf="",
pktdump=False, valgrind=False, gnmi=False, pktdump=False, valgrind=False, gnmi=False,
portcfg=True, onosdevid=None, **kwargs): portcfg=True, onosdevid=None, stratum=False, **kwargs):
Switch.__init__(self, name, **kwargs) Switch.__init__(self, name, **kwargs)
self.grpcPort = grpcport self.grpcPort = grpcport
self.thriftPort = thriftport self.thriftPort = thriftport
self.cpuPort = cpuport self.cpuPort = cpuport
self.json = json self.json = json
self.useStratum = parseBoolean(stratum)
self.debugger = parseBoolean(debugger) self.debugger = parseBoolean(debugger)
self.notifications = parseBoolean(notifications) self.notifications = parseBoolean(notifications)
self.loglevel = loglevel self.loglevel = loglevel
@ -116,7 +152,11 @@ class ONOSBmv2Switch(Switch):
self.onosDeviceId = "device:bmv2:%s" % self.name self.onosDeviceId = "device:bmv2:%s" % self.name
self.logfd = None self.logfd = None
self.bmv2popen = None self.bmv2popen = None
self.stopped = False self.stopped = True
# In case of exceptions, mininet removes *.out files from /tmp. We use
# this as a signal to terminate the switch instance (if active).
self.keepaliveFile = '/tmp/bmv2-%s-watchdog.out' % self.name
self.targetName = STRATUM_BMV2 if self.useStratum else SIMPLE_SWITCH_GRPC
# Remove files from previous executions # Remove files from previous executions
self.cleanupTmpFiles() self.cleanupTmpFiles()
@ -226,29 +266,43 @@ class ONOSBmv2Switch(Switch):
warn("*** WARN: unable to push config to ONOS (%s)\n" % e.reason) warn("*** WARN: unable to push config to ONOS (%s)\n" % e.reason)
def start(self, controllers): def start(self, controllers):
bmv2Args = [SIMPLE_SWITCH_GRPC] + self.grpcTargetArgs()
if self.valgrind:
bmv2Args = VALGRIND_PREFIX.split() + bmv2Args
cmdString = " ".join(bmv2Args) if not self.stopped:
warn("*** %s is already running!\n" % self.name)
return
# Remove files from previous executions (if we are restarting)
self.cleanupTmpFiles()
if self.grpcPort is None:
self.grpcPort = pickUnusedPort()
writeToFile("/tmp/bmv2-%s-grpc-port" % self.name, self.grpcPort)
if self.useStratum:
config_dir = "/tmp/bmv2-%s-stratum" % self.name
os.mkdir(config_dir)
cmdString = self.getStratumCmdString(config_dir)
else:
if self.thriftPort is None:
self.thriftPort = pickUnusedPort()
writeToFile("/tmp/bmv2-%s-thrift-port" % self.name, self.thriftPort)
cmdString = self.getBmv2CmdString()
if self.dryrun: if self.dryrun:
info("\n*** DRY RUN (not executing bmv2)") info("\n*** DRY RUN (not executing %s)\n" % self.targetName)
info("\nStarting BMv2 target: %s\n" % cmdString) debug("\n%s\n" % cmdString)
writeToFile("/tmp/bmv2-%s-grpc-port" % self.name, self.grpcPort)
writeToFile("/tmp/bmv2-%s-thrift-port" % self.name, self.thriftPort)
try: try:
if not self.dryrun: if not self.dryrun:
# Start the switch # Start the switch
self.stopped = False
self.logfd = open(self.logfile, "w") self.logfd = open(self.logfile, "w")
self.bmv2popen = self.popen(cmdString, self.bmv2popen = self.popen(cmdString,
stdout=self.logfd, stdout=self.logfd,
stderr=self.logfd) stderr=self.logfd)
self.waitBmv2Start() self.waitBmv2Start()
# We want to be notified if BMv2 dies... # We want to be notified if BMv2/Stratum dies...
threading.Thread(target=watchDog, args=[self]).start() threading.Thread(target=watchDog, args=[self]).start()
self.doOnosNetcfg(self.controllerIp(controllers)) self.doOnosNetcfg(self.controllerIp(controllers))
@ -258,11 +312,29 @@ class ONOSBmv2Switch(Switch):
self.printBmv2Log() self.printBmv2Log()
raise raise
def grpcTargetArgs(self): def getBmv2CmdString(self):
if self.grpcPort is None: bmv2Args = [SIMPLE_SWITCH_GRPC] + self.bmv2Args()
self.grpcPort = pickUnusedPort() if self.valgrind:
if self.thriftPort is None: bmv2Args = VALGRIND_PREFIX.split() + bmv2Args
self.thriftPort = pickUnusedPort() return " ".join(bmv2Args)
def getStratumCmdString(self, config_dir):
stratumRoot = getStratumRoot()
args = [
stratumRoot + STRATUM_BINARY,
'-device_id=%d' % BMV2_DEFAULT_DEVICE_ID,
'-forwarding_pipeline_configs_file=%s/config.txt' % config_dir,
'-persistent_config_dir=' + config_dir,
'-initial_pipeline=' + stratumRoot + STRATUM_INIT_PIPELINE,
'-cpu_port=%s' % self.cpuPort,
'-external_hercules_urls=0.0.0.0:%d' % self.grpcPort
]
for port, intf in self.intfs.items():
if not intf.IP():
args.append('%d@%s' % (port, intf.name))
return " ".join(args)
def bmv2Args(self):
args = ['--device-id %s' % str(BMV2_DEFAULT_DEVICE_ID)] args = ['--device-id %s' % str(BMV2_DEFAULT_DEVICE_ID)]
for port, intf in self.intfs.items(): for port, intf in self.intfs.items():
if not intf.IP(): if not intf.IP():
@ -299,12 +371,17 @@ class ONOSBmv2Switch(Switch):
while True: while True:
result = sock.connect_ex(('127.0.0.1', self.grpcPort)) result = sock.connect_ex(('127.0.0.1', self.grpcPort))
if result == 0: if result == 0:
# No new line
sys.stdout.write("⚡️ %s @ %d" % (self.targetName, self.bmv2popen.pid))
sys.stdout.flush()
# The port is open. Let's go! (Close socket first) # The port is open. Let's go! (Close socket first)
sock.close() sock.close()
break break
# Port is not open yet. If there is time, we wait a bit. # Port is not open yet. If there is time, we wait a bit.
if endtime > time.time(): if endtime > time.time():
time.sleep(0.1) sys.stdout.write('.')
sys.stdout.flush()
time.sleep(0.05)
else: else:
# Time's up. # Time's up.
raise Exception("Switch did not start before timeout") raise Exception("Switch did not start before timeout")
@ -331,23 +408,35 @@ class ONOSBmv2Switch(Switch):
return random.choice(clist).IP() return random.choice(clist).IP()
def killBmv2(self, log=False): def killBmv2(self, log=False):
self.stopped = True
if self.bmv2popen is not None: if self.bmv2popen is not None:
self.bmv2popen.kill() self.bmv2popen.terminate()
self.bmv2popen.wait()
self.bmv2popen = None
if self.logfd is not None: if self.logfd is not None:
if log: if log:
self.logfd.write("*** PROCESS TERMINATED BY MININET ***\n") self.logfd.write("*** PROCESS TERMINATED BY MININET ***\n")
self.logfd.close() self.logfd.close()
self.logfd = None
def cleanupTmpFiles(self): def cleanupTmpFiles(self):
self.cmd("rm -f /tmp/bmv2-%s-*" % self.name) self.cmd("rm -rf /tmp/bmv2-%s-*" % self.name)
def stop(self, deleteIntfs=True): def stop(self, deleteIntfs=True):
"""Terminate switch.""" """Terminate switch."""
self.stopped = True
self.killBmv2(log=True) self.killBmv2(log=True)
Switch.stop(self, deleteIntfs) Switch.stop(self, deleteIntfs)
class ONOSStratumSwitch(ONOSBmv2Switch):
def __init__(self, name, **kwargs):
kwargs["stratum"] = True
super(ONOSStratumSwitch, self).__init__(name, **kwargs)
# Exports for bin/mn # Exports for bin/mn
switches = {'onosbmv2': ONOSBmv2Switch} switches = {
'onosbmv2': ONOSBmv2Switch,
'stratum': ONOSStratumSwitch,
}
hosts = {'onoshost': ONOSHost} hosts = {'onoshost': ONOSHost}