Skip to content
p4_manager.py 201 KiB
Newer Older
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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.

"""
P4Runtime manager.
"""

import enum
import os
import queue
import time
import logging
from collections import Counter, OrderedDict
from threading import Thread
from tabulate import tabulate
from p4.v1 import p4runtime_pb2
from p4.config.v1 import p4info_pb2

try:
    from .p4_client import P4RuntimeClient, P4RuntimeException,\
        P4RuntimeWriteException, WriteOperation, parse_p4runtime_error
    from .p4_context import P4RuntimeEntity, P4Type, Context
    from .p4_global_options import make_canonical_if_option_set
    from .p4_common import encode,\
        parse_resource_string_from_json, parse_resource_integer_from_json,\
        parse_resource_bytes_from_json, parse_match_operations_from_json,\
        parse_action_parameters_from_json, parse_integer_list_from_json
    from .p4_exception import UserError, InvalidP4InfoError
except ImportError:
    from p4_client import P4RuntimeClient, P4RuntimeException,\
        P4RuntimeWriteException, WriteOperation, parse_p4runtime_error
    from p4_context import P4RuntimeEntity, P4Type, Context
    from p4_global_options import make_canonical_if_option_set
    from p4_common import encode,\
        parse_resource_string_from_json, parse_resource_integer_from_json,\
        parse_resource_bytes_from_json, parse_match_operations_from_json,\
        parse_action_parameters_from_json, parse_integer_list_from_json
    from p4_exception import UserError, InvalidP4InfoError

# Logger instance
LOGGER = logging.getLogger(__name__)

# Global P4Runtime context
CONTEXT = Context()

# Global P4Runtime client

# Constant P4 entities
KEY_TABLE = "table"
KEY_ACTION = "action"
KEY_ACTION_PROFILE = "action_profile"
KEY_COUNTER = "counter"
KEY_DIR_COUNTER = "direct_counter"
KEY_METER = "meter"
KEY_DIR_METER = "direct_meter"
KEY_CTL_PKT_METADATA = "controller_packet_metadata"


def get_context():
    """
    Return P4 context.

    :return: context object
    """
    return CONTEXT

def get_table_type(table):
    """
    Assess the type of P4 table based upon the matching scheme.

    :param table: P4 table
    :return: P4 table type
    """
    for m_f in table.match_fields:
        if m_f.match_type == p4info_pb2.MatchField.EXACT:
            return p4info_pb2.MatchField.EXACT
        if m_f.match_type == p4info_pb2.MatchField.LPM:
            return p4info_pb2.MatchField.LPM
        if m_f.match_type == p4info_pb2.MatchField.TERNARY:
            return p4info_pb2.MatchField.TERNARY
        if m_f.match_type == p4info_pb2.MatchField.RANGE:
            return p4info_pb2.MatchField.RANGE
        if m_f.match_type == p4info_pb2.MatchField.OPTIONAL:
            return p4info_pb2.MatchField.OPTIONAL
    return None


def match_type_to_str(match_type):
    """
    Convert table match type to string.

    :param match_type: table match type object
    :return: table match type string
    """
    if match_type == p4info_pb2.MatchField.EXACT:
        return "Exact"
    if match_type == p4info_pb2.MatchField.LPM:
        return "LPM"
    if match_type == p4info_pb2.MatchField.TERNARY:
        return "Ternary"
    if match_type == p4info_pb2.MatchField.RANGE:
        return "Range"
    if match_type == p4info_pb2.MatchField.OPTIONAL:
        return "Optional"
    return None



class P4Manager:
    """
    Class to manage the runtime entries of a P4 pipeline.
    """
    local_client = None

    def __init__(self, device_id: int, ip_address: str, port: int,
                 election_id: tuple, role_name=None, ssl_options=None):

        self.__id = device_id
        self.__ip_address = ip_address
        self.__port = int(port)
        self.__endpoint = f"{self.__ip_address}:{self.__port}"
        self.key_id = ip_address+str(port)
        CLIENTS[self.key_id] = P4RuntimeClient(
            self.__id, self.__endpoint, election_id, role_name, ssl_options)
        self.__p4info = None
        self.local_client = CLIENTS[self.key_id]

        # Internal memory for whitebox management
        # | -> P4 entities
        self.p4_objects = {}

        # | -> P4 entities
        self.table_entries = {}
        self.counter_entries = {}
        self.direct_counter_entries = {}
        self.meter_entries = {}
        self.direct_meter_entries = {}
        self.multicast_groups = {}
        self.clone_session_entries = {}
        self.action_profile_members = {}
        self.action_profile_groups = {}

    def start(self, p4bin_path, p4info_path):
        """
        Start the P4 manager. This involves:
        (i) setting the forwarding pipeline of the target switch,
        (ii) creating a P4 context object,
        (iii) Discovering all the entities of the pipeline, and
        (iv) initializing necessary data structures of the manager

        :param p4bin_path: Path to the P4 binary file
        :param p4info_path: Path to the P4 info file
        :return: void
        """

        if not p4bin_path or not os.path.exists(p4bin_path):
            LOGGER.warning("P4 binary file not found")

        if not p4info_path or not os.path.exists(p4info_path):
            LOGGER.warning("P4 info file not found")

        # Forwarding pipeline is only set iff both files are present
        if p4bin_path and p4info_path:
            try:
                self.local_client.set_fwd_pipe_config(p4info_path, p4bin_path)
            except FileNotFoundError as ex:
                LOGGER.critical(ex)
                self.local_client.tear_down()
                raise FileNotFoundError(ex) from ex
            except P4RuntimeException as ex:
                LOGGER.critical("Error when setting config")
                LOGGER.critical(ex)
                self.local_client.tear_down()
                raise P4RuntimeException(ex) from ex
            except Exception as ex:  # pylint: disable=broad-except
                LOGGER.critical("Error when setting config")
                self.local_client.tear_down()
                raise Exception(ex) from ex

        try:
            self.__p4info = self.local_client.get_p4info()
        except P4RuntimeException as ex:
            LOGGER.critical("Error when retrieving P4Info")
            LOGGER.critical(ex)
            self.local_client.tear_down()
Loading
Loading full blame…