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:
parent
c9151e767a
commit
aa4298859f
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
# Python byte code
|
# Python byte code
|
||||||
*.pyc
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
|
||||||
# Emacs
|
# Emacs
|
||||||
*~
|
*~
|
||||||
@ -18,3 +19,9 @@ build*/
|
|||||||
|
|
||||||
# Vagrant
|
# Vagrant
|
||||||
.vagrant/
|
.vagrant/
|
||||||
|
|
||||||
|
# Mac OS
|
||||||
|
*.DS_Store
|
||||||
|
|
||||||
|
# IntelliJ / PyCharm
|
||||||
|
.idea/
|
||||||
|
10
P4D2_2017_Fall/exercises/p4runtime/Makefile
Normal file
10
P4D2_2017_Fall/exercises/p4runtime/Makefile
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
include ../../utils/Makefile
|
||||||
|
|
||||||
|
# Override build method to use simple_switch_grpc target
|
||||||
|
run: build
|
||||||
|
sudo python $(RUN_SCRIPT) -t $(TOPO) -b simple_switch_grpc
|
||||||
|
|
||||||
|
# Override p4c step to also produce p4info file
|
||||||
|
P4INFO_ARGS = --p4runtime-file $(basename $@).p4info --p4runtime-format text
|
||||||
|
$(BUILD_DIR)/%.json: %.p4
|
||||||
|
$(P4C) --p4v 16 $(P4INFO_ARGS) -o $@ $<
|
236
P4D2_2017_Fall/exercises/p4runtime/advanced_tunnel.p4
Normal file
236
P4D2_2017_Fall/exercises/p4runtime/advanced_tunnel.p4
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
/* -*- P4_16 -*- */
|
||||||
|
#include <core.p4>
|
||||||
|
#include <v1model.p4>
|
||||||
|
|
||||||
|
const bit<16> TYPE_MYTUNNEL = 0x1212;
|
||||||
|
const bit<16> TYPE_IPV4 = 0x800;
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** H E A D E R S ***********************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
typedef bit<9> egressSpec_t;
|
||||||
|
typedef bit<48> macAddr_t;
|
||||||
|
typedef bit<32> ip4Addr_t;
|
||||||
|
|
||||||
|
header ethernet_t {
|
||||||
|
macAddr_t dstAddr;
|
||||||
|
macAddr_t srcAddr;
|
||||||
|
bit<16> etherType;
|
||||||
|
}
|
||||||
|
|
||||||
|
header myTunnel_t {
|
||||||
|
bit<16> proto_id;
|
||||||
|
bit<16> dst_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
header ipv4_t {
|
||||||
|
bit<4> version;
|
||||||
|
bit<4> ihl;
|
||||||
|
bit<8> diffserv;
|
||||||
|
bit<16> totalLen;
|
||||||
|
bit<16> identification;
|
||||||
|
bit<3> flags;
|
||||||
|
bit<13> fragOffset;
|
||||||
|
bit<8> ttl;
|
||||||
|
bit<8> protocol;
|
||||||
|
bit<16> hdrChecksum;
|
||||||
|
ip4Addr_t srcAddr;
|
||||||
|
ip4Addr_t dstAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct metadata {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
|
||||||
|
struct headers {
|
||||||
|
ethernet_t ethernet;
|
||||||
|
myTunnel_t myTunnel;
|
||||||
|
ipv4_t ipv4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** P A R S E R ***********************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
parser MyParser(packet_in packet,
|
||||||
|
out headers hdr,
|
||||||
|
inout metadata meta,
|
||||||
|
inout standard_metadata_t standard_metadata) {
|
||||||
|
|
||||||
|
state start {
|
||||||
|
transition parse_ethernet;
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_ethernet {
|
||||||
|
packet.extract(hdr.ethernet);
|
||||||
|
transition select(hdr.ethernet.etherType) {
|
||||||
|
TYPE_MYTUNNEL: parse_myTunnel;
|
||||||
|
TYPE_IPV4: parse_ipv4;
|
||||||
|
default: accept;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_myTunnel {
|
||||||
|
packet.extract(hdr.myTunnel);
|
||||||
|
transition select(hdr.myTunnel.proto_id) {
|
||||||
|
TYPE_IPV4: parse_ipv4;
|
||||||
|
default: accept;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_ipv4 {
|
||||||
|
packet.extract(hdr.ipv4);
|
||||||
|
transition accept;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************ C H E C K S U M V E R I F I C A T I O N *************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************** I N G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control MyIngress(inout headers hdr,
|
||||||
|
inout metadata meta,
|
||||||
|
inout standard_metadata_t standard_metadata) {
|
||||||
|
|
||||||
|
action drop() {
|
||||||
|
mark_to_drop();
|
||||||
|
}
|
||||||
|
|
||||||
|
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
|
||||||
|
standard_metadata.egress_spec = port;
|
||||||
|
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
|
||||||
|
hdr.ethernet.dstAddr = dstAddr;
|
||||||
|
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
action myTunnel_ingress(bit<16> dst_id) {
|
||||||
|
hdr.myTunnel.setValid();
|
||||||
|
hdr.myTunnel.dst_id = dst_id;
|
||||||
|
hdr.myTunnel.proto_id = hdr.ethernet.etherType;
|
||||||
|
hdr.ethernet.etherType = TYPE_MYTUNNEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
table ipv4_lpm {
|
||||||
|
key = {
|
||||||
|
hdr.ipv4.dstAddr: lpm;
|
||||||
|
}
|
||||||
|
actions = {
|
||||||
|
ipv4_forward;
|
||||||
|
myTunnel_ingress;
|
||||||
|
drop;
|
||||||
|
NoAction;
|
||||||
|
}
|
||||||
|
size = 1024;
|
||||||
|
default_action = NoAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
direct_counter(CounterType.packets_and_bytes) tunnelCount;
|
||||||
|
|
||||||
|
action myTunnel_forward(egressSpec_t port) {
|
||||||
|
standard_metadata.egress_spec = port;
|
||||||
|
tunnelCount.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
action myTunnel_egress(egressSpec_t port) {
|
||||||
|
standard_metadata.egress_spec = port;
|
||||||
|
hdr.myTunnel.setInvalid();
|
||||||
|
tunnelCount.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
table myTunnel_exact {
|
||||||
|
key = {
|
||||||
|
hdr.myTunnel.dst_id: exact;
|
||||||
|
}
|
||||||
|
actions = {
|
||||||
|
myTunnel_forward;
|
||||||
|
myTunnel_egress;
|
||||||
|
drop;
|
||||||
|
}
|
||||||
|
size = 1024;
|
||||||
|
counters = tunnelCount;
|
||||||
|
default_action = drop();
|
||||||
|
}
|
||||||
|
|
||||||
|
apply {
|
||||||
|
|
||||||
|
if (hdr.ipv4.isValid() && !hdr.myTunnel.isValid()) {
|
||||||
|
// Process only non-tunneled IPv4 packets.
|
||||||
|
ipv4_lpm.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hdr.myTunnel.isValid()) {
|
||||||
|
// Process all tunneled packets.
|
||||||
|
myTunnel_exact.apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
**************** E G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control MyEgress(inout headers hdr,
|
||||||
|
inout metadata meta,
|
||||||
|
inout standard_metadata_t standard_metadata) {
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************* C H E C K S U M C O M P U T A T I O N **************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control MyComputeChecksum(inout headers hdr, inout metadata meta) {
|
||||||
|
apply {
|
||||||
|
update_checksum(
|
||||||
|
hdr.ipv4.isValid(),
|
||||||
|
{ hdr.ipv4.version,
|
||||||
|
hdr.ipv4.ihl,
|
||||||
|
hdr.ipv4.diffserv,
|
||||||
|
hdr.ipv4.totalLen,
|
||||||
|
hdr.ipv4.identification,
|
||||||
|
hdr.ipv4.flags,
|
||||||
|
hdr.ipv4.fragOffset,
|
||||||
|
hdr.ipv4.ttl,
|
||||||
|
hdr.ipv4.protocol,
|
||||||
|
hdr.ipv4.srcAddr,
|
||||||
|
hdr.ipv4.dstAddr },
|
||||||
|
hdr.ipv4.hdrChecksum,
|
||||||
|
HashAlgorithm.csum16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** D E P A R S E R *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control MyDeparser(packet_out packet, in headers hdr) {
|
||||||
|
apply {
|
||||||
|
packet.emit(hdr.ethernet);
|
||||||
|
packet.emit(hdr.myTunnel);
|
||||||
|
packet.emit(hdr.ipv4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** S W I T C H *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
V1Switch(
|
||||||
|
MyParser(),
|
||||||
|
MyVerifyChecksum(),
|
||||||
|
MyIngress(),
|
||||||
|
MyEgress(),
|
||||||
|
MyComputeChecksum(),
|
||||||
|
MyDeparser()
|
||||||
|
) main;
|
135
P4D2_2017_Fall/exercises/p4runtime/p4info/p4browser.py
Normal file
135
P4D2_2017_Fall/exercises/p4runtime/p4info/p4browser.py
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
# 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 re
|
||||||
|
|
||||||
|
import google.protobuf.text_format
|
||||||
|
from p4 import p4runtime_pb2
|
||||||
|
from p4.config import p4info_pb2
|
||||||
|
|
||||||
|
|
||||||
|
class P4InfoBrowser(object):
|
||||||
|
def __init__(self, p4_info_filepath):
|
||||||
|
p4info = p4info_pb2.P4Info()
|
||||||
|
# Load the p4info file into a skeleton P4Info object
|
||||||
|
with open(p4_info_filepath) as p4info_f:
|
||||||
|
google.protobuf.text_format.Merge(p4info_f.read(), p4info)
|
||||||
|
self.p4info = p4info
|
||||||
|
|
||||||
|
def get(self, entity_type, name=None, id=None):
|
||||||
|
if name is not None and id is not None:
|
||||||
|
raise AssertionError("name or id must be None")
|
||||||
|
|
||||||
|
for o in getattr(self.p4info, entity_type):
|
||||||
|
pre = o.preamble
|
||||||
|
if name:
|
||||||
|
if (pre.name == name or pre.alias == name):
|
||||||
|
return o
|
||||||
|
else:
|
||||||
|
if pre.id == id:
|
||||||
|
return o
|
||||||
|
|
||||||
|
if name:
|
||||||
|
raise AttributeError("Could not find %r of type %s" % (name, entity_type))
|
||||||
|
else:
|
||||||
|
raise AttributeError("Could not find id %r of type %s" % (id, entity_type))
|
||||||
|
|
||||||
|
def get_id(self, entity_type, name):
|
||||||
|
return self.get(entity_type, name=name).preamble.id
|
||||||
|
|
||||||
|
def get_name(self, entity_type, id):
|
||||||
|
return self.get(entity_type, id=id).preamble.name
|
||||||
|
|
||||||
|
def get_alias(self, entity_type, id):
|
||||||
|
return self.get(entity_type, id=id).preamble.alias
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
# Synthesize convenience functions for name to id lookups for top-level entities
|
||||||
|
# e.g. get_table_id() or get_action_id()
|
||||||
|
m = re.search("^get_(\w+)_id$", attr)
|
||||||
|
if m:
|
||||||
|
primitive = m.group(1)
|
||||||
|
return lambda name: self.get_id(primitive, name)
|
||||||
|
|
||||||
|
# Synthesize convenience functions for id to name lookups
|
||||||
|
m = re.search("^get_(\w+)_name$", attr)
|
||||||
|
if m:
|
||||||
|
primitive = m.group(1)
|
||||||
|
return lambda id: self.get_name(primitive, id)
|
||||||
|
|
||||||
|
raise AttributeError("%r object has no attribute %r" % (self.__class__, attr))
|
||||||
|
|
||||||
|
# TODO remove
|
||||||
|
def get_table_entry(self, table_name):
|
||||||
|
t = self.get(table_name, "table")
|
||||||
|
entry = p4runtime_pb2.TableEntry()
|
||||||
|
entry.table_id = t.preamble.id
|
||||||
|
entry
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_match_field(self, table_name, match_field_name):
|
||||||
|
for t in self.p4info.tables:
|
||||||
|
pre = t.preamble
|
||||||
|
if pre.name == table_name:
|
||||||
|
for mf in t.match_fields:
|
||||||
|
if mf.name == match_field_name:
|
||||||
|
return mf
|
||||||
|
|
||||||
|
def get_match_field_id(self, table_name, match_field_name):
|
||||||
|
return self.get_match_field(table_name,match_field_name).id
|
||||||
|
|
||||||
|
def get_match_field_pb(self, table_name, match_field_name, value):
|
||||||
|
p4info_match = self.get_match_field(table_name, match_field_name)
|
||||||
|
bw = p4info_match.bitwidth
|
||||||
|
p4runtime_match = p4runtime_pb2.FieldMatch()
|
||||||
|
p4runtime_match.field_id = p4info_match.id
|
||||||
|
# TODO switch on match type and map the value into the appropriate message type
|
||||||
|
match_type = p4info_pb2._MATCHFIELD_MATCHTYPE.values_by_number[
|
||||||
|
p4info_match.match_type].name
|
||||||
|
if match_type == 'EXACT':
|
||||||
|
exact = p4runtime_match.exact
|
||||||
|
exact.value = value
|
||||||
|
elif match_type == 'LPM':
|
||||||
|
lpm = p4runtime_match.lpm
|
||||||
|
lpm.value = value[0]
|
||||||
|
lpm.prefix_len = value[1]
|
||||||
|
# TODO finish cases and validate types and bitwidth
|
||||||
|
# VALID = 1;
|
||||||
|
# EXACT = 2;
|
||||||
|
# LPM = 3;
|
||||||
|
# TERNARY = 4;
|
||||||
|
# RANGE = 5;
|
||||||
|
# and raise exception
|
||||||
|
return p4runtime_match
|
||||||
|
|
||||||
|
def get_action_param(self, action_name, param_name):
|
||||||
|
for a in self.p4info.actions:
|
||||||
|
pre = a.preamble
|
||||||
|
if pre.name == action_name:
|
||||||
|
for p in a.params:
|
||||||
|
if p.name == param_name:
|
||||||
|
return p
|
||||||
|
raise AttributeError("%r has no attribute %r" % (action_name, param_name))
|
||||||
|
|
||||||
|
|
||||||
|
def get_action_param_id(self, action_name, param_name):
|
||||||
|
return self.get_action_param(action_name, param_name).id
|
||||||
|
|
||||||
|
def get_action_param_pb(self, action_name, param_name, value):
|
||||||
|
p4info_param = self.get_action_param(action_name, param_name)
|
||||||
|
#bw = p4info_param.bitwidth
|
||||||
|
p4runtime_param = p4runtime_pb2.Action.Param()
|
||||||
|
p4runtime_param.param_id = p4info_param.id
|
||||||
|
p4runtime_param.value = value # TODO make sure it's the correct bitwidth
|
||||||
|
return p4runtime_param
|
31
P4D2_2017_Fall/exercises/p4runtime/switches/bmv2.py
Normal file
31
P4D2_2017_Fall/exercises/p4runtime/switches/bmv2.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# 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.
|
||||||
|
#
|
||||||
|
from switch import SwitchConnection
|
||||||
|
from p4.tmp import p4config_pb2
|
||||||
|
|
||||||
|
|
||||||
|
def buildDeviceConfig(bmv2_json_file_path=None):
|
||||||
|
"Builds the device config for BMv2"
|
||||||
|
device_config = p4config_pb2.P4DeviceConfig()
|
||||||
|
device_config.reassign = True
|
||||||
|
# set device_config.extra to default instance
|
||||||
|
with open(bmv2_json_file_path) as f:
|
||||||
|
device_config.device_data = f.read()
|
||||||
|
return device_config
|
||||||
|
|
||||||
|
|
||||||
|
class Bmv2SwitchConnection(SwitchConnection):
|
||||||
|
def buildDeviceConfig(self, **kwargs):
|
||||||
|
return buildDeviceConfig(**kwargs)
|
130
P4D2_2017_Fall/exercises/p4runtime/switches/switch.py
Normal file
130
P4D2_2017_Fall/exercises/p4runtime/switches/switch.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
# 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.
|
||||||
|
#
|
||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
import grpc
|
||||||
|
from p4 import p4runtime_pb2
|
||||||
|
from p4.tmp import p4config_pb2
|
||||||
|
|
||||||
|
from p4info import p4browser
|
||||||
|
|
||||||
|
|
||||||
|
def buildSetPipelineRequest(p4info, device_config, device_id):
|
||||||
|
request = p4runtime_pb2.SetForwardingPipelineConfigRequest()
|
||||||
|
config = request.configs.add()
|
||||||
|
config.device_id = device_id
|
||||||
|
config.p4info.CopyFrom(p4info)
|
||||||
|
config.p4_device_config = device_config.SerializeToString()
|
||||||
|
request.action = p4runtime_pb2.SetForwardingPipelineConfigRequest.VERIFY_AND_COMMIT
|
||||||
|
return request
|
||||||
|
|
||||||
|
|
||||||
|
def buildTableEntry(p4info_browser,
|
||||||
|
table_name,
|
||||||
|
match_fields={},
|
||||||
|
action_name=None,
|
||||||
|
action_params={}):
|
||||||
|
table_entry = p4runtime_pb2.TableEntry()
|
||||||
|
table_entry.table_id = p4info_browser.get_tables_id(table_name)
|
||||||
|
if match_fields:
|
||||||
|
table_entry.match.extend([
|
||||||
|
p4info_browser.get_match_field_pb(table_name, match_field_name, value)
|
||||||
|
for match_field_name, value in match_fields.iteritems()
|
||||||
|
])
|
||||||
|
if action_name:
|
||||||
|
action = table_entry.action.action
|
||||||
|
action.action_id = p4info_browser.get_actions_id(action_name)
|
||||||
|
if action_params:
|
||||||
|
action.params.extend([
|
||||||
|
p4info_browser.get_action_param_pb(action_name, field_name, value)
|
||||||
|
for field_name, value in action_params.iteritems()
|
||||||
|
])
|
||||||
|
return table_entry
|
||||||
|
|
||||||
|
|
||||||
|
class SwitchConnection(object):
|
||||||
|
def __init__(self, name, address='127.0.0.1:50051', device_id=0):
|
||||||
|
self.name = name
|
||||||
|
self.address = address
|
||||||
|
self.device_id = device_id
|
||||||
|
self.p4info = None
|
||||||
|
self.channel = grpc.insecure_channel(self.address)
|
||||||
|
# TODO Do want to do a better job managing stub?
|
||||||
|
self.client_stub = p4runtime_pb2.P4RuntimeStub(self.channel)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def buildDeviceConfig(self, **kwargs):
|
||||||
|
return p4config_pb2.P4DeviceConfig()
|
||||||
|
|
||||||
|
def SetForwardingPipelineConfig(self, p4info_file_path, dry_run=False, **kwargs):
|
||||||
|
p4info_broswer = p4browser.P4InfoBrowser(p4info_file_path)
|
||||||
|
device_config = self.buildDeviceConfig(**kwargs)
|
||||||
|
request = buildSetPipelineRequest(p4info_broswer.p4info, device_config, self.device_id)
|
||||||
|
if dry_run:
|
||||||
|
print "P4 Runtime SetForwardingPipelineConfig:", request
|
||||||
|
else:
|
||||||
|
self.client_stub.SetForwardingPipelineConfig(request)
|
||||||
|
# Update the local P4 Info reference
|
||||||
|
self.p4info_broswer = p4info_broswer
|
||||||
|
|
||||||
|
def buildTableEntry(self,
|
||||||
|
table_name,
|
||||||
|
match_fields={},
|
||||||
|
action_name=None,
|
||||||
|
action_params={}):
|
||||||
|
return buildTableEntry(self.p4info_broswer, table_name, match_fields, action_name, action_params)
|
||||||
|
|
||||||
|
def WriteTableEntry(self, table_entry, dry_run=False):
|
||||||
|
request = p4runtime_pb2.WriteRequest()
|
||||||
|
request.device_id = self.device_id
|
||||||
|
update = request.updates.add()
|
||||||
|
update.type = p4runtime_pb2.Update.INSERT
|
||||||
|
update.entity.table_entry.CopyFrom(table_entry)
|
||||||
|
if dry_run:
|
||||||
|
print "P4 Runtime Write:", request
|
||||||
|
else:
|
||||||
|
print self.client_stub.Write(request)
|
||||||
|
|
||||||
|
def ReadTableEntries(self, table_name, dry_run=False):
|
||||||
|
request = p4runtime_pb2.ReadRequest()
|
||||||
|
request.device_id = self.device_id
|
||||||
|
entity = request.entities.add()
|
||||||
|
table_entry = entity.table_entry
|
||||||
|
table_entry.table_id = self.p4info_broswer.get_tables_id(table_name)
|
||||||
|
if dry_run:
|
||||||
|
print "P4 Runtime Read:", request
|
||||||
|
else:
|
||||||
|
for response in self.client_stub.Read(request):
|
||||||
|
yield response
|
||||||
|
|
||||||
|
def ReadDirectCounters(self, table_name=None, counter_name=None, table_entry=None, dry_run=False):
|
||||||
|
request = p4runtime_pb2.ReadRequest()
|
||||||
|
request.device_id = self.device_id
|
||||||
|
entity = request.entities.add()
|
||||||
|
counter_entry = entity.direct_counter_entry
|
||||||
|
if counter_name:
|
||||||
|
counter_entry.counter_id = self.p4info_broswer.get_direct_counters_id(counter_name)
|
||||||
|
else:
|
||||||
|
counter_entry.counter_id = 0
|
||||||
|
# TODO we may not need this table entry
|
||||||
|
if table_name:
|
||||||
|
table_entry.table_id = self.p4info_broswer.get_tables_id(table_name)
|
||||||
|
counter_entry.table_entry.CopyFrom(table_entry)
|
||||||
|
counter_entry.data.packet_count = 0
|
||||||
|
if dry_run:
|
||||||
|
print "P4 Runtime Read:", request
|
||||||
|
else:
|
||||||
|
for response in self.client_stub.Read(request):
|
||||||
|
print response
|
125
P4D2_2017_Fall/utils/p4runtime_switch.py
Normal file
125
P4D2_2017_Fall/utils/p4runtime_switch.py
Normal 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))
|
||||||
|
|
@ -31,24 +31,38 @@ from mininet.topo import Topo
|
|||||||
from mininet.link import TCLink
|
from mininet.link import TCLink
|
||||||
from mininet.cli import CLI
|
from mininet.cli import CLI
|
||||||
|
|
||||||
|
from p4runtime_switch import P4RuntimeSwitch
|
||||||
|
|
||||||
first_thrift_port = 9090
|
|
||||||
next_thrift_port = first_thrift_port
|
|
||||||
|
|
||||||
def configureP4Switch(**switch_args):
|
def configureP4Switch(**switch_args):
|
||||||
""" Helper class that is called by mininet to initialize
|
""" Helper class that is called by mininet to initialize
|
||||||
the virtual P4 switches. The purpose is to ensure each
|
the virtual P4 switches. The purpose is to ensure each
|
||||||
switch's thrift server is using a unique port.
|
switch's thrift server is using a unique port.
|
||||||
"""
|
"""
|
||||||
class ConfiguredP4Switch(P4Switch):
|
if "sw_path" in switch_args and 'grpc' in switch_args['sw_path']:
|
||||||
def __init__(self, *opts, **kwargs):
|
# If grpc appears in the BMv2 switch target, we assume will start P4 Runtime
|
||||||
global next_thrift_port
|
class ConfiguredP4RuntimeSwitch(P4RuntimeSwitch):
|
||||||
kwargs.update(switch_args)
|
def __init__(self, *opts, **kwargs):
|
||||||
kwargs['thrift_port'] = next_thrift_port
|
kwargs.update(switch_args)
|
||||||
next_thrift_port += 1
|
P4RuntimeSwitch.__init__(self, *opts, **kwargs)
|
||||||
P4Switch.__init__(self, *opts, **kwargs)
|
|
||||||
return ConfiguredP4Switch
|
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):
|
class ExerciseTopo(Topo):
|
||||||
@ -312,6 +326,8 @@ class ExerciseRunner:
|
|||||||
been called.
|
been called.
|
||||||
"""
|
"""
|
||||||
self.logger("Starting mininet CLI")
|
self.logger("Starting mininet CLI")
|
||||||
|
for s in self.net.switches:
|
||||||
|
s.describe()
|
||||||
for h in self.net.hosts:
|
for h in self.net.hosts:
|
||||||
h.describe()
|
h.describe()
|
||||||
# Generate a message that will be printed by the Mininet CLI to make
|
# 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('and your initial configuration is loaded. You can interact')
|
||||||
print('with the network using the mininet CLI below.')
|
print('with the network using the mininet CLI below.')
|
||||||
print('')
|
print('')
|
||||||
print('To inspect or change the switch configuration, connect to')
|
if self.switch_json:
|
||||||
print('its CLI from your host operating system using this command:')
|
print('To inspect or change the switch configuration, connect to')
|
||||||
print(' simple_switch_CLI --thrift-port <switch thrift port>')
|
print('its CLI from your host operating system using this command:')
|
||||||
print('')
|
print(' simple_switch_CLI --thrift-port <switch thrift port>')
|
||||||
|
print('')
|
||||||
print('To view a switch log, run this command from your host OS:')
|
print('To view a switch log, run this command from your host OS:')
|
||||||
print(' tail -f %s/<switchname>.log' % self.log_dir)
|
print(' tail -f %s/<switchname>.log' % self.log_dir)
|
||||||
print('')
|
print('')
|
||||||
@ -349,13 +366,16 @@ def get_args():
|
|||||||
type=str, required=False, default='./topology.json')
|
type=str, required=False, default='./topology.json')
|
||||||
parser.add_argument('-l', '--log-dir', type=str, required=False, default=default_logs)
|
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('-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',
|
parser.add_argument('-b', '--behavioral-exe', help='Path to behavioral executable',
|
||||||
type=str, required=False, default='simple_switch')
|
type=str, required=False, default='simple_switch')
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
# from mininet.log import setLogLevel
|
||||||
|
# setLogLevel("info")
|
||||||
|
|
||||||
args = get_args()
|
args = get_args()
|
||||||
exercise = ExerciseRunner(args.topo, args.log_dir, args.pcap_dir,
|
exercise = ExerciseRunner(args.topo, args.log_dir, args.pcap_dir,
|
||||||
args.switch_json, args.behavioral_exe, args.quiet)
|
args.switch_json, args.behavioral_exe, args.quiet)
|
||||||
|
3
P4D2_2017_Fall/vm/Vagrantfile
vendored
3
P4D2_2017_Fall/vm/Vagrantfile
vendored
@ -5,7 +5,8 @@ Vagrant.configure(2) do |config|
|
|||||||
config.vm.box = "bento/ubuntu-16.04"
|
config.vm.box = "bento/ubuntu-16.04"
|
||||||
config.vm.provider "virtualbox" do |vb|
|
config.vm.provider "virtualbox" do |vb|
|
||||||
vb.gui = true
|
vb.gui = true
|
||||||
vb.memory = "2048"
|
vb.memory = 2048
|
||||||
|
vb.cpus = 2
|
||||||
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
|
||||||
end
|
end
|
||||||
config.vm.synced_folder '.', '/vagrant', disabled: true
|
config.vm.synced_folder '.', '/vagrant', disabled: true
|
||||||
|
@ -67,6 +67,7 @@ useradd -m -d /home/p4 -s /bin/bash p4
|
|||||||
echo "p4:p4" | chpasswd
|
echo "p4:p4" | chpasswd
|
||||||
echo "p4 ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/99_p4
|
echo "p4 ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/99_p4
|
||||||
chmod 440 /etc/sudoers.d/99_p4
|
chmod 440 /etc/sudoers.d/99_p4
|
||||||
|
usermod -aG vboxsf p4
|
||||||
|
|
||||||
cd /usr/share/lubuntu/wallpapers/
|
cd /usr/share/lubuntu/wallpapers/
|
||||||
cp /home/vagrant/p4-logo.png .
|
cp /home/vagrant/p4-logo.png .
|
||||||
|
@ -45,6 +45,8 @@ sudo make install
|
|||||||
sudo ldconfig
|
sudo ldconfig
|
||||||
unset LDFLAGS
|
unset LDFLAGS
|
||||||
cd ..
|
cd ..
|
||||||
|
# Install gRPC Python Package
|
||||||
|
sudo pip install grpcio
|
||||||
|
|
||||||
# BMv2 deps (needed by PI)
|
# BMv2 deps (needed by PI)
|
||||||
git clone https://github.com/p4lang/behavioral-model.git
|
git clone https://github.com/p4lang/behavioral-model.git
|
||||||
|
Loading…
x
Reference in New Issue
Block a user