[ONOS-7676] Enable support for fabric.p4 in SegmentRouting ONOS System Tests
Change-Id: I3dde8fe5d90d2c595021ff05ab45ccb37bbfbef6
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py b/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py
index f7b9d2e..313d9b6 100644
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py
@@ -59,7 +59,12 @@
main.useCommonTopo = main.params[ 'DEPENDENCY' ][ 'useCommonTopo' ] == 'True'
main.topoPath = main.path + ( "/.." if main.useCommonTopo else "" ) + "/dependencies/"
main.useCommonConf = main.params[ 'DEPENDENCY' ][ 'useCommonConf' ] == 'True'
+ if main.params[ 'DEPENDENCY' ].get( 'useBmv2' ):
+ main.useBmv2 = main.params[ 'DEPENDENCY' ][ 'useBmv2' ] == 'True'
+ else:
+ main.useBmv2 = False
main.configPath = main.path + ( "/.." if main.useCommonConf else "" ) + "/dependencies/"
+ main.bmv2Path = main.path + "/../dependencies/"
main.forJson = "json/"
main.forChart = "chart/"
main.forConfig = "conf/"
@@ -70,6 +75,7 @@
main.topology = main.params[ 'DEPENDENCY' ][ 'topology' ]
main.topologyLib = main.params[ 'DEPENDENCY' ][ 'lib' ] if 'lib' in main.params[ 'DEPENDENCY' ] else None
main.topologyConf = main.params[ 'DEPENDENCY' ][ 'conf' ] if 'conf' in main.params[ 'DEPENDENCY' ] else None
+ main.bmv2 = "bmv2.py"
main.scale = ( main.params[ 'SCALE' ][ 'size' ] ).split( "," )
main.maxNodes = int( main.params[ 'SCALE' ][ 'max' ] )
@@ -200,6 +206,10 @@
main.configPath + main.forConfig + conf,
"~/",
direction="to" )
+ copyResult = copyResult and main.ONOSbench.scp( main.Mininet1,
+ main.bmv2Path + main.bmv2,
+ main.Mininet1.home + "custom",
+ direction="to" )
stepResult = copyResult
utilities.assert_equals( expect=main.TRUE,
actual=stepResult,
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/bmv2.py b/TestON/tests/USECASE/SegmentRouting/dependencies/bmv2.py
new file mode 100644
index 0000000..eafb482
--- /dev/null
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/bmv2.py
@@ -0,0 +1,353 @@
+import json
+import multiprocessing
+import os
+import random
+import re
+import socket
+import threading
+import urllib2
+from contextlib import closing
+
+import time
+from mininet.log import info, warn
+from mininet.node import Switch, Host
+
+SIMPLE_SWITCH_GRPC = 'simple_switch_grpc'
+PKT_BYTES_TO_DUMP = 80
+VALGRIND_PREFIX = 'valgrind --leak-check=yes'
+SWITCH_START_TIMEOUT = 5 # seconds
+BMV2_LOG_LINES = 5
+BMV2_DEFAULT_DEVICE_ID = 1
+
+
+def parseBoolean(value):
+ if value in ['1', 1, 'true', 'True']:
+ return True
+ else:
+ return False
+
+
+def pickUnusedPort():
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.bind(('localhost', 0))
+ addr, port = s.getsockname()
+ s.close()
+ return port
+
+
+def writeToFile(path, value):
+ with open(path, "w") as f:
+ f.write(str(value))
+
+
+def watchDog(sw):
+ while True:
+ if ONOSBmv2Switch.mininet_exception == 1:
+ sw.killBmv2(log=False)
+ 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: BMv2 instance %s died!\n" % sw.name)
+ sw.printBmv2Log()
+ print ("-" * 80) + "\n"
+ return
+
+
+class ONOSHost(Host):
+ def __init__(self, name, inNamespace=True, **params):
+ Host.__init__(self, name, inNamespace=inNamespace, **params)
+
+ def config(self, **params):
+ r = super(Host, self).config(**params)
+ for off in ["rx", "tx", "sg"]:
+ cmd = "/sbin/ethtool --offload %s %s off" \
+ % (self.defaultIntf(), off)
+ self.cmd(cmd)
+ # disable IPv6
+ self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
+ self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1")
+ self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1")
+ return r
+
+
+class ONOSBmv2Switch(Switch):
+ """BMv2 software switch with gRPC server"""
+ # Shared value used to notify to all instances of this class that a Mininet
+ # exception occurred. Mininet exception handling doesn't call the stop()
+ # method, so the mn process would hang after clean-up since Bmv2 would still
+ # be running.
+ mininet_exception = multiprocessing.Value('i', 0)
+
+ def __init__(self, name, json=None, debugger=False, loglevel="warn",
+ elogger=False, grpcport=None, cpuport=255, notifications=False,
+ thriftport=None, netcfg=True, dryrun=False, pipeconf="",
+ pktdump=False, valgrind=False, gnmi=False,
+ portcfg=True, onosdevid=None, **kwargs):
+ Switch.__init__(self, name, **kwargs)
+ self.grpcPort = grpcport
+ self.thriftPort = thriftport
+ self.cpuPort = cpuport
+ self.json = json
+ self.debugger = parseBoolean(debugger)
+ self.notifications = parseBoolean(notifications)
+ self.loglevel = loglevel
+ # Important: Mininet removes all /tmp/*.log files in case of exceptions.
+ # We want to be able to see the bmv2 log if anything goes wrong, hence
+ # avoid the .log extension.
+ self.logfile = '/tmp/bmv2-%s-log' % self.name
+ self.elogger = parseBoolean(elogger)
+ self.pktdump = parseBoolean(pktdump)
+ self.netcfg = parseBoolean(netcfg)
+ self.dryrun = parseBoolean(dryrun)
+ self.valgrind = parseBoolean(valgrind)
+ self.netcfgfile = '/tmp/bmv2-%s-netcfg.json' % self.name
+ self.pipeconfId = pipeconf
+ self.injectPorts = parseBoolean(portcfg)
+ self.withGnmi = parseBoolean(gnmi)
+ self.longitude = kwargs['longitude'] if 'longitude' in kwargs else None
+ self.latitude = kwargs['latitude'] if 'latitude' in kwargs else None
+ if onosdevid is not None and len(onosdevid) > 0:
+ self.onosDeviceId = onosdevid
+ else:
+ self.onosDeviceId = "device:bmv2:%s" % self.name
+ self.logfd = None
+ self.bmv2popen = None
+ self.stopped = False
+
+ # Remove files from previous executions
+ self.cleanupTmpFiles()
+
+ def getSourceIp(self, dstIP):
+ """
+ Queries the Linux routing table to get the source IP that can talk with
+ dstIP, and vice versa.
+ """
+ ipRouteOut = self.cmd('ip route get %s' % dstIP)
+ r = re.search(r"src (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", ipRouteOut)
+ return r.group(1) if r else None
+
+ def getDeviceConfig(self, srcIP):
+
+ basicCfg = {
+ "driver": "bmv2"
+ }
+
+ if self.longitude and self.latitude:
+ basicCfg["longitude"] = self.longitude
+ basicCfg["latitude"] = self.latitude
+
+ cfgData = {
+ "generalprovider": {
+ "p4runtime": {
+ "ip": srcIP,
+ "port": self.grpcPort,
+ "deviceId": BMV2_DEFAULT_DEVICE_ID,
+ "deviceKeyId": "p4runtime:%s" % self.onosDeviceId
+ },
+ "bmv2-thrift": {
+ "ip": srcIP,
+ "port": self.thriftPort
+ }
+ },
+ "piPipeconf": {
+ "piPipeconfId": self.pipeconfId
+ },
+ "basic": basicCfg
+ }
+
+ if self.withGnmi:
+ cfgData["generalprovider"]["gnmi"] = {
+ "ip": srcIP,
+ "port": self.grpcPort
+ }
+
+ if self.injectPorts:
+ portData = {}
+ portId = 1
+ for intfName in self.intfNames():
+ if intfName == 'lo':
+ continue
+ portData[str(portId)] = {
+ "number": portId,
+ "name": intfName,
+ "enabled": True,
+ "removed": False,
+ "type": "copper",
+ "speed": 10000
+ }
+ portId += 1
+
+ cfgData['ports'] = portData
+
+ return cfgData
+
+ def doOnosNetcfg(self, controllerIP):
+ """
+ Notifies ONOS about the new device via Netcfg.
+ """
+ srcIP = self.getSourceIp(controllerIP)
+ if not srcIP:
+ warn("*** WARN: unable to get switch IP address, won't do netcfg\n")
+ return
+
+ cfgData = {
+ "devices": {
+ self.onosDeviceId: self.getDeviceConfig(srcIP)
+ }
+ }
+ with open(self.netcfgfile, 'w') as fp:
+ json.dump(cfgData, fp, indent=4)
+
+ if not self.netcfg:
+ # Do not push config to ONOS.
+ return
+
+ # Build netcfg URL
+ url = 'http://%s:8181/onos/v1/network/configuration/' % controllerIP
+ # Instantiate password manager for HTTP auth
+ pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
+ pm.add_password(None, url,
+ os.environ['ONOS_WEB_USER'],
+ os.environ['ONOS_WEB_PASS'])
+ urllib2.install_opener(urllib2.build_opener(
+ urllib2.HTTPBasicAuthHandler(pm)))
+ # Push config data to controller
+ req = urllib2.Request(url, json.dumps(cfgData),
+ {'Content-Type': 'application/json'})
+ try:
+ f = urllib2.urlopen(req)
+ print f.read()
+ f.close()
+ except urllib2.URLError as e:
+ warn("*** WARN: unable to push config to ONOS (%s)\n" % e.reason)
+
+ def start(self, controllers):
+ bmv2Args = [SIMPLE_SWITCH_GRPC] + self.grpcTargetArgs()
+ if self.valgrind:
+ bmv2Args = VALGRIND_PREFIX.split() + bmv2Args
+
+ cmdString = " ".join(bmv2Args)
+
+ if self.dryrun:
+ info("\n*** DRY RUN (not executing bmv2)")
+
+ info("\nStarting BMv2 target: %s\n" % cmdString)
+
+ writeToFile("/tmp/bmv2-%s-grpc-port" % self.name, self.grpcPort)
+ writeToFile("/tmp/bmv2-%s-thrift-port" % self.name, self.thriftPort)
+
+ try:
+ if not self.dryrun:
+ # Start the switch
+ self.logfd = open(self.logfile, "w")
+ self.bmv2popen = self.popen(cmdString,
+ stdout=self.logfd,
+ stderr=self.logfd)
+ self.waitBmv2Start()
+ # We want to be notified if BMv2 dies...
+ threading.Thread(target=watchDog, args=[self]).start()
+
+ self.doOnosNetcfg(self.controllerIp(controllers))
+ except Exception:
+ ONOSBmv2Switch.mininet_exception = 1
+ self.killBmv2()
+ self.printBmv2Log()
+ raise
+
+ def grpcTargetArgs(self):
+ if self.grpcPort is None:
+ self.grpcPort = pickUnusedPort()
+ if self.thriftPort is None:
+ self.thriftPort = pickUnusedPort()
+ args = ['--device-id %s' % str(BMV2_DEFAULT_DEVICE_ID)]
+ for port, intf in self.intfs.items():
+ if not intf.IP():
+ args.append('-i %d@%s' % (port, intf.name))
+ args.append('--thrift-port %s' % self.thriftPort)
+ if self.notifications:
+ ntfaddr = 'ipc:///tmp/bmv2-%s-notifications.ipc' % self.name
+ args.append('--notifications-addr %s' % ntfaddr)
+ if self.elogger:
+ nanologaddr = 'ipc:///tmp/bmv2-%s-nanolog.ipc' % self.name
+ args.append('--nanolog %s' % nanologaddr)
+ if self.debugger:
+ dbgaddr = 'ipc:///tmp/bmv2-%s-debug.ipc' % self.name
+ args.append('--debugger-addr %s' % dbgaddr)
+ args.append('--log-console')
+ if self.pktdump:
+ args.append('--pcap --dump-packet-data %s' % PKT_BYTES_TO_DUMP)
+ args.append('-L%s' % self.loglevel)
+ if not self.json:
+ args.append('--no-p4')
+ else:
+ args.append(self.json)
+ # gRPC target-specific options
+ args.append('--')
+ args.append('--cpu-port %s' % self.cpuPort)
+ args.append('--grpc-server-addr 0.0.0.0:%s' % self.grpcPort)
+ return args
+
+ def waitBmv2Start(self):
+ # Wait for switch to open gRPC port, before sending ONOS the netcfg.
+ # Include time-out just in case something hangs.
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ endtime = time.time() + SWITCH_START_TIMEOUT
+ while True:
+ result = sock.connect_ex(('127.0.0.1', self.grpcPort))
+ if result == 0:
+ # The port is open. Let's go! (Close socket first)
+ sock.close()
+ break
+ # Port is not open yet. If there is time, we wait a bit.
+ if endtime > time.time():
+ time.sleep(0.1)
+ else:
+ # Time's up.
+ raise Exception("Switch did not start before timeout")
+
+ def printBmv2Log(self):
+ if os.path.isfile(self.logfile):
+ print "-" * 80
+ print "%s log (from %s):" % (self.name, self.logfile)
+ with open(self.logfile, 'r') as f:
+ lines = f.readlines()
+ if len(lines) > BMV2_LOG_LINES:
+ print "..."
+ for line in lines[-BMV2_LOG_LINES:]:
+ print line.rstrip()
+
+ @staticmethod
+ def controllerIp(controllers):
+ try:
+ # onos.py
+ clist = controllers[0].nodes()
+ except AttributeError:
+ clist = controllers
+ assert len(clist) > 0
+ return random.choice(clist).IP()
+
+ def killBmv2(self, log=False):
+ if self.bmv2popen is not None:
+ self.bmv2popen.kill()
+ if self.logfd is not None:
+ if log:
+ self.logfd.write("*** PROCESS TERMINATED BY MININET ***\n")
+ self.logfd.close()
+
+ def cleanupTmpFiles(self):
+ self.cmd("rm -f /tmp/bmv2-%s-*" % self.name)
+
+ def stop(self, deleteIntfs=True):
+ """Terminate switch."""
+ self.stopped = True
+ self.killBmv2(log=True)
+ Switch.stop(self, deleteIntfs)
+
+
+# Exports for bin/mn
+switches = {'onosbmv2': ONOSBmv2Switch}
+hosts = {'onoshost': ONOSHost}
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/cfgtranslator.py b/TestON/tests/USECASE/SegmentRouting/dependencies/cfgtranslator.py
new file mode 100644
index 0000000..f38603e
--- /dev/null
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/cfgtranslator.py
@@ -0,0 +1,156 @@
+"""
+Copyright 2018 Open Networking Foundation ( ONF )
+
+Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>,
+the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>,
+or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg>
+
+ TestON is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ ( at your option ) any later version.
+
+ TestON is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with TestON. If not, see <http://www.gnu.org/licenses/>.
+"""
+import json
+import re
+
+ONOS_GROUP_ID = 'org.onosproject'
+SR_APP = 'segmentrouting'
+DHCP_APP = 'dhcprelay'
+DHCP_APP_ID = ONOS_GROUP_ID + '.' + DHCP_APP
+
+# Translate configuration JSON file from BMv2 driver to OFDPA-OVS driver.
+def bmv2ToOfdpa( main, cfgFile="" ):
+ if not cfgFile:
+ cfgFile = "%s%s.json" % ( main.configPath + main.forJson,
+ main.cfgName )
+ with open( cfgFile ) as cfg:
+ netcfg = json.load( cfg )
+
+ if 'ports' in netcfg.keys():
+ for port in netcfg[ 'ports' ].keys():
+ searchObj = re.search( "device:bmv2:leaf([1-9][0-9]*)/([0-9]+)", port )
+ if searchObj:
+ new_port = 'of:' + searchObj.group( 1 ).zfill( 16 ) + '/' + searchObj.group( 2 )
+ netcfg[ 'ports' ][ new_port ] = netcfg[ 'ports' ].pop( port )
+
+ if 'hosts' in netcfg.keys():
+ for ( host, hostCfg ) in netcfg[ 'hosts' ].items():
+ if type( hostCfg[ 'basic' ][ 'locations' ] ) is list:
+ new_locations = []
+ for location in hostCfg[ 'basic' ][ 'locations' ]:
+ searchObj = re.search( "device:bmv2:leaf([1-9][0-9]*)/([0-9]+)", location )
+ if searchObj:
+ new_locations.append( 'of:' + searchObj.group( 1 ).zfill( 16 ) + '/' + searchObj.group( 2 ) )
+ else:
+ new_locations.append( location )
+ netcfg[ 'hosts' ][ host ][ 'basic' ][ 'locations' ] = new_locations
+ else:
+ location = hostCfg[ 'basic' ][ 'locations' ]
+ searchObj = re.search( "device:bmv2:leaf([1-9][0-9]*)/([0-9]+)", location )
+ if searchObj:
+ new_location = 'of:' + searchObj.group( 1 ).zfill( 16 ) + '/' + searchObj.group( 2 )
+ netcfg[ 'hosts' ][ host ][ 'basic' ][ 'locations' ] = new_location
+
+ if 'devices' in netcfg.keys():
+ for device in netcfg[ 'devices' ].keys():
+ searchObj = re.search( "device:bmv2:(leaf|spine)([1-9][0-9]*)", device )
+ new_device = device
+ if searchObj:
+ new_device = 'of:' + searchObj.group( 2 ).zfill( 16 )
+ netcfg[ 'devices' ][ new_device ] = netcfg[ 'devices' ].pop( device )
+ if 'pairDeviceId' in netcfg[ 'devices' ][ new_device ][ SR_APP ].keys():
+ searchObj = re.search( "device:bmv2:leaf([1-9][0-9]*)",
+ netcfg[ 'devices' ][ new_device ][ SR_APP ][ 'pairDeviceId' ])
+ if searchObj:
+ netcfg[ 'devices' ][ new_device ][ SR_APP ][ 'pairDeviceId' ] = 'of:' + \
+ searchObj.group( 1 ).zfill( 16 )
+ if 'basic' in netcfg[ 'devices' ][ new_device ].keys():
+ netcfg[ 'devices' ][ new_device ][ 'basic' ].update( { 'driver': 'ofdpa-ovs' } )
+
+ if 'apps' in netcfg.keys():
+ if DHCP_APP_ID in netcfg[ 'apps' ].keys():
+ for i, dhcpcfg in enumerate( netcfg[ 'apps' ][ DHCP_APP_ID ][ 'default' ] ):
+ if 'dhcpServerConnectPoint' in dhcpcfg.keys():
+ searchObj = re.search( "device:bmv2:leaf([1-9][0-9]*)/([0-9]+)",
+ dhcpcfg[ 'dhcpServerConnectPoint' ] )
+ if searchObj:
+ netcfg[ 'apps' ][ DHCP_APP_ID ][ 'default' ][ i ][ 'dhcpServerConnectPoint' ] = \
+ 'of:' + searchObj.group( 1 ).zfill(16) + '/' + searchObj.group( 2 )
+
+ with open( cfgFile, 'w' ) as cfg:
+ cfg.write( json.dumps( netcfg, indent=4, separators=( ',', ':' ) ) )
+
+# Translate configuration JSON file from OFDPA-OVS driver to BMv2 driver.
+def ofdpaToBmv2( main, cfgFile="" ):
+ if not cfgFile:
+ cfgFile = "%s%s.json" % ( main.configPath + main.forJson,
+ main.cfgName )
+ with open( cfgFile ) as cfg:
+ netcfg = json.load( cfg )
+
+ if 'ports' in netcfg.keys():
+ for port in netcfg[ 'ports' ].keys():
+ searchObj = re.search( "of:0*([1-9][0-9]*)/([0-9]+)", port )
+ if searchObj:
+ new_port = 'device:bmv2:leaf' + searchObj.group( 1 ) + '/' + searchObj.group( 2 )
+ netcfg[ 'ports' ][ new_port ] = netcfg[ 'ports' ].pop( port )
+
+ if 'hosts' in netcfg.keys():
+ for ( host, hostCfg ) in netcfg[ 'hosts' ].items():
+ if type( hostCfg[ 'basic' ][ 'locations' ] ) is list:
+ new_locations = []
+ for location in hostCfg[ 'basic' ][ 'locations' ]:
+ searchObj = re.search( "of:0*([1-9][0-9]*)/([0-9]+)", location )
+ if searchObj:
+ new_locations.append( 'device:bmv2:leaf' + searchObj.group( 1 ) + '/' + searchObj.group( 2 ) )
+ else:
+ new_locations.append( location )
+ netcfg[ 'hosts' ][ host ][ 'basic' ][ 'locations' ] = new_locations
+ else:
+ location = hostCfg[ 'basic' ][ 'locations' ]
+ searchObj = re.search( "of:0*([1-9][0-9]*)/([0-9]+)", location )
+ if searchObj:
+ new_location = 'device:bmv2:leaf' + searchObj.group( 1 ) + '/' + searchObj.group( 2 )
+ netcfg[ 'hosts' ][ host ][ 'basic' ][ 'locations' ] = new_location
+
+ if 'devices' in netcfg.keys():
+ for device in netcfg[ 'devices' ].keys():
+ searchObj = re.search( "of:0*([1-9][0-9]*)", device )
+ new_device = device
+ if searchObj:
+ isLeaf = netcfg[ 'devices' ][ device ][ SR_APP ][ 'isEdgeRouter' ]
+ if isLeaf is True:
+ new_device = 'device:bmv2:leaf' + searchObj.group( 1 )
+ else:
+ new_device = 'device:bmv2:spine' + searchObj.group( 1 )
+ netcfg[ 'devices' ][ new_device ] = netcfg[ 'devices' ].pop( device )
+ if 'pairDeviceId' in netcfg[ 'devices' ][ new_device ][ SR_APP ].keys():
+ searchObj = re.search( "of:0*([1-9][0-9]*)",
+ netcfg[ 'devices' ][ new_device ][ SR_APP ][ 'pairDeviceId' ])
+ if searchObj:
+ netcfg[ 'devices' ][ new_device ][ SR_APP ][ 'pairDeviceId' ] = 'device:bmv2:leaf' + \
+ searchObj.group( 1 )
+ if 'basic' in netcfg[ 'devices' ][ new_device ].keys():
+ if 'driver' in netcfg[ 'devices' ][ new_device ][ 'basic' ].keys():
+ del netcfg[ 'devices' ][ new_device ][ 'basic' ][ 'driver' ]
+
+ if 'apps' in netcfg.keys():
+ if DHCP_APP_ID in netcfg[ 'apps' ].keys():
+ for i, dhcpcfg in enumerate( netcfg[ 'apps' ][ DHCP_APP_ID ][ 'default' ] ):
+ if 'dhcpServerConnectPoint' in dhcpcfg.keys():
+ searchObj = re.search( "of:0*([1-9][0-9]*)/([0-9]+)",
+ dhcpcfg[ 'dhcpServerConnectPoint' ] )
+ if searchObj:
+ netcfg[ 'apps' ][ DHCP_APP_ID ][ 'default' ][ i ][ 'dhcpServerConnectPoint' ] = \
+ 'device:bmv2:leaf' + searchObj.group( 1 ) + '/' + searchObj.group( 2 )
+
+ with open( cfgFile, 'w' ) as cfg:
+ cfg.write( json.dumps( netcfg, indent=4, separators=( ',', ':' ) ) )
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/hagg_fabric.py b/TestON/tests/USECASE/SegmentRouting/dependencies/hagg_fabric.py
index b4bb587..814b6fb 100644
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/hagg_fabric.py
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/hagg_fabric.py
@@ -3,7 +3,7 @@
import re
from optparse import OptionParser
from ipaddress import ip_network
-from mininet.node import RemoteController, OVSBridge, Host
+from mininet.node import RemoteController, OVSBridge, Host, OVSSwitch
from mininet.link import TCLink
from mininet.log import setLogLevel
from mininet.net import Mininet
@@ -14,6 +14,8 @@
from routinglib import BgpRouter, RoutedHost
from trellislib import DhcpServer, TaggedRoutedHost, DualHomedRoutedHost, DualHomedTaggedRoutedHost, DhcpClient, Dhcp6Client, DhcpServer, Dhcp6Server, TrellisHost
+from bmv2 import ONOSBmv2Switch
+
# Parse command line options and dump results
def parseOptions():
"Parse command line options"
@@ -27,13 +29,25 @@
parser.add_option( '--ipv4', dest='ipv4', type='int', default=1,
help='Configure hosts with ipv4 or not' )
parser.add_option( '--onos-ip', dest='onosIp', type='str', default='',
- help='IP address list of ONOS instances, separated by comma(,). Overrides --onos option' )
+ help='IP address list of ONOS instances, separated by comma(,). Overrides --onos option' )
+ parser.add_option( '--switch', dest='switch', type='str', default='ovs',
+ help='Switch type: ovs, bmv2 (with fabric.p4)' )
( options, args ) = parser.parse_args()
return options, args
opts, args = parseOptions()
+FABRIC_PIPECONF = "org.onosproject.pipelines.fabric"
+
+SWITCH_TO_PARAMS_DICT = {
+ "ovs": dict(cls=OVSSwitch),
+ "bmv2": dict(cls=ONOSBmv2Switch, pipeconf=FABRIC_PIPECONF)
+}
+if opts.switch not in SWITCH_TO_PARAMS_DICT:
+ raise Exception("Unknown switch type '%s'" % opts.switch)
+SWITCH_PARAMS = SWITCH_TO_PARAMS_DICT[opts.switch]
+
class ComcastLeafSpineFabric(Topo):
spines = dict()
@@ -237,11 +251,15 @@
# Create spine switches
for s in range(spine):
- self.spines[s] = self.addSwitch('spine10%s' % (s + 1), dpid = "00000000010%s" % (s + 1) )
+ self.spines[s] = self.addSwitch( 'spine10%s' % (s + 1),
+ dpid = "00000000010%s" % (s + 1),
+ **SWITCH_PARAMS )
# Create leaf switches
for ls in range(leaf):
- self.leafs[ls] = self.addSwitch('leaf%s' % (ls + 1), dpid = "00000000000%s" % ( ls + 1) )
+ self.leafs[ls] = self.addSwitch( 'leaf%s' % (ls + 1),
+ dpid = "00000000000%s" % (ls + 1),
+ **SWITCH_PARAMS )
# connecting leaf and spines, leafs 1-5 have double links
for s in range(2):
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/trellis_fabric.py b/TestON/tests/USECASE/SegmentRouting/dependencies/trellis_fabric.py
index 73bfda3..e02a675 100644
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/trellis_fabric.py
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/trellis_fabric.py
@@ -16,6 +16,8 @@
from trellislib import TrellisHost, DhcpRelay
from functools import partial
+from bmv2 import ONOSBmv2Switch
+
# Parse command line options and dump results
def parseOptions():
"Parse command line options"
@@ -43,14 +45,24 @@
help='Use another DHCP server for indirectly connected DHCP clients if True' )
parser.add_option( '--remote-dhcp-server', action="store_true", dest='remoteServer', default=False,
help='Connect DHCP server indirectly (via gateway) if True' )
+ parser.add_option( '--switch', dest='switch', type='str', default='ovs',
+ help='Switch type: ovs, bmv2 (with fabric.p4)' )
( options, args ) = parser.parse_args()
return options, args
-
opts, args = parseOptions()
IP6_SUBNET_CLASS = 120
IP4_SUBNET_CLASS = 24
+FABRIC_PIPECONF = "org.onosproject.pipelines.fabric"
+
+SWITCH_TO_PARAMS_DICT = {
+ "ovs": dict(cls=OVSSwitch),
+ "bmv2": dict(cls=ONOSBmv2Switch, pipeconf=FABRIC_PIPECONF)
+}
+if opts.switch not in SWITCH_TO_PARAMS_DICT:
+ raise Exception("Unknown switch type '%s'" % opts.switch)
+SWITCH_PARAMS = SWITCH_TO_PARAMS_DICT[opts.switch]
# TODO: DHCP support
class IpHost( Host ):
@@ -111,11 +123,15 @@
linkopts = dict( bw=100 )
# Create spine switches
for s in range(spine):
- spines[s] = self.addSwitch('spine10%s' % (s + 1), dpid = "00000000010%s" % (s + 1) )
+ spines[s] = self.addSwitch( 'spine10%s' % (s + 1),
+ dpid="00000000010%s" % (s + 1),
+ **SWITCH_PARAMS )
# Create leaf switches
for ls in range(leaf):
- leafs[ls] = self.addSwitch('leaf%s' % (ls + 1), dpid = "00000000000%s" % ( ls + 1) )
+ leafs[ls] = self.addSwitch( 'leaf%s' % (ls + 1),
+ dpid="00000000000%s" % (ls + 1),
+ **SWITCH_PARAMS )
# Connect leaf to all spines with dual link
for s in range( spine ):
@@ -239,11 +255,15 @@
# Create spine switches
for s in range(spine):
- spines[s] = self.addSwitch('spine10%s' % (s + 1), dpid = "00000000010%s" % (s + 1) )
+ spines[s] = self.addSwitch( 'spine10%s' % (s + 1),
+ dpid="00000000010%s" % (s + 1),
+ **SWITCH_PARAMS )
# Create leaf switches
for ls in range(leaf):
- leafs[ls] = self.addSwitch('leaf%s' % (ls + 1), dpid = "00000000000%s" % ( ls + 1) )
+ leafs[ls] = self.addSwitch( 'leaf%s' % (ls + 1),
+ dpid="00000000000%s" % (ls + 1),
+ **SWITCH_PARAMS )
# Connect leaf to all spines
for s in range( spine ):