Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some changes to enable proto/ptf test to pass on system using Python3 #543

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion proto/ptf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ inject and receive test packets.

3. Running the PTF tests (in a second terminal)

sudo python ptf_runner.py \
sudo python3 ptf_runner.py \
--device-config config.bin --p4info p4info.proto.txt \
--ptfdir l3_host_fwd/test/ --port-map bmv2/port_map.json

Expand Down
65 changes: 34 additions & 31 deletions proto/ptf/base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,23 +48,23 @@ def __get__(self, instance, owner):
return partial(self.func, instance,
*(self.args or ()), **(self.keywords or {}))

# Convert integer (with length) to binary byte string
# Equivalent to Python 3.2 int.to_bytes
# See
# https://stackoverflow.com/questions/16022556/has-python-3-to-bytes-been-back-ported-to-python-2-7
# TODO: When P4Runtime implementation is ready for it, use
# minimum-length byte sequences to represent integers. For unsigned
# integers, this should only require removing the zfill() call below.
def stringify(n, length):
def stringify(n, length=0):
"""Take a non-negative integer 'n' as the first parameter, and a
non-negative integer 'length' in units of _bytes_ as the second
parameter. Return a string with binary contents expected by the
Python P4Runtime client operations. If 'n' does not fit in
'length' bytes, it is represented in the fewest number of bytes it
does fit into without loss of precision. It always returns a
string at least one byte long, even if value=width=0."""
h = '%x' % n
s = ('0'*(len(h) % 2) + h).zfill(length*2).decode('hex')
parameter (it defaults to 0 if not provided). Return a string
with binary contents expected by the Python P4Runtime client
operations. If 'n' does not fit in 'length' bytes, it is
represented in the fewest number of bytes it does fit into without
loss of precision. It always returns a string at least one byte
long, even if n=length=0."""
if length == 0 and n == 0:
length = 1
else:
n_size_bits = n.bit_length()
n_size_bytes = (n_size_bits + 7) // 8
if n_size_bytes > length:
length = n_size_bytes
s = n.to_bytes(length, byteorder='big')
return s

def ipv4_to_binary(addr):
Expand All @@ -74,10 +74,10 @@ def ipv4_to_binary(addr):
client operations."""
bytes_ = [int(b, 10) for b in addr.split('.')]
assert len(bytes_) == 4
# Note: The chr(b) call below will throw exception if any b is
# outside of the range [0, 255]], so no need to add a separate
# check for that here.
return "".join(chr(b) for b in bytes_)
# Note: The bytes() call below will throw exception if any
# elements of bytes_ is outside of the range [0, 255]], so no need
# to add a separate check for that here.
return bytes(bytes_)

def mac_to_binary(addr):
"""Take an argument 'addr' containing an Ethernet MAC address written
Expand All @@ -87,10 +87,10 @@ def mac_to_binary(addr):
operations."""
bytes_ = [int(b, 16) for b in addr.split(':')]
assert len(bytes_) == 6
# Note: The chr(b) call below will throw exception if any b is
# outside of the range [0, 255]], so no need to add a separate
# check for that here.
return "".join(chr(b) for b in bytes_)
# Note: The bytes() call below will throw exception if any
# elements of bytes_ is outside of the range [0, 255]], so no need
# to add a separate check for that here.
return bytes(bytes_)

# Used to indicate that the gRPC error Status object returned by the server has
# an incorrect format.
Expand Down Expand Up @@ -402,21 +402,24 @@ def add_to(self, mf_id, mk):
mf = mk.add()
mf.field_id = mf_id
mf.lpm.prefix_len = self.pLen
mf.lpm.value = ''
assert isinstance(self.v, bytes)
orig_v_list = list(self.v)
mod_v_list = []

# P4Runtime now has strict rules regarding ternary matches: in the
# case of LPM, trailing bits in the value (after prefix) must be set
# to 0.
first_byte_masked = self.pLen / 8
first_byte_masked = self.pLen // 8
for i in range(first_byte_masked):
mf.lpm.value += self.v[i]
if first_byte_masked == len(self.v):
mod_v_list.append(orig_v_list[i])
if first_byte_masked == len(orig_v_list):
mf.lpm.value = bytes(mod_v_list)
return
r = self.pLen % 8
mf.lpm.value += chr(
ord(self.v[first_byte_masked]) & (0xff << (8 - r)))
for i in range(first_byte_masked + 1, len(self.v)):
mf.lpm.value += '\x00'
mod_v_list.append(orig_v_list[first_byte_masked] & (0xff << (8 - r)))
for i in range(first_byte_masked + 1, len(orig_v_list)):
mod_v_list.append(0)
mf.lpm.value = bytes(mod_v_list)

class Ternary(MF):
def __init__(self, name, v, mask):
Expand Down
2 changes: 1 addition & 1 deletion proto/ptf/bmv2/gen_bmv2_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3

# Copyright 2013-present Barefoot Networks, Inc.
#
Expand Down
48 changes: 28 additions & 20 deletions proto/ptf/l3_host_fwd/test/l3_host_fwd.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3

# Copyright 2013-present Barefoot Networks, Inc.
#
Expand Down Expand Up @@ -27,7 +27,7 @@

from google.rpc import code_pb2

from base_test import P4RuntimeTest, autocleanup, stringify, ipv4_to_binary
from base_test import P4RuntimeTest, autocleanup, stringify, ipv4_to_binary, mac_to_binary

