Updates for p4runtime example (#71)

* Created p4runtime exercise directory with draft P4 program

* Updating VM

- Adding p4 to vboxsf group for VirtualBox Shared Folders
- Adding gRPC Python package for p4 runtime
- Setting up VM to use 2 CPUs

* Updating .gitignore for PyCharms and Mac OS

* Adding P4RuntimeSwitch type and support in run_exercises

If the grpc switch target is used, we will instantiate a P4RuntimeSwitch.
Ideally, this will get merged with BMv2's P4Switch and can be removed

* Adding p4 runtime and p4info browser libraries

Also, adding a Makefile for this project
This commit is contained in:
Brian O'Connor
2017-11-03 10:52:04 -07:00
committed by Robert Soule
parent c9151e767a
commit aa4298859f
12 changed files with 716 additions and 18 deletions

View File

@@ -0,0 +1,125 @@
# Copyright 2017-present Barefoot Networks, Inc.
# Copyright 2017-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 sys, os, tempfile, socket
from time import sleep
from mininet.node import Switch
from mininet.moduledeps import pathCheck
from mininet.log import info, error, debug
# this path is needed to import p4_mininet.py from the bmv2 repo
sys.path.append('/home/vagrant/behavioral-model/mininet')
from p4_mininet import P4Switch
class P4RuntimeSwitch(P4Switch):
"BMv2 switch with gRPC support"
next_grpc_port = 50051
def __init__(self, name, sw_path = None, json_path = None,
grpc_port = None,
pcap_dump = False,
log_console = False,
verbose = False,
device_id = None,
enable_debugger = False,
**kwargs):
Switch.__init__(self, name, **kwargs)
assert (sw_path)
self.sw_path = sw_path
# make sure that the provided sw_path is valid
pathCheck(sw_path)
if json_path is not None:
# make sure that the provided JSON file exists
if not os.path.isfile(json_path):
error("Invalid JSON file.\n")
exit(1)
self.json_path = json_path
else:
self.json_path = None
if grpc_port is not None:
self.grpc_port = grpc_port
else:
self.grpc_port = P4RuntimeSwitch.next_grpc_port
P4RuntimeSwitch.next_grpc_port += 1
self.verbose = verbose
logfile = "/tmp/p4s.{}.log".format(self.name)
self.output = open(logfile, 'w')
self.pcap_dump = pcap_dump
self.enable_debugger = enable_debugger
self.log_console = log_console
if device_id is not None:
self.device_id = device_id
P4Switch.device_id = max(P4Switch.device_id, device_id)
else:
self.device_id = P4Switch.device_id
P4Switch.device_id += 1
self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id)
def check_switch_started(self, pid):
while True:
if not os.path.exists(os.path.join("/proc", str(pid))):
return False
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(0.5)
result = sock.connect_ex(("localhost", self.grpc_port))
if result == 0:
# TODO It seems like sometimes BMv2 can hang if multiple instances start up too quickly;
# this sleep might also do nothing...
sleep(0.5)
return True
def start(self, controllers):
info("Starting P4 switch {}.\n".format(self.name))
args = [self.sw_path]
for port, intf in self.intfs.items():
if not intf.IP():
args.extend(['-i', str(port) + "@" + intf.name])
if self.pcap_dump:
args.append("--pcap")
if self.nanomsg:
args.extend(['--nanolog', self.nanomsg])
args.extend(['--device-id', str(self.device_id)])
P4Switch.device_id += 1
if self.json_path:
args.append(self.json_path)
else:
args.append("--no-p4")
if self.enable_debugger:
args.append("--debugger")
if self.log_console:
args.append("--log-console")
if self.grpc_port:
args.append("-- --grpc-server-addr 0.0.0.0:" + str(self.grpc_port))
cmd = ' '.join(args)
info(cmd + "\n")
logfile = "/tmp/p4s.{}.log".format(self.name)
pid = None
with tempfile.NamedTemporaryFile() as f:
self.cmd(cmd + ' >' + logfile + ' 2>&1 & echo $! >> ' + f.name)
pid = int(f.read())
debug("P4 switch {} PID is {}.\n".format(self.name, pid))
if not self.check_switch_started(pid):
error("P4 switch {} did not start correctly.\n".format(self.name))
exit(1)
info("P4 switch {} has been started.\n".format(self.name))

