mirror of
https://github.com/opennetworkinglab/onos.git
synced 2025-10-15 09:21:06 +02:00
209 lines
6.8 KiB
Python
209 lines
6.8 KiB
Python
import os
|
|
import socket
|
|
import re
|
|
import json
|
|
import urllib2
|
|
|
|
from mininet.log import info, warn, error
|
|
from mininet.node import Switch
|
|
|
|
if 'ONOS_ROOT' not in os.environ:
|
|
error("ERROR: environment var $ONOS_ROOT not set")
|
|
exit()
|
|
|
|
BMV2_TARGET = 'simple_switch_grpc'
|
|
ONOS_ROOT = os.environ["ONOS_ROOT"]
|
|
CPU_PORT = 255
|
|
|
|
|
|
class ONOSBmv2Switch(Switch):
|
|
"""BMv2 software switch with gRPC server"""
|
|
|
|
deviceId = 0
|
|
instanceCount = 0
|
|
|
|
def __init__(self, name, json=None, debugger=False, loglevel="warn", elogger=False,
|
|
persistent=False, grpcPort=None, thriftPort=None, netcfg=True,
|
|
pipeconfId="", **kwargs):
|
|
Switch.__init__(self, name, **kwargs)
|
|
self.grpcPort = ONOSBmv2Switch.pickUnusedPort() if not grpcPort else grpcPort
|
|
self.thriftPort = ONOSBmv2Switch.pickUnusedPort() if not thriftPort else thriftPort
|
|
if self.dpid:
|
|
self.deviceId = int(self.dpid, 0 if 'x' in self.dpid else 16)
|
|
else:
|
|
self.deviceId = ONOSBmv2Switch.deviceId
|
|
ONOSBmv2Switch.deviceId += 1
|
|
self.json = json
|
|
self.debugger = debugger
|
|
self.loglevel = loglevel
|
|
self.logfile = '/tmp/bmv2-%d.log' % self.deviceId
|
|
self.elogger = elogger
|
|
self.persistent = persistent
|
|
self.netcfg = netcfg
|
|
self.netcfgfile = '/tmp/bmv2-%d-netcfg.json' % self.deviceId
|
|
self.pipeconfId = pipeconfId
|
|
if persistent:
|
|
self.exectoken = "/tmp/bmv2-%d-exec-token" % self.deviceId
|
|
self.cmd("touch %s" % self.exectoken)
|
|
# Store thrift port for future uses.
|
|
self.cmd("echo %d > /tmp/bmv2-%d-grpc-port" % (self.grpcPort, self.deviceId))
|
|
|
|
if 'longitude' in kwargs:
|
|
self.longitude = kwargs['longitude']
|
|
else:
|
|
self.longitude = None
|
|
|
|
if 'latitude' in kwargs:
|
|
self.latitude = kwargs['latitude']
|
|
else:
|
|
self.latitude = None
|
|
|
|
self.onosDeviceId = "device:bmv2:%d" % self.deviceId
|
|
|
|
@classmethod
|
|
def pickUnusedPort(cls):
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
s.bind(('localhost', 0))
|
|
addr, port = s.getsockname()
|
|
s.close()
|
|
return port
|
|
|
|
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):
|
|
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
|
|
|
|
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": self.deviceId,
|
|
"deviceKeyId": "p4runtime:%s" % self.onosDeviceId
|
|
}
|
|
},
|
|
"piPipeconf": {
|
|
"piPipeconfId": self.pipeconfId
|
|
},
|
|
"basic": basicCfg,
|
|
"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 device IP address, won't do onos-netcfg")
|
|
return
|
|
|
|
cfgData = {
|
|
"devices": {
|
|
self.onosDeviceId: self.getDeviceConfig(srcIP)
|
|
}
|
|
}
|
|
with open(self.netcfgfile, 'w') as fp:
|
|
json.dump(cfgData, fp, indent=4)
|
|
# 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)" % e.reason)
|
|
|
|
def start(self, controllers):
|
|
args = [BMV2_TARGET, '--device-id %s' % str(self.deviceId)]
|
|
for port, intf in self.intfs.items():
|
|
if not intf.IP():
|
|
args.append('-i %d@%s' % (port, intf.name))
|
|
if self.elogger:
|
|
nanomsg = 'ipc:///tmp/bmv2-%d-log.ipc' % self.deviceId
|
|
args.append('--nanolog %s' % nanomsg)
|
|
if self.debugger:
|
|
args.append('--debugger')
|
|
args.append('--log-console')
|
|
args.append('-L%s' % self.loglevel)
|
|
args.append('--thrift-port %d' % self.thriftPort)
|
|
if not self.json:
|
|
args.append('--no-p4')
|
|
else:
|
|
args.append(self.json)
|
|
|
|
# gRPC target-specific options.
|
|
args.append('--')
|
|
args.append('--cpu-port %d' % CPU_PORT)
|
|
args.append('--grpc-server-addr 0.0.0.0:%d' % self.grpcPort)
|
|
|
|
bmv2cmd = " ".join(args)
|
|
info("\nStarting BMv2 target: %s\n" % bmv2cmd)
|
|
|
|
if self.persistent:
|
|
# Bash loop to re-exec the switch if it crashes.
|
|
bmv2cmd = "(while [ -e {} ]; do {} ; sleep 1; done;)".format(self.exectoken, bmv2cmd)
|
|
|
|
cmdStr = "{} > {} 2>&1 &".format(bmv2cmd, self.logfile)
|
|
|
|
# Starts the switch.
|
|
out = self.cmd(cmdStr)
|
|
if out:
|
|
print out
|
|
|
|
if self.netcfg:
|
|
try: # onos.py
|
|
clist = controllers[0].nodes()
|
|
except AttributeError:
|
|
clist = controllers
|
|
assert len(clist) > 0
|
|
cip = clist[0].IP()
|
|
self.doOnosNetcfg(cip)
|
|
|
|
def stop(self, deleteIntfs=True):
|
|
"""Terminate switch."""
|
|
self.cmd("rm -f /tmp/bmv2-%d-*" % self.deviceId)
|
|
self.cmd("rm -f /tmp/bmv2-%d.log*" % self.deviceId)
|
|
self.cmd('kill %' + BMV2_TARGET)
|
|
Switch.stop(self, deleteIntfs)
|
|
|
|
|
|
# Exports for bin/mn
|
|
switches = {'onosbmv2': ONOSBmv2Switch}
|