跳转到主要内容

P4Runtime客户端库

项目描述

Finsy P4Runtime控制器库

pypi documentation ci codecov codespace

Finsy是一个用Python编写的P4Runtime控制器库,使用asyncio。Finsy包括对gNMI的支持。

请查看示例目录中的某些演示程序。

安装

Finsy需要Python 3.10或更高版本。要安装最新版本,请输入pip install finsy

P4Runtime脚本

使用Finsy,您可以编写一个Python脚本,用于读取/写入单个交换机的P4Runtime实体。

以下是一个完整的示例,用于从交换机检索P4Info

import finsy as fy

async def main():
    async with fy.Switch("sw1", "127.0.0.1:50001") as sw1:
        # Print out a description of the switch's P4Info, if one is configured.
        print(sw1.p4info)

fy.run(main())

以下是一个打印所有非默认表条目的示例。

import finsy as fy

async def main():
    async with fy.Switch("sw1", "127.0.0.1:50001") as sw1:
        # Do a wildcard read for table entries.
        async for entry in sw1.read(fy.P4TableEntry()):
            print(entry)

fy.run(main())

P4Runtime控制器

您还可以编写一个独立管理多个交换机的P4Runtime控制器。您的控制器可以通过更改P4表的内容来响应交换机的事件。

每个交换机由一个异步的ready_handler函数管理。您的ready_handler函数可以读取或更新交换机中的各种P4Runtime实体。它还可以创建任务来监听数据包或摘要。

当您向交换机写入P4Runtime更新时,您使用一元运算符(+、-、~)来指定操作:插入(+)、删除(-)或修改(~)。

import finsy as fy

async def ready_handler(sw: fy.Switch):
    await sw.delete_all()
    await sw.write(
        [
            # Insert (+) multicast group with ports 1, 2, 3 and CONTROLLER.
            +fy.P4MulticastGroupEntry(1, replicas=[1, 2, 3, 255]),
            # Modify (~) default table entry to flood all unmatched packets.
            ~fy.P4TableEntry(
                "ipv4",
                action=fy.Action("flood"),
                is_default_action=True,
            ),
        ]
    )

    async for packet in sw.read_packets():
        print(f"{sw.name}: {packet}")

使用SwitchOptions类来指定每个交换机的设置,包括p4info/p4blob和ready_handler。使用Controller类来驱动多个交换机连接。每个交换机在P4Runtime连接建立后会回调到您的ready_handler函数。

from pathlib import Path

options = fy.SwitchOptions(
    p4info=Path("hello.p4info.txt"),
    p4blob=Path("hello.json"),
    ready_handler=ready_handler,
)

controller = fy.Controller([
    fy.Switch("sw1", "127.0.0.1:50001", options),
    fy.Switch("sw2", "127.0.0.1:50002", options),
    fy.Switch("sw3", "127.0.0.1:50003", options),
])

fy.run(controller.run())

您的ready_handler可以使用Switch.create_task方法启动并发任务。以这种方式创建的任务将由交换机对象管理其生命周期。

如果交换机断开连接或其角色更改为备份,则运行您的ready_handler(及其启动的所有任务)的任务将被取消,并且ready_handler将重新开始。

有关更多示例,请参阅示例目录

交换机读写API

Switch类提供了与P4Runtime交换机交互的API。您将使用一个“准备处理程序”函数来控制Switch对象。准备处理程序是在交换机准备好接受命令时被调用的异步函数。

您的ready_handler通常会向交换机写入一些控制实体,然后监听传入的事件并对它们做出反应,进行更多写入。您偶尔也可以从交换机读取实体。

当您的ready_handler被调用时,已经建立了一个P4Runtime通道,完成了客户端仲裁,并将管道配置为在SwitchOptions中指定的配置。

以下是一个示例骨架程序。ready_handler被命名为ready()

async def ready(switch: fy.Switch):
    # Check if switch is the primary. If not, we may want to proceed
    # in read-only mode. In this example, ignore switch if it's a backup.
    if not switch.is_primary:
        return

    # If we're reconnecting to a switch, it will already have runtime state.
    # In this example, we just delete all entities and start over.
    await switch.delete_all()

    # Provision the pipeline with one or more `write` transactions. Each
    # `write` is a single WriteRequest which may contain multiple updates.
    await switch.write(
        # [Next section will cover what goes here.]
    )

    # Listen for events and respond to them. This "infinite" loop will
    # continue until the Switch disconnects, changes primary/backup status,
    # or the controller is stopped.
    async for packet in switch.read_packets():
        await handle_packet(switch, packet)

Switch类提供了一个switch.create_task方法来启动一个受管理任务。任务允许您在同一个交换机上执行并发操作。我们可以将上面的最后一段代码(无限循环读取数据包)作为单独的任务来编写。准备处理程序函数可以提前返回;它创建的任何任务仍然会运行。

写入

使用write()方法写入一个或多个P4Runtime更新和数据包。

P4Runtime更新支持三种操作之一:INSERT、MODIFY或DELETE。某些实体支持所有三种操作。其他实体仅支持MODIFY。