View File

@@ -31,24 +31,38 @@ from mininet.topo import Topo
from mininet.link import TCLink
from mininet.cli import CLI
first_thrift_port = 9090
next_thrift_port = first_thrift_port
from p4runtime_switch import P4RuntimeSwitch
def configureP4Switch(**switch_args):
""" Helper class that is called by mininet to initialize
the virtual P4 switches. The purpose is to ensure each
switch's thrift server is using a unique port.
"""
class ConfiguredP4Switch(P4Switch):
def __init__(self, *opts, **kwargs):
global next_thrift_port
kwargs.update(switch_args)
kwargs['thrift_port'] = next_thrift_port
next_thrift_port += 1
P4Switch.__init__(self, *opts, **kwargs)
return ConfiguredP4Switch
if "sw_path" in switch_args and 'grpc' in switch_args['sw_path']:
# If grpc appears in the BMv2 switch target, we assume will start P4 Runtime
class ConfiguredP4RuntimeSwitch(P4RuntimeSwitch):
def __init__(self, *opts, **kwargs):
kwargs.update(switch_args)
P4RuntimeSwitch.__init__(self, *opts, **kwargs)
def describe(self):
print "%s -> gRPC port: %d" % (self.name, self.grpc_port)
return ConfiguredP4RuntimeSwitch
else:
class ConfiguredP4Switch(P4Switch):
next_thrift_port = 9090
def __init__(self, *opts, **kwargs):
global next_thrift_port
kwargs.update(switch_args)
kwargs['thrift_port'] = ConfiguredP4Switch.next_thrift_port
ConfiguredP4Switch.next_thrift_port += 1
P4Switch.__init__(self, *opts, **kwargs)
def describe(self):
print "%s -> Thrift port: %d" % (self.name, self.thrift_port)
return ConfiguredP4Switch
class ExerciseTopo(Topo):
@@ -312,6 +326,8 @@ class ExerciseRunner:
been called.
"""
self.logger("Starting mininet CLI")
for s in self.net.switches:
s.describe()
for h in self.net.hosts:
h.describe()
# Generate a message that will be printed by the Mininet CLI to make
@@ -324,10 +340,11 @@ class ExerciseRunner:
print('and your initial configuration is loaded. You can interact')
print('with the network using the mininet CLI below.')
print('')
print('To inspect or change the switch configuration, connect to')
print('its CLI from your host operating system using this command:')
print(' simple_switch_CLI --thrift-port <switch thrift port>')
print('')
if self.switch_json:
print('To inspect or change the switch configuration, connect to')
print('its CLI from your host operating system using this command:')
print(' simple_switch_CLI --thrift-port <switch thrift port>')
print('')
print('To view a switch log, run this command from your host OS:')
print(' tail -f %s/<switchname>.log' % self.log_dir)
print('')
@@ -349,13 +366,16 @@ def get_args():
type=str, required=False, default='./topology.json')
parser.add_argument('-l', '--log-dir', type=str, required=False, default=default_logs)
parser.add_argument('-p', '--pcap-dir', type=str, required=False, default=default_pcaps)
parser.add_argument('-j', '--switch_json', type=str, required=True)
parser.add_argument('-j', '--switch_json', type=str, required=False)
parser.add_argument('-b', '--behavioral-exe', help='Path to behavioral executable',
type=str, required=False, default='simple_switch')
return parser.parse_args()
if __name__ == '__main__':
# from mininet.log import setLogLevel
# setLogLevel("info")
args = get_args()
exercise = ExerciseRunner(args.topo, args.log_dir, args.pcap_dir,
args.switch_json, args.behavioral_exe, args.quiet)