class L3HostFwdTest(P4RuntimeTest):
pass
Expand All @@ -36,13 +36,15 @@ class FwdTest(L3HostFwdTest):
@autocleanup
def runTest(self):
ip_dst_addr = "10.0.0.1"
ip_dst_addr_str = ipv4_to_binary(ip_dst_addr)
ip_dst_addr_bin = ipv4_to_binary(ip_dst_addr)
ig_port = self.swports(1)
eg_port = self.swports(2)
# port is 9-bit in v1model, i.e. 2 bytes
eg_port_str = stringify(eg_port, 2)
smac = "\xee\xcd\x00\x7e\x70\x00"
dmac = "\xee\x30\xca\x9d\x1e\x00"
smac = "ee:cd:00:7e:70:00"
dmac = "ee:30:ca:9d:1e:00"
smac_bin = mac_to_binary(smac)
dmac_bin = mac_to_binary(dmac)

# we do not care about the src mac address or the src IP address
pkt = testutils.simple_tcp_packet(
Expand All @@ -54,9 +56,9 @@ def runTest(self):

# add a forwarding entry
self.send_request_add_entry_to_action(
"l3_host_fwd", [self.Exact("hdr.ipv4.dst_addr", ip_dst_addr_str)],
"l3_host_fwd", [self.Exact("hdr.ipv4.dst_addr", ip_dst_addr_bin)],
"set_nexthop",
[("port", eg_port_str), ("smac", smac), ("dmac", dmac)])
[("port", eg_port_str), ("smac", smac_bin), ("dmac", dmac_bin)])

# check that the entry is hit and that no other packets are received
exp_pkt = testutils.simple_tcp_packet(
Expand All @@ -67,18 +69,21 @@ def runTest(self):
class DupEntryTest(L3HostFwdTest):
@autocleanup
def runTest(self):
ip_dst_addr_str = "\x0a\x00\x00\x01"
ip_dst_addr = "10.0.0.1"
ip_dst_addr_bin = ipv4_to_binary(ip_dst_addr)
eg_port = self.swports(2)
eg_port_str = stringify(eg_port, 2)
smac = "\xee\xcd\x00\x7e\x70\x00"
dmac = "\xee\x30\xca\x9d\x1e\x00"
smac = "ee:cd:00:7e:70:00"
dmac = "ee:30:ca:9d:1e:00"
smac_bin = mac_to_binary(smac)
dmac_bin = mac_to_binary(dmac)

def add_entry_once():
self.send_request_add_entry_to_action(
"l3_host_fwd",
[self.Exact("hdr.ipv4.dst_addr", ip_dst_addr_str)],
[self.Exact("hdr.ipv4.dst_addr", ip_dst_addr_bin)],
"set_nexthop",
[("port", eg_port_str), ("smac", smac), ("dmac", dmac)])
[("port", eg_port_str), ("smac", smac_bin), ("dmac", dmac_bin)])

add_entry_once()
with self.assertP4RuntimeError():
Expand All @@ -87,28 +92,31 @@ def add_entry_once():
class BadMatchKeyTest(L3HostFwdTest):
@autocleanup
def runTest(self):
ip_dst_addr_str = "\x0a\x00\x00\x01"
bad_ip_dst_addr_str = "\x0a\x00\x00" # missing one byte
ip_dst_addr = "10.0.0.1"
ip_dst_addr_bin = ipv4_to_binary(ip_dst_addr)
bad_ip_dst_addr_bin = ip_dst_addr_bin[0:3] # missing one byte
eg_port = self.swports(2)
eg_port_str = stringify(eg_port, 2)
smac = "\xee\xcd\x00\x7e\x70\x00"
dmac = "\xee\x30\xca\x9d\x1e\x00"
smac = "ee:cd:00:7e:70:00"
dmac = "ee:30:ca:9d:1e:00"
smac_bin = mac_to_binary(smac)
dmac_bin = mac_to_binary(dmac)

# missing one byte
with self.assertP4RuntimeError(code_pb2.INVALID_ARGUMENT):
self.send_request_add_entry_to_action(
"l3_host_fwd",
[self.Exact("hdr.ipv4.dst_addr", bad_ip_dst_addr_str)],
[self.Exact("hdr.ipv4.dst_addr", bad_ip_dst_addr_bin)],
"set_nexthop",
[("port", eg_port_str), ("smac", smac), ("dmac", dmac)])
[("port", eg_port_str), ("smac", smac_bin), ("dmac", dmac_bin)])

# unexpected match type
with self.assertP4RuntimeError(code_pb2.INVALID_ARGUMENT):
self.send_request_add_entry_to_action(
"l3_host_fwd",
[self.Lpm("hdr.ipv4.dst_addr", ip_dst_addr_str, 24)],
[self.Lpm("hdr.ipv4.dst_addr", ip_dst_addr_bin, 24)],
"set_nexthop",
[("port", eg_port_str), ("smac", smac), ("dmac", dmac)])
[("port", eg_port_str), ("smac", smac_bin), ("dmac", dmac_bin)])

class BadChecksumTest(L3HostFwdTest):
@autocleanup
Expand Down
6 changes: 3 additions & 3 deletions proto/ptf/ptf_runner.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3

# Copyright 2013-present Barefoot Networks, Inc.
#
Expand Down Expand Up @@ -53,7 +53,7 @@ def check_ifaces(ifaces):
'''
Checks that required interfaces exist.
'''
ip_out = subprocess.check_output(['ip', 'link'])
ip_out = subprocess.check_output(['ip', 'link'], text=True)
# 'ip link' returns a list of interfaces as
# <idx>: <iface>: ...
# <idx>: <veth>@<peer veth>: ...
Expand Down Expand Up @@ -105,7 +105,7 @@ def run_test(config_path, p4info_path, grpc_addr, device_id,
iface_name = entry["iface_name"]
port_map[p4_port] = iface_name

if not check_ifaces(port_map.values()):
if not check_ifaces(list(port_map.values())):
error("Some interfaces are missing")
return False

Expand Down