实体 允许的操作 相关类
P4TableEntry INSERT、MODIFY、DELETE MatchActionIndirectActionP4MeterConfigP4CounterDataP4MeterCounterData
P4ActionProfileMember INSERT、MODIFY、DELETE
P4ActionProfileGroup INSERT、MODIFY、DELETE P4Member
P4MulticastGroupEntry INSERT、MODIFY、DELETE
P4CloneSessionEntry INSERT、MODIFY、DELETE
P4DigestEntry INSERT、MODIFY、DELETE
P4ExternEntry INSERT、MODIFY、DELETE
P4RegisterEntry MODIFY
P4CounterEntry MODIFY P4CounterData
P4DirectCounterEntry MODIFY P4CounterData
P4MeterEntry MODIFY P4MeterConfigP4MeterCounterData
P4DirectMeterEntry MODIFY
P4ValueSetEntry MODIFY P4ValueSetMember

插入/修改/删除更新

要指定操作,请使用一元运算符+(INSERT)、~(MODIFY)或-(DELETE)。如果您没有指定操作,则write将引发ValueError异常。

以下是一个示例,说明如何在同一个WriteRequest中插入和删除两个不同的实体。

await switch.write([
    +fy.P4TableEntry(          # unary + means INSERT
        "ipv4", 
        match=fy.Match(dest="192.168.1.0/24"),
        action=fy.Action("forward", port=1),
    ),
    -fy.P4TableEntry(          # unary - means DELETE
        "ipv4", 
        match=fy.Match(dest="192.168.2.0/24"),
        action=fy.Action("forward", port=2),
    ),
])

您不应该在同一个WriteRequest中插入、修改或删除相同的条目。

如果您对所有实体执行的是相同的操作,则可以使用Switch的insertdeletemodify方法。

await switch.insert([
    fy.P4MulticastGroupEntry(1, replicas=[1, 2, 3]),
    fy.P4MulticastGroupEntry(2, replicas=[4, 5, 6]),
])

仅修改更新

对于仅支持修改操作的实体,您无需指定操作。(您可以可选地使用~。)

await switch.write([
    fy.P4RegisterEntry("reg1", index=0, data=0),
    fy.P4RegisterEntry("reg1", index=1, data=1),
    fy.P4RegisterEntry("reg1", index=2, data=2),
])

您还可以使用modify方法

await switch.modify([
    fy.P4RegisterEntry("reg1", index=0, data=0),
    fy.P4RegisterEntry("reg1", index=1, data=1),
    fy.P4RegisterEntry("reg1", index=2, data=2),
])

如果您将仅修改实体传递给insertdelete方法,则P4Runtime服务器将返回错误。

发送数据包

使用write方法发送数据包。

await switch.write([fy.P4PacketOut(b"a payload.....", port=3)])

您可以在同一个调用中包含其他实体。任何非更新对象(例如 P4PacketOut、P4DigestListAck)将 WriteRequest 之前发送。

监听数据包

要接收数据包,请使用异步迭代器 Switch.read_packets()。在这个例子中,pkt 是一个 P4PacketIn 对象。

read_packets 可以过滤特定 eth_type

# Read packets filtering only for ARP (eth_type == 0x0806).
async for pkt in switch.read_packets(eth_types={0x0806}):
    # You can access the packet payload `pkt.payload` or any metadata value,
    # e.g. `pkt['ingress_port']`
    print(pkt.payload)
    print(pkt['ingress_port'])

监听摘要

要接收摘要,请使用异步迭代器 Switch.read_digests。您必须指定您的 P4 程序中摘要的名称。

async for digest in switch.read_digests("digest_t"):
    # You can access the digest metadata e.g. `digest['ingress_port']`
    # Your code may need to update table entries based on the digest data.
    # To ack the digest, write `digest.ack()`.
    await switch.write([entry, ...])
    await switch.write([digest.ack()])

要确认摘要条目,您可以编写 digest.ack()

监听空闲超时

要接收空闲超时通知,请使用异步迭代器 Switch.read_idle_timeouts。您将接收到一个包含多个表条目的 P4IdleTimeoutNotification,每个超时的条目都有一个。

async for timeout in switch.read_idle_timeouts():
    for entry in timeout.table_entry:
        print(timeout.timestamp, entry)

其他事件

P4 交换机可能使用 EventEmitter API 报告其他事件。有关事件类型,请参阅 SwitchEvent 类。每个交换机都有一个 switch.ee 属性,允许您的代码注册事件回调。

开发和测试

执行以下步骤以设置您的本地环境进行 Finsy 开发,或尝试 codespace。Finsy 需要 Python 3.10 或更高版本。如果没有安装 poetry,请按照 这些说明 安装它。

克隆并准备虚拟环境

poetry install 命令将所有开发依赖项安装到虚拟环境(venv)中。

$ git clone https://github.com/byllyfish/finsy.git
$ cd finsy
$ python3 -m venv .venv
$ poetry install

运行单元测试

从存储库的顶级目录运行 pytest 时,您将运行单元测试。

$ poetry run pytest

运行集成测试

当您在 examples 目录中运行 pytest 时,您将运行集成测试而不是单元测试。集成测试在 Mininet 网络上运行示例程序。需要 Docker 或 podman。

$ cd examples
$ poetry run pytest

项目详情


下载文件

下载您平台上的文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。

源分发

finsy-0.26.0.tar.gz (195.4 kB 查看哈希值)

上传时间

构建分发

finsy-0.26.0-py3-none-any.whl (238.0 kB 查看哈希值)

上传时间 Python 3

由以下支持