P4 Developer Day 2018 Spring (#159)

* Repository reorganization for 2018 Spring P4 Developer Day.

* Port tutorial exercises to P4Runtime with static controller (#156)

* Switch VM to a minimal Ubuntu 16.04 desktop image

* Add commands to install Protobuf Python bindings to user_bootstrap.sh

* Implement P4Runtime static controller for use in exercises

From the exercise perspective, the main difference is that control plane
rules are now specified using JSON files instead of CLI commands. Such
JSON files define rules that use the same name for tables, keys, etc. as
in the P4Info file.

All P4Runtime requests generated as part of the make run process are
logged in the exercise's “logs” directory, making it easier for students
to see the actual P4Runtime messages sent to the switch.

Only the "basic" exercise has been ported to use P4Runtime.
The "p4runtime" exercise has been updated to work with P4Runtime
protocol changes.

Known issues:
- make run hangs in case of errors when running the P4Runtime controller
    (probably due to gRPC stream channel threads not terminated properly)
- missing support for inserting table entries with default action
    (can specify in P4 program as a workaround)

* Force install protobuf python module

* Fixing Ctrl-C hang by shutdown switches

* Moving gRPC error print to function for readability

Unforuntately, if this gets moved out of the file, the process hangs.
We'll need to figure out how why later.

* Renaming ShutdownAllSwitches -> ShutdownAllSwitchConnections

* Reverting counter index change

* Porting the ECN exercise to use P4 Runtime Static Controller

* updating the README in the ecn exercise to reflect the change in rule files

* Allow set table default action in P4Runtime static controller

* Fixed undefined match string when printing P4Runtime table entry

* Updated basic_tunnel exercise to use P4Runtime controller.

* Changed default action in the basic exercise's ipv4_lpm table to drop

* Porting the MRI exercise to use P4runtime with static controller

* Updating readme to reflect the change of controller for mri

* Update calc exercise for P4Runtime static controller

* Port source_routing to P4 Runtime static controller (#157)

* Port Load Balance to P4 Runtime Static Controller (#158)
This commit is contained in:
Nate Foster
2018-06-01 02:54:33 -04:00
committed by GitHub
parent e7e6899d5c
commit dc08948a34
503 changed files with 1432 additions and 30666 deletions

View File

@@ -0,0 +1,104 @@
import subprocess
from shortest_path import ShortestPath
class AppController:
def __init__(self, manifest=None, target=None, topo=None, net=None, links=None):
self.manifest = manifest
self.target = target
self.conf = manifest['targets'][target]
self.topo = topo
self.net = net
self.links = links
def read_entries(self, filename):
entries = []
with open(filename, 'r') as f:
for line in f:
line = line.strip()
if line == '': continue
entries.append(line)
return entries
def add_entries(self, thrift_port=9090, sw=None, entries=None):
assert entries
if sw: thrift_port = sw.thrift_port
print '\n'.join(entries)
p = subprocess.Popen(['simple_switch_CLI', '--thrift-port', str(thrift_port)], stdin=subprocess.PIPE)
p.communicate(input='\n'.join(entries))
def read_register(self, register, idx, thrift_port=9090, sw=None):
if sw: thrift_port = sw.thrift_port
p = subprocess.Popen(['simple_switch_CLI', '--thrift-port', str(thrift_port)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate(input="register_read %s %d" % (register, idx))
reg_val = filter(lambda l: ' %s[%d]' % (register, idx) in l, stdout.split('\n'))[0].split('= ', 1)[1]
return long(reg_val)
def start(self):
shortestpath = ShortestPath(self.links)
entries = {}
for sw in self.topo.switches():
entries[sw] = []
if 'switches' in self.conf and sw in self.conf['switches'] and 'entries' in self.conf['switches'][sw]:
extra_entries = self.conf['switches'][sw]['entries']
if type(extra_entries) == list: # array of entries
entries[sw] += extra_entries
else: # path to file that contains entries
entries[sw] += self.read_entries(extra_entries)
#entries[sw] += [
# 'table_set_default send_frame _drop',
# 'table_set_default forward _drop',
# 'table_set_default ipv4_lpm _drop']
for host_name in self.topo._host_links:
h = self.net.get(host_name)
for link in self.topo._host_links[host_name].values():
sw = link['sw']
#entries[sw].append('table_add send_frame rewrite_mac %d => %s' % (link['sw_port'], link['sw_mac']))
#entries[sw].append('table_add forward set_dmac %s => %s' % (link['host_ip'], link['host_mac']))
#entries[sw].append('table_add ipv4_lpm set_nhop %s/32 => %s %d' % (link['host_ip'], link['host_ip'], link['sw_port']))
iface = h.intfNames()[link['idx']]
# use mininet to set ip and mac to let it know the change
h.setIP(link['host_ip'], 24)
h.setMAC(link['host_mac'])
#h.cmd('ifconfig %s %s hw ether %s' % (iface, link['host_ip'], link['host_mac']))
h.cmd('arp -i %s -s %s %s' % (iface, link['sw_ip'], link['sw_mac']))
h.cmd('ethtool --offload %s rx off tx off' % iface)
h.cmd('ip route add %s dev %s' % (link['sw_ip'], iface))
h.setDefaultRoute("via %s" % link['sw_ip'])
for h in self.net.hosts:
h_link = self.topo._host_links[h.name].values()[0]
for sw in self.net.switches:
path = shortestpath.get(sw.name, h.name, exclude=lambda n: n[0]=='h')
if not path: continue
if not path[1][0] == 's': continue # next hop is a switch
sw_link = self.topo._sw_links[sw.name][path[1]]
#entries[sw.name].append('table_add send_frame rewrite_mac %d => %s' % (sw_link[0]['port'], sw_link[0]['mac']))
#entries[sw.name].append('table_add forward set_dmac %s => %s' % (h_link['host_ip'], sw_link[1]['mac']))
#entries[sw.name].append('table_add ipv4_lpm set_nhop %s/32 => %s %d' % (h_link['host_ip'], h_link['host_ip'], sw_link[0]['port']))
for h2 in self.net.hosts:
if h == h2: continue
path = shortestpath.get(h.name, h2.name, exclude=lambda n: n[0]=='h')
if not path: continue
h_link = self.topo._host_links[h.name][path[1]]
h2_link = self.topo._host_links[h2.name].values()[0]
h.cmd('ip route add %s via %s' % (h2_link['host_ip'], h_link['sw_ip']))
print "**********"
print "Configuring entries in p4 tables"
for sw_name in entries:
print
print "Configuring switch... %s" % sw_name
sw = self.net.get(sw_name)
if entries[sw_name]:
self.add_entries(sw=sw, entries=entries[sw_name])
print "Configuration complete."
print "**********"
def stop(self):
pass

70
utils/mininet/apptopo.py Normal file
View File

@@ -0,0 +1,70 @@
from mininet.topo import Topo
class AppTopo(Topo):
def __init__(self, links, latencies={}, manifest=None, target=None,
log_dir="/tmp", bws={}, **opts):
Topo.__init__(self, **opts)
nodes = sum(map(list, zip(*links)), [])
host_names = sorted(list(set(filter(lambda n: n[0] == 'h', nodes))))
sw_names = sorted(list(set(filter(lambda n: n[0] == 's', nodes))))
sw_ports = dict([(sw, []) for sw in sw_names])
self._host_links = {}
self._sw_links = dict([(sw, {}) for sw in sw_names])
for sw_name in sw_names:
self.addSwitch(sw_name, log_file="%s/%s.log" %(log_dir, sw_name))
for host_name in host_names:
host_num = int(host_name[1:])
self.addHost(host_name)
self._host_links[host_name] = {}
host_links = filter(lambda l: l[0]==host_name or l[1]==host_name, links)
sw_idx = 0
for link in host_links:
sw = link[0] if link[0] != host_name else link[1]
sw_num = int(sw[1:])
assert sw[0]=='s', "Hosts should be connected to switches, not " + str(sw)
host_ip = "10.0.%d.%d" % (sw_num, host_num)
host_mac = '00:00:00:00:%02x:%02x' % (sw_num, host_num)
delay_key = ''.join([host_name, sw])
delay = latencies[delay_key] if delay_key in latencies else '0ms'
bw = bws[delay_key] if delay_key in bws else None
sw_ports[sw].append(host_name)
self._host_links[host_name][sw] = dict(
idx=sw_idx,
host_mac = host_mac,
host_ip = host_ip,
sw = sw,
sw_mac = "00:00:00:00:%02x:%02x" % (sw_num, host_num),
sw_ip = "10.0.%d.%d" % (sw_num, 254),
sw_port = sw_ports[sw].index(host_name)+1
)
self.addLink(host_name, sw, delay=delay, bw=bw,
addr1=host_mac, addr2=self._host_links[host_name][sw]['sw_mac'])
sw_idx += 1
for link in links: # only check switch-switch links
sw1, sw2 = link
if sw1[0] != 's' or sw2[0] != 's': continue
delay_key = ''.join(sorted([sw1, sw2]))
delay = latencies[delay_key] if delay_key in latencies else '0ms'
bw = bws[delay_key] if delay_key in bws else None
self.addLink(sw1, sw2, delay=delay, bw=bw)#, max_queue_size=10)
sw_ports[sw1].append(sw2)
sw_ports[sw2].append(sw1)
sw1_num, sw2_num = int(sw1[1:]), int(sw2[1:])
sw1_port = dict(mac="00:00:00:%02x:%02x:00" % (sw1_num, sw2_num), port=sw_ports[sw1].index(sw2)+1)
sw2_port = dict(mac="00:00:00:%02x:%02x:00" % (sw2_num, sw1_num), port=sw_ports[sw2].index(sw1)+1)
self._sw_links[sw1][sw2] = [sw1_port, sw2_port]
self._sw_links[sw2][sw1] = [sw2_port, sw1_port]

View File

@@ -0,0 +1,243 @@
#!/usr/bin/env python2
# Copyright 2013-present Barefoot Networks, Inc.
#
# 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 signal
import os
import sys
import subprocess
import argparse
import json
import importlib
import re
from time import sleep
from mininet.net import Mininet
from mininet.topo import Topo
from mininet.link import TCLink
from mininet.log import setLogLevel, info
from mininet.cli import CLI
from p4_mininet import P4Switch, P4Host
import apptopo
import appcontroller
parser = argparse.ArgumentParser(description='Mininet demo')
parser.add_argument('--behavioral-exe', help='Path to behavioral executable',
type=str, action="store", required=True)
parser.add_argument('--thrift-port', help='Thrift server port for table updates',
type=int, action="store", default=9090)
parser.add_argument('--bmv2-log', help='verbose messages in log file', action="store_true")
parser.add_argument('--cli', help="start the mininet cli", action="store_true")
parser.add_argument('--auto-control-plane', help='enable automatic control plane population', action="store_true")
parser.add_argument('--json', help='Path to JSON config file',
type=str, action="store", required=True)
parser.add_argument('--pcap-dump', help='Dump packets on interfaces to pcap files',
action="store_true")
parser.add_argument('--manifest', '-m', help='Path to manifest file',
type=str, action="store", required=True)
parser.add_argument('--target', '-t', help='Target in manifest file to run',
type=str, action="store", required=True)
parser.add_argument('--log-dir', '-l', help='Location to save output to',
type=str, action="store", required=True)
parser.add_argument('--cli-message', help='Message to print before starting CLI',
type=str, action="store", required=False, default=False)
args = parser.parse_args()
next_thrift_port = args.thrift_port
def run_command(command):
return os.WEXITSTATUS(os.system(command))
def configureP4Switch(**switch_args):
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
def main():
with open(args.manifest, 'r') as f:
manifest = json.load(f)
conf = manifest['targets'][args.target]
params = conf['parameters'] if 'parameters' in conf else {}
os.environ.update(dict(map(lambda (k,v): (k, str(v)), params.iteritems())))
def formatParams(s):
for param in params:
s = re.sub('\$'+param+'(\W|$)', str(params[param]) + r'\1', s)
s = s.replace('${'+param+'}', str(params[param]))
return s
AppTopo = apptopo.AppTopo
AppController = appcontroller.AppController
if 'topo_module' in conf:
sys.path.insert(0, os.path.dirname(args.manifest))
topo_module = importlib.import_module(conf['topo_module'])
AppTopo = topo_module.CustomAppTopo
if 'controller_module' in conf:
sys.path.insert(0, os.path.dirname(args.manifest))
controller_module = importlib.import_module(conf['controller_module'])
AppController = controller_module.CustomAppController
if not os.path.isdir(args.log_dir):
if os.path.exists(args.log_dir): raise Exception('Log dir exists and is not a dir')
os.mkdir(args.log_dir)
os.environ['P4APP_LOGDIR'] = args.log_dir
links = [l[:2] for l in conf['links']]
latencies = dict([(''.join(sorted(l[:2])), l[2]) for l in conf['links'] if len(l)>=3])
bws = dict([(''.join(sorted(l[:2])), l[3]) for l in conf['links'] if len(l)>=4])
for host_name in sorted(conf['hosts'].keys()):
host = conf['hosts'][host_name]
if 'latency' not in host: continue
for a, b in links:
if a != host_name and b != host_name: continue
other = a if a != host_name else b
latencies[host_name+other] = host['latency']
for l in latencies:
if isinstance(latencies[l], (str, unicode)):
latencies[l] = formatParams(latencies[l])
else:
latencies[l] = str(latencies[l]) + "ms"
bmv2_log = args.bmv2_log or ('bmv2_log' in conf and conf['bmv2_log'])
pcap_dump = args.pcap_dump or ('pcap_dump' in conf and conf['pcap_dump'])
topo = AppTopo(links, latencies, manifest=manifest, target=args.target,
log_dir=args.log_dir, bws=bws)
switchClass = configureP4Switch(
sw_path=args.behavioral_exe,
json_path=args.json,
log_console=bmv2_log,
pcap_dump=pcap_dump)
net = Mininet(topo = topo,
link = TCLink,
host = P4Host,
switch = switchClass,
controller = None)
net.start()
sleep(1)
controller = None
if args.auto_control_plane or 'controller_module' in conf:
controller = AppController(manifest=manifest, target=args.target,
topo=topo, net=net, links=links)
controller.start()
for h in net.hosts:
h.describe()
if args.cli_message is not None:
with open(args.cli_message, 'r') as message_file:
print message_file.read()
if args.cli or ('cli' in conf and conf['cli']):
CLI(net)
stdout_files = dict()
return_codes = []
host_procs = []
def formatCmd(cmd):
for h in net.hosts:
cmd = cmd.replace(h.name, h.defaultIntf().updateIP())
return cmd
def _wait_for_exit(p, host):
print p.communicate()
if p.returncode is None:
p.wait()
print p.communicate()
return_codes.append(p.returncode)
if host_name in stdout_files:
stdout_files[host_name].flush()
stdout_files[host_name].close()
print '\n'.join(map(lambda (k,v): "%s: %s"%(k,v), params.iteritems())) + '\n'
for host_name in sorted(conf['hosts'].keys()):
host = conf['hosts'][host_name]
if 'cmd' not in host: continue
h = net.get(host_name)
stdout_filename = os.path.join(args.log_dir, h.name + '.stdout')
stdout_files[h.name] = open(stdout_filename, 'w')
cmd = formatCmd(host['cmd'])
print h.name, cmd
p = h.popen(cmd, stdout=stdout_files[h.name], shell=True, preexec_fn=os.setpgrp)
if 'startup_sleep' in host: sleep(host['startup_sleep'])
if 'wait' in host and host['wait']:
_wait_for_exit(p, host_name)
else:
host_procs.append((p, host_name))
for p, host_name in host_procs:
if 'wait' in conf['hosts'][host_name] and conf['hosts'][host_name]['wait']:
_wait_for_exit(p, host_name)
for p, host_name in host_procs:
if 'wait' in conf['hosts'][host_name] and conf['hosts'][host_name]['wait']:
continue
if p.returncode is None:
run_command('pkill -INT -P %d' % p.pid)
sleep(0.2)
rc = run_command('pkill -0 -P %d' % p.pid) # check if it's still running
if rc == 0: # the process group is still running, send TERM
sleep(1) # give it a little more time to exit gracefully
run_command('pkill -TERM -P %d' % p.pid)
_wait_for_exit(p, host_name)
if 'after' in conf and 'cmd' in conf['after']:
cmds = conf['after']['cmd'] if type(conf['after']['cmd']) == list else [conf['after']['cmd']]
for cmd in cmds:
os.system(cmd)
if controller: controller.stop()
net.stop()
# if bmv2_log:
# os.system('bash -c "cp /tmp/p4s.s*.log \'%s\'"' % args.log_dir)
# if pcap_dump:
# os.system('bash -c "cp *.pcap \'%s\'"' % args.log_dir)
bad_codes = [rc for rc in return_codes if rc != 0]
if len(bad_codes): sys.exit(1)
if __name__ == '__main__':
setLogLevel( 'info' )
main()

161
utils/mininet/p4_mininet.py Normal file
View File

@@ -0,0 +1,161 @@
# Copyright 2013-present Barefoot Networks, Inc.
#
# 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.
#
from mininet.net import Mininet
from mininet.node import Switch, Host
from mininet.log import setLogLevel, info, error, debug
from mininet.moduledeps import pathCheck
from sys import exit
from time import sleep
import os
import tempfile
import socket
class P4Host(Host):
def config(self, **params):
r = super(P4Host, self).config(**params)
for off in ["rx", "tx", "sg"]:
cmd = "/sbin/ethtool --offload %s %s off" % (self.defaultIntf().name, 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
def describe(self, sw_addr=None, sw_mac=None):
print "**********"
print "Network configuration for: %s" % self.name
print "Default interface: %s\t%s\t%s" %(
self.defaultIntf().name,
self.defaultIntf().IP(),
self.defaultIntf().MAC()
)
if sw_addr is not None or sw_mac is not None:
print "Default route to switch: %s (%s)" % (sw_addr, sw_mac)
print "**********"
class P4Switch(Switch):
"""P4 virtual switch"""
device_id = 0
def __init__(self, name, sw_path = None, json_path = None,
log_file = None,
thrift_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)
assert(json_path)
# make sure that the provided sw_path is valid
pathCheck(sw_path)
# make sure that the provided JSON file exists
if not os.path.isfile(json_path):
error("Invalid JSON file.\n")
exit(1)
self.sw_path = sw_path
self.json_path = json_path
self.verbose = verbose
self.log_file = log_file
if self.log_file is None:
self.log_file = "/tmp/p4s.{}.log".format(self.name)
self.output = open(self.log_file, 'w')
self.thrift_port = thrift_port
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)
@classmethod
def setup(cls):
pass
def check_switch_started(self, pid):
"""While the process is running (pid exists), we check if the Thrift
server has been started. If the Thrift server is ready, we assume that
the switch was started successfully. This is only reliable if the Thrift
server is started at the end of the init process"""
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.thrift_port))
if result == 0:
return True
def start(self, controllers):
"Start up a new P4 switch"
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")
# args.append("--useFiles")
if self.thrift_port:
args.extend(['--thrift-port', str(self.thrift_port)])
if self.nanomsg:
args.extend(['--nanolog', self.nanomsg])
args.extend(['--device-id', str(self.device_id)])
P4Switch.device_id += 1
args.append(self.json_path)
if self.enable_debugger:
args.append("--debugger")
if self.log_console:
args.append("--log-console")
info(' '.join(args) + "\n")
pid = None
with tempfile.NamedTemporaryFile() as f:
# self.cmd(' '.join(args) + ' > /dev/null 2>&1 &')
self.cmd(' '.join(args) + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name)
pid = int(f.read())
debug("P4 switch {} PID is {}.\n".format(self.name, pid))
sleep(1)
if not self.check_switch_started(pid):
error("P4 switch {} did not start correctly."
"Check the switch log file.\n".format(self.name))
exit(1)
info("P4 switch {} has been started.\n".format(self.name))
def stop(self):
"Terminate P4 switch."
self.output.flush()
self.cmd('kill %' + self.sw_path)
self.cmd('wait')
self.deleteIntfs()
def attach(self, intf):
"Connect a data port"
assert(0)
def detach(self, intf):
"Disconnect a data port"
assert(0)

View File

@@ -0,0 +1,78 @@
class ShortestPath:
def __init__(self, edges=[]):
self.neighbors = {}
for edge in edges:
self.addEdge(*edge)
def addEdge(self, a, b):
if a not in self.neighbors: self.neighbors[a] = []
if b not in self.neighbors[a]: self.neighbors[a].append(b)
if b not in self.neighbors: self.neighbors[b] = []
if a not in self.neighbors[b]: self.neighbors[b].append(a)
def get(self, a, b, exclude=lambda node: False):
# Shortest path from a to b
return self._recPath(a, b, [], exclude)
def _recPath(self, a, b, visited, exclude):
if a == b: return [a]
new_visited = visited + [a]
paths = []
for neighbor in self.neighbors[a]:
if neighbor in new_visited: continue
if exclude(neighbor) and neighbor != b: continue
path = self._recPath(neighbor, b, new_visited, exclude)
if path: paths.append(path)
paths.sort(key=len)
return [a] + paths[0] if len(paths) else None
if __name__ == '__main__':
edges = [
(1, 2),
(1, 3),
(1, 5),
(2, 4),
(3, 4),
(3, 5),
(3, 6),
(4, 6),
(5, 6),
(7, 8)
]
sp = ShortestPath(edges)
assert sp.get(1, 1) == [1]
assert sp.get(2, 2) == [2]
assert sp.get(1, 2) == [1, 2]
assert sp.get(2, 1) == [2, 1]
assert sp.get(1, 3) == [1, 3]
assert sp.get(3, 1) == [3, 1]
assert sp.get(4, 6) == [4, 6]
assert sp.get(6, 4) == [6, 4]
assert sp.get(2, 6) == [2, 4, 6]
assert sp.get(6, 2) == [6, 4, 2]
assert sp.get(1, 6) in [[1, 3, 6], [1, 5, 6]]
assert sp.get(6, 1) in [[6, 3, 1], [6, 5, 1]]
assert sp.get(2, 5) == [2, 1, 5]
assert sp.get(5, 2) == [5, 1, 2]
assert sp.get(4, 5) in [[4, 3, 5], [4, 6, 5]]
assert sp.get(5, 4) in [[5, 3, 4], [6, 6, 4]]
assert sp.get(7, 8) == [7, 8]
assert sp.get(8, 7) == [8, 7]
assert sp.get(1, 7) == None
assert sp.get(7, 2) == None

View File

@@ -0,0 +1,133 @@
#!/usr/bin/env python2
# Copyright 2013-present Barefoot Networks, Inc.
#
# 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.
#
from mininet.net import Mininet
from mininet.topo import Topo
from mininet.log import setLogLevel, info
from mininet.cli import CLI
from p4_mininet import P4Switch, P4Host
import argparse
from subprocess import PIPE, Popen
from time import sleep
parser = argparse.ArgumentParser(description='Mininet demo')
parser.add_argument('--behavioral-exe', help='Path to behavioral executable',
type=str, action="store", required=True)
parser.add_argument('--thrift-port', help='Thrift server port for table updates',
type=int, action="store", default=9090)
parser.add_argument('--num-hosts', help='Number of hosts to connect to switch',
type=int, action="store", default=2)
parser.add_argument('--mode', choices=['l2', 'l3'], type=str, default='l3')
parser.add_argument('--json', help='Path to JSON config file',
type=str, action="store", required=True)
parser.add_argument('--log-file', help='Path to write the switch log file',
type=str, action="store", required=False)
parser.add_argument('--pcap-dump', help='Dump packets on interfaces to pcap files',
type=str, action="store", required=False, default=False)
parser.add_argument('--switch-config', help='simple_switch_CLI script to configure switch',
type=str, action="store", required=False, default=False)
parser.add_argument('--cli-message', help='Message to print before starting CLI',
type=str, action="store", required=False, default=False)
args = parser.parse_args()
class SingleSwitchTopo(Topo):
"Single switch connected to n (< 256) hosts."
def __init__(self, sw_path, json_path, log_file,
thrift_port, pcap_dump, n, **opts):
# Initialize topology and default options
Topo.__init__(self, **opts)
switch = self.addSwitch('s1',
sw_path = sw_path,
json_path = json_path,
log_console = True,
log_file = log_file,
thrift_port = thrift_port,
enable_debugger = False,
pcap_dump = pcap_dump)
for h in xrange(n):
host = self.addHost('h%d' % (h + 1),
ip = "10.0.%d.10/24" % h,
mac = '00:04:00:00:00:%02x' %h)
print "Adding host", str(host)
self.addLink(host, switch)
def main():
num_hosts = args.num_hosts
mode = args.mode
topo = SingleSwitchTopo(args.behavioral_exe,
args.json,
args.log_file,
args.thrift_port,
args.pcap_dump,
num_hosts)
net = Mininet(topo = topo,
host = P4Host,
switch = P4Switch,
controller = None)
net.start()
sw_mac = ["00:aa:bb:00:00:%02x" % n for n in xrange(num_hosts)]
sw_addr = ["10.0.%d.1" % n for n in xrange(num_hosts)]
for n in xrange(num_hosts):
h = net.get('h%d' % (n + 1))
if mode == "l2":
h.setDefaultRoute("dev %s" % h.defaultIntf().name)
else:
h.setARP(sw_addr[n], sw_mac[n])
h.setDefaultRoute("dev %s via %s" % (h.defaultIntf().name, sw_addr[n]))
for n in xrange(num_hosts):
h = net.get('h%d' % (n + 1))
h.describe(sw_addr[n], sw_mac[n])
sleep(1)
if args.switch_config is not None:
print
print "Reading switch configuration script:", args.switch_config
with open(args.switch_config, 'r') as config_file:
switch_config = config_file.read()
print "Configuring switch..."
proc = Popen(["simple_switch_CLI"], stdin=PIPE)
proc.communicate(input=switch_config)
print "Configuration complete."
print
print "Ready !"
if args.cli_message is not None:
with open(args.cli_message, 'r') as message_file:
print message_file.read()
CLI( net )
net.stop()
if __name__ == '__main__':
setLogLevel( 'info' )
main()