Adding P4D2_2018_East Folder (#116)
* Copying P4D2 Fall 2017 into P4D2 2018 East. * Updated P4D2_2018_East VM. Added vagrant URL workaround, cdrom to VM. Updated to latest commits of BMV2, p4c, PI. Known issue with p4runtime exercise. * Applied patch from @antoninbas in and updated solution
This commit is contained in:
30
P4D2_2018_East/exercises/p4runtime/p4runtime_lib/bmv2.py
Normal file
30
P4D2_2018_East/exercises/p4runtime/p4runtime_lib/bmv2.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# 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
|
||||
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)
|
||||
119
P4D2_2018_East/exercises/p4runtime/p4runtime_lib/convert.py
Normal file
119
P4D2_2018_East/exercises/p4runtime/p4runtime_lib/convert.py
Normal file
@@ -0,0 +1,119 @@
|
||||
# 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 socket
|
||||
|
||||
import math
|
||||
|
||||
'''
|
||||
This package contains several helper functions for encoding to and decoding from byte strings:
|
||||
- integers
|
||||
- IPv4 address strings
|
||||
- Ethernet address strings
|
||||
'''
|
||||
|
||||
mac_pattern = re.compile('^([\da-fA-F]{2}:){5}([\da-fA-F]{2})$')
|
||||
def matchesMac(mac_addr_string):
|
||||
return mac_pattern.match(mac_addr_string) is not None
|
||||
|
||||
def encodeMac(mac_addr_string):
|
||||
return mac_addr_string.replace(':', '').decode('hex')
|
||||
|
||||
def decodeMac(encoded_mac_addr):
|
||||
return ':'.join(s.encode('hex') for s in encoded_mac_addr)
|
||||
|
||||
ip_pattern = re.compile('^(\d{1,3}\.){3}(\d{1,3})$')
|
||||
def matchesIPv4(ip_addr_string):
|
||||
return ip_pattern.match(ip_addr_string) is not None
|
||||
|
||||
def encodeIPv4(ip_addr_string):
|
||||
return socket.inet_aton(ip_addr_string)
|
||||
|
||||
def decodeIPv4(encoded_ip_addr):
|
||||
return socket.inet_ntoa(encoded_ip_addr)
|
||||
|
||||
def bitwidthToBytes(bitwidth):
|
||||
return int(math.ceil(bitwidth / 8.0))
|
||||
|
||||
def encodeNum(number, bitwidth):
|
||||
byte_len = bitwidthToBytes(bitwidth)
|
||||
num_str = '%x' % number
|
||||
if number >= 2 ** bitwidth:
|
||||
raise Exception("Number, %d, does not fit in %d bits" % (number, bitwidth))
|
||||
return ('0' * (byte_len * 2 - len(num_str)) + num_str).decode('hex')
|
||||
|
||||
def decodeNum(encoded_number):
|
||||
return int(encoded_number.encode('hex'), 16)
|
||||
|
||||
def encode(x, bitwidth):
|
||||
'Tries to infer the type of `x` and encode it'
|
||||
byte_len = bitwidthToBytes(bitwidth)
|
||||
if (type(x) == list or type(x) == tuple) and len(x) == 1:
|
||||
x = x[0]
|
||||
encoded_bytes = None
|
||||
if type(x) == str:
|
||||
if matchesMac(x):
|
||||
encoded_bytes = encodeMac(x)
|
||||
elif matchesIPv4(x):
|
||||
encoded_bytes = encodeIPv4(x)
|
||||
else:
|
||||
# Assume that the string is already encoded
|
||||
encoded_bytes = x
|
||||
elif type(x) == int:
|
||||
encoded_bytes = encodeNum(x, bitwidth)
|
||||
else:
|
||||
raise Exception("Encoding objects of %r is not supported" % type(x))
|
||||
assert(len(encoded_bytes) == byte_len)
|
||||
return encoded_bytes
|
||||
|
||||
if __name__ == '__main__':
|
||||
# TODO These tests should be moved out of main eventually
|
||||
mac = "aa:bb:cc:dd:ee:ff"
|
||||
enc_mac = encodeMac(mac)
|
||||
assert(enc_mac == '\xaa\xbb\xcc\xdd\xee\xff')
|
||||
dec_mac = decodeMac(enc_mac)
|
||||
assert(mac == dec_mac)
|
||||
|
||||
ip = "10.0.0.1"
|
||||
enc_ip = encodeIPv4(ip)
|
||||
assert(enc_ip == '\x0a\x00\x00\x01')
|
||||
dec_ip = decodeIPv4(enc_ip)
|
||||
assert(ip == dec_ip)
|
||||
|
||||
num = 1337
|
||||
byte_len = 5
|
||||
enc_num = encodeNum(num, byte_len * 8)
|
||||
assert(enc_num == '\x00\x00\x00\x05\x39')
|
||||
dec_num = decodeNum(enc_num)
|
||||
assert(num == dec_num)
|
||||
|
||||
assert(matchesIPv4('10.0.0.1'))
|
||||
assert(not matchesIPv4('10.0.0.1.5'))
|
||||
assert(not matchesIPv4('1000.0.0.1'))
|
||||
assert(not matchesIPv4('10001'))
|
||||
|
||||
assert(encode(mac, 6 * 8) == enc_mac)
|
||||
assert(encode(ip, 4 * 8) == enc_ip)
|
||||
assert(encode(num, 5 * 8) == enc_num)
|
||||
assert(encode((num,), 5 * 8) == enc_num)
|
||||
assert(encode([num], 5 * 8) == enc_num)
|
||||
|
||||
num = 256
|
||||
byte_len = 2
|
||||
try:
|
||||
enc_num = encodeNum(num, 8)
|
||||
raise Exception("expected exception")
|
||||
except Exception as e:
|
||||
print e
|
||||
183
P4D2_2018_East/exercises/p4runtime/p4runtime_lib/helper.py
Normal file
183
P4D2_2018_East/exercises/p4runtime/p4runtime_lib/helper.py
Normal file
@@ -0,0 +1,183 @@
|
||||
# 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
|
||||
|
||||
from p4runtime_lib.convert import encode
|
||||
|
||||
class P4InfoHelper(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_tables_id(name_string) or get_actions_id(name_string)
|
||||
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
|
||||
# e.g. get_tables_name(id) or get_actions_name(id)
|
||||
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))
|
||||
|
||||
def get_match_field(self, table_name, name=None, id=None):
|
||||
for t in self.p4info.tables:
|
||||
pre = t.preamble
|
||||
if pre.name == table_name:
|
||||
for mf in t.match_fields:
|
||||
if name is not None:
|
||||
if mf.name == name:
|
||||
return mf
|
||||
elif id is not None:
|
||||
if mf.id == id:
|
||||
return mf
|
||||
raise AttributeError("%r has no attribute %r" % (table_name, name if name is not None else id))
|
||||
|
||||
def get_match_field_id(self, table_name, match_field_name):
|
||||
return self.get_match_field(table_name, name=match_field_name).id
|
||||
|
||||
def get_match_field_name(self, table_name, match_field_id):
|
||||
return self.get_match_field(table_name, id=match_field_id).name
|
||||
|
||||
def get_match_field_pb(self, table_name, match_field_name, value):
|
||||
p4info_match = self.get_match_field(table_name, match_field_name)
|
||||
bitwidth = p4info_match.bitwidth
|
||||
p4runtime_match = p4runtime_pb2.FieldMatch()
|
||||
p4runtime_match.field_id = p4info_match.id
|
||||
match_type = p4info_match.match_type
|
||||
if match_type == p4info_pb2.MatchField.VALID:
|
||||
valid = p4runtime_match.valid
|
||||
valid.value = bool(value)
|
||||
elif match_type == p4info_pb2.MatchField.EXACT:
|
||||
exact = p4runtime_match.exact
|
||||
exact.value = encode(value, bitwidth)
|
||||
elif match_type == p4info_pb2.MatchField.LPM:
|
||||
lpm = p4runtime_match.lpm
|
||||
lpm.value = encode(value[0], bitwidth)
|
||||
lpm.prefix_len = value[1]
|
||||
elif match_type == p4info_pb2.MatchField.TERNARY:
|
||||
lpm = p4runtime_match.ternary
|
||||
lpm.value = encode(value[0], bitwidth)
|
||||
lpm.mask = encode(value[1], bitwidth)
|
||||
elif match_type == p4info_pb2.MatchField.RANGE:
|
||||
lpm = p4runtime_match.range
|
||||
lpm.low = encode(value[0], bitwidth)
|
||||
lpm.high = encode(value[1], bitwidth)
|
||||
else:
|
||||
raise Exception("Unsupported match type with type %r" % match_type)
|
||||
return p4runtime_match
|
||||
|
||||
def get_match_field_value(self, match_field):
|
||||
match_type = match_field.WhichOneof("field_match_type")
|
||||
if match_type == 'valid':
|
||||
return match_field.valid.value
|
||||
elif match_type == 'exact':
|
||||
return match_field.exact.value
|
||||
elif match_type == 'lpm':
|
||||
return (match_field.lpm.value, match_field.lpm.prefix_len)
|
||||
elif match_type == 'ternary':
|
||||
return (match_field.ternary.value, match_field.ternary.mask)
|
||||
elif match_type == 'range':
|
||||
return (match_field.range.low, match_field.range.high)
|
||||
else:
|
||||
raise Exception("Unsupported match type with type %r" % match_type)
|
||||
|
||||
def get_action_param(self, action_name, name=None, id=None):
|
||||
for a in self.p4info.actions:
|
||||
pre = a.preamble
|
||||
if pre.name == action_name:
|
||||
for p in a.params:
|
||||
if name is not None:
|
||||
if p.name == name:
|
||||
return p
|
||||
elif id is not None:
|
||||
if p.id == id:
|
||||
return p
|
||||
raise AttributeError("action %r has no param %r" % (action_name, name if name is not None else id))
|
||||
|
||||
def get_action_param_id(self, action_name, param_name):
|
||||
return self.get_action_param(action_name, name=param_name).id
|
||||
|
||||
def get_action_param_name(self, action_name, param_id):
|
||||
return self.get_action_param(action_name, id=param_id).name
|
||||
|
||||
def get_action_param_pb(self, action_name, param_name, value):
|
||||
p4info_param = self.get_action_param(action_name, param_name)
|
||||
p4runtime_param = p4runtime_pb2.Action.Param()
|
||||
p4runtime_param.param_id = p4info_param.id
|
||||
p4runtime_param.value = encode(value, p4info_param.bitwidth)
|
||||
return p4runtime_param
|
||||
|
||||
def buildTableEntry(self,
|
||||
table_name,
|
||||
match_fields={},
|
||||
action_name=None,
|
||||
action_params={}):
|
||||
table_entry = p4runtime_pb2.TableEntry()
|
||||
table_entry.table_id = self.get_tables_id(table_name)
|
||||
if match_fields:
|
||||
table_entry.match.extend([
|
||||
self.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 = self.get_actions_id(action_name)
|
||||
if action_params:
|
||||
action.params.extend([
|
||||
self.get_action_param_pb(action_name, field_name, value)
|
||||
for field_name, value in action_params.iteritems()
|
||||
])
|
||||
return table_entry
|
||||
88
P4D2_2018_East/exercises/p4runtime/p4runtime_lib/switch.py
Normal file
88
P4D2_2018_East/exercises/p4runtime/p4runtime_lib/switch.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# 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
|
||||
|
||||
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)
|
||||
self.client_stub = p4runtime_pb2.P4RuntimeStub(self.channel)
|
||||
|
||||
@abstractmethod
|
||||
def buildDeviceConfig(self, **kwargs):
|
||||
return p4config_pb2.P4DeviceConfig()
|
||||
|
||||
def SetForwardingPipelineConfig(self, p4info, dry_run=False, **kwargs):
|
||||
device_config = self.buildDeviceConfig(**kwargs)
|
||||
request = p4runtime_pb2.SetForwardingPipelineConfigRequest()
|
||||
request.device_id = self.device_id
|
||||
config = request.config
|
||||
config.p4info.CopyFrom(p4info)
|
||||
config.p4_device_config = device_config.SerializeToString()
|
||||
request.action = p4runtime_pb2.SetForwardingPipelineConfigRequest.VERIFY_AND_COMMIT
|
||||
if dry_run:
|
||||
print "P4 Runtime SetForwardingPipelineConfig:", request
|
||||
else:
|
||||
self.client_stub.SetForwardingPipelineConfig(request)
|
||||
|
||||
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:
|
||||
self.client_stub.Write(request)
|
||||
|
||||
def ReadTableEntries(self, table_id=None, dry_run=False):
|
||||
request = p4runtime_pb2.ReadRequest()
|
||||
request.device_id = self.device_id
|
||||
entity = request.entities.add()
|
||||
table_entry = entity.table_entry
|
||||
if table_id is not None:
|
||||
table_entry.table_id = table_id
|
||||
else:
|
||||
table_entry.table_id = 0
|
||||
if dry_run:
|
||||
print "P4 Runtime Read:", request
|
||||
else:
|
||||
for response in self.client_stub.Read(request):
|
||||
yield response
|
||||
|
||||
def ReadCounters(self, counter_id=None, index=None, dry_run=False):
|
||||
request = p4runtime_pb2.ReadRequest()
|
||||
request.device_id = self.device_id
|
||||
entity = request.entities.add()
|
||||
counter_entry = entity.counter_entry
|
||||
if counter_id is not None:
|
||||
counter_entry.counter_id = counter_id
|
||||
else:
|
||||
counter_entry.counter_id = 0
|
||||
if index is not None:
|
||||
counter_entry.index = index
|
||||
if dry_run:
|
||||
print "P4 Runtime Read:", request
|
||||
else:
|
||||
for response in self.client_stub.Read(request):
|
||||
yield response
|
||||
Reference in New Issue
Block a user