From 4fe8934f963788f2979276e5abe1b0ad4263eaf9 Mon Sep 17 00:00:00 2001 From: fruffy-g <147277354+fruffy-g@users.noreply.github.com> Date: Thu, 30 Nov 2023 15:00:17 -0500 Subject: [PATCH] [P4Testgen] Introduce a new Protobuf backend which uses P4 PDPI instead of P4Runtime (#4221) * Implement the Protobuf IR BMv2 back end. * Add Bazel rules for completeness. * Review comments. --- backends/p4tools/BUILD.bazel | 35 ++ .../testgen/targets/bmv2/CMakeLists.txt | 1 + .../testgen/targets/bmv2/proto/ir.proto | 564 ++++++++++++++++++ .../targets/bmv2/proto/p4testgen_ir.proto | 36 ++ .../testgen/targets/bmv2/table_stepper.cpp | 3 +- .../bmv2/test/BMV2ProtobufIrXfail.cmake | 161 +++++ .../testgen/targets/bmv2/test/P4Tests.cmake | 13 +- .../targets/bmv2/test/TestTemplate.cmake | 24 +- .../testgen/targets/bmv2/test_backend.cpp | 8 +- .../targets/bmv2/test_backend/protobuf.cpp | 125 ++-- .../targets/bmv2/test_backend/protobuf_ir.cpp | 188 ++++++ .../targets/bmv2/test_backend/protobuf_ir.h | 43 ++ control-plane/google/rpc/code.proto | 186 ++++++ control-plane/google/rpc/status.proto | 73 +-- 14 files changed, 1336 insertions(+), 124 deletions(-) create mode 100644 backends/p4tools/modules/testgen/targets/bmv2/proto/ir.proto create mode 100644 backends/p4tools/modules/testgen/targets/bmv2/proto/p4testgen_ir.proto create mode 100644 backends/p4tools/modules/testgen/targets/bmv2/test/BMV2ProtobufIrXfail.cmake create mode 100644 backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.cpp create mode 100644 backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.h create mode 100644 control-plane/google/rpc/code.proto diff --git a/backends/p4tools/BUILD.bazel b/backends/p4tools/BUILD.bazel index a93e5787d8..494346e7f4 100644 --- a/backends/p4tools/BUILD.bazel +++ b/backends/p4tools/BUILD.bazel @@ -16,6 +16,41 @@ filegroup( visibility = ["//visibility:public"], # So p4c can compile these. ) +proto_library( + name = "ir_proto", + srcs = ["modules/testgen/targets/bmv2/proto/ir.proto"], + deps = [ + "@com_github_p4lang_p4runtime//:p4info_proto", + "@com_github_p4lang_p4runtime//:p4runtime_proto", + "@com_google_googleapis//google/rpc:code_proto", + "@com_google_googleapis//google/rpc:status_proto", + ], +) + +proto_library( + name = "p4testgen_proto", + srcs = ["modules/testgen/targets/bmv2/proto/p4testgen.proto"], + deps = ["@com_github_p4lang_p4runtime//:p4runtime_proto"], +) + +proto_library( + name = "p4testgen_ir_proto", + srcs = ["modules/testgen/targets/bmv2/proto/p4testgen_ir.proto"], + deps = [":ir_proto"], +) + +cc_proto_library( + name = "p4testgen_cc_proto", + visibility = ["//visibility:public"], + deps = [":p4testgen_proto"], +) + +cc_proto_library( + name = "p4testgen_ir_cc_proto", + visibility = ["//visibility:public"], + deps = [":p4testgen_ir_proto"], +) + genrule( name = "version", srcs = [ diff --git a/backends/p4tools/modules/testgen/targets/bmv2/CMakeLists.txt b/backends/p4tools/modules/testgen/targets/bmv2/CMakeLists.txt index 144626c119..381f45d05e 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/CMakeLists.txt +++ b/backends/p4tools/modules/testgen/targets/bmv2/CMakeLists.txt @@ -10,6 +10,7 @@ set( ${TESTGEN_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/test_backend/common.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_backend/protobuf.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_backend/protobuf_ir.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_backend/metadata.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_backend/ptf.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_backend/stf.cpp diff --git a/backends/p4tools/modules/testgen/targets/bmv2/proto/ir.proto b/backends/p4tools/modules/testgen/targets/bmv2/proto/ir.proto new file mode 100644 index 0000000000..5ab79beeb1 --- /dev/null +++ b/backends/p4tools/modules/testgen/targets/bmv2/proto/ir.proto @@ -0,0 +1,564 @@ +// Copyright 2023 Google LLC +// +// 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. + +syntax = "proto3"; + +package pdpi; + +import "google/rpc/code.proto"; +import "google/rpc/status.proto"; +import "p4/config/v1/p4info.proto"; +import "p4/v1/p4runtime.proto"; + +// -- P4Info ------------------------------------------------------------------- + +// Describes the format of a value which has a bit width `num_bits` (unless for +// the STRING format, which has no limit in the number of characters). +enum Format { + // Hex string, e.g. 0x0a8b. All lowercase, and always of length + // ceil(num_bits/4)+2 (1 character for every 4 bits, zero-padded to be + // divisible by 4, and 2 characters for the '0x' prefix). + HEX_STRING = 0; + // MAC address, e.g. 00:11:ab:cd:ef:22. All lowercase, and always 17 + // characters long. + MAC = 1; + // IPv4 address, e.g. 10.0.0.2. + IPV4 = 2; + // IPv6 address, e.g. fe80::21a:11ff:fe17:5f80. All lowercase, formatted + // according to RFC5952. This can be used for any num_bits of 128 or less. If + // num_bits is less than 128, then by convention only the upper num_bits bits + // can be set. + IPV6 = 3; + // String format, only printable characters. + STRING = 4; +} + +// Describes a match field that is being referred to from an action parameter or +// another match field. +// TODO: b/306016407 - Remove this message once all P4 infrastructure has been +// moved to `IrTableReference`. +message IrMatchFieldReference { + option deprecated = true; // Use `IrTableReference` instead. + + string table = 1; + string match_field = 2; + uint32 table_id = 3; + uint32 match_field_id = 4; +} + +// Uniquely identifies a user-defined (not built-in) match field within the +// context of a table. +message IrMatchField { + string field_name = 1; + uint32 field_id = 2; +} + +// Uniquely identifies a user-defined (not built-in) parameter of an action +// within the context of a table. +message IrActionField { + string action_name = 1; + uint32 action_id = 2; + string parameter_name = 3; + uint32 parameter_id = 4; +} + +// Uniquely identifies a built-in (not user-defined) field within the +// context of a table. Each field corresponds to a field in the P4Runtime proto +// and has a unique string representation to be used within +// `@refers_to`/`@referenced_by` annotations. +enum IrBuiltInField { + BUILT_IN_FIELD_UNSPECIFIED = 0; + // Corresponds to p4::v1::MulticastGroupEntry::multicast_group_id. + // String representation in annotations: "multicast_group_id" + BUILT_IN_FIELD_MULTICAST_GROUP_ID = 1; + // Corresponds to p4::v1::Replica::port. + // String representation in annotations: "replica.port" + BUILT_IN_FIELD_REPLICA_PORT = 2; + // Corresponds to p4::v1::Replica::instance. + // String representation in annotations: "replica.instance" + BUILT_IN_FIELD_REPLICA_INSTANCE = 3; +} + +// Uniquely identifies a field within the context of a table. +message IrField { + oneof field { + IrMatchField match_field = 1; + IrActionField action_field = 2; + IrBuiltInField built_in_field = 3; + } +} + +// Uniquely identifies a user-defined (not built-in) table in the scope of a P4 +// program. +message IrP4Table { + string table_name = 1; + uint32 table_id = 2; +} + +// Uniquely identifies a built-in (not user-defined) table in the scope of a P4 +// program. Each table corresponds to a field in the P4Runtime proto and has a +// unique string representation to be used within `@refers_to`/`@referenced_by` +// annotations. +enum IrBuiltInTable { + BUILT_IN_TABLE_UNSPECIFIED = 0; + // Corresponds to p4::v1::MulticastGroupEntry + // String representation in annotations: "builtin::multicast_group_table" + BUILT_IN_TABLE_MULTICAST_GROUP_TABLE = 1; +} + +// Uniquely identifies a table in the scope of a P4 program. +message IrTable { + oneof table { + IrP4Table p4_table = 1; + IrBuiltInTable built_in_table = 2; + } +} + +// Describles a reference from one table to another. References are +// generated from @refers_to/@referenced_by annotations. There exists at most +// one `IrTableReference` for a given ordered pair of tables. +// +// Semantics: For each entry in the `source_table`, there must exist an entry in +// the `destination_table` such that for each field reference, if the `source` +// field exists in the source entry, the `destination` field must exist with +// the same value in the destination entry. +message IrTableReference { + IrTable source_table = 1; + IrTable destination_table = 2; + + // The set of ALL references from a field in the `source_table` to a field in + // the `destination_table`. Each reference is encoded In P4 via either: + // (a) an `@refers_to(dst_table, dst_field)` annotation on a match or action + // field of the source table, + // (b) an `@referenced_by(src_table, src_field)` annotation on a match field + // of the destination table. + repeated FieldReference field_references = 3; + message FieldReference { + IrField source = 1; + IrField destination = 2; + } +} + +// Describes a match field. +message IrMatchFieldDefinition { + // Required. From P4Info. + p4.config.v1.MatchField match_field = 1; + // Required, the format of this field as deduced from the match field type + // and annotations. + Format format = 2; + // Optional. The set of references. + // TODO: b/306016407 - Remove this message once all P4 infrastructure has been + // moved to `IrTableReference`. + repeated IrMatchFieldReference references = 3 [deprecated = true]; + // True if match field has @unsupported annotation, otherwise false. Indicates + // that the match field is not (yet) supported by the target. + // See go/unblocking-sai-p4 for more details. + bool is_unsupported = 14; +} + +// Describes a meter. +message IrMeter { + // Required. Unit of the meter. + p4.config.v1.MeterSpec.Unit unit = 1; +} + +// Describes a counter +message IrCounter { + // Required. Unit for the counter. + p4.config.v1.CounterSpec.Unit unit = 1; +} + +// Describes a P4 table. +message IrTableDefinition { + // Required. From P4Info. + p4.config.v1.Preamble preamble = 1; + // Required. Maps match field IDs to match fields. + map match_fields_by_id = 2; + // Required. Maps match field names to match fields. + map match_fields_by_name = 3; + // Optional. The set of actions for this table usable by table entries, if + // any. + repeated IrActionReference entry_actions = 4; + // Optional. These actions are only available for the default action. + repeated IrActionReference default_only_actions = 5; + // Required. Max number of entries in table. + int64 size = 6; + // Required. Does this table use one-shot action selector programming? Implies + // that this is a WCMP table. Only oneshot WCMP tables are supported. + bool uses_oneshot = 7; + // Optional. P4 id of the "implementation" for this table (i.e. action profile + // id, since this is the only currently supported implementation); if this + // value does not exist, the table is a regular (direct) match table, + // otherwise, this is a WCMP table. + oneof implementation_id { + uint32 action_profile_id = 13; + } + // Required. Does this table require priority? + bool requires_priority = 8; + // Optional. Meter configuration. + IrMeter meter = 9; + // Optional. Counter configuration. + IrCounter counter = 10; + // Optional. Constant default action for this table. Cannot be overridden. + IrActionDefinition const_default_action = 11; + // Optional. P4Runtime role of this table. + string role = 12; + // True if table has @unsupported annotation, otherwise false. Indicates that + // the table is not (yet) supported by the target. + // See go/unblocking-sai-p4 for more details. + bool is_unsupported = 14; + // `IrTableReference` where this table is the `source_table`. + repeated IrTableReference outgoing_references = 15; + // `IrTableReference` where this table is the `destination_table`. + repeated IrTableReference incoming_references = 16; + // Next: 17 +} + +// Describes a reference to an action (from a table). +message IrActionReference { + // Required. From P4Info. + p4.config.v1.ActionRef ref = 1; + // Required. The definition of this action. + IrActionDefinition action = 2; + // Required for non-default-only actions. Proto ID for this action reference. + uint32 proto_id = 3; +} + +// Describes an action definition. +message IrActionDefinition { + // Describes an action parameter definition. + message IrActionParamDefinition { + // Required. From P4Info. + p4.config.v1.Action.Param param = 1; + // Required, the format of this parameter as deduced from the parameter type + // and annotations. + Format format = 2; + // Optional. The set of references. + // TODO: b/306016407 - Remove this message once all P4 infrastructure has + // been moved to `IrTableReference`. + repeated IrMatchFieldReference references = 3 [deprecated = true]; + } + // Required. From P4Info. + p4.config.v1.Preamble preamble = 1; + // Required. Maps parameter IDs to parameters. + map params_by_id = 2; + // Required. Maps parameter names to parameters. + map params_by_name = 3; + // True if action has @unsupported annotation, otherwise false. Indicates that + // the action is not (yet) supported by the target. + // See go/unblocking-sai-p4 for more details. + bool is_unsupported = 4; +} + +// Describes an action profile definition. +message IrActionProfileDefinition { + // Required. The action profile from the P4Info. + p4.config.v1.ActionProfile action_profile = 1; +} + +// Describes a packet-io metadata header. +message IrPacketIoMetadataDefinition { + // Required. From P4Info. + p4.config.v1.ControllerPacketMetadata.Metadata metadata = 1; + // Required, the format of this parameter as deduced from the parameter type + // and annotations. + Format format = 2; + // True if metadata field has @padding (go/pdpi-padding) annotation present, + // otherwise false. Indicates that metadata is excluded from IR and PD proto, + // and zero-valued in PI proto. + bool is_padding = 3; +} + +// Describes a built-in table. +message IrBuiltInTableDefinition { + IrBuiltInTable built_in_table = 1; + // `IrTableReference` where this table is the `source_table`. + repeated IrTableReference outgoing_references = 2; + // `IrTableReference` where this table is the `destination_table`. + repeated IrTableReference incoming_references = 3; +} + +// Describes an entire P4 program. +message IrP4Info { + // Top-level package documentation describing the forwarding pipeline config. + p4.config.v1.PkgInfo pkg_info = 1; + // Maps table IDs to tables. + map tables_by_id = 2; + // Maps table names to tables. + map tables_by_name = 3; + // Maps action IDs to actions. + map actions_by_id = 4; + // Maps action names to actions. + map actions_by_name = 5; + // Maps action profile IDs to action profiles. + map action_profiles_by_id = 11; + // Maps action profile names to action profiles. + map action_profiles_by_name = 12; + // Maps packet-in metadata IDs to metadata. + map packet_in_metadata_by_id = 6; + // Maps packet-in metadata names to metadata. + map packet_in_metadata_by_name = 7; + // Maps packet-out metadata IDs to metadata. + map packet_out_metadata_by_id = 8; + // Maps packet-out metadata names to metadata. + map packet_out_metadata_by_name = 9; + // All match field references. + // TODO: b/306016407 - Remove this message once all P4 infrastructure has been + // moved to `IrTableReference`. + repeated IrMatchFieldReference references = 10 [deprecated = true]; + // Maps built-in table name to definitions. Refer to `IrBuiltInTable` for + // valid built-in table names. + map built_in_tables = 13; +} + +// -- Table entries ------------------------------------------------------------ + +// Describes a value. +message IrValue { + // Required. Parallel to Format enum. + oneof format { + string hex_str = 1; + string ipv4 = 2; + string ipv6 = 3; + string mac = 4; + string str = 5; + } +} + +// Describes a match of a table entry +message IrMatch { + // Required. The name of the field being matched on. + string name = 1; + + message IrTernaryMatch { + IrValue value = 1; + IrValue mask = 2; + } + + message IrLpmMatch { + IrValue value = 1; + int32 prefix_length = 2; + } + + message IrOptionalMatch { + IrValue value = 1; + } + + // Required. Described the value being matched. + oneof match_value { + IrValue exact = 2; + IrLpmMatch lpm = 3; + IrTernaryMatch ternary = 4; + IrOptionalMatch optional = 5; + } +} + +// Describes an invocation of table action, with a concrete set of parameters. +message IrActionInvocation { + // Required, the name of the action that is invoked. + string name = 1; + + // Describes a concrete value to be passed as a parameter. + message IrActionParam { + // Required, the name of the parameter. + string name = 1; + // Required, the value of the parameter. + IrValue value = 2; + } + // Set of parameters for the action. + repeated IrActionParam params = 2; +} + +// Describes an action invocation of an action set. +message IrActionSetInvocation { + // The actual action invocation. + IrActionInvocation action = 1; + // Required. Weight of this action. Must be positive. + int32 weight = 2; + // Optional. Watch port. + string watch_port = 3; +} + +// Describes an action profile action set (for WCMP tables). +message IrActionSet { + // Required. The set of actions. + repeated IrActionSetInvocation actions = 1; +} + +// Describes a table entry (matches, priority and action, plus some metadata). +message IrTableEntry { + // Required, the name of the table this entry belong to. + string table_name = 1; + // Required, the set of matches. + repeated IrMatch matches = 2; + // The priority. 0 for tables with only exact matches, and required otherwise. + int32 priority = 3; + // Required, the action to be invoked. + oneof type { + IrActionInvocation action = 4; + IrActionSet action_set = 5; + } + // Meter config (required for tables with a meter, absent otherwise). + p4.v1.MeterConfig meter_config = 6; + // Optional. Counter data. + p4.v1.CounterData counter_data = 7; + // Optional. Meter counter data. + p4.v1.MeterCounterData meter_counter_data = 9; + // Optional, the metadata from the controller. + bytes controller_metadata = 8; +} + +// Describes a list of table entries. +message IrTableEntries { + repeated IrTableEntry entries = 1; +} + +//-- Packet Replication Engine ------------------------------------------------- + +// Describes a PacketReplicationEngine (PRE) entry. Currently, the only +// supported PRE entry is multicast group entry. +message IrPacketReplicationEngineEntry { + oneof type { + IrMulticastGroupEntry multicast_group_entry = 1; + } +} + +message IrReplica { + string port = 1; + uint32 instance = 2; +} + +message IrMulticastGroupEntry { + uint32 multicast_group_id = 1; + repeated IrReplica replicas = 2; +} + +// -- Packet IO ---------------------------------------------------------------- + +// Describes a packet in. +message IrPacketIn { + bytes payload = 1; + repeated IrPacketMetadata metadata = 2; +} +// Describes a packet out. +message IrPacketOut { + bytes payload = 1; + repeated IrPacketMetadata metadata = 2; +} + +// A single packet io metadata header. +message IrPacketMetadata { + string name = 1; + IrValue value = 2; +} + +// -- RPC messages ------------------------------------------------------------- + +// Describes an entity within an Update. +message IrEntity { + oneof entity { + IrTableEntry table_entry = 1; + IrPacketReplicationEngineEntry packet_replication_engine_entry = 2; + } +} + +// Describes a list of entities. +message IrEntities { + repeated IrEntity entities = 1; +} + +// Describes an update in a Write RPC request. +message IrUpdate { + // Required. + p4.v1.Update.Type type = 1; + // Required. + IrEntity entity = 2; +} + +// Describes a Write RPC request. +message IrWriteRequest { + // Required. + uint64 device_id = 1; + // Required. + p4.v1.Uint128 election_id = 2; + // Required. + repeated IrUpdate updates = 3; +} + +// Describes the status of a single update in a Write RPC. +message IrUpdateStatus { + // Required. + google.rpc.Code code = 1; + // Required for non-OK status. + string message = 2; +} + +// Describes the result of a Write RPC. +message IrWriteRpcStatus { + oneof status { + google.rpc.Status rpc_wide_error = 1; + IrWriteResponse rpc_response = 2; + } +} + +// Describes a Write RPC response. +message IrWriteResponse { + // Same order as `updates` in `WriteRequest`. + repeated IrUpdateStatus statuses = 1; +} + +// Read requests. +message IrReadRequest { + // Required. + uint64 device_id = 1; + // Indicates if counter data should be read. + bool read_counter_data = 2; + // Indicates if meter configs should be read. + bool read_meter_configs = 3; +} + +// A read request response. +message IrReadResponse { + // The table entries read by the switch. + repeated IrEntity entities = 1; +} + +// A stream message request +message IrStreamMessageRequest { + oneof update { + p4.v1.MasterArbitrationUpdate arbitration = 1; + IrPacketOut packet = 2; + } +} + +// A stream error message +message IrStreamError { + google.rpc.Status status = 1; + // Used by the server to convey additional information about the error. + // The field must be set (so that the client can identify which type of + // stream message triggered the error), but that field may be set to its + // default value. + IrPacketOut packet_out = 2; +} + +// A stream message response +message IrStreamMessageResponse { + oneof update { + p4.v1.MasterArbitrationUpdate arbitration = 1; + IrPacketIn packet = 2; + // Used by the server to asynchronously report errors which occur when + // processing StreamMessageRequest messages. + IrStreamError error = 3; + } +} diff --git a/backends/p4tools/modules/testgen/targets/bmv2/proto/p4testgen_ir.proto b/backends/p4tools/modules/testgen/targets/bmv2/proto/p4testgen_ir.proto new file mode 100644 index 0000000000..2d93ad0f7d --- /dev/null +++ b/backends/p4tools/modules/testgen/targets/bmv2/proto/p4testgen_ir.proto @@ -0,0 +1,36 @@ +// P4Testgen Protobuf IR template. +syntax = "proto3"; + +package p4testgen_ir; + +import "backends/p4tools/modules/testgen/targets/bmv2/proto/ir.proto"; + +message InputPacketAtPort { + // The raw bytes of the test packet. + bytes packet = 1; + // The raw bytes of the port associated with the packet. + int32 port = 2; +} + +message OutputPacketAtPort { + // The raw bytes of the test packet. + bytes packet = 1; + // The raw bytes of the port associated with the packet. + int32 port = 2; + // The don't care mask of the packet. + bytes packet_mask = 3; +} + +message TestCase { + // The input packet. + InputPacketAtPort input_packet = 1; + // The corresponding expected output packet. + repeated OutputPacketAtPort expected_output_packet = 2; + // The entities (e.g., table entries) to install on the switch before + // injecting the `input_packet`. + repeated pdpi.IrEntity entities = 3; + // The trace associated with this particular test. + repeated string traces = 4; + // Additional metadata and information. + repeated string metadata = 5; +} diff --git a/backends/p4tools/modules/testgen/targets/bmv2/table_stepper.cpp b/backends/p4tools/modules/testgen/targets/bmv2/table_stepper.cpp index 52f08a1cf1..83c5e98105 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/table_stepper.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/table_stepper.cpp @@ -63,7 +63,8 @@ const IR::Expression *Bmv2V1ModelTableStepper::computeTargetMatchType( keyExpr->type, properties.tableName + "_mask_" + keyProperties.name); // Encode P4Runtime constraints for PTF and Protobuf tests. // (https://p4.org/p4-spec/docs/p4runtime-spec-working-draft-html-version.html#sec-match-format) - if (testgenOptions.testBackend == "PTF" || testgenOptions.testBackend == "PROTOBUF") { + if (testgenOptions.testBackend == "PTF" || testgenOptions.testBackend == "PROTOBUF" || + testgenOptions.testBackend == "PROTOBUF_IR") { hitCondition = new IR::LAnd( hitCondition, new IR::Equ(new IR::BAnd(ctrlPlaneKey, ternaryMask), ctrlPlaneKey)); } diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test/BMV2ProtobufIrXfail.cmake b/backends/p4tools/modules/testgen/targets/bmv2/test/BMV2ProtobufIrXfail.cmake new file mode 100644 index 0000000000..2a50f937d0 --- /dev/null +++ b/backends/p4tools/modules/testgen/targets/bmv2/test/BMV2ProtobufIrXfail.cmake @@ -0,0 +1,161 @@ +# XFAILS: tests that *temporarily* fail +# ================================================ +# Xfails are _temporary_ failures: the tests should work but we haven't fixed p4testgen yet. + +#################################################################################################### +# 1. P4C Toolchain Issues +# These are issues either with the P4 compiler or the behavioral model executing the code. +# These issues needed to be tracked and fixed in P4C. +#################################################################################################### + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Message type .* has no field named .*" + up4.p4 # Message type "pdpi.IrMatch" has no field named "range". +) + +#################################################################################################### +# 2. P4Testgen Issues +#################################################################################################### +# These are failures in P4Testgen that need to be fixed. + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Non-numeric, non-boolean member expression: .* Type: Type_Stack" + # We can not expand stacks in parsers because information about .next is lost. + # P4Testgen needs to maintain its own internal .next variable for stacks. + array-copy-bmv2.p4 +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Unknown or unimplemented extern method: recirculate_preserving_field_list" +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Unknown or unimplemented extern method: extract" +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Non-numeric, non-boolean member expression" +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "with type .* is not a Constant" + # Most of these come from varbits +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Cast failed" + # push front can not handled tainted header validity. + header-stack-ops-bmv2.p4 +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "is trying to match on a tainted key set" + invalid-hdr-warnings1.p4 # unimlemented feature (for select statement) + issue692-bmv2.p4 +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Only registers with bit or int types are currently supported" + issue907-bmv2.p4 +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Could not find type for" + # Related to postorder(IR::Member* expression) in ParserUnroll, + # and more specifically when member is passed to getTypeArray +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "is not a constant" + # Using an uninitialized variable as a header stack index in the parser. + parser-unroll-test10.p4 +) + +#################################################################################################### +# 3. WONTFIX +#################################################################################################### +# These are failures that can not be solved by changing P4Testgen + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Assert/assume can not be executed under a tainted condition" + # Assert/Assume error: assert/assume(false). + bmv2_assert.p4 +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Computations are not supported in update_checksum" +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Error compiling" +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Checksum16.get is deprecated and not supported." + issue841.p4 # Not supported +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Unknown or unimplemented extern method: increment" + issue1882-1-bmv2.p4 # user defined extern + issue1882-bmv2.p4 # user defined extern +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Unknown or unimplemented extern method: update" + issue2664-bmv2.p4 # user defined extern +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Unknown extern method count from type jnf_counter" +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "Unknown or unimplemented extern method: fn_foo" + issue3091.p4 # user defined extern +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "with type Type_Specialized is not a Type_Declaration" + # Pipeline as a parameter of a switch, not a valid v1model program + issue1304.p4 +) + +#################################################################################################### +# 4. PARAMETERS NEEDED +#################################################################################################### +# These tests require additional input parameters to compile properly. + +# TODO: For these test we should add the --permissive flag. +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-protobuf-ir" + "The validity bit of .* is tainted" + control-hs-index-test3.p4 + control-hs-index-test5.p4 + gauntlet_hdr_function_cast-bmv2.p4 + issue2345-1.p4 + issue2345-2.p4 + issue2345-multiple_dependencies.p4 + issue2345-with_nested_if.p4 + issue2345.p4 +) diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test/P4Tests.cmake b/backends/p4tools/modules/testgen/targets/bmv2/test/P4Tests.cmake index df102a2b01..daf30acfb2 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/test/P4Tests.cmake +++ b/backends/p4tools/modules/testgen/targets/bmv2/test/P4Tests.cmake @@ -28,7 +28,8 @@ set(P4C_V1_TEST_SUITES_P416 ${P4_16_V1_TESTS} ${BMV2_P4_16_V1_TESTS}) # TEST SUITES ############################################################################# option(P4TOOLS_TESTGEN_BMV2_TEST_METADATA "Run tests on the Metadata test back end" ON) -option(P4TOOLS_TESTGEN_BMV2_TEST_PROTOBUF "Run tests on the Protobuf test back end" ON) +option(P4TOOLS_TESTGEN_BMV2_TEST_PROTOBUF "Run tests on the Protobuf test back end" OFF) +option(P4TOOLS_TESTGEN_BMV2_TEST_PROTOBUF_IR "Run tests on the Protobuf test back end" ON) option(P4TOOLS_TESTGEN_BMV2_TEST_PTF "Run tests on the PTF test back end" ON) option(P4TOOLS_TESTGEN_BMV2_TEST_STF "Run tests on the STF test back end" ON) # Test settings. @@ -47,6 +48,16 @@ if(P4TOOLS_TESTGEN_BMV2_TEST_PROTOBUF) include(${CMAKE_CURRENT_LIST_DIR}/BMV2ProtobufXfail.cmake) endif() +# Protobuf IR +if(P4TOOLS_TESTGEN_BMV2_TEST_PROTOBUF_IR) + p4tools_add_tests( + TESTS "${P4C_V1_TEST_SUITES_P416}" + TAG "testgen-p4c-bmv2-protobuf-ir" DRIVER ${P4TESTGEN_DRIVER} + TARGET "bmv2" ARCH "v1model" VALIDATE_PROTOBUF_IR TEST_ARGS "--test-backend PROTOBUF_IR ${EXTRA_OPTS} " + ) + include(${CMAKE_CURRENT_LIST_DIR}/BMV2ProtobufIrXfail.cmake) +endif() + # Metadata if(P4TOOLS_TESTGEN_BMV2_TEST_METADATA) p4tools_add_tests( diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test/TestTemplate.cmake b/backends/p4tools/modules/testgen/targets/bmv2/test/TestTemplate.cmake index d8116897be..f02d3b6882 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/test/TestTemplate.cmake +++ b/backends/p4tools/modules/testgen/targets/bmv2/test/TestTemplate.cmake @@ -46,10 +46,26 @@ function(validate_protobuf testfile testfolder) file(APPEND ${testfile} "for item in \${txtpbfiles[@]}\n") file(APPEND ${testfile} "do\n") file(APPEND ${testfile} "\techo \"Found \${item}\"\n") - file(APPEND ${testfile} "\t${PROTOBUF_PROTOC_EXECUTABLE} --proto_path=${CMAKE_CURRENT_LIST_DIR}/../proto --proto_path=${P4RUNTIME_STD_DIR} --proto_path=${P4C_SOURCE_DIR}/control-plane --encode=p4testgen.TestCase p4testgen.proto < \${item}\n") + file(APPEND ${testfile} "\t${PROTOBUF_PROTOC_EXECUTABLE} --proto_path=${CMAKE_CURRENT_LIST_DIR}/../proto --proto_path=${P4RUNTIME_STD_DIR} --proto_path=${P4C_SOURCE_DIR}/control-plane --encode=p4testgen.TestCase p4testgen.proto < \${item} > /dev/null\n") file(APPEND ${testfile} "done\n") endfunction(validate_protobuf) +# Write the script to validate whether a given protobuf IR text format file has a valid format. +# Arguments: +# - testfile is the testing script that this script is written to. +# - testfolder is target folder of the test. +function(validate_protobuf_ir testfile testfolder) + # Find all the proto tests generated for this P4 file and validate their correctness. + file(APPEND ${testfile} "txtpbfiles=($(find ${testfolder} -name \"*.txtpb\" | sort -n ))\n") + file(APPEND ${testfile} "for item in \${txtpbfiles[@]}\n") + file(APPEND ${testfile} "do\n") + file(APPEND ${testfile} "\techo \"Found \${item}\"\n") + file(APPEND ${testfile} "\t${PROTOBUF_PROTOC_EXECUTABLE} --proto_path=${CMAKE_CURRENT_LIST_DIR}/../proto --proto_path=${P4RUNTIME_STD_DIR} --proto_path=${P4C_SOURCE_DIR} --proto_path=${P4C_SOURCE_DIR}/control-plane --encode=p4testgen_ir.TestCase p4testgen_ir.proto < \${item} > /dev/null\n") + file(APPEND ${testfile} "done\n") +endfunction(validate_protobuf_ir) + + + # Write the script to validate whether a given protobuf file has a valid format. # Arguments: # - testfile is the testing script that this script is written to. @@ -72,6 +88,7 @@ endfunction(check_empty_folder) # - ARCH is the p4 architecture # - ENABLE_RUNNER is the flag to execute BMv2 on the generated tests. # - VALIDATE_PROTOBUF is the flag to check whether the generated Protobuf tests are valid. +# - VALIDATE_PROTOBUF is the flag to check whether the generated Protobuf IR tests are valid. # - TEST_ARGS is a list of arguments to pass to the test # - CMAKE_ARGS are additional arguments to pass to the test # @@ -80,7 +97,7 @@ endfunction(check_empty_folder) # Sets the timeout on tests at 300s. For the slow CI machines. function(p4tools_add_test_with_args) # Parse arguments. - set(options ENABLE_RUNNER VALIDATE_PROTOBUF P416_PTF USE_ASSERT_MODE DISABLE_ASSUME_MODE CHECK_EMPTY) + set(options ENABLE_RUNNER VALIDATE_PROTOBUF VALIDATE_PROTOBUF_IR P416_PTF USE_ASSERT_MODE DISABLE_ASSUME_MODE CHECK_EMPTY) set(oneValueArgs TAG DRIVER ALIAS P4TEST TARGET ARCH) set(multiValueArgs TEST_ARGS CMAKE_ARGS) cmake_parse_arguments( @@ -140,6 +157,9 @@ function(p4tools_add_test_with_args) if(${TOOLS_BMV2_TESTS_VALIDATE_PROTOBUF}) validate_protobuf(${__testfile} ${__testfolder}) endif() + if(${TOOLS_BMV2_TESTS_VALIDATE_PROTOBUF_IR}) + validate_protobuf_ir(${__testfile} ${__testfolder}) + endif() endif() execute_process(COMMAND chmod +x ${__testfile}) diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test_backend.cpp b/backends/p4tools/modules/testgen/targets/bmv2/test_backend.cpp index d0ef059bd0..5689566239 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/test_backend.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/test_backend.cpp @@ -27,6 +27,7 @@ #include "backends/p4tools/modules/testgen/targets/bmv2/program_info.h" #include "backends/p4tools/modules/testgen/targets/bmv2/test_backend/metadata.h" #include "backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf.h" +#include "backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.h" #include "backends/p4tools/modules/testgen/targets/bmv2/test_backend/ptf.h" #include "backends/p4tools/modules/testgen/targets/bmv2/test_backend/stf.h" #include "backends/p4tools/modules/testgen/targets/bmv2/test_spec.h" @@ -36,7 +37,7 @@ namespace P4Tools::P4Testgen::Bmv2 { const big_int Bmv2TestBackend::ZERO_PKT_VAL = 0x2000000; const big_int Bmv2TestBackend::ZERO_PKT_MAX = 0xffffffff; const std::set Bmv2TestBackend::SUPPORTED_BACKENDS = {"PTF", "STF", "PROTOBUF", - "METADATA"}; + "PROTOBUF_IR", "METADATA"}; Bmv2TestBackend::Bmv2TestBackend(const ProgramInfo &programInfo, SymbolicExecutor &symbex, const std::filesystem::path &testPath) @@ -56,7 +57,12 @@ Bmv2TestBackend::Bmv2TestBackend(const ProgramInfo &programInfo, SymbolicExecuto } else if (testBackendString == "STF") { testWriter = new STF(testPath, seed); } else if (testBackendString == "PROTOBUF") { + ::warning( + "The PROTOBUF test back end is deprecated. " + "Please use the PROTOBUF_IR test back end, which uses P4_PDPI."); testWriter = new Protobuf(testPath, seed); + } else if (testBackendString == "PROTOBUF_IR") { + testWriter = new ProtobufIr(testPath, seed); } else if (testBackendString == "METADATA") { testWriter = new Metadata(testPath, seed); } else { diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf.cpp b/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf.cpp index 2fec5aae5d..c6c8ce25cb 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf.cpp @@ -215,7 +215,10 @@ inja::json Protobuf::getExpectedPacket(const TestSpec *testSpec) const { std::string Protobuf::getTestCaseTemplate() { static std::string TEST_CASE( - R"""(# A P4TestGen-generated test case for {{test_name}}.p4 + R"""( +# proto-file: p4testgen.proto +# proto-message: TestCase +# A P4TestGen-generated test case for {{test_name}}.p4 metadata: "p4testgen seed: {{ default(seed, "none") }}" metadata: "Date generated: {{timestamp}}" ## if length(selected_branches) > 0 @@ -241,99 +244,97 @@ expected_output_packet { ## endif ## if control_plane -entities : [ ## for table in control_plane.tables ## for rule in table.rules - # Table {{table.table_name}} - { - table_entry { - table_id: {{table.id}} +# Table {{table.table_name}} +entities { + table_entry { + table_id: {{table.id}} ## if rule.rules.needs_priority - priority: {{rule.priority}} + priority: {{rule.priority}} ## endif ## for r in rule.rules.single_exact_matches - # Match field {{r.field_name}} - match { - field_id: {{r.id}} - exact { - value: "{{r.value}}" - } - } -## endfor -## for r in rule.rules.optional_matches # Match field {{r.field_name}} match { field_id: {{r.id}} - optional { + exact { value: "{{r.value}}" } } ## endfor +## for r in rule.rules.optional_matches + # Match field {{r.field_name}} + match { + field_id: {{r.id}} + optional { + value: "{{r.value}}" + } + } +## endfor ## for r in rule.rules.range_matches - # Match field {{r.field_name}} - match { - field_id: {{r.id}} - range { - low: "{{r.lo}}" - high: "{{r.hi}}" - } + # Match field {{r.field_name}} + match { + field_id: {{r.id}} + range { + low: "{{r.lo}}" + high: "{{r.hi}}" } + } ## endfor ## for r in rule.rules.ternary_matches - # Match field {{r.field_name}} - match { - field_id: {{r.id}} - ternary { - value: "{{r.value}}" - mask: "{{r.mask}}" - } + # Match field {{r.field_name}} + match { + field_id: {{r.id}} + ternary { + value: "{{r.value}}" + mask: "{{r.mask}}" } + } ## endfor ## for r in rule.rules.lpm_matches - # Match field {{r.field_name}} - match { - field_id: {{r.id}} - lpm { - value: "{{r.value}}" - prefix_len: {{r.prefix_len}} - } + # Match field {{r.field_name}} + match { + field_id: {{r.id}} + lpm { + value: "{{r.value}}" + prefix_len: {{r.prefix_len}} } + } ## endfor - # Action {{rule.action_name}} - action { + # Action {{rule.action_name}} + action { ## if existsIn(table, "has_ap") - action_profile_action_set { - action_profile_actions { - action { - action_id: {{rule.action_id}} + action_profile_action_set { + action_profile_actions { + action { + action_id: {{rule.action_id}} ## for act_param in rule.rules.act_args - # Param {{act_param.param}} - params { - param_id: {{act_param.id}} - value: "{{act_param.value}}" - } -## endfor + # Param {{act_param.param}} + params { + param_id: {{act_param.id}} + value: "{{act_param.value}}" } +## endfor } } + } ## else - action { - action_id: {{rule.action_id}} + action { + action_id: {{rule.action_id}} ## for act_param in rule.rules.act_args - # Param {{act_param.param}} - params { - param_id: {{act_param.id}} - value: "{{act_param.value}}" - } -## endfor + # Param {{act_param.param}} + params { + param_id: {{act_param.id}} + value: "{{act_param.value}}" } -## endif - } ## endfor + } +## endif } - }{% if not loop.is_last %},{% endif %} ## endfor -] + } +} +## endfor ## endif )"""); return TEST_CASE; diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.cpp b/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.cpp new file mode 100644 index 0000000000..0fe412f3c6 --- /dev/null +++ b/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.cpp @@ -0,0 +1,188 @@ +#include "backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.h" + +#include +#include +#include +#include +#include + +#include + +#include "backends/p4tools/common/lib/util.h" +#include "lib/log.h" +#include "nlohmann/json.hpp" + +namespace P4Tools::P4Testgen::Bmv2 { + +ProtobufIr::ProtobufIr(std::filesystem::path basePath, std::optional seed) + : Bmv2TestFramework(std::move(basePath), seed) {} + +std::string ProtobufIr::getTestCaseTemplate() { + static std::string TEST_CASE( + R"""(# proto-file: p4testgen_ir.proto +# proto-message: TestCase + +# A P4TestGen-generated test case for {{test_name}}.p4 +metadata: "p4testgen seed: {{ default(seed, "none") }}" +metadata: "Date generated: {{timestamp}}" +## if length(selected_branches) > 0 +metadata: "{{selected_branches}}" +## endif +metadata: "Current node coverage: {{coverage}}" + +## for trace_item in trace +traces: '{{trace_item}}' +## endfor + +input_packet { + packet: "{{send.pkt}}" + port: {{send.ig_port}} +} + +## if verify +expected_output_packet { + packet: "{{verify.exp_pkt}}" + port: {{verify.eg_port}} + packet_mask: "{{verify.ignore_mask}}" +} +## endif + +## if control_plane +## for table in control_plane.tables +## for rule in table.rules +# Table {{table.table_name}} +entities { + table_entry { + table_name: "{{table.table_name}}" +## if rule.rules.needs_priority + priority: {{rule.priority}} +## endif +## for r in rule.rules.single_exact_matches + # Match field {{r.field_name}} + matches { + name: "{{r.field_name}}" + exact: { hex_str: "{{r.value}}" } + } +## endfor +## for r in rule.rules.optional_matches + # Match field {{r.field_name}} + matches { + name: "{{r.field_name}}" + optional { + value: { hex_str: "{{r.value}}" } + } + } +## endfor +## for r in rule.rules.range_matches + # Match field {{r.field_name}} + matches { + name: "{{r.field_name}}" + range { + low: { hex_str: "{{r.lo}}" } + high: { hex_str: "{{r.hi}}" } + } + } +## endfor +## for r in rule.rules.ternary_matches + # Match field {{r.field_name}} + matches { + name: "{{r.field_name}}" + ternary { + value: { hex_str: "{{r.value}}" } + mask: { hex_str: "{{r.mask}}" } + } + } +## endfor +## for r in rule.rules.lpm_matches + # Match field {{r.field_name}} + matches { + name: "{{r.field_name}}" + lpm { + value: { hex_str: "{{r.value}}" } + prefix_length: {{r.prefix_len}} + } + } +## endfor + # Action {{rule.action_name}} +## if existsIn(table, "has_ap") + action_set { + actions { + action { + name: "{{rule.action_name}}" +## for act_param in rule.rules.act_args + # Param {{act_param.param}} + params { + name: "{{act_param.param}}" + value: { hex_str: "{{act_param.value}}" } + } +## endfor + } + } + } +## else + action { + name: "{{rule.action_name}}" +## for act_param in rule.rules.act_args + # Param {{act_param.param}} + params { + name: "{{act_param.param}}" + value: { hex_str: "{{act_param.value}}" } + } +## endfor + } +## endif +## endfor + } +} +## endfor +## endif +)"""); + return TEST_CASE; +} + +void ProtobufIr::emitTestcase(const TestSpec *testSpec, cstring selectedBranches, size_t testId, + const std::string &testCase, float currentCoverage) { + inja::json dataJson; + if (selectedBranches != nullptr) { + dataJson["selected_branches"] = selectedBranches.c_str(); + } + if (seed) { + dataJson["seed"] = *seed; + } + dataJson["test_name"] = basePath.stem(); + dataJson["test_id"] = testId; + dataJson["trace"] = getTrace(testSpec); + dataJson["control_plane"] = getControlPlane(testSpec); + dataJson["send"] = getSend(testSpec); + dataJson["verify"] = getExpectedPacket(testSpec); + dataJson["timestamp"] = Utils::getTimeStamp(); + std::stringstream coverageStr; + coverageStr << std::setprecision(2) << currentCoverage; + dataJson["coverage"] = coverageStr.str(); + + // Check whether this test has a clone configuration. + // These are special because they require additional instrumentation and produce two output + // packets. + auto cloneSpecs = testSpec->getTestObjectCategory("clone_specs"); + if (!cloneSpecs.empty()) { + dataJson["clone_specs"] = getClone(cloneSpecs); + } + auto meterValues = testSpec->getTestObjectCategory("meter_values"); + dataJson["meter_values"] = getMeter(meterValues); + + LOG5("ProtobufIR test back end: emitting testcase:" << std::setw(4) << dataJson); + auto incrementedbasePath = basePath; + incrementedbasePath.concat("_" + std::to_string(testId)); + incrementedbasePath.replace_extension(".txtpb"); + auto protobufFileStream = std::ofstream(incrementedbasePath); + inja::render_to(protobufFileStream, testCase, dataJson); + protobufFileStream.flush(); +} + +void ProtobufIr::outputTest(const TestSpec *testSpec, cstring selectedBranches, size_t testId, + float currentCoverage) { + std::string testCase = getTestCaseTemplate(); + emitTestcase(testSpec, selectedBranches, testId, testCase, currentCoverage); +} + +} // namespace P4Tools::P4Testgen::Bmv2 diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.h b/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.h new file mode 100644 index 0000000000..bd0a7c950b --- /dev/null +++ b/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.h @@ -0,0 +1,43 @@ +#ifndef BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_BMV2_TEST_BACKEND_PROTOBUF_IR_H_ +#define BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_BMV2_TEST_BACKEND_PROTOBUF_IR_H_ + +#include +#include +#include +#include + +#include + +#include "lib/cstring.h" + +#include "backends/p4tools/modules/testgen/lib/test_spec.h" +#include "backends/p4tools/modules/testgen/targets/bmv2/test_backend/common.h" + +namespace P4Tools::P4Testgen::Bmv2 { + +/// Extracts information from the @testSpec to emit a Protobuf IR test case. +class ProtobufIr : public Bmv2TestFramework { + public: + explicit ProtobufIr(std::filesystem::path basePath, + std::optional seed = std::nullopt); + + /// Produce a ProtobufIr test. + void outputTest(const TestSpec *spec, cstring selectedBranches, size_t testId, + float currentCoverage) override; + + private: + /// Emits a test case. + /// @param testId specifies the test name. + /// @param selectedBranches enumerates the choices the interpreter made for this path. + /// @param currentCoverage contains statistics about the current coverage of this test and its + /// preceding tests. + void emitTestcase(const TestSpec *testSpec, cstring selectedBranches, size_t testId, + const std::string &testCase, float currentCoverage); + + /// @returns the inja test case template as a string. + static std::string getTestCaseTemplate(); +}; + +} // namespace P4Tools::P4Testgen::Bmv2 + +#endif /* BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_BMV2_TEST_BACKEND_PROTOBUF_IR_H_ */ diff --git a/control-plane/google/rpc/code.proto b/control-plane/google/rpc/code.proto new file mode 100644 index 0000000000..7c810af40f --- /dev/null +++ b/control-plane/google/rpc/code.proto @@ -0,0 +1,186 @@ +// Copyright 2022 Google LLC +// +// 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. + +syntax = "proto3"; + +package google.rpc; + +option go_package = "google.golang.org/genproto/googleapis/rpc/code;code"; +option java_multiple_files = true; +option java_outer_classname = "CodeProto"; +option java_package = "com.google.rpc"; +option objc_class_prefix = "RPC"; + +// The canonical error codes for gRPC APIs. +// +// +// Sometimes multiple error codes may apply. Services should return +// the most specific error code that applies. For example, prefer +// `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply. +// Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`. +enum Code { + // Not an error; returned on success. + // + // HTTP Mapping: 200 OK + OK = 0; + + // The operation was cancelled, typically by the caller. + // + // HTTP Mapping: 499 Client Closed Request + CANCELLED = 1; + + // Unknown error. For example, this error may be returned when + // a `Status` value received from another address space belongs to + // an error space that is not known in this address space. Also + // errors raised by APIs that do not return enough error information + // may be converted to this error. + // + // HTTP Mapping: 500 Internal Server Error + UNKNOWN = 2; + + // The client specified an invalid argument. Note that this differs + // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments + // that are problematic regardless of the state of the system + // (e.g., a malformed file name). + // + // HTTP Mapping: 400 Bad Request + INVALID_ARGUMENT = 3; + + // The deadline expired before the operation could complete. For operations + // that change the state of the system, this error may be returned + // even if the operation has completed successfully. For example, a + // successful response from a server could have been delayed long + // enough for the deadline to expire. + // + // HTTP Mapping: 504 Gateway Timeout + DEADLINE_EXCEEDED = 4; + + // Some requested entity (e.g., file or directory) was not found. + // + // Note to server developers: if a request is denied for an entire class + // of users, such as gradual feature rollout or undocumented allowlist, + // `NOT_FOUND` may be used. If a request is denied for some users within + // a class of users, such as user-based access control, `PERMISSION_DENIED` + // must be used. + // + // HTTP Mapping: 404 Not Found + NOT_FOUND = 5; + + // The entity that a client attempted to create (e.g., file or directory) + // already exists. + // + // HTTP Mapping: 409 Conflict + ALREADY_EXISTS = 6; + + // The caller does not have permission to execute the specified + // operation. `PERMISSION_DENIED` must not be used for rejections + // caused by exhausting some resource (use `RESOURCE_EXHAUSTED` + // instead for those errors). `PERMISSION_DENIED` must not be + // used if the caller can not be identified (use `UNAUTHENTICATED` + // instead for those errors). This error code does not imply the + // request is valid or the requested entity exists or satisfies + // other pre-conditions. + // + // HTTP Mapping: 403 Forbidden + PERMISSION_DENIED = 7; + + // The request does not have valid authentication credentials for the + // operation. + // + // HTTP Mapping: 401 Unauthorized + UNAUTHENTICATED = 16; + + // Some resource has been exhausted, perhaps a per-user quota, or + // perhaps the entire file system is out of space. + // + // HTTP Mapping: 429 Too Many Requests + RESOURCE_EXHAUSTED = 8; + + // The operation was rejected because the system is not in a state + // required for the operation's execution. For example, the directory + // to be deleted is non-empty, an rmdir operation is applied to + // a non-directory, etc. + // + // Service implementors can use the following guidelines to decide + // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`: + // (a) Use `UNAVAILABLE` if the client can retry just the failing call. + // (b) Use `ABORTED` if the client should retry at a higher level. For + // example, when a client-specified test-and-set fails, indicating the + // client should restart a read-modify-write sequence. + // (c) Use `FAILED_PRECONDITION` if the client should not retry until + // the system state has been explicitly fixed. For example, if an "rmdir" + // fails because the directory is non-empty, `FAILED_PRECONDITION` + // should be returned since the client should not retry unless + // the files are deleted from the directory. + // + // HTTP Mapping: 400 Bad Request + FAILED_PRECONDITION = 9; + + // The operation was aborted, typically due to a concurrency issue such as + // a sequencer check failure or transaction abort. + // + // See the guidelines above for deciding between `FAILED_PRECONDITION`, + // `ABORTED`, and `UNAVAILABLE`. + // + // HTTP Mapping: 409 Conflict + ABORTED = 10; + + // The operation was attempted past the valid range. E.g., seeking or + // reading past end-of-file. + // + // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may + // be fixed if the system state changes. For example, a 32-bit file + // system will generate `INVALID_ARGUMENT` if asked to read at an + // offset that is not in the range [0,2^32-1], but it will generate + // `OUT_OF_RANGE` if asked to read from an offset past the current + // file size. + // + // There is a fair bit of overlap between `FAILED_PRECONDITION` and + // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific + // error) when it applies so that callers who are iterating through + // a space can easily look for an `OUT_OF_RANGE` error to detect when + // they are done. + // + // HTTP Mapping: 400 Bad Request + OUT_OF_RANGE = 11; + + // The operation is not implemented or is not supported/enabled in this + // service. + // + // HTTP Mapping: 501 Not Implemented + UNIMPLEMENTED = 12; + + // Internal errors. This means that some invariants expected by the + // underlying system have been broken. This error code is reserved + // for serious errors. + // + // HTTP Mapping: 500 Internal Server Error + INTERNAL = 13; + + // The service is currently unavailable. This is most likely a + // transient condition, which can be corrected by retrying with + // a backoff. Note that it is not always safe to retry + // non-idempotent operations. + // + // See the guidelines above for deciding between `FAILED_PRECONDITION`, + // `ABORTED`, and `UNAVAILABLE`. + // + // HTTP Mapping: 503 Service Unavailable + UNAVAILABLE = 14; + + // Unrecoverable data loss or corruption. + // + // HTTP Mapping: 500 Internal Server Error + DATA_LOSS = 15; +} diff --git a/control-plane/google/rpc/status.proto b/control-plane/google/rpc/status.proto index 8fca6ab22d..923e169381 100644 --- a/control-plane/google/rpc/status.proto +++ b/control-plane/google/rpc/status.proto @@ -1,4 +1,4 @@ -// Copyright (c) 2015, Google Inc. +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,73 +18,32 @@ package google.rpc; import "google/protobuf/any.proto"; +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/rpc/status;status"; option java_multiple_files = true; option java_outer_classname = "StatusProto"; option java_package = "com.google.rpc"; +option objc_class_prefix = "RPC"; - -// The `Status` type defines a logical error model that is suitable for different -// programming environments, including REST APIs and RPC APIs. It is used by -// [gRPC](https://github.com/grpc). The error model is designed to be: -// -// - Simple to use and understand for most users -// - Flexible enough to meet unexpected needs -// -// # Overview -// -// The `Status` message contains three pieces of data: error code, error message, -// and error details. The error code should be an enum value of -// [google.rpc.Code][google.rpc.Code], but it may accept additional error codes if needed. The -// error message should be a developer-facing English message that helps -// developers *understand* and *resolve* the error. If a localized user-facing -// error message is needed, put the localized message in the error details or -// localize it in the client. The optional error details may contain arbitrary -// information about the error. There is a predefined set of error detail types -// in the package `google.rpc` which can be used for common error conditions. -// -// # Language mapping -// -// The `Status` message is the logical representation of the error model, but it -// is not necessarily the actual wire format. When the `Status` message is -// exposed in different client libraries and different wire protocols, it can be -// mapped differently. For example, it will likely be mapped to some exceptions -// in Java, but more likely mapped to some error codes in C. -// -// # Other uses -// -// The error model and the `Status` message can be used in a variety of -// environments, either with or without APIs, to provide a -// consistent developer experience across different environments. -// -// Example uses of this error model include: -// -// - Partial errors. If a service needs to return partial errors to the client, -// it may embed the `Status` in the normal response to indicate the partial -// errors. -// -// - Workflow errors. A typical workflow has multiple steps. Each step may -// have a `Status` message for error reporting purpose. -// -// - Batch operations. If a client uses batch request and batch response, the -// `Status` message should be used directly inside batch response, one for -// each error sub-response. -// -// - Asynchronous operations. If an API call embeds asynchronous operation -// results in its response, the status of those operations should be -// represented directly using the `Status` message. +// The `Status` type defines a logical error model that is suitable for +// different programming environments, including REST APIs and RPC APIs. It is +// used by [gRPC](https://github.com/grpc). Each `Status` message contains +// three pieces of data: error code, error message, and error details. // -// - Logging. If some API errors are stored in logs, the message `Status` could -// be used directly after any stripping needed for security/privacy reasons. +// You can find out more about this error model and how to work with it in the +// [API Design Guide](https://cloud.google.com/apis/design/errors). message Status { - // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + // The status code, which should be an enum value of + // [google.rpc.Code][google.rpc.Code]. int32 code = 1; // A developer-facing error message, which should be in English. Any // user-facing error message should be localized and sent in the - // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + // [google.rpc.Status.details][google.rpc.Status.details] field, or localized + // by the client. string message = 2; - // A list of messages that carry the error details. There will be a - // common set of message types for APIs to use. + // A list of messages that carry the error details. There is a common set of + // message types for APIs to use. repeated google.protobuf.Any details = 3; }