diff --git a/lib/p4rt/packet_listener.h b/lib/p4rt/packet_listener.h index bf897f21..f082e5d9 100644 --- a/lib/p4rt/packet_listener.h +++ b/lib/p4rt/packet_listener.h @@ -43,8 +43,8 @@ class PacketListener : public thinkit::PacketGenerationFinalizer { PacketListener(pdpi::P4RuntimeSession *session, P4rtProgrammingContext context, sai::Instantiation instantiation, - const absl::flat_hash_map - *interface_port_id_to_name); + const absl::flat_hash_map* + interface_port_id_to_name); absl::Status HandlePacketsFor(absl::Duration duration, thinkit::PacketCallback callback_) override; @@ -60,8 +60,8 @@ class PacketListener : public thinkit::PacketGenerationFinalizer { pdpi::P4RuntimeSession *session_; P4rtProgrammingContext context_; sai::Instantiation instantiation_; - const absl::flat_hash_map - &interface_port_id_to_name_; + const absl::flat_hash_map& + interface_port_id_to_name_; }; } // namespace pins_test diff --git a/p4_symbolic/BUILD.bazel b/p4_symbolic/BUILD.bazel index d3f2cd24..b4283043 100644 --- a/p4_symbolic/BUILD.bazel +++ b/p4_symbolic/BUILD.bazel @@ -98,7 +98,6 @@ cc_test( deps = [ ":deparser", ":test_util", - ":z3_util", "//gutil:status_matchers", "//p4_pdpi/packetlib", "//p4_pdpi/packetlib:packetlib_cc_proto", diff --git a/p4_symbolic/deparser_test.cc b/p4_symbolic/deparser_test.cc index 50ad2400..7945f4e6 100644 --- a/p4_symbolic/deparser_test.cc +++ b/p4_symbolic/deparser_test.cc @@ -29,7 +29,6 @@ #include "p4_symbolic/symbolic/symbolic.h" #include "p4_symbolic/symbolic/util.h" #include "p4_symbolic/test_util.h" -#include "p4_symbolic/z3_util.h" #include "z3++.h" namespace p4_symbolic { @@ -55,7 +54,6 @@ class IPv4RoutingBasicTest : public testing::Test { ASSERT_OK_AND_ASSIGN( std::vector entries, GetPiTableEntriesFromPiUpdatesProtoTextFile(entries_path)); - Z3Context(/*renew=*/true); ASSERT_OK_AND_ASSIGN(state_, symbolic::EvaluateP4Program(config, entries)); } @@ -73,12 +71,12 @@ TEST_F(IPv4RoutingBasicTest, DeparseIngressAndEgressHeadersWithoutConstraints) { TEST_F(IPv4RoutingBasicTest, DeparseIngressHeaders1) { // Add constraints. { + z3::context &z3_context = *state_->context.z3_context; ASSERT_OK_AND_ASSIGN( z3::expr eth_dst_addr, state_->context.egress_headers.Get("ethernet.dstAddr")); - state_->solver->add(eth_dst_addr == Z3Context().bv_val(0, 48)); - state_->solver->add(state_->context.egress_port == - Z3Context().bv_val(1, 9)); + state_->solver->add(eth_dst_addr == z3_context.bv_val(0, 48)); + state_->solver->add(state_->context.egress_port == z3_context.bv_val(1, 9)); ASSERT_OK_AND_ASSIGN( z3::expr ipv4_valid, @@ -105,13 +103,13 @@ TEST_F(IPv4RoutingBasicTest, DeparseIngressHeaders1) { TEST_F(IPv4RoutingBasicTest, DeparseIngressHeaders2) { // Add constraints. { + z3::context &z3_context = *state_->context.z3_context; ASSERT_OK_AND_ASSIGN( z3::expr eth_dst_addr, state_->context.egress_headers.Get("ethernet.dstAddr")); state_->solver->add(eth_dst_addr == - Z3Context().bv_val((22UL << 40) + 22, 48)); - state_->solver->add(state_->context.egress_port == - Z3Context().bv_val(1, 9)); + z3_context.bv_val((22UL << 40) + 22, 48)); + state_->solver->add(state_->context.egress_port == z3_context.bv_val(1, 9)); ASSERT_OK_AND_ASSIGN( z3::expr ipv4_valid, @@ -139,10 +137,11 @@ TEST_F(IPv4RoutingBasicTest, DeparseIngressHeaders2) { TEST_F(IPv4RoutingBasicTest, DeparseEgressHeaders) { // Add constraints. { + z3::context &z3_context = *state_->context.z3_context; ASSERT_OK_AND_ASSIGN(z3::expr ipv4_dst_addr, state_->context.ingress_headers.Get("ipv4.dstAddr")); state_->solver->add(ipv4_dst_addr == - Z3Context().bv_val((10UL << 24) + 1, 32)); + z3_context.bv_val((10UL << 24) + 1, 32)); ASSERT_OK_AND_ASSIGN( z3::expr ipv4_valid, @@ -180,7 +179,6 @@ class SaiDeparserTest : public testing::Test { ASSERT_OK_AND_ASSIGN( p4::v1::ForwardingPipelineConfig config, ParseToForwardingPipelineConfig(bmv2_json_path, p4info_path)); - Z3Context(/*renew=*/true); ASSERT_OK_AND_ASSIGN(state_, symbolic::EvaluateP4Program(config, /*entries=*/{})); } diff --git a/p4_symbolic/packet_synthesizer/util.cc b/p4_symbolic/packet_synthesizer/util.cc index 9c99f889..271cd865 100644 --- a/p4_symbolic/packet_synthesizer/util.cc +++ b/p4_symbolic/packet_synthesizer/util.cc @@ -38,8 +38,8 @@ namespace { // The use of a template here is a hack to improve readability. // TODO: Move to p4-symbolic utility library. template -inline z3::expr Bitvector(uint64_t value) { - return p4_symbolic::Z3Context().bv_val(value, num_bits); +inline z3::expr Bitvector(uint64_t value, z3::context& z3_context) { + return z3_context.bv_val(value, num_bits); } // TODO: Move to netaddr. @@ -76,41 +76,57 @@ absl::StatusOr> GetUnicastIpv6Ranges() { } // TODO: Move to p4-symbolic utility library and add unit tests. -absl::StatusOr Ipv6PrefixLengthToZ3Mask(int prefix_length) { +absl::StatusOr Ipv6PrefixLengthToZ3Mask(int prefix_length, + z3::context& z3_context) { if (prefix_length < 0 || prefix_length > 128) { return gutil::InvalidArgumentErrorBuilder() << "invalid IPv6 prefix length: " << prefix_length; } if (prefix_length > 64) { - return Bitvector<128>(0xFFFF'FFFF'FFFF'FFFFu).rotate_left(64) | - Bitvector<128>(0xFFFF'FFFF'FFFF'FFFFu << (128 - prefix_length)); + return Bitvector<128>(0xFFFF'FFFF'FFFF'FFFFu, z3_context).rotate_left(64) | + Bitvector<128>(0xFFFF'FFFF'FFFF'FFFFu << (128 - prefix_length), + z3_context); } else { - return Bitvector<128>(0xFFFF'FFFF'FFFF'FFFFu << (64 - prefix_length)) + return Bitvector<128>(0xFFFF'FFFF'FFFF'FFFFu << (64 - prefix_length), + z3_context) .rotate_left(64); } } // TODO: Move to p4-symbolic utility library. absl::StatusOr Ipv6AddressToZ3Bitvector( - const netaddr::Ipv6Address& ipv6) { + const netaddr::Ipv6Address& ipv6, z3::context& z3_context) { std::bitset<128> bits = ipv6.ToBitset(); uint64_t upper = (bits >> 64).to_ullong(); uint64_t lower = (bits & std::bitset<128>(0xFFFF'FFFF'FFFF'FFFFu)).to_ullong(); - return Bitvector<128>(upper).rotate_left(64) | Bitvector<128>(lower); + return Bitvector<128>(upper, z3_context).rotate_left(64) | + Bitvector<128>(lower, z3_context); } absl::StatusOr IsIpv6UnicastAddress(z3::expr ipv6_address) { - z3::expr result = p4_symbolic::Z3Context().bool_val(false); + z3::context& z3_context = ipv6_address.ctx(); + z3::expr result = z3_context.bool_val(false); ASSIGN_OR_RETURN(std::vector ranges, GetUnicastIpv6Ranges()); for (auto& range : ranges) { - ASSIGN_OR_RETURN(z3::expr value, Ipv6AddressToZ3Bitvector(range.value)); + ASSIGN_OR_RETURN(z3::expr value, + Ipv6AddressToZ3Bitvector(range.value, z3_context)); ASSIGN_OR_RETURN(z3::expr mask, - Ipv6PrefixLengthToZ3Mask(range.prefix_length)); + Ipv6PrefixLengthToZ3Mask(range.prefix_length, z3_context)); result = result || ((ipv6_address & mask) == value); } return result; } + +absl::StatusOr IrValueToZ3Bitvector(const pdpi::IrValue& value, + int bitwidth, + z3::context& z3_context) { + ASSIGN_OR_RETURN(const std::string bytes, + pdpi::IrValueToNormalizedByteString(value, bitwidth)); + const std::string hex_string = pdpi::ByteStringToHexString(bytes); + return HexStringToZ3Bitvector(z3_context, hex_string, bitwidth); +} + } // namespace // TODO: We need extra constraints to avoid generating packets @@ -119,6 +135,8 @@ absl::StatusOr IsIpv6UnicastAddress(z3::expr ipv6_address) { // function would disappear. absl::Status AddSanePacketConstraints( p4_symbolic::symbolic::SolverState& state) { + z3::context& z3_context = *state.context.z3_context; + // ======Ethernet constraints====== ASSIGN_OR_RETURN(z3::expr ethernet_src_addr, state.context.ingress_headers.Get("ethernet.src_addr")); @@ -134,13 +152,14 @@ absl::Status AddSanePacketConstraints( // such packets still continue to hit the acl_ingress table and some of the // entries cause such packets to get punted. So we should not disallow the // generation of such packets. - state.solver->add((ethernet_src_addr & Bitvector<48>(0x03'00'00'00'00'00)) == - Bitvector<48>(0x02'00'00'00'00'00)); + state.solver->add( + (ethernet_src_addr & Bitvector<48>(0x03'00'00'00'00'00, z3_context)) == + Bitvector<48>(0x02'00'00'00'00'00, z3_context)); for (auto& mac_address : {ethernet_src_addr, ethernet_dst_addr}) { // Require key parts of the address to be nonzero to avoid corner cases. auto nonzero_masks = std::vector({ - Bitvector<48>(0x00'FF'FF'00'00'00), // OUI + Bitvector<48>(0x00'FF'FF'00'00'00, z3_context), // OUI }); for (auto& nonzero_mask : nonzero_masks) { state.solver->add((mac_address & nonzero_mask) != 0); @@ -156,15 +175,16 @@ absl::Status AddSanePacketConstraints( state.context.ingress_headers.Get("ipv4.dst_addr")); // Avoid martian IP addresses. // https://tools.ietf.org/html/rfc1122#section-3.2.1.3 - state.solver->add((ipv4_src_addr & Bitvector<32>(0xFF'00'00'00)) != 0); + state.solver->add( + (ipv4_src_addr & Bitvector<32>(0xFF'00'00'00, z3_context)) != 0); // Src IP address cannot be 255.255.255.255 (broadcast). - state.solver->add(ipv4_src_addr != Bitvector<32>(0xFF'FF'FF'FF)); + state.solver->add(ipv4_src_addr != Bitvector<32>(0xFF'FF'FF'FF, z3_context)); // Neither src nor dst IPv4 addresses can be 0. - state.solver->add(ipv4_src_addr != Bitvector<32>(0)); - state.solver->add(ipv4_dst_addr != Bitvector<32>(0)); + state.solver->add(ipv4_src_addr != Bitvector<32>(0, z3_context)); + state.solver->add(ipv4_dst_addr != Bitvector<32>(0, z3_context)); // Neither src nor dst IPv4 addresses can be 127.0.0.1 (loopback). - state.solver->add(ipv4_src_addr != Bitvector<32>(0x7F'00'00'01)); - state.solver->add(ipv4_dst_addr != Bitvector<32>(0x7F'00'00'01)); + state.solver->add(ipv4_src_addr != Bitvector<32>(0x7F'00'00'01, z3_context)); + state.solver->add(ipv4_dst_addr != Bitvector<32>(0x7F'00'00'01, z3_context)); // ======Ipv6 constraints====== ASSIGN_OR_RETURN(z3::expr ipv6_src_addr, @@ -177,11 +197,11 @@ absl::Status AddSanePacketConstraints( state.solver->add(constraint); } // Neither src nor dst IPv6 addresses can be 0. - state.solver->add(ipv6_src_addr != Bitvector<128>(0)); - state.solver->add(ipv6_dst_addr != Bitvector<128>(0)); + state.solver->add(ipv6_src_addr != Bitvector<128>(0, z3_context)); + state.solver->add(ipv6_dst_addr != Bitvector<128>(0, z3_context)); // Neither src nor dst IPv6 addresses can be ::1 (loopback). - state.solver->add(ipv6_src_addr != Bitvector<128>(1)); - state.solver->add(ipv6_dst_addr != Bitvector<128>(1)); + state.solver->add(ipv6_src_addr != Bitvector<128>(1, z3_context)); + state.solver->add(ipv6_dst_addr != Bitvector<128>(1, z3_context)); // ======VLAN constraints========== // TODO: Make unconditional when we no longer need @@ -193,40 +213,34 @@ absl::Status AddSanePacketConstraints( state.context.ingress_headers.Get("vlan.vlan_id")); // TODO: Consider testing the following VIDs when PacketIO is // properly modeled. - state.solver->add(vlan_id != Bitvector<12>(0xFFF)); - state.solver->add(vlan_id != Bitvector<12>(0x001)); + state.solver->add(vlan_id != Bitvector<12>(0xFFF, z3_context)); + state.solver->add(vlan_id != Bitvector<12>(0x001, z3_context)); } return absl::OkStatus(); } -absl::StatusOr IrValueToZ3Bitvector(const pdpi::IrValue& value, - int bitwidth) { - ASSIGN_OR_RETURN(const std::string bytes, - pdpi::IrValueToNormalizedByteString(value, bitwidth)); - const std::string hex_string = pdpi::ByteStringToHexString(bytes); - return HexStringToZ3Bitvector(Z3Context(), hex_string, bitwidth); -} - // The following functions return Z3 constraints corresponding to `field` // matching the given pdpi::IrMatch value. absl::StatusOr GetFieldMatchConstraints(z3::expr field, int bitwidth, const pdpi::IrValue& match) { - ASSIGN_OR_RETURN(z3::expr value, IrValueToZ3Bitvector(match, bitwidth)); + ASSIGN_OR_RETURN(z3::expr value, + IrValueToZ3Bitvector(match, bitwidth, field.ctx())); return Eq(field, value); } absl::StatusOr GetFieldMatchConstraints( z3::expr field, int bitwidth, const pdpi::IrMatch::IrLpmMatch& match) { ASSIGN_OR_RETURN(z3::expr value, - IrValueToZ3Bitvector(match.value(), bitwidth)); + IrValueToZ3Bitvector(match.value(), bitwidth, field.ctx())); return PrefixEq(field, value, static_cast(match.prefix_length())); } absl::StatusOr GetFieldMatchConstraints( z3::expr field, int bitwidth, const pdpi::IrMatch::IrTernaryMatch& match) { ASSIGN_OR_RETURN(z3::expr value, - IrValueToZ3Bitvector(match.value(), bitwidth)); - ASSIGN_OR_RETURN(z3::expr mask, IrValueToZ3Bitvector(match.mask(), bitwidth)); + IrValueToZ3Bitvector(match.value(), bitwidth, field.ctx())); + ASSIGN_OR_RETURN(z3::expr mask, + IrValueToZ3Bitvector(match.mask(), bitwidth, field.ctx())); ASSIGN_OR_RETURN(z3::expr masked_field, BitAnd(field, mask)); return Eq(masked_field, value); } diff --git a/p4_symbolic/packet_synthesizer/util.h b/p4_symbolic/packet_synthesizer/util.h index df9c70f0..5fef9228 100644 --- a/p4_symbolic/packet_synthesizer/util.h +++ b/p4_symbolic/packet_synthesizer/util.h @@ -18,7 +18,7 @@ #include "absl/status/status.h" #include "p4_symbolic/symbolic/symbolic.h" -// This file contains varrious utility functions and classes used by packet +// This file contains various utility functions and classes used by packet // synthesizer. namespace p4_symbolic::packet_synthesizer { diff --git a/p4_symbolic/symbolic/BUILD.bazel b/p4_symbolic/symbolic/BUILD.bazel index 77af8734..b5e302f4 100644 --- a/p4_symbolic/symbolic/BUILD.bazel +++ b/p4_symbolic/symbolic/BUILD.bazel @@ -136,6 +136,7 @@ cc_test( "//gutil:testing", "//p4_pdpi:ir_cc_proto", "//p4_symbolic:z3_util", + "@com_github_z3prover_z3//:api", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/strings", "@com_google_googletest//:gtest_main", @@ -149,7 +150,6 @@ cc_test( ":symbolic", "//gutil:proto", "//gutil:status_matchers", - "//p4_symbolic:z3_util", "//p4_symbolic/ir", "//p4_symbolic/ir:ir_cc_proto", "@com_github_google_glog//:glog", diff --git a/p4_symbolic/symbolic/action.cc b/p4_symbolic/symbolic/action.cc index d48d1936..389c02a1 100644 --- a/p4_symbolic/symbolic/action.cc +++ b/p4_symbolic/symbolic/action.cc @@ -24,6 +24,7 @@ #include "gutil/collections.h" #include "p4_symbolic/symbolic/operators.h" #include "p4_symbolic/symbolic/symbolic.h" +#include "p4_symbolic/symbolic/v1model.h" #include "p4_symbolic/z3_util.h" namespace p4_symbolic { @@ -32,22 +33,24 @@ namespace action { absl::Status EvaluateStatement(const ir::Statement &statement, SymbolicPerPacketState *state, - ActionContext *context, const z3::expr &guard) { + ActionContext *context, z3::context &z3_context, + const z3::expr &guard) { switch (statement.statement_case()) { case ir::Statement::kAssignment: { return EvaluateAssignmentStatement(statement.assignment(), state, context, - guard); + z3_context, guard); } case ir::Statement::kClone: { // TODO: Add support for cloning. return state->Set(std::string(kGotClonedPseudoField), - Z3Context().bool_val(true), guard); + z3_context.bool_val(true), guard); } case ir::Statement::kDrop: { // https://github.com/p4lang/p4c/blob/7ee76d16da63883c5092ab0c28321f04c2646759/p4include/v1model.p4#L435 const std::string &header_name = statement.drop().header().header_name(); RETURN_IF_ERROR(state->Set(absl::StrFormat("%s.egress_spec", header_name), - EgressSpecDroppedValue(), guard)); + v1model::EgressSpecDroppedValue(z3_context), + guard)); return absl::OkStatus(); } case ir::Statement::kHash: { @@ -84,11 +87,11 @@ absl::Status EvaluateStatement(const ir::Statement &statement, // the action scope. absl::Status EvaluateAssignmentStatement( const ir::AssignmentStatement &assignment, SymbolicPerPacketState *state, - ActionContext *context, const z3::expr &guard) { + ActionContext *context, z3::context &z3_context, const z3::expr &guard) { // Evaluate RValue recursively, evaluate LValue in this function, then // assign RValue to the scope at LValue. - ASSIGN_OR_RETURN(z3::expr right, - EvaluateRValue(assignment.right(), *state, *context)); + ASSIGN_OR_RETURN(z3::expr right, EvaluateRValue(assignment.right(), *state, + *context, z3_context)); switch (assignment.left().lvalue_case()) { case ir::LValue::kFieldValue: { @@ -115,25 +118,27 @@ absl::Status EvaluateAssignmentStatement( // to its type. absl::StatusOr EvaluateRValue(const ir::RValue &rvalue, const SymbolicPerPacketState &state, - const ActionContext &context) { + const ActionContext &context, + z3::context &z3_context) { switch (rvalue.rvalue_case()) { case ir::RValue::kFieldValue: return EvaluateFieldValue(rvalue.field_value(), state); case ir::RValue::kHexstrValue: - return EvaluateHexStr(rvalue.hexstr_value()); + return EvaluateHexStr(rvalue.hexstr_value(), z3_context); case ir::RValue::kBoolValue: - return EvaluateBool(rvalue.bool_value()); + return EvaluateBool(rvalue.bool_value(), z3_context); case ir::RValue::kStringValue: - return EvaluateString(rvalue.string_value()); + return EvaluateString(rvalue.string_value(), z3_context); case ir::RValue::kVariableValue: return EvaluateVariable(rvalue.variable_value(), context); case ir::RValue::kExpressionValue: - return EvaluateRExpression(rvalue.expression_value(), state, context); + return EvaluateRExpression(rvalue.expression_value(), state, context, + z3_context); default: return absl::UnimplementedError( @@ -149,7 +154,8 @@ absl::StatusOr EvaluateFieldValue( } // Turns bmv2 values to Symbolic Expressions. -absl::StatusOr EvaluateHexStr(const ir::HexstrValue &hexstr) { +absl::StatusOr EvaluateHexStr(const ir::HexstrValue &hexstr, + z3::context &z3_context) { if (hexstr.negative()) { return absl::UnimplementedError( "Negative hex string values are not supported!"); @@ -157,15 +163,17 @@ absl::StatusOr EvaluateHexStr(const ir::HexstrValue &hexstr) { ASSIGN_OR_RETURN(pdpi::IrValue parsed_value, values::ParseIrValue(hexstr.value())); - return HexStringToZ3Bitvector(Z3Context(), hexstr.value()); + return HexStringToZ3Bitvector(z3_context, hexstr.value()); } -absl::StatusOr EvaluateBool(const ir::BoolValue &bool_value) { - return Z3Context().bool_val(bool_value.value()); +absl::StatusOr EvaluateBool(const ir::BoolValue &bool_value, + z3::context &z3_context) { + return z3_context.bool_val(bool_value.value()); } -absl::StatusOr EvaluateString(const ir::StringValue &string_value) { - return Z3Context().string_val(string_value.value().c_str()); +absl::StatusOr EvaluateString(const ir::StringValue &string_value, + z3::context &z3_context) { + return z3_context.string_val(string_value.value().c_str()); } // Looks up the symbolic value of the variable in the action scope. @@ -184,14 +192,14 @@ absl::StatusOr EvaluateVariable(const ir::Variable &variable, // Recursively evaluate expressions. absl::StatusOr EvaluateRExpression( const ir::RExpression &expr, const SymbolicPerPacketState &state, - const ActionContext &context) { + const ActionContext &context, z3::context &z3_context) { switch (expr.expression_case()) { case ir::RExpression::kBinaryExpression: { ir::BinaryExpression bin_expr = expr.binary_expression(); - ASSIGN_OR_RETURN(z3::expr left, - EvaluateRValue(bin_expr.left(), state, context)); - ASSIGN_OR_RETURN(z3::expr right, - EvaluateRValue(bin_expr.right(), state, context)); + ASSIGN_OR_RETURN(z3::expr left, EvaluateRValue(bin_expr.left(), state, + context, z3_context)); + ASSIGN_OR_RETURN(z3::expr right, EvaluateRValue(bin_expr.right(), state, + context, z3_context)); switch (bin_expr.operation()) { case ir::BinaryExpression::PLUS: return operators::Plus(left, right); @@ -236,8 +244,9 @@ absl::StatusOr EvaluateRExpression( case ir::RExpression::kUnaryExpression: { ir::UnaryExpression un_expr = expr.unary_expression(); - ASSIGN_OR_RETURN(z3::expr operand, - EvaluateRValue(un_expr.operand(), state, context)); + ASSIGN_OR_RETURN( + z3::expr operand, + EvaluateRValue(un_expr.operand(), state, context, z3_context)); switch (un_expr.operation()) { case ir::UnaryExpression::NOT: return operators::Not(operand); @@ -253,12 +262,13 @@ absl::StatusOr EvaluateRExpression( case ir::RExpression::kTernaryExpression: { ir::TernaryExpression tern_expr = expr.ternary_expression(); - ASSIGN_OR_RETURN(z3::expr condition, - EvaluateRValue(tern_expr.condition(), state, context)); - ASSIGN_OR_RETURN(z3::expr left, - EvaluateRValue(tern_expr.left(), state, context)); - ASSIGN_OR_RETURN(z3::expr right, - EvaluateRValue(tern_expr.right(), state, context)); + ASSIGN_OR_RETURN( + z3::expr condition, + EvaluateRValue(tern_expr.condition(), state, context, z3_context)); + ASSIGN_OR_RETURN(z3::expr left, EvaluateRValue(tern_expr.left(), state, + context, z3_context)); + ASSIGN_OR_RETURN(z3::expr right, EvaluateRValue(tern_expr.right(), state, + context, z3_context)); return operators::Ite(condition, left, right); } @@ -268,7 +278,7 @@ absl::StatusOr EvaluateRExpression( std::vector args; for (const auto &arg_value : builtin_expr.arguments()) { ASSIGN_OR_RETURN(z3::expr arg, - EvaluateRValue(arg_value, state, context)); + EvaluateRValue(arg_value, state, context, z3_context)); args.push_back(arg); } @@ -301,7 +311,7 @@ absl::Status EvaluateAction(const ir::Action &action, pdpi::IrActionInvocation::IrActionParam> &args, SymbolicPerPacketState *state, values::P4RuntimeTranslator *translator, - const z3::expr &guard) { + z3::context &z3_context, const z3::expr &guard) { // Construct this action's context. ActionContext context; context.action_name = action.action_definition().preamble().name(); @@ -329,7 +339,7 @@ absl::Status EvaluateAction(const ir::Action &action, ASSIGN_OR_RETURN( z3::expr parameter_value, values::FormatP4RTValue( - Z3Context(), /*field_name=*/"", + z3_context, /*field_name=*/"", param_definition->param().type_name().name(), arg.value(), param_definition->param().bitwidth(), translator)); context.scope.insert({param_definition->param().name(), parameter_value}); @@ -337,7 +347,8 @@ absl::Status EvaluateAction(const ir::Action &action, // Iterate over the body in order, and evaluate each statement. for (const auto &statement : action.action_implementation().action_body()) { - RETURN_IF_ERROR(EvaluateStatement(statement, state, &context, guard)); + RETURN_IF_ERROR( + EvaluateStatement(statement, state, &context, z3_context, guard)); } return absl::OkStatus(); diff --git a/p4_symbolic/symbolic/action.h b/p4_symbolic/symbolic/action.h index 64da0027..5a4ab7f0 100644 --- a/p4_symbolic/symbolic/action.h +++ b/p4_symbolic/symbolic/action.h @@ -42,7 +42,7 @@ absl::Status EvaluateAction(const ir::Action &action, pdpi::IrActionInvocation::IrActionParam> &args, SymbolicPerPacketState *state, values::P4RuntimeTranslator *translator, - const z3::expr &guard); + z3::context &z3_context, const z3::expr &guard); // Internal functions used to Evaluate statements and expressions within an // action body. These are internal functions not used beyond this header and its @@ -58,32 +58,36 @@ struct ActionContext { // appropriate function. absl::Status EvaluateStatement(const ir::Statement &statement, SymbolicPerPacketState *state, - ActionContext *context, const z3::expr &guard); + ActionContext *context, z3::context &z3_context, + const z3::expr &guard); // Constructs a symbolic expression for the assignment value, and either // constrains it in an enclosing assignment expression, or stores it in // the action scope. -absl::Status -EvaluateAssignmentStatement(const ir::AssignmentStatement &assignment, - SymbolicPerPacketState *state, - ActionContext *context, const z3::expr &guard); +absl::Status EvaluateAssignmentStatement( + const ir::AssignmentStatement &assignment, SymbolicPerPacketState *state, + ActionContext *context, z3::context &z3_context, const z3::expr &guard); // Constructs a symbolic expression corresponding to this value, according // to its type. absl::StatusOr EvaluateRValue(const ir::RValue &rvalue, const SymbolicPerPacketState &state, - const ActionContext &context); + const ActionContext &context, + z3::context &z3_context); // Extract the field symbolic value from the symbolic state. absl::StatusOr EvaluateFieldValue( const ir::FieldValue &field_value, const SymbolicPerPacketState &state); // Parse and format literal values as symbolic expression. -absl::StatusOr EvaluateHexStr(const ir::HexstrValue &hexstr); +absl::StatusOr EvaluateHexStr(const ir::HexstrValue &hexstr, + z3::context &z3_context); -absl::StatusOr EvaluateBool(const ir::BoolValue &bool_value); +absl::StatusOr EvaluateBool(const ir::BoolValue &bool_value, + z3::context &z3_context); -absl::StatusOr EvaluateString(const ir::StringValue &string_value); +absl::StatusOr EvaluateString(const ir::StringValue &string_value, + z3::context &z3_context); // Looks up the symbolic value of the variable in the action scope. absl::StatusOr EvaluateVariable(const ir::Variable &variable, @@ -91,10 +95,9 @@ absl::StatusOr EvaluateVariable(const ir::Variable &variable, // Evaluate expression by recursively evaluating operands and applying the // symbolic version of the operator to them. -absl::StatusOr -EvaluateRExpression(const ir::RExpression &expr, - const SymbolicPerPacketState &state, - const ActionContext &context); +absl::StatusOr EvaluateRExpression( + const ir::RExpression &expr, const SymbolicPerPacketState &state, + const ActionContext &context, z3::context &z3_context); } // namespace action } // namespace symbolic diff --git a/p4_symbolic/symbolic/conditional.cc b/p4_symbolic/symbolic/conditional.cc index 8ccc95cf..6334f96c 100644 --- a/p4_symbolic/symbolic/conditional.cc +++ b/p4_symbolic/symbolic/conditional.cc @@ -35,12 +35,12 @@ namespace conditional { absl::StatusOr EvaluateConditional( const ir::Dataplane &data_plane, const ir::Conditional &conditional, SymbolicPerPacketState *state, values::P4RuntimeTranslator *translator, - const z3::expr &guard) { + z3::context &z3_context, const z3::expr &guard) { // Evaluate the condition. action::ActionContext fake_context = {conditional.name(), {}}; - ASSIGN_OR_RETURN( - z3::expr condition, - action::EvaluateRValue(conditional.condition(), *state, fake_context)); + ASSIGN_OR_RETURN(z3::expr condition, + action::EvaluateRValue(conditional.condition(), *state, + fake_context, z3_context)); ASSIGN_OR_RETURN(z3::expr negated_condition, operators::Not(condition)); // Build new guards for each branch. @@ -60,17 +60,17 @@ absl::StatusOr EvaluateConditional( SymbolicTableMatches if_matches, control::EvaluateControl( data_plane, get_next_control_for_branch(conditional.if_branch()), - state, translator, if_guard)); + state, translator, z3_context, if_guard)); ASSIGN_OR_RETURN( SymbolicTableMatches else_matches, control::EvaluateControl( data_plane, get_next_control_for_branch(conditional.else_branch()), - state, translator, else_guard)); + state, translator, z3_context, else_guard)); // Now we have two traces that need merging. - ASSIGN_OR_RETURN( - SymbolicTableMatches merged_matches, - util::MergeMatchesOnCondition(condition, if_matches, else_matches)); + ASSIGN_OR_RETURN(SymbolicTableMatches merged_matches, + util::MergeMatchesOnCondition(condition, if_matches, + else_matches, z3_context)); if (!conditional.optimized_symbolic_execution_info() .continue_to_merge_point()) { @@ -84,7 +84,7 @@ absl::StatusOr EvaluateConditional( control::EvaluateControl( data_plane, conditional.optimized_symbolic_execution_info().merge_point(), - state, translator, guard)); + state, translator, z3_context, guard)); // Merge the result of execution from the merge point with the result of // merged if/else branches. diff --git a/p4_symbolic/symbolic/conditional.h b/p4_symbolic/symbolic/conditional.h index c9b9febf..4b1e9a0d 100644 --- a/p4_symbolic/symbolic/conditional.h +++ b/p4_symbolic/symbolic/conditional.h @@ -31,7 +31,7 @@ namespace conditional { absl::StatusOr EvaluateConditional( const ir::Dataplane &data_plane, const ir::Conditional &conditional, SymbolicPerPacketState *state, values::P4RuntimeTranslator *translator, - const z3::expr &guard); + z3::context &z3_context, const z3::expr &guard); } // namespace conditional } // namespace symbolic diff --git a/p4_symbolic/symbolic/context.h b/p4_symbolic/symbolic/context.h index 4ec76c96..35faeee0 100644 --- a/p4_symbolic/symbolic/context.h +++ b/p4_symbolic/symbolic/context.h @@ -15,11 +15,11 @@ #ifndef PINS_P4_SYMBOLIC_SYMBOLIC_CONTEXT_H_ #define PINS_P4_SYMBOLIC_SYMBOLIC_CONTEXT_H_ +#include #include #include "absl/container/btree_map.h" #include "p4_symbolic/symbolic/guarded_map.h" -#include "p4_symbolic/z3_util.h" #include "z3++.h" namespace p4_symbolic { @@ -83,7 +83,8 @@ struct ConcreteTrace { // Provides symbolic handles for the trace the symbolic packet is constrained // to take in the program. struct SymbolicTrace { - SymbolicTrace() : dropped(Z3Context()), got_cloned(Z3Context()) {} + SymbolicTrace(z3::context &z3_context) + : dropped(z3_context), got_cloned(z3_context) {} // Full table name to its symbolic match. // TODO: Rename to matches_by_table_name. @@ -113,8 +114,17 @@ struct ConcreteContext { // Assertions are defined on a symbolic context. class SymbolicContext { public: - SymbolicContext() : ingress_port(Z3Context()), egress_port(Z3Context()) {} - + SymbolicContext() + : z3_context(std::make_unique()), + ingress_port(*z3_context), + egress_port(*z3_context), + trace(*z3_context) {} + + // The Z3 context for the symbolic evaluation. + // Note that this has to precede other z3 objects so that they will be + // destroyed before z3_context during destruction. The `unique_ptr` wrapper is + // required because `z3::context` has an implicitly-deleted move constructor. + std::unique_ptr z3_context; z3::expr ingress_port; z3::expr egress_port; SymbolicPerPacketState ingress_headers; diff --git a/p4_symbolic/symbolic/control.cc b/p4_symbolic/symbolic/control.cc index 4a5c0ba0..cdac32e6 100644 --- a/p4_symbolic/symbolic/control.cc +++ b/p4_symbolic/symbolic/control.cc @@ -36,11 +36,11 @@ namespace p4_symbolic::symbolic::control { absl::StatusOr EvaluatePipeline( const ir::Dataplane &data_plane, const std::string &pipeline_name, SymbolicPerPacketState *state, values::P4RuntimeTranslator *translator, - const z3::expr &guard) { + z3::context &z3_context, const z3::expr &guard) { if (auto it = data_plane.program.pipeline().find(pipeline_name); it != data_plane.program.pipeline().end()) { return EvaluateControl(data_plane, it->second.initial_control(), state, - translator, guard); + translator, z3_context, guard); } return gutil::InvalidArgumentErrorBuilder() << "cannot evaluate unknown pipeline: '" << pipeline_name << "'"; @@ -49,7 +49,7 @@ absl::StatusOr EvaluatePipeline( absl::StatusOr EvaluateControl( const ir::Dataplane &data_plane, const std::string &control_name, SymbolicPerPacketState *state, values::P4RuntimeTranslator *translator, - const z3::expr &guard) { + z3::context &z3_context, const z3::expr &guard) { // Base case: we got to the end of the evaluation, no more controls! if (control_name == ir::EndOfPipeline()) return SymbolicTableMatches(); @@ -62,13 +62,13 @@ absl::StatusOr EvaluateControl( table_entries = data_plane.entries.at(control_name); } return table::EvaluateTable(data_plane, table, table_entries, state, - translator, guard); + translator, z3_context, guard); } else if (data_plane.program.conditionals().count(control_name) == 1) { // Conditional: let EvaluateConditional handle it. const ir::Conditional &conditional = data_plane.program.conditionals().at(control_name); return conditional::EvaluateConditional(data_plane, conditional, state, - translator, guard); + translator, z3_context, guard); } else { // Something else: unsupported. return absl::UnimplementedError( diff --git a/p4_symbolic/symbolic/control.h b/p4_symbolic/symbolic/control.h index 8fe5b974..6be661bd 100644 --- a/p4_symbolic/symbolic/control.h +++ b/p4_symbolic/symbolic/control.h @@ -72,13 +72,13 @@ namespace control { absl::StatusOr EvaluatePipeline( const ir::Dataplane &data_plane, const std::string &pipeline_name, SymbolicPerPacketState *state, values::P4RuntimeTranslator *translator, - const z3::expr &guard); + z3::context &z3_context, const z3::expr &guard); // Evaluate the passed control construct. absl::StatusOr EvaluateControl( const ir::Dataplane &data_plane, const std::string &control_name, SymbolicPerPacketState *state, values::P4RuntimeTranslator *translator, - const z3::expr &guard); + z3::context &z3_context, const z3::expr &guard); } // namespace control } // namespace symbolic diff --git a/p4_symbolic/symbolic/guarded_map.cc b/p4_symbolic/symbolic/guarded_map.cc index 62ad0f21..57e04c30 100644 --- a/p4_symbolic/symbolic/guarded_map.cc +++ b/p4_symbolic/symbolic/guarded_map.cc @@ -27,8 +27,9 @@ namespace p4_symbolic { namespace symbolic { absl::StatusOr SymbolicGuardedMap::CreateSymbolicGuardedMap( + z3::context &z3_context, const google::protobuf::Map &headers) { - ASSIGN_OR_RETURN(auto map, util::FreeSymbolicHeaders(headers)); + ASSIGN_OR_RETURN(auto map, util::FreeSymbolicHeaders(z3_context, headers)); return SymbolicGuardedMap(map); } diff --git a/p4_symbolic/symbolic/guarded_map.h b/p4_symbolic/symbolic/guarded_map.h index 0ec28dcc..0e251e35 100644 --- a/p4_symbolic/symbolic/guarded_map.h +++ b/p4_symbolic/symbolic/guarded_map.h @@ -57,6 +57,7 @@ class SymbolicGuardedMap { // By passing the headers definition, the constructor will fill the map with a // free symbolic variable per header field. static absl::StatusOr CreateSymbolicGuardedMap( + z3::context &z3_context, const google::protobuf::Map &headers); SymbolicGuardedMap() = default; diff --git a/p4_symbolic/symbolic/operators.cc b/p4_symbolic/symbolic/operators.cc index 453fdb62..5b979b38 100644 --- a/p4_symbolic/symbolic/operators.cc +++ b/p4_symbolic/symbolic/operators.cc @@ -25,19 +25,20 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "gutil/status.h" -#include "p4_symbolic/z3_util.h" #include "z3++.h" namespace p4_symbolic { namespace symbolic { namespace operators { +namespace { + // Pads bitvector with padsize-many zero bits // will fail with a runtime error if bitvector is not of bv sort, use after // checking that the sort is correct. z3::expr Pad(const z3::expr &bitvector, int pad_size) { if (pad_size > 0) { - return z3::concat(Z3Context().bv_val(0, pad_size), bitvector); + return z3::concat(bitvector.ctx().bv_val(0, pad_size), bitvector); } return bitvector; } @@ -56,6 +57,8 @@ z3::expr Suffix(const z3::expr &bitvector, unsigned int suffix_size) { return bitvector; } +} // namespace + // Check that the two expressions have compatible sorts, and return an // absl error otherwise. // If the expressions are bitvector, the shortest will be padded to match @@ -124,13 +127,13 @@ absl::StatusOr FreeVariable(const std::string &variable_base_name, absl::StrFormat("%s.%d", variable_base_name, counter++); switch (sort.sort_kind()) { case Z3_BOOL_SORT: { - return Z3Context().bool_const(variable_name.c_str()); + return sort.ctx().bool_const(variable_name.c_str()); } case Z3_INT_SORT: { - return Z3Context().int_const(variable_name.c_str()); + return sort.ctx().int_const(variable_name.c_str()); } case Z3_BV_SORT: { - return Z3Context().bv_const(variable_name.c_str(), sort.bv_size()); + return sort.ctx().bv_const(variable_name.c_str(), sort.bv_size()); } default: return absl::InvalidArgumentError( @@ -293,17 +296,16 @@ absl::StatusOr ToBoolSort(const z3::expr &a) { if (a.get_sort().is_bool()) { return a; } else if (a.get_sort().is_bv()) { - return Gte(a, Z3Context().bv_val(1, 1)); + return Gte(a, a.ctx().bv_val(1, 1)); } else if (a.get_sort().is_int()) { - return a >= Z3Context().int_val(1); + return a >= a.ctx().int_val(1); } else { return absl::InvalidArgumentError("Illegal conversion to bool sort"); } } absl::StatusOr ToBitVectorSort(const z3::expr &a, unsigned int size) { if (a.get_sort().is_bool()) { - z3::expr bits = - z3::ite(a, Z3Context().bv_val(1, 1), Z3Context().bv_val(0, 1)); + z3::expr bits = z3::ite(a, a.ctx().bv_val(1, 1), a.ctx().bv_val(0, 1)); return Pad(bits, size - 1); } else if (a.get_sort().is_bv()) { if (a.get_sort().bv_size() <= size) { diff --git a/p4_symbolic/symbolic/parser.cc b/p4_symbolic/symbolic/parser.cc index e8894cb0..5e3bc24f 100644 --- a/p4_symbolic/symbolic/parser.cc +++ b/p4_symbolic/symbolic/parser.cc @@ -27,7 +27,6 @@ #include "p4_symbolic/symbolic/operators.h" #include "p4_symbolic/symbolic/symbolic.h" #include "p4_symbolic/symbolic/util.h" -#include "p4_symbolic/z3_util.h" #include "z3++.h" namespace p4_symbolic::symbolic::parser { @@ -43,7 +42,8 @@ namespace { absl::Status EvaluateExtractParserOperation( const ir::P4Program &program, const ir::ParserOperation::Extract &extract_op, - SymbolicPerPacketState &state, const z3::expr &guard) { + SymbolicPerPacketState &state, z3::context &z3_context, + const z3::expr &guard) { // The extracted header must exists. const std::string &header_name = extract_op.header().header_name(); auto it = program.headers().find(header_name); @@ -53,9 +53,9 @@ absl::Status EvaluateExtractParserOperation( // Set the "valid" and "extracted" fields of the header to `guard`. RETURN_IF_ERROR(state.Set(header_name, kValidPseudoField, - Z3Context().bool_val(true), guard)); + z3_context.bool_val(true), guard)); RETURN_IF_ERROR(state.Set(header_name, kExtractedPseudoField, - Z3Context().bool_val(true), guard)); + z3_context.bool_val(true), guard)); // Verify if all fields of the header are single, free bit-vector variables. for (const auto &[field_name, ir_field] : Ordered(it->second.fields())) { @@ -78,13 +78,13 @@ absl::Status EvaluateExtractParserOperation( // set operation. absl::StatusOr EvaluateSetOperationRValue( const ir::ParserOperation::Set &set_op, const SymbolicPerPacketState &state, - const action::ActionContext &context) { + const action::ActionContext &context, z3::context &z3_context) { switch (set_op.rvalue_case()) { case ir::ParserOperation::Set::RvalueCase::kFieldRvalue: { return action::EvaluateFieldValue(set_op.field_rvalue(), state); } case ir::ParserOperation::Set::RvalueCase::kHexstrRvalue: { - return action::EvaluateHexStr(set_op.hexstr_rvalue()); + return action::EvaluateHexStr(set_op.hexstr_rvalue(), z3_context); } case ir::ParserOperation::Set::RvalueCase::kLookaheadRvalue: { return gutil::UnimplementedErrorBuilder() @@ -92,7 +92,7 @@ absl::StatusOr EvaluateSetOperationRValue( } case ir::ParserOperation::Set::RvalueCase::kExpressionRvalue: { return action::EvaluateRExpression(set_op.expression_rvalue(), state, - context); + context, z3_context); } default: { return gutil::InvalidArgumentErrorBuilder() @@ -106,11 +106,12 @@ absl::StatusOr EvaluateSetOperationRValue( absl::Status EvaluateSetParserOperation(const ir::ParserOperation::Set &set_op, SymbolicPerPacketState &state, const action::ActionContext &context, + z3::context &z3_context, const z3::expr &guard) { // Get the field name of the L-value and the expression of the R-value. const ir::FieldValue &field_value = set_op.lvalue(); - ASSIGN_OR_RETURN(z3::expr rvalue, - EvaluateSetOperationRValue(set_op, state, context)); + ASSIGN_OR_RETURN(z3::expr rvalue, EvaluateSetOperationRValue( + set_op, state, context, z3_context)); // Set the header field to the symbolic expression. RETURN_IF_ERROR(state.Set(field_value.header_name(), field_value.field_name(), rvalue, guard)); @@ -121,11 +122,11 @@ absl::Status EvaluateSetParserOperation(const ir::ParserOperation::Set &set_op, absl::Status EvaluatePrimitiveParserOperation( const ir::ParserOperation::Primitive &primitive, SymbolicPerPacketState &state, action::ActionContext &context, - const z3::expr &guard) { + z3::context &z3_context, const z3::expr &guard) { switch (primitive.statement_case()) { case ir::ParserOperation::Primitive::StatementCase::kAssignment: { return action::EvaluateAssignmentStatement(primitive.assignment(), &state, - &context, guard); + &context, z3_context, guard); } default: { return gutil::UnimplementedErrorBuilder() @@ -143,18 +144,20 @@ absl::Status EvaluateParserOperation(const ir::P4Program &program, const ir::ParserOperation &op, SymbolicPerPacketState &state, action::ActionContext &context, + z3::context &z3_context, const z3::expr &guard) { switch (op.operation_case()) { case ir::ParserOperation::OperationCase::kExtract: { return EvaluateExtractParserOperation(program, op.extract(), state, - guard); + z3_context, guard); } case ir::ParserOperation::OperationCase::kSet: { - return EvaluateSetParserOperation(op.set(), state, context, guard); + return EvaluateSetParserOperation(op.set(), state, context, z3_context, + guard); } case ir::ParserOperation::OperationCase::kPrimitive: { return EvaluatePrimitiveParserOperation(op.primitive(), state, context, - guard); + z3_context, guard); } default: { return gutil::InvalidArgumentErrorBuilder() @@ -174,7 +177,9 @@ absl::Status EvaluateParserOperation(const ir::P4Program &program, absl::StatusOr ConstructHexStringMatchCondition( const ir::HexstrValue &ir_value, const ir::HexstrValue &ir_mask, const z3::expr &transition_key) { - ASSIGN_OR_RETURN(z3::expr value, action::EvaluateHexStr(ir_value)); + z3::context &z3_context = transition_key.ctx(); + ASSIGN_OR_RETURN(z3::expr value, + action::EvaluateHexStr(ir_value, z3_context)); if (ir_mask.value().empty()) { // The mask is not set. The key is matched against a single value. @@ -185,7 +190,8 @@ absl::StatusOr ConstructHexStringMatchCondition( // The mask is set. A key is said to be matched if and only if (key & mask // == value & mask). Reference: // https://p4.org/p4-spec/docs/P4-16-v-1.2.3.html#sec-cubes - ASSIGN_OR_RETURN(z3::expr mask, action::EvaluateHexStr(ir_mask)); + ASSIGN_OR_RETURN(z3::expr mask, + action::EvaluateHexStr(ir_mask, z3_context)); ASSIGN_OR_RETURN(z3::expr masked_value, operators::BitAnd(value, mask)); ASSIGN_OR_RETURN(z3::expr masked_key, operators::BitAnd(transition_key, mask)); @@ -199,9 +205,9 @@ absl::StatusOr ConstructHexStringMatchCondition( // `parse_state` matches another transition or not. absl::StatusOr> ConstructMatchConditions( const ir::ParseState &parse_state, const SymbolicPerPacketState &state, - const z3::expr &guard) { + z3::context &z3_context, const z3::expr &guard) { // Collect all transition key fields to construct the transition key. - z3::expr_vector key_fields(Z3Context()); + z3::expr_vector key_fields(z3_context); for (const ir::ParserTransitionKeyField &key_field : parse_state.transition_key_fields()) { @@ -251,7 +257,7 @@ absl::StatusOr> ConstructMatchConditions( // "default" or "_" is referred to as the "universal set" that contains // any transition key, which means the match condition is always true. // Ref: https://p4.org/p4-spec/docs/P4-16-v-1.2.3.html#sec-universal-set - match_conditions.push_back(Z3Context().bool_val(true)); + match_conditions.push_back(z3_context.bool_val(true)); break; } case ir::ParserTransition::TransitionCase::kHexStringTransition: { @@ -349,11 +355,12 @@ absl::StatusOr GetNextState( } absl::Status SetParserError(const ir::P4Program &program, + z3::context &z3_context, SymbolicPerPacketState &state, const std::string &error_name, const z3::expr &guard) { ASSIGN_OR_RETURN(z3::expr error_code, - GetErrorCodeExpression(program, error_name)); + GetErrorCodeExpression(program, error_name, z3_context)); return state.Set(kParserErrorField, std::move(error_code), guard); } @@ -362,6 +369,7 @@ absl::Status EvaluateParseState(const ir::P4Program &program, const ir::Parser &parser, const std::string &state_name, SymbolicPerPacketState &state, + z3::context &z3_context, const z3::expr &guard) { // Base case. We got to the end of the parser execution path. if (state_name == ir::EndOfParser()) { @@ -396,13 +404,14 @@ absl::Status EvaluateParseState(const ir::P4Program &program, // Evaluate the parser operations in this parse state. action::ActionContext fake_context = {state_name, {}}; for (const ir::ParserOperation &op : parse_state.parser_ops()) { - RETURN_IF_ERROR( - EvaluateParserOperation(program, op, state, fake_context, guard)); + RETURN_IF_ERROR(EvaluateParserOperation(program, op, state, fake_context, + z3_context, guard)); } // Construct the match condition of each transition. - ASSIGN_OR_RETURN(std::vector match_conditions, - ConstructMatchConditions(parse_state, state, guard)); + ASSIGN_OR_RETURN( + std::vector match_conditions, + ConstructMatchConditions(parse_state, state, z3_context, guard)); // Construct the transition guard of each transition. std::vector transition_guards = ConstructTransitionGuards(match_conditions, guard); @@ -416,7 +425,7 @@ absl::Status EvaluateParseState(const ir::P4Program &program, GetNextState(parse_state.transitions(i))); if (next_state != merge_point) { RETURN_IF_ERROR(EvaluateParseState(program, parser, next_state, state, - transition_guards[i])); + z3_context, transition_guards[i])); } } @@ -435,7 +444,7 @@ absl::Status EvaluateParseState(const ir::P4Program &program, // fall-through guard will always be evaluated to FALSE, so we evaluate this // conditionally to save Z3 some effort. if (!match_conditions.empty() && - match_conditions.back() != Z3Context().bool_val(true)) { + match_conditions.back() != z3_context.bool_val(true)) { z3::expr fall_through_guard = ConstructFallThroughGuard(match_conditions, guard); @@ -443,8 +452,8 @@ absl::Status EvaluateParseState(const ir::P4Program &program, // state" in this case will be the end of parser, so there is no need to do // anything else. The fall-through guard should be sufficient to ensure that // if the fall-through guard is true, no other transitions will happen. - RETURN_IF_ERROR( - SetParserError(program, state, "NoMatch", fall_through_guard)); + RETURN_IF_ERROR(SetParserError(program, z3_context, state, "NoMatch", + fall_through_guard)); // If a fall-through is possible, the `merge_point_guard` should be the // negation of the `fall_through_guard` under the current `guard`. @@ -456,7 +465,7 @@ absl::Status EvaluateParseState(const ir::P4Program &program, // different path. if (parse_state.optimized_symbolic_execution_info() .continue_to_merge_point()) { - return EvaluateParseState(program, parser, merge_point, state, + return EvaluateParseState(program, parser, merge_point, state, z3_context, merge_point_guard); } else { return absl::OkStatus(); @@ -466,7 +475,8 @@ absl::Status EvaluateParseState(const ir::P4Program &program, } // namespace absl::StatusOr GetErrorCodeExpression(const ir::P4Program &program, - const std::string &error_name) { + const std::string &error_name, + z3::context &z3_context) { // Obtain the error code from the given `error_name`. auto err_it = program.errors().find(error_name); if (err_it == program.errors().end()) { @@ -478,12 +488,12 @@ absl::StatusOr GetErrorCodeExpression(const ir::P4Program &program, ASSIGN_OR_RETURN(unsigned int bitwidth, util::GetFieldBitwidth(kParserErrorField, program)); - return Z3Context().bv_val(error_code, bitwidth); + return z3_context.bv_val(error_code, bitwidth); } absl::StatusOr EvaluateParsers( - const ir::P4Program &program, - const SymbolicPerPacketState &ingress_headers) { + const ir::P4Program &program, const SymbolicPerPacketState &ingress_headers, + z3::context &z3_context) { // Make sure there is exactly one parser in the P4-Symbolic IR. if (program.parsers_size() != 1) { return gutil::InvalidArgumentErrorBuilder() @@ -496,8 +506,8 @@ absl::StatusOr EvaluateParsers( SymbolicPerPacketState parsed_headers = ingress_headers; const ir::Parser &parser = program.parsers().begin()->second; RETURN_IF_ERROR(EvaluateParseState(program, parser, parser.initial_state(), - parsed_headers, - Z3Context().bool_val(true))); + parsed_headers, z3_context, + z3_context.bool_val(true))); return parsed_headers; } diff --git a/p4_symbolic/symbolic/parser.h b/p4_symbolic/symbolic/parser.h index c93ea09a..6f037dae 100644 --- a/p4_symbolic/symbolic/parser.h +++ b/p4_symbolic/symbolic/parser.h @@ -16,7 +16,7 @@ #define PINS_P4_SYMBOLIC_SYMBOLIC_PARSER_H_ #include "p4_symbolic/ir/ir.pb.h" -#include "p4_symbolic/symbolic/symbolic.h" +#include "p4_symbolic/symbolic/context.h" #include "z3++.h" namespace p4_symbolic { @@ -26,7 +26,8 @@ namespace parser { // Returns a Z3 bit-vector representing the `parser_error` field value of the // given `error_name`. absl::StatusOr GetErrorCodeExpression(const ir::P4Program &program, - const std::string &error_name); + const std::string &error_name, + z3::context &z3_context); // Evaluates all parsers in the P4 program and returns a map of symbolic headers // that captures the effect of parser execution given the `ingress_headers`. @@ -39,9 +40,9 @@ absl::StatusOr GetErrorCodeExpression(const ir::P4Program &program, // set to the corresponding error code and the function returns immediately. The // current implementation may only result in 2 types of parser error, NoError // and NoMatch. -absl::StatusOr -EvaluateParsers(const ir::P4Program &program, - const SymbolicPerPacketState &ingress_headers); +absl::StatusOr EvaluateParsers( + const ir::P4Program &program, const SymbolicPerPacketState &ingress_headers, + z3::context &z3_context); } // namespace parser } // namespace symbolic diff --git a/p4_symbolic/symbolic/parser_test.cc b/p4_symbolic/symbolic/parser_test.cc index b9ff9bbd..6033b8c4 100644 --- a/p4_symbolic/symbolic/parser_test.cc +++ b/p4_symbolic/symbolic/parser_test.cc @@ -30,7 +30,6 @@ #include "p4_symbolic/ir/ir.pb.h" #include "p4_symbolic/symbolic/symbolic.h" #include "p4_symbolic/symbolic/v1model.h" -#include "p4_symbolic/z3_util.h" #include "z3++.h" namespace p4_symbolic::symbolic::parser { @@ -218,10 +217,11 @@ constexpr absl::string_view kProgramWithTwoParsers = R"pb( TEST(EvaluateParsers, ReturnsErrorForMoreThanOneParser) { ASSERT_OK_AND_ASSIGN(const ir::Dataplane data_plane, ParseProgramTextProto(kProgramWithTwoParsers)); + z3::context z3_context; ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); - EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers), + z3_context, data_plane.program.headers())); + EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers, z3_context), StatusIs(absl::StatusCode::kInvalidArgument, "Invalid number of parsers: 2")); } @@ -229,10 +229,11 @@ TEST(EvaluateParsers, ReturnsErrorForMoreThanOneParser) { TEST(EvaluateParsers, ReturnsErrorForLessThanOneParser) { ASSERT_OK_AND_ASSIGN(const ir::Dataplane data_plane, ParseProgramTextProto("")); + z3::context z3_context; ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); - EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers), + z3_context, data_plane.program.headers())); + EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers, z3_context), StatusIs(absl::StatusCode::kInvalidArgument, "Invalid number of parsers: 0")); } @@ -257,11 +258,12 @@ constexpr absl::string_view kProgramWithUnknownParseState = R"pb( TEST(EvaluateParsers, ReturnsErrorForUnknownParseState) { ASSERT_OK_AND_ASSIGN(const ir::Dataplane data_plane, ParseProgramTextProto(kProgramWithUnknownParseState)); + z3::context z3_context; ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); + z3_context, data_plane.program.headers())); EXPECT_THAT( - EvaluateParsers(data_plane.program, ingress_headers), + EvaluateParsers(data_plane.program, ingress_headers, z3_context), StatusIs(absl::StatusCode::kNotFound, "Parse state not found: unknown")); } @@ -286,11 +288,12 @@ constexpr absl::string_view kProgramWithUnknownHeaderExtract = R"pb( TEST(EvaluateParsers, ReturnsErrorForUnknownHeaderExtract) { ASSERT_OK_AND_ASSIGN(const ir::Dataplane data_plane, ParseProgramTextProto(kProgramWithUnknownHeaderExtract)); + z3::context z3_context; ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); + z3_context, data_plane.program.headers())); EXPECT_THAT( - EvaluateParsers(data_plane.program, ingress_headers), + EvaluateParsers(data_plane.program, ingress_headers, z3_context), StatusIs(absl::StatusCode::kNotFound, "Header not found: unknown")); } @@ -315,12 +318,13 @@ constexpr absl::string_view kProgramExtractEthernet = R"pb( TEST(EvaluateParsers, ReturnsErrorForNonFreeBitVectorHeaderField) { ASSERT_OK_AND_ASSIGN(const ir::Dataplane data_plane, ParseProgramTextProto(kProgramExtractEthernet)); + z3::context z3_context; ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); - ASSERT_OK(ingress_headers.Set("ethernet.dst_addr", Z3Context().bv_val(0, 48), - Z3Context().bool_val(true))); - EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers), + z3_context, data_plane.program.headers())); + ASSERT_OK(ingress_headers.Set("ethernet.dst_addr", z3_context.bv_val(0, 48), + z3_context.bool_val(true))); + EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers, z3_context), StatusIs(absl::StatusCode::kInvalidArgument, "Field 'ethernet.dst_addr' should be a free bit-vector. " "Found: (ite true #x000000000000 ethernet.dst_addr)")); @@ -352,11 +356,12 @@ constexpr absl::string_view kProgramWithUnknownFieldSet = R"pb( TEST(EvaluateParsers, ReturnsErrorForUnknownFieldSet) { ASSERT_OK_AND_ASSIGN(const ir::Dataplane data_plane, ParseProgramTextProto(kProgramWithUnknownFieldSet)); + z3::context z3_context; ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); + z3_context, data_plane.program.headers())); EXPECT_THAT( - EvaluateParsers(data_plane.program, ingress_headers), + EvaluateParsers(data_plane.program, ingress_headers, z3_context), StatusIs( absl::StatusCode::kInvalidArgument, "Cannot assign to key \"ethernet.unknown\" in SymbolicGuardedMap!")); @@ -388,11 +393,12 @@ constexpr absl::string_view kProgramWithLookaheadSet = R"pb( TEST(EvaluateParsers, ReturnsErrorForUnimplementedLookaheadSet) { ASSERT_OK_AND_ASSIGN(const ir::Dataplane data_plane, ParseProgramTextProto(kProgramWithLookaheadSet)); + z3::context z3_context; ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); + z3_context, data_plane.program.headers())); EXPECT_THAT( - EvaluateParsers(data_plane.program, ingress_headers), + EvaluateParsers(data_plane.program, ingress_headers, z3_context), StatusIs(absl::StatusCode::kUnimplemented, "Lookahead R-values for set operations are not supported.")); } @@ -419,10 +425,11 @@ TEST(EvaluateParsers, ReturnsErrorForNonHeaderFieldTransitionKey) { ASSERT_OK_AND_ASSIGN( const ir::Dataplane data_plane, ParseProgramTextProto(kProgramWithNonHeaderFieldTransitionKey)); + z3::context z3_context; ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); - EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers), + z3_context, data_plane.program.headers())); + EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers, z3_context), StatusIs(absl::StatusCode::kUnimplemented)); } @@ -450,10 +457,11 @@ TEST(EvaluateParsers, ReturnsErrorForUnknownFieldTransitionKey) { ASSERT_OK_AND_ASSIGN( const ir::Dataplane data_plane, ParseProgramTextProto(kProgramWithUnknownFieldTransitionKey)); + z3::context z3_context; ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); - EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers), + z3_context, data_plane.program.headers())); + EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers, z3_context), StatusIs(absl::StatusCode::kInvalidArgument, "Cannot find key \"un.known\" in SymbolicGuardedMap!")); } @@ -495,10 +503,11 @@ constexpr absl::string_view kProgramWithDeadCodeTransition = R"pb( TEST(EvaluateParsers, ReturnsErrorForDeadCodeTransition) { ASSERT_OK_AND_ASSIGN(const ir::Dataplane data_plane, ParseProgramTextProto(kProgramWithDeadCodeTransition)); + z3::context z3_context; ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); - EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers), + z3_context, data_plane.program.headers())); + EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers, z3_context), StatusIs(absl::StatusCode::kInvalidArgument)); } @@ -529,11 +538,12 @@ constexpr absl::string_view kProgramWithNoTransitionKey = R"pb( TEST(EvaluateParsers, ReturnsErrorForNoTransitionKey) { ASSERT_OK_AND_ASSIGN(const ir::Dataplane data_plane, ParseProgramTextProto(kProgramWithNoTransitionKey)); + z3::context z3_context; ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); + z3_context, data_plane.program.headers())); EXPECT_THAT( - EvaluateParsers(data_plane.program, ingress_headers), + EvaluateParsers(data_plane.program, ingress_headers, z3_context), StatusIs( absl::StatusCode::kNotFound, "No transition key specified but hex string transitions exist.")); @@ -944,8 +954,7 @@ std::vector GetParserTestInstances() { } TEST_P(EvaluateParsersTest, ValidateParsedHeadersSMTFormulae) { - // TODO: a workaround for using global Z3 context. - Z3Context(/*renew=*/true); + z3::context z3_context; // Parse the P4 program from IR text proto and evaluate the parsers. const ParserTestParam& param = GetParam(); @@ -953,27 +962,27 @@ TEST_P(EvaluateParsersTest, ValidateParsedHeadersSMTFormulae) { ParseProgramTextProto(param.ir_program_text_proto)); ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); - ASSERT_OK( - v1model::InitializeIngressHeaders(data_plane.program, ingress_headers)); - ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState parsed_headers, - EvaluateParsers(data_plane.program, ingress_headers)); + z3_context, data_plane.program.headers())); + ASSERT_OK(v1model::InitializeIngressHeaders(data_plane.program, + ingress_headers, z3_context)); + ASSERT_OK_AND_ASSIGN( + SymbolicPerPacketState parsed_headers, + EvaluateParsers(data_plane.program, ingress_headers, z3_context)); // Construct the expected parsed headers. ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState expected_parsed_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); - ASSERT_OK(v1model::InitializeIngressHeaders(data_plane.program, - expected_parsed_headers)); + z3_context, data_plane.program.headers())); + ASSERT_OK(v1model::InitializeIngressHeaders( + data_plane.program, expected_parsed_headers, z3_context)); // Update the expected SMT formulae of certain fields specified by the test. for (const auto& [field_name, field] : - param.expected_symbolic_parsed_headers(Z3Context())) { + param.expected_symbolic_parsed_headers(z3_context)) { ASSERT_TRUE(expected_parsed_headers.ContainsKey(field_name)); ASSERT_OK(expected_parsed_headers.UnguardedSet(field_name, field)); } - std::unique_ptr solver = - std::make_unique(Z3Context()); + auto solver = std::make_unique(z3_context); // Check if the SMT formulae of the parsed headers are semantically the // same as the expected parsed headers. @@ -983,7 +992,7 @@ TEST_P(EvaluateParsersTest, ValidateParsedHeadersSMTFormulae) { expected_parsed_headers.Get(field_name)); LOG(INFO) << "Check semantic equivalence for " << field_name << ": " << expected_value << " vs " << actual_value; - z3::expr_vector assumptions(Z3Context()); + z3::expr_vector assumptions(z3_context); assumptions.push_back(expected_value != actual_value); EXPECT_EQ(solver->check(assumptions), z3::check_result::unsat); } diff --git a/p4_symbolic/symbolic/symbolic.cc b/p4_symbolic/symbolic/symbolic.cc index 75815607..e53920e7 100644 --- a/p4_symbolic/symbolic/symbolic.cc +++ b/p4_symbolic/symbolic/symbolic.cc @@ -22,12 +22,12 @@ #include "absl/status/statusor.h" #include "absl/types/optional.h" #include "glog/logging.h" +#include "gutil/status.h" #include "p4_pdpi/internal/ordered_map.h" #include "p4_symbolic/ir/parser.h" #include "p4_symbolic/symbolic/util.h" #include "p4_symbolic/symbolic/v1model.h" #include "p4_symbolic/symbolic/values.h" -#include "p4_symbolic/z3_util.h" #include "z3++.h" namespace p4_symbolic { @@ -55,10 +55,6 @@ std::string SolverState::GetHeadersAndSolverConstraintsSMT() { return result.str(); } -z3::expr EgressSpecDroppedValue() { - return Z3Context().bv_val(kDropPort, kPortBitwidth); -} - absl::StatusOr> EvaluateP4Program( const p4::v1::ForwardingPipelineConfig &config, const std::vector &entries, @@ -90,7 +86,7 @@ absl::StatusOr> EvaluateP4Program( if (auto it = translation_per_type.find(type); it != translation_per_type.end() && !it->second.dynamic_translation) { ASSIGN_OR_RETURN(z3::expr field_expr, context.ingress_headers.Get(field)); - z3::expr constraint = Z3Context().bool_val(false); + z3::expr constraint = context.z3_context->bool_val(false); for (const auto &[string_value, numeric_value] : it->second.static_mapping) { constraint = @@ -106,8 +102,8 @@ absl::StatusOr> EvaluateP4Program( solver_state->solver->add(context.ingress_port != kCpuPort); solver_state->solver->add(context.ingress_port != kDropPort); } else { - z3::expr ingress_port_is_physical = Z3Context().bool_val(false); - z3::expr egress_port_is_physical = Z3Context().bool_val(false); + z3::expr ingress_port_is_physical = context.z3_context->bool_val(false); + z3::expr egress_port_is_physical = context.z3_context->bool_val(false); for (int port : physical_ports) { ingress_port_is_physical = ingress_port_is_physical || context.ingress_port == port; diff --git a/p4_symbolic/symbolic/symbolic.h b/p4_symbolic/symbolic/symbolic.h index 270c4f60..b247c768 100644 --- a/p4_symbolic/symbolic/symbolic.h +++ b/p4_symbolic/symbolic/symbolic.h @@ -18,12 +18,11 @@ #ifndef P4_SYMBOLIC_SYMBOLIC_SYMBOLIC_H_ #define P4_SYMBOLIC_SYMBOLIC_SYMBOLIC_H_ -#include #include #include #include -#include +#include "absl/container/btree_map.h" #include "absl/strings/string_view.h" #include "p4/v1/p4runtime.pb.h" #include "p4_symbolic/ir/table_entries.h" @@ -63,11 +62,6 @@ constexpr absl::string_view kGotClonedPseudoField = "$got_cloned$"; constexpr absl::string_view kParserErrorField = "standard_metadata.parser_error"; -// V1model's `mark_to_drop` primitive sets the `egress_spec` field to this -// value to indicate the packet should be dropped at the end of ingress/egress -// processing. See v1model.p4 for details. -z3::expr EgressSpecDroppedValue(); - // The overall state of our symbolic solver/interpreter. // This is returned by our main analysis/interpration function, and is used // to find concrete test packets and for debugging. @@ -81,7 +75,7 @@ class SolverState { SolverState(ir::P4Program program, ir::TableEntries entries) : program(std::move(program)), entries(std::move(entries)), - solver(std::make_unique(Z3Context())) {} + solver(std::make_unique(*context.z3_context)) {} SolverState(const SolverState &) = delete; SolverState(SolverState &&) = default; ~SolverState() { @@ -104,7 +98,10 @@ class SolverState { // Maps the name of a table to a list of its entries. ir::TableEntries entries; // The symbolic context of our interpretation/analysis of the program, - // including symbolic handles on packet headers and its trace. + // including symbolic handles on packet headers and its trace. A new + // z3::context object is created within each symbolic context. Note that this + // has to precede the solver object so that the solver will be destroyed + // before the z3 context during destruction. SymbolicContext context; // Having the z3 solver defined here allows Z3 to remember interesting // deductions it made while solving for one particular assertion, and re-use diff --git a/p4_symbolic/symbolic/table.cc b/p4_symbolic/symbolic/table.cc index 83d199d7..059938f6 100644 --- a/p4_symbolic/symbolic/table.cc +++ b/p4_symbolic/symbolic/table.cc @@ -42,7 +42,6 @@ #include "p4_symbolic/symbolic/operators.h" #include "p4_symbolic/symbolic/util.h" #include "p4_symbolic/symbolic/values.h" -#include "p4_symbolic/z3_util.h" #include "z3++.h" namespace p4_symbolic { @@ -183,9 +182,10 @@ std::vector> SortEntries( // Analyze a single match that is part of a table entry. // Constructs a symbolic expression that semantically corresponds to this match. absl::StatusOr EvaluateSingleMatch( - z3::context &context, p4::config::v1::MatchField match_definition, + const p4::config::v1::MatchField &match_definition, const std::string &field_name, const z3::expr &field_expression, - const pdpi::IrMatch &match, values::P4RuntimeTranslator *translator) { + const pdpi::IrMatch &match, values::P4RuntimeTranslator *translator, + z3::context &z3_context) { if (match_definition.match_case() != p4::config::v1::MatchField::kMatchType) { // Arch-specific match type. return absl::InvalidArgumentError( @@ -202,7 +202,7 @@ absl::StatusOr EvaluateSingleMatch( auto GetZ3Value = [&](const pdpi::IrValue &value) -> absl::StatusOr { - return values::FormatP4RTValue(context, field_name, + return values::FormatP4RTValue(z3_context, field_name, match_definition.type_name().name(), value, match_definition.bitwidth(), translator); }; @@ -259,11 +259,11 @@ absl::StatusOr EvaluateSingleMatch( absl::StatusOr EvaluateTableEntryCondition( const ir::Table &table, const pdpi::IrTableEntry &entry, const SymbolicPerPacketState &state, - values::P4RuntimeTranslator *translator) { + values::P4RuntimeTranslator *translator, z3::context &z3_context) { const std::string &table_name = table.table_definition().preamble().name(); // Construct the match condition expression. - z3::expr condition_expression = Z3Context().bool_val(true); + z3::expr condition_expression = z3_context.bool_val(true); const google::protobuf::Map &match_to_fields = table.table_implementation().match_name_to_field(); @@ -304,8 +304,8 @@ absl::StatusOr EvaluateTableEntryCondition( action::EvaluateFieldValue(match_field, state)); ASSIGN_OR_RETURN( z3::expr match_expression, - EvaluateSingleMatch(Z3Context(), match_definition, field_name, - match_field_expr, match, translator)); + EvaluateSingleMatch(match_definition, field_name, match_field_expr, + match, translator, z3_context)); ASSIGN_OR_RETURN(condition_expression, operators::And(condition_expression, match_expression)); } @@ -317,14 +317,14 @@ absl::Status EvaluateSingeTableEntryAction( const pdpi::IrActionInvocation &action, const google::protobuf::Map &actions, SymbolicPerPacketState *state, values::P4RuntimeTranslator *translator, - const z3::expr &guard) { + z3::context &z3_context, const z3::expr &guard) { // Check that the action invoked by entry exists. if (actions.count(action.name()) != 1) { return gutil::InvalidArgumentErrorBuilder() << "unknown action '" << action.name() << "'"; } return action::EvaluateAction(actions.at(action.name()), action.params(), - state, translator, guard); + state, translator, z3_context, guard); } // Constructs a symbolic expressions that represents the action invocation @@ -333,11 +333,12 @@ absl::Status EvaluateTableEntryAction( const ir::Table &table, const pdpi::IrTableEntry &entry, int entry_index, const google::protobuf::Map &actions, SymbolicPerPacketState *state, values::P4RuntimeTranslator *translator, - const z3::expr &guard) { + z3::context &z3_context, const z3::expr &guard) { switch (entry.type_case()) { case pdpi::IrTableEntry::kAction: RETURN_IF_ERROR(EvaluateSingeTableEntryAction(entry.action(), actions, - state, translator, guard)) + state, translator, + z3_context, guard)) .SetPrepend() << "In table entry '" << entry.ShortDebugString() << "':"; return absl::OkStatus(); @@ -349,15 +350,15 @@ absl::Status EvaluateTableEntryAction( std::string selector_name = absl::StrFormat("action selector for entry #%d of table '%s'", entry_index, entry.table_name()); - z3::expr selector = Z3Context().int_const(selector_name.c_str()); - z3::expr unselected = Z3Context().bool_val(true); + z3::expr selector = z3_context.int_const(selector_name.c_str()); + z3::expr unselected = z3_context.bool_val(true); for (int i = 0; i < action_set.size(); ++i) { auto &action = action_set.at(i).action(); bool is_last_action = i == action_set.size() - 1; z3::expr selected = is_last_action ? unselected : (selector == i); unselected = unselected && !selected; RETURN_IF_ERROR(EvaluateSingeTableEntryAction(action, actions, state, - translator, + translator, z3_context, guard && selected)) .SetPrepend() << "In table entry '" << entry.ShortDebugString() << "':"; @@ -377,7 +378,8 @@ absl::Status EvaluateTableEntryAction( absl::StatusOr EvaluateTable( const ir::Dataplane &data_plane, const ir::Table &table, const std::vector &entries, SymbolicPerPacketState *state, - values::P4RuntimeTranslator *translator, const z3::expr &guard) { + values::P4RuntimeTranslator *translator, z3::context &z3_context, + const z3::expr &guard) { const std::string &table_name = table.table_definition().preamble().name(); // Sort entries by priority deduced from match types. std::vector> sorted_entries = @@ -429,9 +431,9 @@ absl::StatusOr EvaluateTable( // We are passing state by const reference here, so we do not need // any guard yet. - ASSIGN_OR_RETURN(z3::expr entry_match, - EvaluateTableEntryCondition(table, entry.concrete_entry(), - *state, translator)); + ASSIGN_OR_RETURN(z3::expr entry_match, EvaluateTableEntryCondition( + table, entry.concrete_entry(), + *state, translator, z3_context)); entries_matches.push_back(entry_match); } @@ -473,17 +475,17 @@ absl::StatusOr EvaluateTable( } // Start with the default entry - z3::expr match_index = Z3Context().int_val(kDefaultActionEntryIndex); + z3::expr match_index = z3_context.int_val(kDefaultActionEntryIndex); RETURN_IF_ERROR(EvaluateTableEntryAction( table, default_entry.concrete_entry(), kDefaultActionEntryIndex, - data_plane.program.actions(), state, translator, + data_plane.program.actions(), state, translator, z3_context, default_entry_assignment_guard)); // Continue evaluating each table entry in reverse priority for (int row = sorted_entries.size() - 1; row >= 0; row--) { int old_index = sorted_entries.at(row).first; const ir::TableEntry &entry = sorted_entries.at(row).second; - z3::expr row_symbol = Z3Context().int_val(old_index); + z3::expr row_symbol = z3_context.int_val(old_index); // The condition used in the big if_else_then construct. ASSIGN_OR_RETURN(z3::expr entry_match, @@ -495,7 +497,7 @@ absl::StatusOr EvaluateTable( z3::expr entry_assignment_guard = assignment_guards.at(row); RETURN_IF_ERROR(EvaluateTableEntryAction( table, entry.concrete_entry(), old_index, data_plane.program.actions(), - state, translator, entry_assignment_guard)); + state, translator, z3_context, entry_assignment_guard)); } const std::string &merge_point = table.table_implementation() @@ -520,13 +522,13 @@ absl::StatusOr EvaluateTable( action == ir::TableHitAction() ? (match_index != kDefaultActionEntryIndex) : (match_index == kDefaultActionEntryIndex); - ASSIGN_OR_RETURN( - SymbolicTableMatches branch_matches, - control::EvaluateControl(data_plane, next_control, state, - translator, guard && branch_condition)); - ASSIGN_OR_RETURN(merged_matches, - util::MergeMatchesOnCondition( - branch_condition, branch_matches, merged_matches)); + ASSIGN_OR_RETURN(SymbolicTableMatches branch_matches, + control::EvaluateControl(data_plane, next_control, + state, translator, z3_context, + guard && branch_condition)); + ASSIGN_OR_RETURN(merged_matches, util::MergeMatchesOnCondition( + branch_condition, branch_matches, + merged_matches, z3_context)); } else { return absl::UnimplementedError( absl::Substitute("Conditional on executed table action is not " @@ -545,7 +547,7 @@ absl::StatusOr EvaluateTable( // Evaluate the next control. ASSIGN_OR_RETURN(SymbolicTableMatches result, control::EvaluateControl(data_plane, continuation, state, - translator, guard)); + translator, z3_context, guard)); // Merge the result of execution from the merge point with the result of // execution from action_to_next_control (up to the merge point). ASSIGN_OR_RETURN(result, diff --git a/p4_symbolic/symbolic/table.h b/p4_symbolic/symbolic/table.h index 27d0fb27..f7916c62 100644 --- a/p4_symbolic/symbolic/table.h +++ b/p4_symbolic/symbolic/table.h @@ -40,7 +40,8 @@ constexpr int kDefaultActionEntryIndex = -1; absl::StatusOr EvaluateTable( const ir::Dataplane &data_plane, const ir::Table &table, const std::vector &entries, SymbolicPerPacketState *state, - values::P4RuntimeTranslator *translator, const z3::expr &guard); + values::P4RuntimeTranslator *translator, z3::context &z3_context, + const z3::expr &guard); } // namespace table } // namespace symbolic diff --git a/p4_symbolic/symbolic/util.cc b/p4_symbolic/symbolic/util.cc index 0a861a2e..94fc2f86 100644 --- a/p4_symbolic/symbolic/util.cc +++ b/p4_symbolic/symbolic/util.cc @@ -67,6 +67,7 @@ absl::StatusOr GetFieldDefinition( } // namespace absl::StatusOr> FreeSymbolicHeaders( + z3::context &z3_context, const google::protobuf::Map &headers) { // Loop over every header instance in the p4 program. // Find its type, and loop over every field in it, creating a symbolic free @@ -80,7 +81,7 @@ absl::StatusOr> FreeSymbolicHeaders( absl::StrFormat("%s.%s", header_name, pseudo_field_name); // TODO: Set these fields to false while removing SAI parser // code. - z3::expr free_expr = Z3Context().bool_const(field_name.c_str()); + z3::expr free_expr = z3_context.bool_const(field_name.c_str()); symbolic_headers.insert({field_name, free_expr}); } @@ -94,7 +95,7 @@ absl::StatusOr> FreeSymbolicHeaders( std::string field_full_name = absl::StrFormat("%s.%s", header_name, field_name); z3::expr field_expression = - Z3Context().bv_const(field_full_name.c_str(), field.bitwidth()); + z3_context.bv_const(field_full_name.c_str(), field.bitwidth()); symbolic_headers.insert({field_full_name, field_expression}); } } @@ -102,16 +103,16 @@ absl::StatusOr> FreeSymbolicHeaders( // Initialize pseudo header fields. symbolic_headers.insert({ std::string(kGotClonedPseudoField), - Z3Context().bool_val(false), + z3_context.bool_val(false), }); return symbolic_headers; } -SymbolicTableMatch DefaultTableMatch() { +SymbolicTableMatch DefaultTableMatch(z3::context &z3_context) { return { - Z3Context().bool_val(false), // No match yet! - Z3Context().int_val(-1) // No match index. + z3_context.bool_val(false), // No match yet! + z3_context.int_val(-1) // No match index. }; } @@ -173,7 +174,7 @@ absl::StatusOr ExtractFromModel( absl::StatusOr MergeMatchesOnCondition( const z3::expr &condition, const SymbolicTableMatches &true_matches, - const SymbolicTableMatches &false_matches) { + const SymbolicTableMatches &false_matches, z3::context &z3_context) { SymbolicTableMatches merged; // Merge all tables matches in true_trace (including ones in both traces). @@ -181,7 +182,7 @@ absl::StatusOr MergeMatchesOnCondition( // Find match in other trace (or use default). const SymbolicTableMatch false_match = false_matches.contains(name) ? false_matches.at(name) - : DefaultTableMatch(); + : DefaultTableMatch(z3_context); // Merge this match. ASSIGN_OR_RETURN( @@ -205,7 +206,7 @@ absl::StatusOr MergeMatchesOnCondition( } // Get the default match for the true branch. - const SymbolicTableMatch true_match = DefaultTableMatch(); + const SymbolicTableMatch true_match = DefaultTableMatch(z3_context); // Merge this match. ASSIGN_OR_RETURN( diff --git a/p4_symbolic/symbolic/util.h b/p4_symbolic/symbolic/util.h index c07a9d31..2dd6b661 100644 --- a/p4_symbolic/symbolic/util.h +++ b/p4_symbolic/symbolic/util.h @@ -24,7 +24,7 @@ #include "absl/strings/string_view.h" #include "google/protobuf/map.h" #include "p4_symbolic/ir/ir.pb.h" -#include "p4_symbolic/symbolic/symbolic.h" +#include "p4_symbolic/symbolic/context.h" #include "p4_symbolic/symbolic/values.h" #include "z3++.h" @@ -35,12 +35,13 @@ namespace util { // Free (unconstrained) symbolic headers consisting of free symbolic variables // for every field in every header instance defined in the P4 program. absl::StatusOr> FreeSymbolicHeaders( + z3::context &z3_context, const google::protobuf::Map &headers); // Returns an symbolic table match containing default values. // The table match expression is false, the index is -1, and the value is // undefined. -SymbolicTableMatch DefaultTableMatch(); +SymbolicTableMatch DefaultTableMatch(z3::context &z3_context); // Extract a concrete context by evaluating every component's corresponding // expression in the model. @@ -52,10 +53,9 @@ absl::StatusOr ExtractFromModel( // map has the value of `true_matches` if the condition is true, and the // value of `false_matches` otherwise. // The two maps must contain disjoint keys, otherwise an error is returned. -absl::StatusOr -MergeMatchesOnCondition(const z3::expr &condition, - const SymbolicTableMatches &true_matches, - const SymbolicTableMatches &false_matches); +absl::StatusOr MergeMatchesOnCondition( + const z3::expr &condition, const SymbolicTableMatches &true_matches, + const SymbolicTableMatches &false_matches, z3::context &z3_context); // Merges two maps of table matches into a single map. The two maps must contain // disjoint keys, otherwise an error is returned. diff --git a/p4_symbolic/symbolic/v1model.cc b/p4_symbolic/symbolic/v1model.cc index 00f39a19..94015842 100644 --- a/p4_symbolic/symbolic/v1model.cc +++ b/p4_symbolic/symbolic/v1model.cc @@ -21,7 +21,7 @@ #include "p4_symbolic/symbolic/operators.h" #include "p4_symbolic/symbolic/parser.h" #include "p4_symbolic/symbolic/symbolic.h" -#include "p4_symbolic/z3_util.h" +#include "z3++.h" namespace p4_symbolic { namespace symbolic { @@ -62,10 +62,11 @@ absl::Status CheckPhysicalPortsConformanceToV1Model( return absl::OkStatus(); } -absl::StatusOr IsDropped(const SymbolicPerPacketState &state) { +absl::StatusOr IsDropped(const SymbolicPerPacketState &state, + z3::context &z3_context) { ASSIGN_OR_RETURN(z3::expr egress_spec, state.Get("standard_metadata.egress_spec")); - return operators::Eq(egress_spec, EgressSpecDroppedValue()); + return operators::Eq(egress_spec, EgressSpecDroppedValue(z3_context)); } absl::StatusOr GotCloned(const SymbolicPerPacketState &state) { @@ -74,12 +75,17 @@ absl::StatusOr GotCloned(const SymbolicPerPacketState &state) { } // namespace +z3::expr EgressSpecDroppedValue(z3::context &z3_context) { + return z3_context.bv_val(kDropPort, kPortBitwidth); +} + absl::Status InitializeIngressHeaders(const ir::P4Program &program, - SymbolicPerPacketState &ingress_headers) { + SymbolicPerPacketState &ingress_headers, + z3::context &z3_context) { // TODO: Consider initializing all metadata fields to 0. // Set the `$valid$` and `$extracted$` fields of all headers to false. - const z3::expr false_expr = Z3Context().bool_val(false); + const z3::expr false_expr = z3_context.bool_val(false); for (const auto &[header_name, _] : Ordered(program.headers())) { RETURN_IF_ERROR(ingress_headers.UnguardedSet(header_name, kValidPseudoField, false_expr)); @@ -88,8 +94,8 @@ absl::Status InitializeIngressHeaders(const ir::P4Program &program, } // Set the `standard_metadata.parser_error` field to error.NoError. - ASSIGN_OR_RETURN(z3::expr no_error, - parser::GetErrorCodeExpression(program, "NoError")); + ASSIGN_OR_RETURN(z3::expr no_error, parser::GetErrorCodeExpression( + program, "NoError", z3_context)); RETURN_IF_ERROR(ingress_headers.UnguardedSet(kParserErrorField, no_error)); return absl::OkStatus(); @@ -107,10 +113,10 @@ absl::Status EvaluateV1model(const ir::Dataplane &data_plane, // variable for each header field. ASSIGN_OR_RETURN(context.ingress_headers, SymbolicGuardedMap::CreateSymbolicGuardedMap( - data_plane.program.headers())); + *context.z3_context, data_plane.program.headers())); // Initialize the symbolic ingress packet before evaluation. - RETURN_IF_ERROR( - InitializeIngressHeaders(data_plane.program, context.ingress_headers)); + RETURN_IF_ERROR(InitializeIngressHeaders( + data_plane.program, context.ingress_headers, *context.z3_context)); // Evaluate all parsers in the P4 program. // If a parser error occurs, the `standard_metadata.parser_error` field will @@ -119,7 +125,8 @@ absl::Status EvaluateV1model(const ir::Dataplane &data_plane, // will only yield 2 kinds of parser error, NoError and NoMatch. ASSIGN_OR_RETURN( context.parsed_headers, - parser::EvaluateParsers(data_plane.program, context.ingress_headers)); + parser::EvaluateParsers(data_plane.program, context.ingress_headers, + *context.z3_context)); // Update the ingress_headers' valid fields to be parsed_headers' extracted // fields. @@ -144,9 +151,10 @@ absl::Status EvaluateV1model(const ir::Dataplane &data_plane, ASSIGN_OR_RETURN( SymbolicTableMatches matches, control::EvaluatePipeline(data_plane, "ingress", &context.egress_headers, - &translator, - /*guard=*/Z3Context().bool_val(true))); - ASSIGN_OR_RETURN(z3::expr dropped, IsDropped(context.egress_headers)); + &translator, *context.z3_context, + /*guard=*/context.z3_context->bool_val(true))); + ASSIGN_OR_RETURN(z3::expr dropped, + IsDropped(context.egress_headers, *context.z3_context)); // Assign egress_spec to egress_port if the packet is not dropped. // https://github.com/p4lang/behavioral-model/blob/main/docs/simple_switch.md @@ -160,13 +168,14 @@ absl::Status EvaluateV1model(const ir::Dataplane &data_plane, ASSIGN_OR_RETURN( SymbolicTableMatches egress_matches, control::EvaluatePipeline(data_plane, "egress", &context.egress_headers, - &translator, + &translator, *context.z3_context, /*guard=*/!dropped)); matches.merge(std::move(egress_matches)); // Populate and build the symbolic context. context.trace.matched_entries = std::move(matches); - ASSIGN_OR_RETURN(context.trace.dropped, IsDropped(context.egress_headers)); + ASSIGN_OR_RETURN(context.trace.dropped, + IsDropped(context.egress_headers, *context.z3_context)); ASSIGN_OR_RETURN(context.trace.got_cloned, GotCloned(context.egress_headers)); ASSIGN_OR_RETURN(context.ingress_port, context.ingress_headers.Get( "standard_metadata.ingress_port")); diff --git a/p4_symbolic/symbolic/v1model.h b/p4_symbolic/symbolic/v1model.h index f437d848..237a3ad7 100644 --- a/p4_symbolic/symbolic/v1model.h +++ b/p4_symbolic/symbolic/v1model.h @@ -20,14 +20,21 @@ #include "p4_symbolic/ir/ir.h" #include "p4_symbolic/symbolic/context.h" #include "p4_symbolic/symbolic/values.h" +#include "z3++.h" namespace p4_symbolic { namespace symbolic { namespace v1model { +// V1model's `mark_to_drop` primitive sets the `egress_spec` field to +// `kDropPort` to indicate the packet should be dropped at the end of +// ingress/egress processing. See v1model.p4 for details. +z3::expr EgressSpecDroppedValue(z3::context &z3_context); + // Initializes the ingress headers to appropriate values. absl::Status InitializeIngressHeaders(const ir::P4Program &program, - SymbolicPerPacketState &ingress_headers); + SymbolicPerPacketState &ingress_headers, + z3::context &z3_context); // Symbolically evaluates the parser, ingress, and egress pipelines of the given // P4 program with the given entries, assuming the program is written against V1 diff --git a/p4_symbolic/symbolic/values_test.cc b/p4_symbolic/symbolic/values_test.cc index 93277d91..4fc95c71 100644 --- a/p4_symbolic/symbolic/values_test.cc +++ b/p4_symbolic/symbolic/values_test.cc @@ -9,6 +9,7 @@ #include "gutil/testing.h" #include "p4_pdpi/ir.pb.h" #include "p4_symbolic/z3_util.h" +#include "z3++.h" namespace p4_symbolic::symbolic::values { namespace { @@ -20,6 +21,7 @@ constexpr char kFieldType[] = "dummy_field_type"; TEST(TranslateValueToP4RT, ReverseTranslatedValuesAreEqualToTheOriginalOnes) { constexpr int kNumIds = 256; + z3::context z3_context; // Prepare the translator and expected values. P4RuntimeTranslator translator; @@ -30,7 +32,7 @@ TEST(TranslateValueToP4RT, ReverseTranslatedValuesAreEqualToTheOriginalOnes) { ir_value.set_str(id); ASSERT_OK_AND_ASSIGN( z3::expr expr, - FormatP4RTValue(Z3Context(), kFieldName, kFieldType, ir_value, + FormatP4RTValue(z3_context, kFieldName, kFieldType, ir_value, /*bitwidth=*/0, &translator)); z3_value_to_id[expr.to_string()] = id; } @@ -47,54 +49,59 @@ TEST(TranslateValueToP4RT, ReverseTranslatedValuesAreEqualToTheOriginalOnes) { // Make sure the code conforms to the peculiarities of PDPI's value conversion. TEST(FormatP4RTValue, WorksFor64BitIPv6) { P4RuntimeTranslator translator; + z3::context z3_context; ASSERT_OK_AND_ASSIGN(auto ir_value, gutil::ParseTextProto( R"pb(ipv6: "0000:ffff:ffff:ffff::")pb")); ASSERT_OK_AND_ASSIGN( z3::expr z3_expr, - FormatP4RTValue(Z3Context(), kFieldName, kFieldType, ir_value, + FormatP4RTValue(z3_context, kFieldName, kFieldType, ir_value, /*bitwidth=*/64, &translator)); ASSERT_EQ(Z3ValueStringToInt(z3_expr.to_string()), 0x0000'ffff'ffff'ffffULL); } TEST(FormatP4RTValue, WorksForIpv4) { P4RuntimeTranslator translator; + z3::context z3_context; ASSERT_OK_AND_ASSIGN(auto ir_value, gutil::ParseTextProto( R"pb(ipv4: "127.0.0.1")pb")); ASSERT_OK_AND_ASSIGN( z3::expr z3_expr, - FormatP4RTValue(Z3Context(), kFieldName, kFieldType, ir_value, + FormatP4RTValue(z3_context, kFieldName, kFieldType, ir_value, /*bitwidth=*/32, &translator)); ASSERT_EQ(Z3ValueStringToInt(z3_expr.to_string()), 0x7f000001); } TEST(FormatP4RTValue, WorksForMac) { P4RuntimeTranslator translator; + z3::context z3_context; ASSERT_OK_AND_ASSIGN(auto ir_value, gutil::ParseTextProto( R"pb(mac: "01:02:03:04:05:06")pb")); ASSERT_OK_AND_ASSIGN( z3::expr z3_expr, - FormatP4RTValue(Z3Context(), kFieldName, kFieldType, ir_value, + FormatP4RTValue(z3_context, kFieldName, kFieldType, ir_value, /*bitwidth=*/48, &translator)); ASSERT_EQ(Z3ValueStringToInt(z3_expr.to_string()), 0x01'02'03'04'05'06ULL); } TEST(FormatP4RTValue, WorksForHexString) { P4RuntimeTranslator translator; + z3::context z3_context; ASSERT_OK_AND_ASSIGN(auto ir_value, gutil::ParseTextProto( R"pb(hex_str: "0xabcd")pb")); ASSERT_OK_AND_ASSIGN( z3::expr z3_expr, - FormatP4RTValue(Z3Context(), kFieldName, kFieldType, ir_value, + FormatP4RTValue(z3_context, kFieldName, kFieldType, ir_value, /*bitwidth=*/16, &translator)); ASSERT_EQ(Z3ValueStringToInt(z3_expr.to_string()), 0xabcd); } TEST(FormatP4RTValue, FailsForStringWithNonZeroBitwidth) { P4RuntimeTranslator translator; + z3::context z3_context; ASSERT_OK_AND_ASSIGN(auto ir_value, gutil::ParseTextProto( R"pb(str: "dummy_value")pb")); - ASSERT_THAT(FormatP4RTValue(Z3Context(), kFieldName, kFieldType, ir_value, + ASSERT_THAT(FormatP4RTValue(z3_context, kFieldName, kFieldType, ir_value, /*bitwidth=*/16, &translator), StatusIs(absl::StatusCode::kInvalidArgument)); } diff --git a/p4_symbolic/tests/sai_p4_component_test.cc b/p4_symbolic/tests/sai_p4_component_test.cc index 6fe6d80d..1add4fa2 100644 --- a/p4_symbolic/tests/sai_p4_component_test.cc +++ b/p4_symbolic/tests/sai_p4_component_test.cc @@ -106,7 +106,40 @@ class P4SymbolicComponentTest : public testing::Test { /*mask_known_failures=*/true); }; -TEST(P4SymbolicComponentTest, CanGenerateTestPacketsForSimpleSaiP4Entries) { +absl::StatusOr GenerateSmtForSaiPiplelineWithSimpleEntries() { + const auto config = sai::GetNonstandardForwardingPipelineConfig( + sai::Instantiation::kMiddleblock, sai::NonstandardPlatform::kP4Symbolic); + ASSIGN_OR_RETURN(const pdpi::IrP4Info ir_p4info, + pdpi::CreateIrP4Info(config.p4info())); + auto pd_entries = ParseProtoOrDie(kTableEntries); + std::vector pi_entries; + for (auto& pd_entry : pd_entries.entries()) { + ASSIGN_OR_RETURN( + pi_entries.emplace_back(), + pdpi::PartialPdTableEntryToPiTableEntry(ir_p4info, pd_entry)); + } + + ASSIGN_OR_RETURN(std::unique_ptr solver, + symbolic::EvaluateP4Program(config, pi_entries)); + return solver->GetSolverSMT(); +} + +// Generate SMT constraints for the SAI pipeline from scratch multiple times and +// make sure the results remain the same. +TEST_F(P4SymbolicComponentTest, + DISABLED_ConstraintGenerationIsDeterministicForSai) { + constexpr int kNumberOfRuns = 5; + ASSERT_OK_AND_ASSIGN(const std::string reference_smt_formula, + GenerateSmtForSaiPiplelineWithSimpleEntries()); + for (int run = 0; run < kNumberOfRuns; ++run) { + LOG(INFO) << "Run " << run; + ASSERT_OK_AND_ASSIGN(const std::string smt_formula, + GenerateSmtForSaiPiplelineWithSimpleEntries()); + ASSERT_THAT(smt_formula, Eq(reference_smt_formula)); + } +} + +TEST_F(P4SymbolicComponentTest, CanGenerateTestPacketsForSimpleSaiP4Entries) { // Some constants. auto env = thinkit::BazelTestEnvironment(/*mask_known_failures=*/false); const auto config = sai::GetNonstandardForwardingPipelineConfig( diff --git a/p4_symbolic/z3_util.cc b/p4_symbolic/z3_util.cc index 62ee5b55..1e5efdf5 100644 --- a/p4_symbolic/z3_util.cc +++ b/p4_symbolic/z3_util.cc @@ -111,17 +111,6 @@ absl::Status AppendHexCharStringToPDPIBitString( } // namespace -z3::context& Z3Context(bool renew) { - static z3::context* z3_context = new z3::context(); - - if (renew) { - delete z3_context; - z3_context = new z3::context(); - } - - return *z3_context; -} - absl::StatusOr EvalZ3Bool(const z3::expr& bool_expr, const z3::model& model) { // TODO: Ensure this doesn't crash by checking sort first. diff --git a/p4_symbolic/z3_util.h b/p4_symbolic/z3_util.h index 652fc4bd..17267ab1 100644 --- a/p4_symbolic/z3_util.h +++ b/p4_symbolic/z3_util.h @@ -20,12 +20,6 @@ namespace p4_symbolic { -// Returns the global z3::context used for creating symbolic expressions during -// symbolic evaluation. If parameter `renew` is set to true, it deletes the -// older context and returns a new one. -// TODO: `renew` is a workaround for using a global context. -z3::context &Z3Context(bool renew = false); - // -- Evaluation --------------------------------------------------------------- absl::StatusOr EvalZ3Bool(const z3::expr &bool_expr, diff --git a/pins_infra_deps.bzl b/pins_infra_deps.bzl index fd05c783..ec7d5f13 100644 --- a/pins_infra_deps.bzl +++ b/pins_infra_deps.bzl @@ -102,10 +102,10 @@ def pins_infra_deps(): if not native.existing_rule("com_github_otg_models"): http_archive( name = "com_github_otg_models", - url = "https://github.com/open-traffic-generator/models/archive/v0.11.8.zip", - strip_prefix = "models-0.11.8", + url = "https://github.com/open-traffic-generator/models/archive/refs/tags/v0.12.5.zip", + strip_prefix = "models-0.12.5", build_file = "@//:bazel/BUILD.otg-models.bazel", - sha256 = "caf3a9c9cdf8f9f4fb16da2cc9123823af6446f40dff71a1f2c1b35e76ae36f5", + sha256 = "1a63e769f1d7f42c79bc1115babf54acbc44761849a77ac28f47a74567f10090", ) # Needed to make glog happy. diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 9ee6942b..6bdd1608 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -74,6 +74,7 @@ cc_library( "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", "@com_google_googletest//:gtest", ], ) diff --git a/tests/forwarding/BUILD.bazel b/tests/forwarding/BUILD.bazel index 7f9aac80..5255b4ec 100644 --- a/tests/forwarding/BUILD.bazel +++ b/tests/forwarding/BUILD.bazel @@ -306,6 +306,7 @@ cc_library( "//sai_p4/instantiations/google:sai_p4info_cc", "//sai_p4/instantiations/google:sai_pd_cc_proto", "//tests:thinkit_sanity_tests", + "//tests/lib:p4rt_fixed_table_programming_helper", "//thinkit:mirror_testbed", "//thinkit:mirror_testbed_fixture", "//thinkit:switch", diff --git a/tests/forwarding/l3_admit_test.cc b/tests/forwarding/l3_admit_test.cc index c756e4e0..56f3f070 100644 --- a/tests/forwarding/l3_admit_test.cc +++ b/tests/forwarding/l3_admit_test.cc @@ -253,8 +253,11 @@ absl::Status SendUdpPacket(pdpi::P4RuntimeSession& session, absl::string_view payload) { LOG(INFO) << absl::StreamFormat("Sending %d packets with %s, %s to port %s.", packet_count, dst_mac, dst_ip, port_id); - ASSIGN_OR_RETURN(std::string packet, UdpPacket(dst_mac, dst_ip, payload)); + for (int i = 0; i < packet_count; ++i) { + ASSIGN_OR_RETURN(std::string packet, + UdpPacket(dst_mac, dst_ip, + absl::Substitute("[Packet:$0] $1", i, payload))); RETURN_IF_ERROR(InjectEgressPacket(port_id, packet, ir_p4info, &session)); } return absl::OkStatus(); @@ -329,7 +332,7 @@ TEST_P(L3AdmitTestFixture, packetlib::Packet packet_in = packetlib::ParsePacket(response.packet().payload()); if (response.update_case() == p4::v1::StreamMessageResponse::kPacket && - packet_in.payload() == kGoodPayload) { + absl::StrContains(packet_in.payload(), kGoodPayload)) { ++good_packet_count; } else if (response.update_case() == p4::v1::StreamMessageResponse::kPacket && @@ -412,7 +415,7 @@ TEST_P(L3AdmitTestFixture, L3AdmitCanUseMaskToAllowMultipleMacAddresses) { packetlib::Packet packet_in = packetlib::ParsePacket(response.packet().payload()); if (response.update_case() == p4::v1::StreamMessageResponse::kPacket && - packet_in.payload() == kGoodPayload) { + absl::StrContains(packet_in.payload(), kGoodPayload)) { ++good_packet_count; } else { LOG(WARNING) << "Unexpected response: " << response.DebugString(); @@ -503,7 +506,7 @@ TEST_P(L3AdmitTestFixture, DISABLED_L3AdmitCanUseInPortToRestrictMacAddresses) { packetlib::Packet packet_in = packetlib::ParsePacket(response.packet().payload()); if (response.update_case() == p4::v1::StreamMessageResponse::kPacket && - packet_in.payload() == kGoodPayload) { + absl::StrContains(packet_in.payload(), kGoodPayload)) { ++good_packet_count; } else if (response.update_case() == p4::v1::StreamMessageResponse::kPacket && @@ -576,7 +579,7 @@ ASSERT_OK( packetlib::Packet packet_in = packetlib::ParsePacket(response.packet().payload()); if (response.update_case() == p4::v1::StreamMessageResponse::kPacket && - packet_in.payload() == kGoodPayload) { + absl::StrContains(packet_in.payload(), kGoodPayload)) { ++good_packet_count; } else { LOG(WARNING) << "Unexpected response: " << response.DebugString(); @@ -682,7 +685,7 @@ TEST_P(L3AdmitTestFixture, L3PacketsCanBeClassifiedByDestinationMac) { packetlib::Packet packet_in = packetlib::ParsePacket(response.packet().payload()); if (response.update_case() == p4::v1::StreamMessageResponse::kPacket && - packet_in.payload() == kGoodPayload) { + absl::StrContains(packet_in.payload(), kGoodPayload)) { ++good_packet_count; } else if (response.update_case() == p4::v1::StreamMessageResponse::kPacket && diff --git a/tests/forwarding/watch_port_test.cc b/tests/forwarding/watch_port_test.cc index cfb4d4d0..e5e6176e 100644 --- a/tests/forwarding/watch_port_test.cc +++ b/tests/forwarding/watch_port_test.cc @@ -64,6 +64,7 @@ #include "tests/forwarding/group_programming_util.h" #include "tests/forwarding/packet_test_util.h" #include "tests/forwarding/util.h" +#include "tests/lib/p4rt_fixed_table_programming_helper.h" #include "tests/thinkit_sanity_tests.h" #include "thinkit/mirror_testbed.h" #include "thinkit/mirror_testbed_fixture.h" @@ -273,6 +274,17 @@ absl::StatusOr> CountNumPacketsPerPort( return num_packets_per_port; } +// Program L3 Admit table for the given mac-address. +absl::Status ProgramL3Admit(pdpi::P4RuntimeSession& p4_session, + const pdpi::IrP4Info& ir_p4info, + const L3AdmitOptions& options) { + p4::v1::WriteRequest write_request; + ASSIGN_OR_RETURN( + *write_request.add_updates(), + L3AdmitTableUpdate(ir_p4info, p4::v1::Update::INSERT, options)); + return pdpi::SetMetadataAndSendPiWriteRequest(&p4_session, write_request); +} + // Sends N packets from the control switch to sut at a rate of 500 packets/sec. absl::Status SendNPacketsToSut(int num_packets, const TestConfiguration& test_config, @@ -581,6 +593,15 @@ TEST_P(WatchPortTestFixture, VerifyBasicWcmpPacketDistribution) { p4::v1::Update::INSERT)) << "Failed to program WCMP group: "; + // Allow the destination mac address to L3. + ASSERT_OK(ProgramL3Admit( + *sut_p4_session_, ir_p4info, + L3AdmitOptions{ + .priority = 2070, + .dst_mac = std ::make_pair(pins::GetIthDstMac(0).ToString(), + "FF:FF:FF:FF:FF:FF"), + })); + // Program default routing for all packets on SUT. ASSERT_OK(ProgramDefaultRoutes(*sut_p4_session_, ir_p4info, kVrfId, p4::v1::Update::INSERT)); @@ -660,6 +681,16 @@ TEST_P(WatchPortTestFixture, VerifyBasicWatchPortAction) { ASSERT_OK(pins::ProgramGroupWithMembers(environment, *sut_p4_session_, ir_p4info, kGroupId, members, p4::v1::Update::INSERT)); + + // Allow the destination mac address to L3. + ASSERT_OK(ProgramL3Admit( + *sut_p4_session_, ir_p4info, + L3AdmitOptions{ + .priority = 2070, + .dst_mac = std ::make_pair(pins::GetIthDstMac(0).ToString(), + "FF:FF:FF:FF:FF:FF"), + })); + // Program default routing for all packets on SUT. ASSERT_OK(ProgramDefaultRoutes(*sut_p4_session_, ir_p4info, kVrfId, p4::v1::Update::INSERT)); @@ -774,6 +805,16 @@ TEST_P(WatchPortTestFixture, VerifyWatchPortActionInCriticalState) { ASSERT_OK(pins::ProgramGroupWithMembers(environment, *sut_p4_session_, ir_p4info, kGroupId, members, p4::v1::Update::INSERT)); + + // Allow the destination mac address to L3. + ASSERT_OK(ProgramL3Admit( + *sut_p4_session_, ir_p4info, + L3AdmitOptions{ + .priority = 2070, + .dst_mac = std ::make_pair(pins::GetIthDstMac(0).ToString(), + "FF:FF:FF:FF:FF:FF"), + })); + // Program default routing for all packets on SUT. ASSERT_OK(ProgramDefaultRoutes(*sut_p4_session_, ir_p4info, kVrfId, p4::v1::Update::INSERT)); @@ -881,6 +922,16 @@ TEST_P(WatchPortTestFixture, VerifyWatchPortActionForSingleMember) { ASSERT_OK(pins::ProgramGroupWithMembers(environment, *sut_p4_session_, ir_p4info, kGroupId, members, p4::v1::Update::INSERT)); + + // Allow the destination mac address to L3. + ASSERT_OK(ProgramL3Admit( + *sut_p4_session_, ir_p4info, + L3AdmitOptions{ + .priority = 2070, + .dst_mac = std ::make_pair(pins::GetIthDstMac(0).ToString(), + "FF:FF:FF:FF:FF:FF"), + })); + // Program default routing for all packets on SUT. ASSERT_OK(ProgramDefaultRoutes(*sut_p4_session_, ir_p4info, kVrfId, p4::v1::Update::INSERT)); @@ -994,6 +1045,16 @@ TEST_P(WatchPortTestFixture, VerifyWatchPortActionForMemberModify) { ASSERT_OK(pins::ProgramGroupWithMembers(environment, *sut_p4_session_, ir_p4info, kGroupId, members, p4::v1::Update::INSERT)); + + // Allow the destination mac address to L3. + ASSERT_OK(ProgramL3Admit( + *sut_p4_session_, ir_p4info, + L3AdmitOptions{ + .priority = 2070, + .dst_mac = std ::make_pair(pins::GetIthDstMac(0).ToString(), + "FF:FF:FF:FF:FF:FF"), + })); + // Program default routing for all packets on SUT. ASSERT_OK(ProgramDefaultRoutes(*sut_p4_session_, ir_p4info, kVrfId, p4::v1::Update::INSERT)); @@ -1123,6 +1184,16 @@ TEST_P(WatchPortTestFixture, VerifyWatchPortActionForDownPortMemberInsert) { ASSERT_OK(pins::ProgramGroupWithMembers(environment, *sut_p4_session_, ir_p4info, kGroupId, members, p4::v1::Update::INSERT)); + + // Allow the destination mac address to L3. + ASSERT_OK(ProgramL3Admit( + *sut_p4_session_, ir_p4info, + L3AdmitOptions{ + .priority = 2070, + .dst_mac = std ::make_pair(pins::GetIthDstMac(0).ToString(), + "FF:FF:FF:FF:FF:FF"), + })); + // Program default routing for all packets on SUT. ASSERT_OK(ProgramDefaultRoutes(*sut_p4_session_, ir_p4info, kVrfId, p4::v1::Update::INSERT)); diff --git a/tests/integration/system/BUILD.bazel b/tests/integration/system/BUILD.bazel index 1e40d3ca..d47aec10 100644 --- a/tests/integration/system/BUILD.bazel +++ b/tests/integration/system/BUILD.bazel @@ -44,6 +44,7 @@ cc_library( "//sai_p4/fixed:p4_roles", "//sai_p4/instantiations/google:instantiations", "//sai_p4/instantiations/google:sai_p4info_cc", + "//tests/lib:switch_test_setup_helpers", "//thinkit:generic_testbed", "//thinkit:generic_testbed_fixture", "//thinkit:switch", @@ -52,11 +53,11 @@ cc_library( "@com_github_gnmi//proto/gnmi:gnmi_cc_grpc_proto", "@com_github_gnmi//proto/gnmi:gnmi_cc_proto", "@com_github_google_glog//:glog", + "@com_github_grpc_grpc//:grpc++", + "@com_github_p4lang_p4runtime//:p4info_cc_proto", "@com_github_p4lang_p4runtime//:p4runtime_cc_grpc", "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", - "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/cleanup", - "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/random", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", @@ -123,9 +124,8 @@ cc_library( "//gutil:status", "//gutil:status_matchers", "//gutil:testing", - "//lib:ixia_helper", - "//lib:ixia_helper_cc_proto", "//lib/gnmi:gnmi_helper", + "//lib/utils:generic_testbed_utils", "//lib/utils:json_utils", "//lib/validator:validator_lib", "//p4_pdpi:ir", @@ -145,7 +145,12 @@ cc_library( "//tests/qos:qos_test_util", "//thinkit:generic_testbed", "//thinkit:generic_testbed_fixture", + "//thinkit/proto:generic_testbed_cc_proto", "@com_github_gnmi//proto/gnmi:gnmi_cc_grpc_proto", + "@com_github_google_glog//:glog", + "@com_github_grpc_grpc//:grpc++", + "@com_github_otg_models//:otg_cc_proto", + "@com_github_otg_models//:otg_grpc_proto", "@com_github_p4lang_p4runtime//:p4info_cc_proto", "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", "@com_google_absl//absl/cleanup", @@ -153,6 +158,7 @@ cc_library( "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/time", @@ -160,3 +166,67 @@ cc_library( ], alwayslink = True, ) + +cc_library( + name = "control_device_reboot_test", + testonly = True, + srcs = ["control_device_reboot_test.cc"], + hdrs = ["control_device_reboot_test.h"], + deps = [ + "//gutil:status_matchers", + "//gutil:testing", + "//thinkit:control_device", + "//thinkit:generic_testbed", + "//thinkit:generic_testbed_fixture", + "//thinkit/proto:generic_testbed_cc_proto", + "@com_github_google_glog//:glog", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + "@com_google_googletest//:gtest_main", + ], + alwayslink = True, +) + +cc_library( + name = "alpm_miss_counter_tests", + testonly = 1, + srcs = [ + "alpm_miss_counter_tests.cc", + ], + hdrs = [ + "alpm_miss_counter_tests.h", + ], + deps = [ + "//gutil:collections", + "//gutil:proto", + "//gutil:status_matchers", + "//gutil:testing", + "//lib/gnmi:gnmi_helper", + "//lib/validator:validator_lib", + "//p4_pdpi:p4_runtime_session", + "//p4_pdpi:p4_runtime_session_extras", + "//p4_pdpi/netaddr:mac_address", + "//p4_pdpi/packetlib", + "//p4_pdpi/packetlib:packetlib_cc_proto", + "//sai_p4/instantiations/google:sai_pd_cc_proto", + "//tests/lib:switch_test_setup_helpers", + "//thinkit:control_device", + "//thinkit:generic_testbed", + "//thinkit:generic_testbed_fixture", + "//thinkit:switch", + "@com_github_gnmi//proto/gnmi:gnmi_cc_grpc_proto", + "@com_github_google_glog//:glog", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/random", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", + "@com_google_googletest//:gtest", + ], + alwayslink = 1, +) + diff --git a/tests/integration/system/alpm_miss_counter_tests.cc b/tests/integration/system/alpm_miss_counter_tests.cc new file mode 100644 index 00000000..252bb41c --- /dev/null +++ b/tests/integration/system/alpm_miss_counter_tests.cc @@ -0,0 +1,617 @@ +// Copyright 2024 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. + +#include "tests/integration/system/alpm_miss_counter_tests.h" + +#include +#include +#include +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/random/random.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/match.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "glog/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gutil/collections.h" +#include "gutil/proto.h" +#include "gutil/status.h" +#include "gutil/testing.h" +#include "lib/gnmi/gnmi_helper.h" +#include "lib/validator/validator_lib.h" +#include "p4_pdpi/netaddr/mac_address.h" +#include "p4_pdpi/p4_runtime_session.h" +#include "p4_pdpi/p4_runtime_session_extras.h" +#include "p4_pdpi/packetlib/packetlib.h" +#include "p4_pdpi/packetlib/packetlib.pb.h" +#include "proto/gnmi/gnmi.grpc.pb.h" +#include "sai_p4/instantiations/google/sai_pd.pb.h" +#include "tests/lib/switch_test_setup_helpers.h" +#include "thinkit/control_device.h" +#include "thinkit/switch.h" + +namespace pins_test { + +constexpr char kIpv4DstIpForL3Route[] = "10.10.20.0"; +constexpr int kIpv4DstIpPrefixLenForL3Route = 24; +constexpr char kIpv4DstIpForL3Hit[] = "10.10.20.10"; +constexpr char kIpv4DstIpForL3Miss[] = "10.10.30.10"; + +constexpr char kIpv6DstIpForL3Route[] = "F105:0102::"; +constexpr int kIpv6DstIpPrefixLenForL3Route = 64; +constexpr char kIpv6DstIpForL3Hit[] = "F105:0102::2356"; +constexpr char kIpv6DstIpForL3Miss[] = "F205:0102::9845"; + +constexpr uint32_t kPacketsToSend = 1000; +constexpr uint32_t kNumberOfPacketsMargin = 50; + +enum class IpVersion { + kIpv4, + kIpv6, + kIpv4And6, +}; + +void CreateAlpmRouteParams(struct AlpmRouteParams& route_params, + const std::string& p4rt_egress_port_id) { + route_params.neighbor_id = + netaddr::MacAddress(1, 3, 5, 7, 9, 9).ToLinkLocalIpv6Address().ToString(); + route_params.vrf = absl::StrCat("vrf-", p4rt_egress_port_id); + route_params.rif = absl::StrCat("rif-", p4rt_egress_port_id); + route_params.nexthop = absl::StrCat("nexthop-", p4rt_egress_port_id); + route_params.p4_port_id = p4rt_egress_port_id; +} + +absl::StatusOr ConstructTestEntries( + struct AlpmRouteParams& route_params, IpVersion ip_version) { + sai::TableEntries test_entries; + ASSIGN_OR_RETURN( + test_entries, + gutil::ParseTextProto(absl::Substitute( + R"pb( + entries { + vrf_table_entry { + match { vrf_id: "$0" } + action { no_action {} } + } + } + entries { + acl_pre_ingress_table_entry { + match { is_ip { value: "0x1" } } + action { set_vrf { vrf_id: "$0" } } + priority: 1 + } + } + entries { + router_interface_table_entry { + match { router_interface_id: "$2" } + action { + set_port_and_src_mac { + port: "$4" + src_mac: "09:08:07:08:09:09" + } + } + } + } + entries { + neighbor_table_entry { + match { router_interface_id: "$2" neighbor_id: "$1" } + action { set_dst_mac { dst_mac: "01:03:05:07:09:09" } } + } + } + entries { + nexthop_table_entry { + match { nexthop_id: "$3" } + action { + set_ip_nexthop { router_interface_id: "$2" neighbor_id: "$1" } + } + } + } + )pb", + route_params.vrf, route_params.neighbor_id, route_params.rif, + route_params.nexthop, route_params.p4_port_id))); + + if (ip_version == IpVersion::kIpv4 || ip_version == IpVersion::kIpv4And6) { + *test_entries.add_entries() = + gutil::ParseProtoOrDie(absl::Substitute( + R"pb( + ipv4_table_entry { + match { + vrf_id: "$0" + ipv4_dst: { value: "$2" prefix_length: $3 } + } + action { set_nexthop_id { nexthop_id: "$1" } } + } + )pb", + route_params.vrf, route_params.nexthop, kIpv4DstIpForL3Route, + kIpv4DstIpPrefixLenForL3Route)); + } + + if (ip_version == IpVersion::kIpv6 || ip_version == IpVersion::kIpv4And6) { + *test_entries.add_entries() = gutil::ParseProtoOrDie( + absl::Substitute(R"pb( + ipv6_table_entry { + match { + vrf_id: "$0" + ipv6_dst: { value: "$2" prefix_length: $3 } + } + action { set_nexthop_id { nexthop_id: "$1" } } + } + )pb", + route_params.vrf, route_params.nexthop, + kIpv6DstIpForL3Route, kIpv6DstIpPrefixLenForL3Route)); + } + + // L3 admit entry. + *test_entries.add_entries() = gutil::ParseProtoOrDie(R"pb( + l3_admit_table_entry { + match {} # Wildcard. + action { admit_to_l3 {} } + priority: 1 + } + )pb"); + + return test_entries; +} + +absl::StatusOr GetPortId(gnmi::gNMI::StubInterface& gnmi_stub, + absl::string_view interface) { + absl::flat_hash_map interface_to_port_id; + ASSIGN_OR_RETURN(interface_to_port_id, + pins_test::GetAllInterfaceNameToPortId(gnmi_stub)); + return gutil::FindOrStatus(interface_to_port_id, interface); +} + +// Test packet proto message sent from control switch to SUT. +constexpr absl::string_view kIpv4TestPacket = R"pb( + headers { + ethernet_header { + ethernet_destination: "00:1A:11:17:5F:80" + ethernet_source: "00:01:02:03:04:05" + ethertype: "0x0800" + } + } + headers { + ipv4_header { + version: "0x4" + ihl: "0x5" + dscp: "0x03" + ecn: "0x0" + identification: "0x0000" + flags: "0x0" + fragment_offset: "0x0000" + ttl: "0x20" + protocol: "0x11" + ipv4_source: "1.2.3.4" + ipv4_destination: "$0" + } + } + headers { udp_header { source_port: "0x0000" destination_port: "0x0000" } } + payload: "Basic IPv4 test packet")pb"; + +constexpr absl::string_view kIpv6TestPacket = R"pb( + headers { + ethernet_header { + ethernet_destination: "00:1A:11:17:5F:80" + ethernet_source: "00:01:02:03:04:05" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + dscp: "0x03" + ecn: "0x0" + flow_label: "0x00000" + next_header: "0xfd" # Reserved for experimentation. + hop_limit: "0x40" + ipv6_source: "2001:db8:0:12::1" + ipv6_destination: "$0" + } + } + payload: "Basic IPv6 test packet")pb"; + +absl::StatusOr GetGnmiStats(gnmi::gNMI::StubInterface& gnmi_stub, + absl::string_view state_path, + absl::string_view resp_parse_str) { + ASSIGN_OR_RETURN( + std::string stat_response, + GetGnmiStatePathInfo(&gnmi_stub, state_path, resp_parse_str)); + + uint64_t stat; + if (!absl::SimpleAtoi(StripQuotes(stat_response), &stat)) { + return absl::InternalError( + absl::StrCat("Unable to parse counter from ", stat_response)); + } + return stat; +} + +absl::StatusOr GetGnmiInterfaceStat( + absl::string_view stat_name, absl::string_view interface, + gnmi::gNMI::StubInterface& gnmi_stub) { + std::string state_path = absl::StrCat("interfaces/interface[name=", interface, + "]/state/counters/", stat_name); + std::string resp_parse_str = + absl::StrCat("openconfig-interfaces:", stat_name); + return GetGnmiStats(gnmi_stub, state_path, resp_parse_str); +} + +absl::StatusOr GetAlpmMissStat(gnmi::gNMI::StubInterface& gnmi_stub) { + std::string state_path = + "components/component[name=integrated_circuit0]/integrated-circuit/" + "pipeline-counters/drop/lookup-block/state/no-route"; + std::string resp_parse_str = "openconfig-platform-pipeline-counters:no-route"; + + return GetGnmiStats(gnmi_stub, state_path, resp_parse_str); +} + +absl::StatusOr IsPlatformTypeExperimental( + gnmi::gNMI::StubInterface& gnmi_stub) { + std::string state_path = + "components/component[name=chassis]/chassis/state/platform"; + std::string resp_parse_str = "openconfig-pins-platform-chassis:platform"; + + ASSIGN_OR_RETURN( + std::string state_response, + GetGnmiStatePathInfo(&gnmi_stub, state_path, resp_parse_str)); + + return absl::StrContains(state_response, "Experimental"); +} + +// Control switch sends packets to SUT. +void SendPackets(gnmi::gNMI::StubInterface& sut_gnmi_stub, + thinkit::ControlDevice& control_device, + absl::string_view sut_port, absl::string_view control_port, + IpVersion ip_version, bool l3_miss = false) { + // Make test packet. + packetlib::Packet test_packet; + if (ip_version == IpVersion::kIpv4) { + test_packet = gutil::ParseProtoOrDie(absl::Substitute( + kIpv4TestPacket, l3_miss ? kIpv4DstIpForL3Miss : kIpv4DstIpForL3Hit)); + } else { + test_packet = gutil::ParseProtoOrDie(absl::Substitute( + kIpv6TestPacket, l3_miss ? kIpv6DstIpForL3Miss : kIpv6DstIpForL3Hit)); + } + ASSERT_OK_AND_ASSIGN(std::string test_packet_data, + packetlib::SerializePacket(test_packet)); + LOG(INFO) << "Test packet to send: " << test_packet.DebugString(); + + ASSERT_OK_AND_ASSIGN( + uint64_t initial_in_pkts_on_sut_port, + GetGnmiInterfaceStat("in-pkts", sut_port, sut_gnmi_stub)); + LOG(INFO) << "Initial Rx packets count on " << sut_port << ": " + << initial_in_pkts_on_sut_port; + + // Send packet to SUT. + for (uint32_t i = 0; i < kPacketsToSend; ++i) { + ASSERT_OK(control_device.SendPacket(control_port, test_packet_data)) + << "failed to inject the packet."; + absl::SleepFor(absl::Milliseconds(10)); + } + LOG(INFO) << "Successfully sent " << kPacketsToSend << " packets."; + // Wait some time before capturing the port stats. + absl::SleepFor(absl::Seconds(15)); + + ASSERT_OK_AND_ASSIGN( + uint64_t final_in_pkts_on_sut_port, + GetGnmiInterfaceStat("in-pkts", sut_port, sut_gnmi_stub)); + LOG(INFO) << "Final Rx packets count on " << sut_port << ": " + << final_in_pkts_on_sut_port; + + // Verify port stats. + uint64_t in_pkts_on_sut_port = + final_in_pkts_on_sut_port - initial_in_pkts_on_sut_port; + + // There is a possibility that a few non-test protocol packets flowed on the + // links while test traffic was running. + EXPECT_GE(in_pkts_on_sut_port, kPacketsToSend); +} + +absl::Status ValidatePortsUp( + thinkit::Switch& sut, thinkit::ControlDevice& control_device, + const std::vector& sut_interfaces, + const std::vector& control_device_interfaces) { + absl::Status sut_ports_up_status = + pins_test::PortsUp(sut, absl::Span(sut_interfaces)); + absl::Status control_switch_ports_up_status = control_device.ValidatePortsUp( + absl::Span(control_device_interfaces)); + + if (sut_ports_up_status.ok() && control_switch_ports_up_status.ok()) { + return absl::OkStatus(); + } + + EXPECT_OK(sut_ports_up_status); + EXPECT_OK(control_switch_ports_up_status); + return absl::InternalError("PortsUp validation failed."); +} + +// Tests that when IPv4 L3 route is added and Ipv4 test packets hit the added +// route, ALPM miss counter doesn't go up. +TEST_P(AlpmMissCountersTest, Ipv4AlpmRouteHit) { + ASSERT_NO_FATAL_FAILURE( + InitializeTestEnvironment("d9716769-9ca5-477b-b53b-1b96fce60e13")); + + ASSERT_OK_AND_ASSIGN(bool is_sut_Experimental, + IsPlatformTypeExperimental(*sut_gnmi_stub_)); + if (!is_sut_Experimental) { + GTEST_SKIP() << "Test is not supported on non-Experimental SUT."; + } + if (!generic_testbed_->ControlDevice().SupportsSendPacket()) { + GTEST_SKIP() << "Control device does not support SendPacket"; + } + // Test requires at least 1 SUT interface. + if (sut_interfaces_.empty()) { + GTEST_SKIP() << "Need at least 1 SUT interface to test but got: " + << sut_interfaces_.size(); + } + + thinkit::Switch& sut = generic_testbed_->Sut(); + thinkit::ControlDevice& control_device = generic_testbed_->ControlDevice(); + ASSERT_OK( + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); + + std::shuffle(sut_interfaces_.begin(), sut_interfaces_.end(), absl::BitGen()); + std::string test_sut_interface = sut_interfaces_[0]; + ASSERT_OK_AND_ASSIGN(std::string sut_port_id, + GetPortId(*sut_gnmi_stub_, test_sut_interface)); + CreateAlpmRouteParams(route_params_, sut_port_id); + + ASSERT_OK_AND_ASSIGN(std::unique_ptr p4_session, + pins_test::ConfigureSwitchAndReturnP4RuntimeSession( + sut, + /*gnmi_config=*/std::nullopt, GetParam().p4_info)); + + ASSERT_OK_AND_ASSIGN(sai::TableEntries sut_test_entries, + ConstructTestEntries(route_params_, IpVersion::kIpv4)); + ASSERT_OK(pdpi::ClearTableEntries(p4_session.get())); + + LOG(INFO) << "Installing entries:" << sut_test_entries.ShortDebugString(); + ASSERT_OK(pdpi::InstallPdTableEntries(*p4_session, sut_test_entries)); + + ASSERT_OK_AND_ASSIGN(uint64_t initial_miss_count, + GetAlpmMissStat(*sut_gnmi_stub_)); + LOG(INFO) << "Initial miss count: " << initial_miss_count; + + LOG(INFO) << "Sending test packets on port " << test_sut_interface; + ASSERT_NO_FATAL_FAILURE(SendPackets( + *sut_gnmi_stub_, control_device, test_sut_interface, + sut_to_peer_interface_mapping_[test_sut_interface], IpVersion::kIpv4)); + + ASSERT_OK_AND_ASSIGN(uint64_t final_miss_count, + GetAlpmMissStat(*sut_gnmi_stub_)); + LOG(INFO) << "Final miss count: " << final_miss_count; + + // There is a possibility that a few non-test packets flowed on the links + // while test was running and may have miss the L3 route, so give margin on + // l3 miss counter. + EXPECT_GE(final_miss_count, initial_miss_count); + EXPECT_LE(final_miss_count, initial_miss_count + kNumberOfPacketsMargin); + + ASSERT_OK( + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); +} + +// Tests that when IPv4 L3 route is added and Ipv4 test packets miss the added +// route, ALPM miss counter goes up. +TEST_P(AlpmMissCountersTest, Ipv4AlpmRouteMiss) { + ASSERT_NO_FATAL_FAILURE( + InitializeTestEnvironment("07dda215-ad21-4c25-b89e-1128b3806c27")); + + ASSERT_OK_AND_ASSIGN(bool is_sut_Experimental, + IsPlatformTypeExperimental(*sut_gnmi_stub_)); + if (!is_sut_Experimental) { + GTEST_SKIP() << "Test is not supported on non-Experimental SUT."; + } + if (!generic_testbed_->ControlDevice().SupportsSendPacket()) { + GTEST_SKIP() << "Control device does not support SendPacket"; + } + // Test requires at least 1 SUT interface. + if (sut_interfaces_.empty()) { + GTEST_SKIP() << "Need at least 1 SUT interface to test but got: " + << sut_interfaces_.size(); + } + + thinkit::Switch& sut = generic_testbed_->Sut(); + thinkit::ControlDevice& control_device = generic_testbed_->ControlDevice(); + ASSERT_OK( + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); + + std::shuffle(sut_interfaces_.begin(), sut_interfaces_.end(), absl::BitGen()); + std::string test_sut_interface = sut_interfaces_[0]; + ASSERT_OK_AND_ASSIGN(std::string sut_port_id, + GetPortId(*sut_gnmi_stub_, test_sut_interface)); + CreateAlpmRouteParams(route_params_, sut_port_id); + + ASSERT_OK_AND_ASSIGN(std::unique_ptr p4_session, + pins_test::ConfigureSwitchAndReturnP4RuntimeSession( + sut, + /*gnmi_config=*/std::nullopt, GetParam().p4_info)); + + ASSERT_OK_AND_ASSIGN(sai::TableEntries sut_test_entries, + ConstructTestEntries(route_params_, IpVersion::kIpv4)); + ASSERT_OK(pdpi::ClearTableEntries(p4_session.get())); + + LOG(INFO) << "Installing entries:" << sut_test_entries.ShortDebugString(); + ASSERT_OK(pdpi::InstallPdTableEntries(*p4_session, sut_test_entries)); + + ASSERT_OK_AND_ASSIGN(uint64_t initial_miss_count, + GetAlpmMissStat(*sut_gnmi_stub_)); + LOG(INFO) << "Initial miss count: " << initial_miss_count; + + LOG(INFO) << "Sending test packets on port " << test_sut_interface; + ASSERT_NO_FATAL_FAILURE( + SendPackets(*sut_gnmi_stub_, control_device, test_sut_interface, + sut_to_peer_interface_mapping_[test_sut_interface], + IpVersion::kIpv4, /*l3_miss=*/true)); + + ASSERT_OK_AND_ASSIGN(uint64_t final_miss_count, + GetAlpmMissStat(*sut_gnmi_stub_)); + LOG(INFO) << "Final miss count: " << final_miss_count; + + // There is a possibility that a few non-test packets flowed on the links + // while test was running and may have miss the L3 route, so give + // margin on l3 miss counter. + EXPECT_GE(final_miss_count, initial_miss_count + kPacketsToSend); + EXPECT_LE(final_miss_count, + initial_miss_count + kPacketsToSend + kNumberOfPacketsMargin); + + ASSERT_OK( + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); +} + +// Tests that when IPv6 L3 route is added and Ipv6 test packets hit the added +// route, ALPM miss counter doesn't go up. +TEST_P(AlpmMissCountersTest, Ipv6AlpmRouteHit) { + ASSERT_NO_FATAL_FAILURE( + InitializeTestEnvironment("552ebd5d-fa98-4298-b4ef-efab286bcc89")); + + ASSERT_OK_AND_ASSIGN(bool is_sut_Experimental, + IsPlatformTypeExperimental(*sut_gnmi_stub_)); + if (!is_sut_Experimental) { + GTEST_SKIP() << "Test is not supported on non-Experimental SUT."; + } + if (!generic_testbed_->ControlDevice().SupportsSendPacket()) { + GTEST_SKIP() << "Control device does not support SendPacket"; + } + // Test requires at least 1 SUT interface. + if (sut_interfaces_.empty()) { + GTEST_SKIP() << "Need at least 1 SUT interface to test but got: " + << sut_interfaces_.size(); + } + + thinkit::Switch& sut = generic_testbed_->Sut(); + thinkit::ControlDevice& control_device = generic_testbed_->ControlDevice(); + ASSERT_OK( + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); + + std::shuffle(sut_interfaces_.begin(), sut_interfaces_.end(), absl::BitGen()); + std::string test_sut_interface = sut_interfaces_[0]; + ASSERT_OK_AND_ASSIGN(std::string sut_port_id, + GetPortId(*sut_gnmi_stub_, test_sut_interface)); + CreateAlpmRouteParams(route_params_, sut_port_id); + + ASSERT_OK_AND_ASSIGN(std::unique_ptr p4_session, + pins_test::ConfigureSwitchAndReturnP4RuntimeSession( + sut, + /*gnmi_config=*/std::nullopt, GetParam().p4_info)); + + ASSERT_OK_AND_ASSIGN(sai::TableEntries sut_test_entries, + ConstructTestEntries(route_params_, IpVersion::kIpv6)); + ASSERT_OK(pdpi::ClearTableEntries(p4_session.get())); + + LOG(INFO) << "Installing entries:" << sut_test_entries.ShortDebugString(); + ASSERT_OK(pdpi::InstallPdTableEntries(*p4_session, sut_test_entries)); + + ASSERT_OK_AND_ASSIGN(uint64_t initial_miss_count, + GetAlpmMissStat(*sut_gnmi_stub_)); + LOG(INFO) << "Initial miss count: " << initial_miss_count; + + LOG(INFO) << "Sending test packets on port " << test_sut_interface; + ASSERT_NO_FATAL_FAILURE(SendPackets( + *sut_gnmi_stub_, control_device, test_sut_interface, + sut_to_peer_interface_mapping_[test_sut_interface], IpVersion::kIpv6)); + + ASSERT_OK_AND_ASSIGN(uint64_t final_miss_count, + GetAlpmMissStat(*sut_gnmi_stub_)); + LOG(INFO) << "Final miss count: " << final_miss_count; + + // There is a possibility that a few non-test packets flowed on the links + // while test was running and may have miss the L3 route, so give margin on + // l3 miss counter. + EXPECT_GE(final_miss_count, initial_miss_count); + EXPECT_LE(final_miss_count, initial_miss_count + kNumberOfPacketsMargin); + + ASSERT_OK( + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); +} + +// Tests that when IPv6 L3 route is added and Ipv6 test packets miss the added +// route, ALPM miss counter goes up. +TEST_P(AlpmMissCountersTest, Ipv6AlpmRouteMiss) { + ASSERT_NO_FATAL_FAILURE( + InitializeTestEnvironment("b8c1ba7f-a8ca-429b-bb8b-9fc479fc7e71")); + + ASSERT_OK_AND_ASSIGN(bool is_sut_Experimental, + IsPlatformTypeExperimental(*sut_gnmi_stub_)); + if (!is_sut_Experimental) { + GTEST_SKIP() << "Test is not supported on non-Experimental SUT."; + } + if (!generic_testbed_->ControlDevice().SupportsSendPacket()) { + GTEST_SKIP() << "Control device does not support SendPacket"; + } + // Test requires at least 1 SUT interface. + if (sut_interfaces_.empty()) { + GTEST_SKIP() << "Need at least 1 SUT interface to test but got: " + << sut_interfaces_.size(); + } + + thinkit::Switch& sut = generic_testbed_->Sut(); + thinkit::ControlDevice& control_device = generic_testbed_->ControlDevice(); + ASSERT_OK( + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); + + std::shuffle(sut_interfaces_.begin(), sut_interfaces_.end(), absl::BitGen()); + std::string test_sut_interface = sut_interfaces_[0]; + ASSERT_OK_AND_ASSIGN(std::string sut_port_id, + GetPortId(*sut_gnmi_stub_, test_sut_interface)); + CreateAlpmRouteParams(route_params_, sut_port_id); + + ASSERT_OK_AND_ASSIGN(std::unique_ptr p4_session, + pins_test::ConfigureSwitchAndReturnP4RuntimeSession( + sut, + /*gnmi_config=*/std::nullopt, GetParam().p4_info)); + + ASSERT_OK_AND_ASSIGN(sai::TableEntries sut_test_entries, + ConstructTestEntries(route_params_, IpVersion::kIpv6)); + ASSERT_OK(pdpi::ClearTableEntries(p4_session.get())); + + LOG(INFO) << "Installing entries:" << sut_test_entries.ShortDebugString(); + ASSERT_OK(pdpi::InstallPdTableEntries(*p4_session, sut_test_entries)); + + ASSERT_OK_AND_ASSIGN(uint64_t initial_miss_count, + GetAlpmMissStat(*sut_gnmi_stub_)); + LOG(INFO) << "Initial miss count: " << initial_miss_count; + + LOG(INFO) << "Sending test packets on port " << test_sut_interface; + ASSERT_NO_FATAL_FAILURE( + SendPackets(*sut_gnmi_stub_, control_device, test_sut_interface, + sut_to_peer_interface_mapping_[test_sut_interface], + IpVersion::kIpv6, /*l3_miss=*/true)); + + ASSERT_OK_AND_ASSIGN(uint64_t final_miss_count, + GetAlpmMissStat(*sut_gnmi_stub_)); + LOG(INFO) << "Final miss count: " << final_miss_count; + + // There is a possibility that a few non-test packets flowed on the links + // while test was running and may have miss the L3 route, so give margin on + // l3 miss counter. + EXPECT_GE(final_miss_count, initial_miss_count + kPacketsToSend); + EXPECT_LE(final_miss_count, + initial_miss_count + kPacketsToSend + kNumberOfPacketsMargin); + + ASSERT_OK( + ValidatePortsUp(sut, control_device, sut_interfaces_, peer_interfaces_)); +} + +} // namespace pins_test diff --git a/tests/integration/system/alpm_miss_counter_tests.h b/tests/integration/system/alpm_miss_counter_tests.h new file mode 100644 index 00000000..1cfa45de --- /dev/null +++ b/tests/integration/system/alpm_miss_counter_tests.h @@ -0,0 +1,89 @@ +// Copyright 2024 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. + +#ifndef PINS_TESTS_INTEGRATION_SYSTEM_ALPM_MISS_COUNTER_TESTS_H_ +#define PINS_TESTS_INTEGRATION_SYSTEM_ALPM_MISS_COUNTER_TESTS_H_ + +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gutil/status_matchers.h" +#include "gutil/testing.h" +#include "p4_pdpi/p4_runtime_session.h" +#include "proto/gnmi/gnmi.grpc.pb.h" +#include "thinkit/generic_testbed.h" +#include "thinkit/generic_testbed_fixture.h" +#include "thinkit/switch.h" + +namespace pins_test { + +struct AlpmRouteParams { + std::string neighbor_id; + std::string vrf; + std::string rif; + std::string nexthop; + std::string p4_port_id; +}; + +class AlpmMissCountersTest : public thinkit::GenericTestbedFixture<> { + public: + AlpmMissCountersTest() : generic_testbed_(nullptr), sut_gnmi_stub_(nullptr) {} + + void InitializeTestEnvironment(absl::string_view test_id) { + thinkit::TestRequirements requirements = + gutil::ParseProtoOrDie( + R"pb(interface_requirements { + count: 1 + interface_mode: CONTROL_INTERFACE + })pb"); + + ASSERT_OK_AND_ASSIGN(generic_testbed_, + GetTestbedWithRequirements(requirements)); + generic_testbed_->Environment().SetTestCaseID(test_id); + + absl::flat_hash_map interface_info = + generic_testbed_->GetSutInterfaceInfo(); + + ASSERT_OK_AND_ASSIGN(sut_gnmi_stub_, + generic_testbed_->Sut().CreateGnmiStub()); + + for (const auto &[interface, info] : interface_info) { + if (info.interface_modes.contains(thinkit::CONTROL_INTERFACE)) { + sut_interfaces_.push_back(interface); + peer_interfaces_.push_back(info.peer_interface_name); + sut_to_peer_interface_mapping_[interface] = info.peer_interface_name; + } + } + } + + protected: + std::unique_ptr generic_testbed_; + std::unique_ptr sut_gnmi_stub_; + // List of SUT interfaces. + std::vector sut_interfaces_; + // List of control switch interfaces. + std::vector peer_interfaces_; + // A mapping of SUT interface and its peer interface on control switch. + absl::flat_hash_map sut_to_peer_interface_mapping_; + struct AlpmRouteParams route_params_; +}; + +} // namespace pins_test + +#endif // PINS_TESTS_INTEGRATION_SYSTEM_ALPM_MISS_COUNTER_TESTS_H_ diff --git a/tests/integration/system/control_device_reboot_test.cc b/tests/integration/system/control_device_reboot_test.cc new file mode 100644 index 00000000..b77e7c9d --- /dev/null +++ b/tests/integration/system/control_device_reboot_test.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2024, Google Inc. +// +// 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. + +#include "tests/integration/system/control_device_reboot_test.h" + +#include +#include // NOLINT +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "absl/strings/substitute.h" +#include "absl/time/time.h" +#include "glog/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gutil/status_matchers.h" +#include "gutil/testing.h" +#include "thinkit/control_device.h" +#include "thinkit/generic_testbed.h" +#include "thinkit/proto/generic_testbed.pb.h" + +namespace pins_test { +namespace { + +constexpr absl::Duration kTurnDownTimeout = absl::Minutes(2); +constexpr absl::Duration kTurnUpTimeout = absl::Minutes(20); + +enum class ControlDeviceState { kUp, kDown }; + +absl::Status WaitForControlDevice(thinkit::ControlDevice& control_device, + ControlDeviceState state, + absl::Duration timeout) { + std::string elapsed_time = ""; + std::string state_name = (state == ControlDeviceState::kUp) ? "kUp" : "kDown"; + absl::Status device_ready = absl::InternalError("Uninitialized"); + + absl::Time start_time = absl::Now(); + while (absl::Now() - start_time < timeout) { + device_ready = control_device.CheckUp(); + + // For kDown state, control device is ready when it is no longer reachable. + if (state == ControlDeviceState::kDown) { + device_ready = + device_ready.ok() + ? absl::InternalError( + "Control device is reachable. Expected: unreachable.") + : absl::OkStatus(); + } + + if (device_ready.ok()) { + elapsed_time = absl::FormatDuration(absl::Now() - start_time); + LOG(INFO) << absl::Substitute("Control device $0 state reached in $1.", + state_name, elapsed_time); + return absl::OkStatus(); + } + } + + return absl::DeadlineExceededError(absl::Substitute( + "Control device $0 state not reached after $1. Status: $2", state_name, + absl::FormatDuration(timeout), device_ready.message())); +} + +} // namespace + +// Reboots control device and validates that the link to the device comes up. +TEST_P(ControlDeviceRebootTestFixture, TestControlDeviceReboot) { + LOG(INFO) << "Get testbed with at least one thinkit::CONTROL_INTERFACE."; + thinkit::TestRequirements requirements = + gutil::ParseProtoOrDie( + R"pb(interface_requirements { + count: 1 + interface_mode: CONTROL_INTERFACE + })pb"); + ASSERT_OK_AND_ASSIGN(std::unique_ptr generic_testbed, + GetTestbedWithRequirements(requirements)); + + // Connect to TestTracker for test status + generic_testbed->Environment().SetTestCaseID( + "3b8dc5fd-d5f9-4e8d-856f-9495c802d39f"); + + LOG(INFO) << "Get all ports on control device connected to sut."; + absl::flat_hash_map interface_info = + generic_testbed->GetSutInterfaceInfo(); + + absl::flat_hash_map> peer_interfaces; + for (const auto& [interface, info] : interface_info) { + if (info.interface_modes.contains(thinkit::CONTROL_INTERFACE)) { + LOG(INFO) << absl::StrFormat( + "sut_interface: %s, peer_interface_name: %s, peer_device_index: %d", + interface, info.peer_interface_name, info.peer_device_index); + peer_interfaces[info.peer_device_index].push_back( + info.peer_interface_name); + } + } + + std::vector threads; + for (const auto& [index, interfaces] : peer_interfaces) { + std::thread reboot_thread = std::thread([&, &index = index, + &interfaces = interfaces]() { + LOG(INFO) << absl::StrFormat( + "(control device index: %d) Check that all ports are up", index); + ASSERT_OK( + generic_testbed->ControlDevice(index).ValidatePortsUp(interfaces)); + + LOG(INFO) << absl::StrFormat( + "(control device index: %d): Rebooting control device", index); + ASSERT_OK(generic_testbed->ControlDevice(index).Reboot( + thinkit::RebootType::kCold)); + + ASSERT_OK(WaitForControlDevice(generic_testbed->ControlDevice(index), + ControlDeviceState::kDown, + kTurnDownTimeout)); + + ASSERT_OK(WaitForControlDevice(generic_testbed->ControlDevice(index), + ControlDeviceState::kUp, kTurnUpTimeout)); + + LOG(INFO) << absl::StrFormat( + "(control device index: %d): Check that all ports are up after " + "reboot", + index); + ASSERT_OK( + generic_testbed->ControlDevice(index).ValidatePortsUp(interfaces)); + LOG(INFO) << absl::StrFormat( + "(control device index: %d): All ports are up after reboot", index); + }); + threads.push_back(std::move(reboot_thread)); + } + + for (auto& t : threads) { + t.join(); + } +} + +} // namespace pins_test diff --git a/tests/integration/system/control_device_reboot_test.h b/tests/integration/system/control_device_reboot_test.h new file mode 100644 index 00000000..8317326c --- /dev/null +++ b/tests/integration/system/control_device_reboot_test.h @@ -0,0 +1,31 @@ +// Copyright (c) 2024, Google Inc. +// +// 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. + +#ifndef PINS_TESTS_INTEGRATION_SYSTEM_CONTROL_DEVICE_REBOOT_TEST_H_ +#define PINS_TESTS_INTEGRATION_SYSTEM_CONTROL_DEVICE_REBOOT_TEST_H_ + +#include "thinkit/generic_testbed_fixture.h" + +namespace pins_test { + +class ControlDeviceRebootTestFixture : public thinkit::GenericTestbedFixture<> { + protected: + ControlDeviceRebootTestFixture() { + GetParam().testbed_interface->ExpectLinkFlaps(); + } +}; + +} // namespace pins_test + +#endif // PINS_TESTS_INTEGRATION_SYSTEM_CONTROL_DEVICE_REBOOT_TEST_H_ diff --git a/tests/integration/system/gnmi_subscription_request.textproto b/tests/integration/system/gnmi_subscription_request.textproto index ec140ea1..b44d67bf 100644 --- a/tests/integration/system/gnmi_subscription_request.textproto +++ b/tests/integration/system/gnmi_subscription_request.textproto @@ -8,697 +8,226 @@ subscribe { } subscription { path { - elem { - name: "interfaces" - } + elem { name: "interfaces" } elem { name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "admin-status" + key { key: "name" value: "*" } } + elem { name: "state" } + elem { name: "admin-status" } } mode: ON_CHANGE } subscription { path { - elem { - name: "interfaces" - } + elem { name: "interfaces" } elem { name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "ethernet" - } - elem { - name: "state" - } - elem { - name: "mac-address" + key { key: "name" value: "*" } } + elem { name: "ethernet" } + elem { name: "state" } + elem { name: "mac-address" } } mode: ON_CHANGE } subscription { path { - elem { - name: "interfaces" - } + elem { name: "interfaces" } elem { name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "hardware-port" + key { key: "name" value: "*" } } + elem { name: "state" } + elem { name: "hardware-port" } } mode: ON_CHANGE } subscription { path { - elem { - name: "interfaces" - } + elem { name: "interfaces" } elem { name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "health-indicator" + key { key: "name" value: "*" } } + elem { name: "state" } + elem { name: "health-indicator" } } mode: ON_CHANGE } subscription { path { - elem { - name: "interfaces" - } + elem { name: "interfaces" } elem { name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "id" + key { key: "name" value: "*" } } + elem { name: "state" } + elem { name: "id" } } mode: ON_CHANGE } subscription { path { - elem { - name: "interfaces" - } + elem { name: "interfaces" } elem { name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "oper-status" + key { key: "name" value: "*" } } + elem { name: "state" } + elem { name: "oper-status" } } mode: ON_CHANGE } subscription { path { - elem { - name: "interfaces" - } + elem { name: "interfaces" } elem { name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "ethernet" - } - elem { - name: "state" - } - elem { - name: "port-speed" + key { key: "name" value: "*" } } + elem { name: "ethernet" } + elem { name: "state" } + elem { name: "port-speed" } } mode: ON_CHANGE } subscription { path { - elem { - name: "components" - } + elem { name: "components" } elem { name: "component" - key { - key: "name" - value: "*" - } - } - elem { - name: "integrated-circuit" - } - elem { - name: "state" - } - elem { - name: "node-id" + key { key: "name" value: "*" } } + elem { name: "integrated-circuit" } + elem { name: "state" } + elem { name: "node-id" } } mode: ON_CHANGE } subscription { path { - elem { - name: "components" - } + elem { name: "components" } elem { name: "component" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "parent" + key { key: "name" value: "*" } } + elem { name: "state" } + elem { name: "parent" } } mode: ON_CHANGE } subscription { path { - elem { - name: "components" - } + elem { name: "components" } elem { name: "component" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "oper-status" + key { key: "name" value: "*" } } + elem { name: "state" } + elem { name: "oper-status" } } mode: ON_CHANGE } subscription { path { - elem { - name: "components" - } + elem { name: "components" } elem { name: "component" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "software-version" + key { key: "name" value: "*" } } + elem { name: "state" } + elem { name: "software-version" } } mode: ON_CHANGE } subscription { path { - elem { - name: "components" - } + elem { name: "components" } elem { name: "component" - key { - key: "name" - value: "*" - } - } - elem { - name: "software-module" - } - elem { - name: "state" - } - elem { - name: "module-type" + key { key: "name" value: "*" } } + elem { name: "software-module" } + elem { name: "state" } + elem { name: "module-type" } } mode: ON_CHANGE } subscription { path { - elem { - name: "system" - } - elem { - name: "alarms" - } + elem { name: "system" } + elem { name: "alarms" } elem { name: "alarm" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "severity" + key { key: "id" value: "*" } } + elem { name: "state" } + elem { name: "severity" } } mode: ON_CHANGE } subscription { path { - elem { - name: "interfaces" - } - elem { - name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "counters" - } - elem { - name: "in-octets" - } - } - } - subscription { - path { - elem { - name: "interfaces" - } - elem { - name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "counters" - } - elem { - name: "in-unicast-pkts" - } - } - } - subscription { - path { - elem { - name: "interfaces" - } - elem { - name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "counters" - } - elem { - name: "in-broadcast-pkts" - } - } - } - subscription { - path { - elem { - name: "interfaces" - } - elem { - name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "counters" - } - elem { - name: "in-multicast-pkts" - } - } - } - subscription { - path { - elem { - name: "interfaces" - } - elem { - name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "counters" - } - elem { - name: "in-discards" - } - } - } - subscription { - path { - elem { - name: "interfaces" - } - elem { - name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "counters" - } - elem { - name: "in-errors" - } - } - } - subscription { - path { - elem { - name: "interfaces" - } - elem { - name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "counters" - } - elem { - name: "in-fcs-errors" - } - } - } - subscription { - path { - elem { - name: "interfaces" - } - elem { - name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "counters" - } - elem { - name: "out-unicast-pkts" - } - } - } - subscription { - path { - elem { - name: "interfaces" - } - elem { - name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "counters" - } - elem { - name: "out-broadcast-pkts" - } - } - } - subscription { - path { - elem { - name: "interfaces" - } - elem { - name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "counters" - } - elem { - name: "out-multicast-pkts" - } - } - } - subscription { - path { - elem { - name: "interfaces" - } - elem { - name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "counters" - } - elem { - name: "out-octets" - } + elem { name: "system" } + elem { name: "state" } + elem { name: "config-meta-data" } } + mode: ON_CHANGE } subscription { path { - elem { - name: "interfaces" - } + elem { name: "interfaces" } elem { name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "counters" - } - elem { - name: "out-discards" + key { key: "name" value: "*" } } + elem { name: "ethernet" } + elem { name: "state" } + elem { name: "aggregate-id" } } + mode: SAMPLE + sample_interval: 1000000000 } subscription { path { - elem { - name: "interfaces" - } + elem { name: "interfaces" } elem { name: "interface" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "counters" - } - elem { - name: "out-errors" - } - } - } - subscription { - path { - elem { - name: "qos" - } - elem { - name: "interfaces" - } - elem { - name: "interface" - key { - key: "interface-id" - value: "*" - } - } - elem { - name: "output" - } - elem { - name: "queues" - } - elem { - name: "queue" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "transmit-pkts" + key { key: "name" value: "*" } } + elem { name: "aggregation" } + elem { name: "state" } + elem { name: "lag-speed" } } + mode: ON_CHANGE } subscription { path { - elem { - name: "qos" - } - elem { - name: "interfaces" - } + elem { name: "interfaces" } elem { name: "interface" - key { - key: "interface-id" - value: "*" - } - } - elem { - name: "output" - } - elem { - name: "queues" - } - elem { - name: "queue" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "transmit-octets" + key { key: "name" value: "*" } } + elem { name: "state" } + elem { name: "counters" } } } subscription { path { - elem { - name: "qos" - } - elem { - name: "interfaces" - } + elem { name: "qos" } + elem { name: "interfaces" } elem { name: "interface" - key { - key: "interface-id" - value: "*" - } - } - elem { - name: "output" - } - elem { - name: "queues" + key { key: "interface-id" value: "*" } } + elem { name: "output" } + elem { name: "queues" } elem { name: "queue" - key { - key: "name" - value: "*" - } - } - elem { - name: "state" - } - elem { - name: "dropped-pkts" + key { key: "name" value: "*" } } + elem { name: "state" } } } encoding: PROTO diff --git a/tests/integration/system/linerate_traffic_test.cc b/tests/integration/system/linerate_traffic_test.cc index 2ee16c47..26fcb893 100644 --- a/tests/integration/system/linerate_traffic_test.cc +++ b/tests/integration/system/linerate_traffic_test.cc @@ -33,13 +33,18 @@ #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/status/status.h" +#include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" #include "absl/time/clock.h" #include "absl/time/time.h" +#include "artifacts/otg.grpc.pb.h" +#include "artifacts/otg.pb.h" +#include "glog/logging.h" #include "gmock/gmock.h" +#include "grpcpp/client_context.h" #include "gtest/gtest.h" #include "gutil/collections.h" #include "gutil/proto.h" @@ -47,8 +52,7 @@ #include "gutil/status_matchers.h" #include "gutil/testing.h" #include "lib/gnmi/gnmi_helper.h" -#include "lib/ixia_helper.h" -#include "lib/ixia_helper.pb.h" +#include "lib/utils/generic_testbed_utils.h" #include "lib/utils/json_utils.h" #include "lib/validator/validator_lib.h" #include "p4/v1/p4runtime.pb.h" @@ -69,9 +73,12 @@ #include "tests/qos/packet_in_receiver.h" #include "tests/qos/qos_test_util.h" #include "thinkit/generic_testbed.h" +#include "thinkit/proto/generic_testbed.pb.h" namespace pins_test { +using ::otg::Openapi; + // Installs the given table `entries` using the given P4Runtime session, // respecting dependencies between entries by sequencing them into batches // according to `p4info`. @@ -181,14 +188,17 @@ TEST_P(LineRateTrafficTest, PortToPortLineRateTrafficTest) { // Pick 2 SUT ports connected to the Ixia, one for ingress and one for egress. ASSERT_OK_AND_ASSIGN(auto gnmi_stub, testbed->Sut().CreateGnmiStub()); - ASSERT_OK_AND_ASSIGN(std::vector ready_links, - ixia::GetReadyIxiaLinks(*testbed, *gnmi_stub)); - ASSERT_GE(ready_links.size(), 2) + ASSERT_OK_AND_ASSIGN(std::vector up_links, + GetUpLinks(GetAllTrafficGeneratorLinks, *testbed)); + ASSERT_GE(up_links.size(), 2) << "Test requires at least 2 SUT ports connected to an Ixia"; - const std::string kIxiaSrcPort = ready_links[0].ixia_interface; - const std::string kIxiaDstPort = ready_links[1].ixia_interface; - const std::string kSutIngressPort = ready_links[0].sut_interface; - const std::string kSutEgressPort = ready_links[1].sut_interface; + const std::string kIxiaSrcPort = up_links[0].peer_interface; + const std::string kIxiaDstPort = up_links[1].peer_interface; + const std::string kSutIngressPort = up_links[0].sut_interface; + const std::string kSutEgressPort = up_links[1].sut_interface; + const std::string kIxiaSrcLoc = up_links[0].peer_traffic_location; + const std::string kIxiaDstLoc = up_links[1].peer_traffic_location; + Openapi::StubInterface *traffic_client = testbed->GetTrafficClient(); LOG(INFO) << absl::StrFormat( "Test packet route: [Ixia: %s] => [SUT: %s] -> [SUT: %s] => [Ixia: %s]", kIxiaSrcPort, kSutIngressPort, kSutEgressPort, kIxiaDstPort); @@ -226,14 +236,18 @@ TEST_P(LineRateTrafficTest, PortToPortLineRateTrafficTest) { ASSERT_OK_AND_ASSIGN( const int64_t kSutIngressPortSpeedInBitsPerSecond, GetPortSpeedInBitsPerSecond(kSutIngressPort, *gnmi_stub)); + ASSERT_OK_AND_ASSIGN( + const otg::Layer1::Speed::Enum kSutIngressPortSpeed, + GetLayer1SpeedFromBitsPerSecond(kSutIngressPortSpeedInBitsPerSecond)); - // Connect to Ixia and fix global parameters. - ASSERT_OK_AND_ASSIGN(const std::string kIxiaHandle, - ixia::ConnectToIxia(*testbed)); - ASSERT_OK_AND_ASSIGN(const std::string kIxiaSrcPortHandle, - ixia::IxiaVport(kIxiaHandle, kIxiaSrcPort, *testbed)); - ASSERT_OK_AND_ASSIGN(const std::string kIxiaDstPortHandle, - ixia::IxiaVport(kIxiaHandle, kIxiaDstPort, *testbed)); + // Calculate the duration to send the traffic along with the duration to wait. + const int64_t kTestTrafficInBits = + kTestFrameCount * kTestFrameSizeInBytes * 8; + const double kLineRate = + kPercentageLineRate / 100.0 * kSutIngressPortSpeedInBitsPerSecond; + const absl::Duration kTrafficDuration = + absl::Seconds(kTestTrafficInBits / kLineRate); + const absl::Duration kWaitDuration = kTrafficDuration + absl::Seconds(120); // Run the test for IPv4 and then IPv6. for (bool is_ipv4 : {true, false}) { @@ -248,56 +262,128 @@ TEST_P(LineRateTrafficTest, PortToPortLineRateTrafficTest) { LOG(INFO) << "Initial Counters for " << queue << ": " << counters; } - // Configure & start test packet flow. + // Configure & start test packet flow. + otg::SetConfigRequest set_config_req; + otg::SetConfigResponse set_config_res; + grpc::ClientContext set_config_ctx; + otg::Config *config = set_config_req.mutable_config(); const std::string kTrafficName = absl::StrCat((is_ipv4 ? "IPv4" : "IPv6"), " traffic at line rate"); SCOPED_TRACE(kTrafficName); - ASSERT_OK_AND_ASSIGN( - const std::string kIxiaTrafficHandle, - ixia::SetUpTrafficItem(kIxiaSrcPortHandle, kIxiaDstPortHandle, - kTrafficName, *testbed)); - auto delete_traffic_item = absl::Cleanup([&, kIxiaTrafficHandle] { - ASSERT_OK(ixia::DeleteTrafficItem(kIxiaTrafficHandle, *testbed)); - }); - auto traffic_parameters = ixia::TrafficParameters{ - .frame_count = kTestFrameCount, - .frame_size_in_bytes = kTestFrameSizeInBytes, - .traffic_speed = ixia::PercentOfMaxLineRate{kPercentageLineRate}, - }; + + // Add traffic ports + otg::Port *src_port = config->add_ports(); + otg::Port *dst_port = config->add_ports(); + src_port->set_name(kIxiaSrcPort); + dst_port->set_name(kIxiaDstPort); + src_port->set_location(kIxiaSrcLoc); + dst_port->set_location(kIxiaDstLoc); + + // Add ports to layer1 + otg::Layer1 *layer1 = config->add_layer1(); + layer1->set_name("ly"); + layer1->add_port_names(kIxiaSrcPort); + layer1->add_port_names(kIxiaDstPort); + layer1->set_speed(kSutIngressPortSpeed); + + // Create a flow + otg::Flow *flow = config->add_flows(); + flow->set_name(kTrafficName); + + // Set flow parameters + flow->mutable_tx_rx()->set_choice(otg::FlowTxRx::Choice::port); + flow->mutable_tx_rx()->mutable_port()->set_tx_name(kIxiaSrcPort); + flow->mutable_tx_rx()->mutable_port()->set_rx_name(kIxiaDstPort); + + flow->mutable_size()->set_choice(otg::FlowSize::Choice::fixed); + flow->mutable_size()->set_fixed(kTestFrameSizeInBytes); + + flow->mutable_rate()->set_choice(otg::FlowRate::Choice::percentage); + flow->mutable_rate()->set_percentage(kPercentageLineRate); + + flow->mutable_duration()->set_choice( + otg::FlowDuration::Choice::fixed_seconds); + flow->mutable_duration()->mutable_fixed_seconds()->set_seconds( + absl::ToDoubleSeconds(kTrafficDuration)); + + // Craft packet (order of FlowHeaders determine order of packet headers on + // wire). + otg::FlowHeader *eth_packet = flow->add_packet(); + eth_packet->set_choice(otg::FlowHeader::Choice::ethernet); + eth_packet->mutable_ethernet()->mutable_src()->set_choice( + otg::PatternFlowEthernetSrc::Choice::value); + eth_packet->mutable_ethernet()->mutable_dst()->set_choice( + otg::PatternFlowEthernetDst::Choice::value); + eth_packet->mutable_ethernet()->mutable_src()->set_value( + netaddr::MacAddress(2, 2, 2, 2, 2, 2).ToString()); + eth_packet->mutable_ethernet()->mutable_dst()->set_value( + netaddr::MacAddress(0, 1, 2, 3, 4, 5).ToString()); + if (is_ipv4) { - traffic_parameters.ip_parameters = ixia::Ipv4TrafficParameters{ - .src_ipv4 = netaddr::Ipv4Address(192, 168, 2, 1), - .dst_ipv4 = netaddr::Ipv4Address(172, 0, 0, 1), - }; + otg::FlowHeader *ipv4 = flow->add_packet(); + ipv4->set_choice(otg::FlowHeader::Choice::ipv4); + ipv4->mutable_ipv4()->mutable_src()->set_choice( + otg::PatternFlowIpv4Src::Choice::value); + ipv4->mutable_ipv4()->mutable_dst()->set_choice( + otg::PatternFlowIpv4Dst::Choice::value); + ipv4->mutable_ipv4()->mutable_src()->set_value( + netaddr::Ipv4Address(192, 168, 2, 1).ToString()); + ipv4->mutable_ipv4()->mutable_dst()->set_value( + netaddr::Ipv4Address(172, 0, 0, 1).ToString()); } else { - traffic_parameters.ip_parameters = ixia::Ipv6TrafficParameters{ - .src_ipv6 = netaddr::Ipv6Address(0x1000, 0, 0, 0, 0, 0, 0, 1), - .dst_ipv6 = netaddr::Ipv6Address(0x2000, 0, 0, 0, 0, 0, 0, 1), - }; + otg::FlowHeader *ipv6 = flow->add_packet(); + ipv6->set_choice(otg::FlowHeader::Choice::ipv6); + ipv6->mutable_ipv6()->mutable_src()->set_choice( + otg::PatternFlowIpv6Src::Choice::value); + ipv6->mutable_ipv6()->mutable_dst()->set_choice( + otg::PatternFlowIpv6Dst::Choice::value); + ipv6->mutable_ipv6()->mutable_src()->set_value( + netaddr::Ipv6Address(0x1000, 0, 0, 0, 0, 0, 0, 1).ToString()); + ipv6->mutable_ipv6()->mutable_dst()->set_value( + netaddr::Ipv6Address(0x2000, 0, 0, 0, 0, 0, 0, 1).ToString()); } + + // Set capture metrics + flow->mutable_metrics()->set_enable(true); + flow->mutable_metrics()->set_loss(false); + flow->mutable_metrics()->set_timestamps(true); + flow->mutable_metrics()->mutable_latency()->set_enable(true); + flow->mutable_metrics()->mutable_latency()->set_mode( + otg::FlowLatencyMetrics::Mode::cut_through); + + ASSERT_OK(traffic_client->SetConfig(&set_config_ctx, set_config_req, + &set_config_res)); + LOG(INFO) << "Starting " << kTrafficName; - ASSERT_OK(ixia::SetTrafficParameters(kIxiaTrafficHandle, traffic_parameters, - *testbed)); - // Occasionally the Ixia API cannot keep up and starting traffic fails, - // so we try up to 3 times. - ASSERT_OK(pins::TryUpToNTimes(3, /*delay=*/absl::Seconds(1), [&] { - return ixia::StartTraffic(kIxiaTrafficHandle, kIxiaHandle, *testbed); - })); - - // Calculate the duration taken to send the traffic in test parameters with - // a buffer. - const auto kTestTrafficInBits = kTestFrameCount * kTestFrameSizeInBytes * 8; - const auto kLineRate = - kPercentageLineRate * 0.01 * kSutIngressPortSpeedInBitsPerSecond; - const absl::Duration kTrafficDuration = - absl::Seconds(kTestTrafficInBits / kLineRate) + absl::Seconds(120); - LOG(INFO) << "Traffic started, waiting for " << kTrafficDuration + otg::SetControlStateRequest set_state_req; + otg::SetControlStateResponse set_state_res; + grpc::ClientContext set_state_ctx; + + set_state_req.mutable_control_state()->set_choice( + otg::ControlState::Choice::traffic); + set_state_req.mutable_control_state()->mutable_traffic()->set_choice( + otg::StateTraffic::Choice::flow_transmit); + set_state_req.mutable_control_state() + ->mutable_traffic() + ->mutable_flow_transmit() + ->set_state(otg::StateTrafficFlowTransmit::State::start); + ASSERT_OK(traffic_client->SetControlState(&set_state_ctx, set_state_req, + &set_state_res)); + + LOG(INFO) << "Traffic started, waiting for " << kWaitDuration << " to complete"; - absl::SleepFor(kTrafficDuration); + absl::SleepFor(kWaitDuration); - ASSERT_OK_AND_ASSIGN( - const ixia::TrafficItemStats kIxiaTrafficStats, - ixia::GetTrafficItemStats(kIxiaHandle, kTrafficName, *testbed)); + // Get traffic flow metrics. + otg::GetMetricsRequest get_metrics_req; + otg::GetMetricsResponse get_metrics_res; + grpc::ClientContext get_metrics_ctx; + get_metrics_req.mutable_metrics_request()->set_choice( + otg::MetricsRequest::Choice::flow); + get_metrics_req.mutable_metrics_request()->mutable_flow()->add_flow_names( + kTrafficName); + ASSERT_OK(traffic_client->GetMetrics(&get_metrics_ctx, get_metrics_req, + &get_metrics_res)); // Log counters for all queues on the egress port after. for (const std::string queue : @@ -307,11 +393,15 @@ TEST_P(LineRateTrafficTest, PortToPortLineRateTrafficTest) { GetGnmiQueueCounters(kSutEgressPort, queue, *gnmi_stub)); LOG(INFO) << "Counters for " << queue << ": " << counters; } - EXPECT_EQ(kIxiaTrafficStats.num_tx_frames(), - kIxiaTrafficStats.num_rx_frames()) + + ASSERT_EQ(get_metrics_res.metrics_response().flow_metrics_size(), 1); + + const otg::FlowMetric &traffic_stats = + get_metrics_res.metrics_response().flow_metrics(0); + + EXPECT_EQ(traffic_stats.frames_tx(), traffic_stats.frames_rx()) << "Tx and Rx frames are not the same. Packets lost: " - << kIxiaTrafficStats.num_tx_frames() - kIxiaTrafficStats.num_rx_frames() - << ". Stats: " << kIxiaTrafficStats.DebugString(); + << traffic_stats.frames_tx() - traffic_stats.frames_rx(); ASSERT_OK(SwitchReady(testbed->Sut())) << "Switch ready checks failed after traffic test"; diff --git a/tests/integration/system/packet_forwarding_tests.cc b/tests/integration/system/packet_forwarding_tests.cc index 0e9ab99e..ef00ada8 100644 --- a/tests/integration/system/packet_forwarding_tests.cc +++ b/tests/integration/system/packet_forwarding_tests.cc @@ -16,7 +16,6 @@ #include #include -#include #include #include "absl/container/flat_hash_map.h" @@ -33,6 +32,7 @@ #include "absl/synchronization/mutex.h" #include "absl/time/clock.h" #include "absl/time/time.h" +#include "absl/types/span.h" #include "glog/logging.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -55,6 +55,7 @@ #include "p4_pdpi/pd.h" #include "sai_p4/instantiations/google/instantiations.h" #include "sai_p4/instantiations/google/sai_p4info.h" +#include "proto/gnmi/gnmi.grpc.pb.h" #include "sai_p4/instantiations/google/sai_pd.pb.h" #include "tests/lib/switch_test_setup_helpers.h" #include "thinkit/control_device.h" @@ -108,24 +109,6 @@ absl::StatusOr> P4InfoPush( testbed.Sut(), /*gnmi_config=*/std::nullopt, p4info); } -// Sets up route from source port to destination port on sut. -absl::Status SetupRoute(pdpi::P4RuntimeSession& p4_session, int src_port_id, - int dst_port_id) { - RETURN_IF_ERROR(pins_test::basic_traffic::ProgramTrafficVrf(&p4_session, - sai::GetIrP4Info(sai::Instantiation::kMiddleblock))); - - RETURN_IF_ERROR( - basic_traffic::ProgramRouterInterface(&p4_session, src_port_id, - sai::GetIrP4Info(sai::Instantiation::kMiddleblock))); - - RETURN_IF_ERROR( - basic_traffic::ProgramRouterInterface(&p4_session, dst_port_id, - sai::GetIrP4Info(sai::Instantiation::kMiddleblock))); - - return basic_traffic::ProgramIPv4Route(&p4_session, dst_port_id, - sai::GetIrP4Info(sai::Instantiation::kMiddleblock)); -} - TEST_P(PacketForwardingTestFixture, PacketForwardingTest) { thinkit::TestRequirements requirements = gutil::ParseProtoOrDie( @@ -173,7 +156,8 @@ TEST_P(PacketForwardingTestFixture, PacketForwardingTest) { testbed->Sut(), sai::GetP4Info(sai::Instantiation::kMiddleblock))); // Set up a route between the source and destination interfaces. - ASSERT_OK(SetupRoute(*p4_session, source_port_id, destination_port_id)); + ASSERT_OK_AND_ASSIGN(auto port_id_from_sut_interface, + GetAllInterfaceNameToPortId(*stub)); // Make test packet based on destination port ID. const auto test_packet = @@ -199,7 +183,20 @@ TEST_P(PacketForwardingTestFixture, PacketForwardingTest) { << "failed to inject the packet."; LOG(INFO) << "SendPacket completed"; } - absl::SleepFor(absl::Seconds(30)); + ASSERT_OK(finalizer->HandlePacketsFor( + absl::Seconds(30), + [&](absl::string_view interface, absl::string_view packet) { + if (interface != destination_interface.peer_interface) { + return; + } + packetlib::Packet parsed_packet = packetlib::ParsePacket(packet); + if (!absl::StrContains(parsed_packet.payload(), + "Basic L3 test packet")) { + return; + } + absl::MutexLock lock(&mutex); + received_packets.push_back(std::string(packet)); + })); } EXPECT_EQ(received_packets.size(), kPacketsToSend); } diff --git a/tests/integration/system/random_blackbox_events_tests.cc b/tests/integration/system/random_blackbox_events_tests.cc index 2fbc5bab..79f508cf 100644 --- a/tests/integration/system/random_blackbox_events_tests.cc +++ b/tests/integration/system/random_blackbox_events_tests.cc @@ -15,17 +15,15 @@ #include "tests/integration/system/random_blackbox_events_tests.h" #include -#include #include #include +#include #include #include //NOLINT #include #include -#include "absl/algorithm/container.h" #include "absl/cleanup/cleanup.h" -#include "absl/container/flat_hash_map.h" #include "absl/random/random.h" #include "absl/status/statusor.h" #include "absl/time/time.h" @@ -47,6 +45,7 @@ #include "lib/p4rt/p4rt_port.h" #include "lib/utils/generic_testbed_utils.h" #include "lib/validator/validator_lib.h" +#include "p4/config/v1/p4info.pb.h" #include "p4/v1/p4runtime.grpc.pb.h" #include "p4/v1/p4runtime.pb.h" #include "p4_fuzzer/annotation_util.h" @@ -63,6 +62,7 @@ #include "sai_p4/fixed/roles.h" #include "sai_p4/instantiations/google/instantiations.h" #include "sai_p4/instantiations/google/sai_p4info.h" +#include "tests/lib/switch_test_setup_helpers.h" #include "thinkit/generic_testbed.h" #include "thinkit/proto/generic_testbed.pb.h" #include "thinkit/switch.h" @@ -136,7 +136,6 @@ TEST_P(RandomBlackboxEventsTest, ControlPlaneWithTrafficWithoutValidation) { pdpi::GetForwardingPipelineConfig(p4rt_session.get())); p4_info = std::move(*response.mutable_config()->mutable_p4info()); } - ASSERT_OK_AND_ASSIGN( p4_fuzzer::FuzzerConfig config, p4_fuzzer::FuzzerConfig::Create( @@ -149,9 +148,10 @@ TEST_P(RandomBlackboxEventsTest, ControlPlaneWithTrafficWithoutValidation) { .tables_for_which_to_not_exceed_resource_guarantees = {"vrf_table", "mirror_session_table"}, })); - ASSERT_OK_AND_ASSIGN(auto p4rt_session, - pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables( - testbed->Sut(), GetParam().p4_info)); + ASSERT_OK_AND_ASSIGN( + auto p4rt_session, + pins_test::ConfigureSwitchAndReturnP4RuntimeSession( + testbed->Sut(), /*gnmi_config=*/std::nullopt, std::move(p4_info))); { ScopedThread p4rt_fuzzer([&config, &p4rt_session](const bool& time_to_exit) { @@ -190,7 +190,7 @@ TEST_P(RandomBlackboxEventsTest, ControlPlaneWithTrafficWithoutValidation) { } }); - // Send traffic and report discrepencies. + // Send traffic and report discrepancies. const auto test_packet = gutil::ParseProtoOrDie(R"pb( headers { ethernet_header { diff --git a/tests/lib/BUILD.bazel b/tests/lib/BUILD.bazel index 543322d0..2aa8bb46 100644 --- a/tests/lib/BUILD.bazel +++ b/tests/lib/BUILD.bazel @@ -85,6 +85,7 @@ cc_library( deps = [ "//gutil:collections", "//gutil:proto", + "//gutil:status", "//lib/gnmi:gnmi_helper", "//lib/gnmi:openconfig_cc_proto", "//lib/validator:validator_lib", @@ -106,6 +107,7 @@ cc_library( "@com_google_absl//absl/time", "@com_google_absl//absl/types:optional", "@com_google_absl//absl/types:span", + "@com_google_googletest//:gtest", ], ) diff --git a/tests/lib/switch_test_setup_helpers.cc b/tests/lib/switch_test_setup_helpers.cc index fc116bc9..d8382fee 100644 --- a/tests/lib/switch_test_setup_helpers.cc +++ b/tests/lib/switch_test_setup_helpers.cc @@ -18,6 +18,7 @@ #include "absl/types/optional.h" #include "absl/types/span.h" #include "glog/logging.h" +#include "gtest/gtest.h" #include "gutil/collections.h" #include "gutil/proto.h" #include "gutil/status.h" @@ -36,13 +37,27 @@ namespace { constexpr absl::Duration kGnmiTimeoutDefault = absl::Minutes(3); constexpr char kPortNamedType[] = "port_id_t"; -absl::Status ClearTableEntries( +// Only clears table entries if a P4RT session can be established. +// +// P4RT requires a device ID to be pushed over gNMI which is not enforced by +// this helper function. Given that we can't know the switch's state in all +// cases where this will be called, we default to best effort for clearing the +// entries. +absl::Status TryClearingTableEntries( thinkit::Switch& thinkit_switch, const pdpi::P4RuntimeSessionOptionalArgs& metadata) { - ASSIGN_OR_RETURN(std::unique_ptr session, - pdpi::P4RuntimeSession::Create(thinkit_switch, metadata)); - RETURN_IF_ERROR(pdpi::ClearTableEntries(session.get())); - RETURN_IF_ERROR(session->Finish()); + absl::StatusOr> session = + pdpi::P4RuntimeSession::Create(thinkit_switch, metadata); + if (!session.ok()) { + LOG(WARNING) + << "P4RT session could not be established to clear tables. This is " + "expected if no gNMI config has been previously pushed: " + << session.status(); + return absl::OkStatus(); + } + + RETURN_IF_ERROR(pdpi::ClearTableEntries(session.value().get())); + RETURN_IF_ERROR(session.value()->Finish()); return absl::OkStatus(); } @@ -54,6 +69,21 @@ absl::Status PushGnmiAndWaitForConvergence(thinkit::Switch& thinkit_switch, gnmi_timeout); } +// Wrapper around `TestGnoiSystemColdReboot` that ensures we don't ignore fatal +// failures. +absl::Status Reboot(thinkit::Switch& thinkit_switch) { + if (::testing::Test::HasFatalFailure()) { + return gutil::UnknownErrorBuilder() + << "skipping switch reboot due to pre-existing, fatal test failure"; + } + TestGnoiSystemColdReboot(thinkit_switch); + if (::testing::Test::HasFatalFailure()) { + return gutil::UnknownErrorBuilder() + << "switch reboot failed with fatal error"; + } + return absl::OkStatus(); +} + absl::StatusOr> CreateP4RuntimeSessionAndOptionallyPushP4Info( thinkit::Switch& thinkit_switch, @@ -75,7 +105,7 @@ CreateP4RuntimeSessionAndOptionallyPushP4Info( "but I am asked to push a P4Info with the following diff:\n" << p4info_diff; RETURN_IF_ERROR(session->Finish()); - TestGnoiSystemColdReboot(thinkit_switch); + RETURN_IF_ERROR(Reboot(thinkit_switch)); // Reconnect after reboot. ASSIGN_OR_RETURN( session, pdpi::P4RuntimeSession::Create(thinkit_switch, metadata)); @@ -129,7 +159,7 @@ ConfigureSwitchAndReturnP4RuntimeSession( const pdpi::P4RuntimeSessionOptionalArgs& metadata) { // Since the gNMI Config push relies on tables being cleared, we construct a // P4RuntimeSession and clear the tables first. - RETURN_IF_ERROR(ClearTableEntries(thinkit_switch, metadata)); + RETURN_IF_ERROR(TryClearingTableEntries(thinkit_switch, metadata)); if (gnmi_config.has_value()) { RETURN_IF_ERROR( diff --git a/tests/qos/cpu_qos_test.cc b/tests/qos/cpu_qos_test.cc index 94823195..1356df5b 100644 --- a/tests/qos/cpu_qos_test.cc +++ b/tests/qos/cpu_qos_test.cc @@ -71,6 +71,7 @@ #include "tests/forwarding/util.h" #include "tests/lib/switch_test_setup_helpers.h" #include "tests/qos/gnmi_parsers.h" +#include "tests/qos/packet_in_receiver.h" #include "tests/qos/qos_test_util.h" #include "thinkit/control_device.h" #include "thinkit/generic_testbed.h" @@ -802,8 +803,9 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopbackIpGetsMappedToCorrectQueues) { *sut_gnmi_stub)); } while ( // It may take several seconds for the queue counters to update. - TotalPacketsForQueue(queue_counters_after_test_packet) == - TotalPacketsForQueue(queue_counters_before_test_packet) && + TotalPacketsForQueue(queue_counters_after_test_packet) < + TotalPacketsForQueue(queue_counters_before_test_packet) + + kPacketCount && absl::Now() - time_packet_sent < kMaxQueueCounterUpdateTime); // We terminate early if this fails, as that can cause this loop to get // out of sync when counters increment after a long delay, resulting in @@ -898,7 +900,7 @@ TEST_P(CpuQosTestWithIxia, TestCPUQueueAssignmentAndQueueRateLimit) { ASSERT_OK(generic_testbed->Environment().StoreTestArtifact( "gnmi_config.txt", GetParam().gnmi_config)); - ASSERT_GT(GetParam().control_plane_bandwidth_bps, 0); + ASSERT_GT(GetParam().control_plane_bandwidth_bytes_per_second, 0); thinkit::Switch &sut = generic_testbed->Sut(); @@ -932,6 +934,224 @@ TEST_P(CpuQosTestWithIxia, TestCPUQueueAssignmentAndQueueRateLimit) { absl::SleepFor(kPollInterval); } + + // Wait to let the links come up. Switch guarantees state paths to reflect + // in 10s. Lets wait for a bit more. + LOG(INFO) << "Sleeping " << kTimeToWaitForGnmiConfigToApply + << " to wait for config to be applied/links to come up."; + absl::SleepFor(kTimeToWaitForGnmiConfigToApply); + ASSERT_OK( + pins_test::WaitForGnmiPortIdConvergence(sut, GetParam().gnmi_config, + /*timeout=*/absl::Minutes(3))); + + ASSERT_OK_AND_ASSIGN(std::vector ready_links, + GetReadyIxiaLinks(*generic_testbed, *gnmi_stub)); + + // If links didnt come, lets try 100GB as some testbeds have 100GB + // IXIA connections. + const absl::flat_hash_map + interface_info = generic_testbed->GetSutInterfaceInfo(); + if (ready_links.empty()) { + for (const auto &[interface, info] : interface_info) { + if (info.interface_modes.contains(thinkit::TRAFFIC_GENERATOR)) { + ASSERT_OK(SetPortSpeedInBitsPerSecond( + "\"openconfig-if-ethernet:SPEED_100GB\"", interface, *gnmi_stub)); + } + } + // Wait to let the links come up. Switch guarantees state paths to reflect + // in 10s. Lets wait for a bit more. + LOG(INFO) << "Sleeping " << kTimeToWaitForGnmiConfigToApply + << " to wait for config to be applied/links to come up."; + absl::SleepFor(kTimeToWaitForGnmiConfigToApply); + + ASSERT_OK_AND_ASSIGN(ready_links, + GetReadyIxiaLinks(*generic_testbed, *gnmi_stub)); + } + + ASSERT_FALSE(ready_links.empty()) << "Ixia links are not ready"; + + std::string ixia_interface = ready_links[0].ixia_interface; + std::string sut_interface = ready_links[0].sut_interface; + + // Set up Ixia traffic. + // Send Ixia traffic. + // Stop Ixia traffic. + + ASSERT_OK_AND_ASSIGN(ixia::IxiaPortInfo ixia_port, + ixia::ExtractPortInfo(ixia_interface)); + + ASSERT_OK_AND_ASSIGN( + std::string topology_ref, + pins_test::ixia::IxiaConnect(ixia_port.hostname, *generic_testbed)); + + ASSERT_OK_AND_ASSIGN( + std::string vport_ref, + pins_test::ixia::IxiaVport(topology_ref, ixia_port.card, ixia_port.port, + *generic_testbed)); + + ASSERT_OK_AND_ASSIGN( + std::string traffic_ref, + pins_test::ixia::IxiaSession(vport_ref, *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetFrameRate(traffic_ref, kFramesPerSecond, + *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetFrameCount(traffic_ref, kTotalFrames, + *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetFrameSize(traffic_ref, kDefaultFrameSize, + *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetSrcMac(traffic_ref, source_mac.ToString(), + *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetDestMac(traffic_ref, dest_mac.ToString(), + *generic_testbed)); + + ASSERT_OK(pins_test::ixia::AppendIPv4(traffic_ref, *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetSrcIPv4(traffic_ref, source_ip.ToString(), + *generic_testbed)); + + ASSERT_OK(pins_test::ixia::SetDestIPv4(traffic_ref, dest_ip.ToString(), + *generic_testbed)); + + // Listen for punted packets from the SUT. + PacketReceiveInfo packet_receive_info; + + PacketInReceiver receiver(*sut_p4_session, [&packet_receive_info](auto) { + absl::MutexLock lock(&packet_receive_info.mutex); + if (packet_receive_info.num_packets_punted == 0) { + packet_receive_info.time_first_packet_punted = absl::Now(); + } + packet_receive_info.time_last_packet_punted = absl::Now(); + packet_receive_info.num_packets_punted++; + return; + }); + + // Get Queues. + ASSERT_OK_AND_ASSIGN(auto queues, + ExtractQueueInfoViaGnmiConfig(GetParam().gnmi_config)); + + for (auto &[queue_name, queue_info] : queues) { + // TODO : Need to fix supported CPU queues. Currently, punting + // to queue 0 is not supported by OA in SONiC. + if (generic_testbed->Environment().MaskKnownFailures() && + queue_info.p4_queue_name == "0x0") { + continue; + } + + LOG(INFO) << "\n\n\nTesting Queue : " << queue_info.gnmi_queue_name + << "\n===================\n\n\n"; + + // Set framesize based on supported control plane bandwidth. + int frame_size = GetParam().control_plane_bandwidth_bytes_per_second / + queue_info.rate_packets_per_second; + // Framesize lesser than 64 bytes is not a viable frame, hence we will + // skip end to end rate check. + if (frame_size < kMinFrameSize) { + LOG(INFO) + << "Skipping, as queue rate " << queue_info.rate_packets_per_second + << "(pps) is infeasible to test with control plane bandwidth of " + << GetParam().control_plane_bandwidth_bytes_per_second + << " bytes per second."; + continue; + } + + if (frame_size > kMaxFrameSize) { + frame_size = kMaxFrameSize; + } + + ASSERT_OK(pins_test::ixia::SetFrameSize(traffic_ref, frame_size, + *generic_testbed)); + + ASSERT_OK(SetUpPuntToCPU(dest_mac, source_ip, dest_ip, + queue_info.p4_queue_name, GetParam().p4info, + *sut_p4_session)); + ASSERT_OK_AND_ASSIGN( + QueueCounters initial_counters, + GetGnmiQueueCounters("CPU", queue_info.gnmi_queue_name, *gnmi_stub)); + + // Reset received packet count at tester. + { + absl::MutexLock lock(&packet_receive_info.mutex); + packet_receive_info.num_packets_punted = 0; + } + + ASSERT_OK(pins_test::ixia::StartTraffic(traffic_ref, topology_ref, + *generic_testbed)); + + // Wait for Traffic to be sent. + absl::SleepFor(kTrafficDuration); + + static constexpr absl::Duration kPollInterval = absl::Seconds(5); + static constexpr absl::Duration kTotalTime = absl::Seconds(30); + static const int kIterations = kTotalTime / kPollInterval; + + QueueCounters final_counters; + QueueCounters delta_counters; + // Check for counters every 5 seconds upto 30 seconds till they match. + for (int gnmi_counters_check = 0; gnmi_counters_check < kIterations; + gnmi_counters_check++) { + absl::SleepFor(kPollInterval); + ASSERT_OK_AND_ASSIGN( + final_counters, + GetGnmiQueueCounters("CPU", queue_info.gnmi_queue_name, *gnmi_stub)); + delta_counters = { + .num_packets_transmitted = final_counters.num_packets_transmitted - + initial_counters.num_packets_transmitted, + .num_packets_dropped = final_counters.num_packets_dropped - + initial_counters.num_packets_dropped, + }; + LOG(INFO) << "Tx = " << delta_counters.num_packets_transmitted + << " Drop = " << delta_counters.num_packets_dropped; + if (delta_counters.num_packets_transmitted + + delta_counters.num_packets_dropped == + kTotalFrames) { + break; + } + ASSERT_NE(gnmi_counters_check, kIterations - 1) + << "GNMI packet count " + << delta_counters.num_packets_transmitted + + delta_counters.num_packets_dropped + << " != Packets sent from Ixia " << kTotalFrames; + } + + { + absl::MutexLock lock(&packet_receive_info.mutex); + // Verify the received packets matches gNMI queue stats. + ASSERT_LE(packet_receive_info.num_packets_punted, + delta_counters.num_packets_transmitted); + ASSERT_GE(packet_receive_info.num_packets_punted, + delta_counters.num_packets_transmitted * + (1 - kTolerancePercent / 100)); + absl::Duration duration = packet_receive_info.time_last_packet_punted - + packet_receive_info.time_first_packet_punted; + + LOG(INFO) << "Packets received at Controller: " + << packet_receive_info.num_packets_punted; + LOG(INFO) << "Packet size: " << frame_size; + LOG(INFO) << "Timestamp of first received packet: " + << packet_receive_info.time_first_packet_punted; + LOG(INFO) << "Timestamp of last received packet: " + << packet_receive_info.time_last_packet_punted; + LOG(INFO) << "Duration of packets received: " << duration; + int rate_received = 0; + if (int64_t useconds = absl::ToInt64Microseconds(duration); + useconds != 0) { + rate_received = + packet_receive_info.num_packets_punted * 1000000 / useconds; + LOG(INFO) << "Rate of packets received (pps): " << rate_received; + } + EXPECT_LE(rate_received, queue_info.rate_packets_per_second * + (1 + kTolerancePercent / 100)); + EXPECT_GE(rate_received, queue_info.rate_packets_per_second * + (1 - kTolerancePercent / 100)); + } + } // for each queue. + + // Stop receiving at tester. + receiver.Destroy(); } TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { @@ -953,7 +1173,7 @@ TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { ASSERT_OK(generic_testbed->Environment().StoreTestArtifact( "gnmi_config.txt", GetParam().gnmi_config)); - ASSERT_GT(GetParam().control_plane_bandwidth_bps, 0); + ASSERT_GT(GetParam().control_plane_bandwidth_bytes_per_second, 0); thinkit::Switch &sut = generic_testbed->Sut(); @@ -1080,9 +1300,9 @@ TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { (kMaxFrameSize * queue_info.rate_packets_per_second) / 2; if (flow_rate_limit_in_bytes_per_second > - GetParam().control_plane_bandwidth_bps) { + GetParam().control_plane_bandwidth_bytes_per_second) { flow_rate_limit_in_bytes_per_second = - GetParam().control_plane_bandwidth_bps / 2; + GetParam().control_plane_bandwidth_bytes_per_second / 2; } // TODO : Need to fix supported CPU queues. Currently, punting @@ -1199,7 +1419,7 @@ TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { packet_receive_info.num_packets_punted * kMaxFrameSize; LOG(INFO) << "Num bytes received: " << num_bytes; rate_received_in_bytes_per_second = num_bytes * 1000000 / useconds; - LOG(INFO) << "Rate of packets received (bps): " + LOG(INFO) << "Rate of packets received (bytes per second): " << rate_received_in_bytes_per_second; EXPECT_LE( rate_received_in_bytes_per_second, diff --git a/tests/qos/cpu_qos_test.h b/tests/qos/cpu_qos_test.h index 7faa7b71..431920fc 100644 --- a/tests/qos/cpu_qos_test.h +++ b/tests/qos/cpu_qos_test.h @@ -99,7 +99,7 @@ struct ParamsForTestsWithIxia { // This is be the minimum guaranteed bandwidth for control path to Tester in // the testbed. This is required to ensure the per queue rate limits to be // tested are within this guaranteed end to end bandwidth. - int control_plane_bandwidth_bps; + int control_plane_bandwidth_bytes_per_second; }; class CpuQosTestWithIxia diff --git a/tests/qos/frontpanel_qos_test.cc b/tests/qos/frontpanel_qos_test.cc index dfb39c8d..fb3774bb 100644 --- a/tests/qos/frontpanel_qos_test.cc +++ b/tests/qos/frontpanel_qos_test.cc @@ -627,6 +627,84 @@ TEST_P(FrontpanelQosTest, WeightedRoundRobinWeightsAreRespected) { "corrupted, causing subsequent test to fail"; }); + // Save Buffer config and restore at end of the test. + ASSERT_OK_AND_ASSIGN( + const std::string kSutEgressPortBufferProfile, + GetBufferAllocationProfileByEgressPort(kSutEgressPort, *gnmi_stub)); + // Before we update the buffer config, save the current config and + // prepare to restore it at the end of the test. + ASSERT_OK_AND_ASSIGN(const std::string kInitialBufferConfig, + GetBufferAllocationProfileConfig( + kSutEgressPortBufferProfile, *gnmi_stub)); + const auto kRestoreBufferConfig = absl::Cleanup([&] { + EXPECT_OK(UpdateBufferAllocationProfileConfig( + kSutEgressPortBufferProfile, kInitialBufferConfig, *gnmi_stub)) + << "failed to restore initial buffer config -- switch config may be " + "corrupted, causing subsequent tests to fail"; + }); + + // Set equal bufer for all queues. + absl::flat_hash_map bufferConfigByQueueName = { + {"LLQ1", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"LLQ2", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"BE1", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"AF1", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"AF2", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"AF3", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"AF4", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"NC1", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + }; + ASSERT_OK(SetBufferConfigParameters(kSutEgressPortBufferProfile, + bufferConfigByQueueName, *gnmi_stub)); + // Set lower & upper bounds (CIRs/PIRs) such that: // - Round-robin-scheduled queues are not rate limited. // - Auxilliary traffic to strictly prioritized queue uses at most 95% of @@ -779,7 +857,7 @@ TEST_P(FrontpanelQosTest, WeightedRoundRobinWeightsAreRespected) { const double kAbsoluteError = kActualFraction - kExpectedFraction; const double kRelativeErrorPercent = 100. * kAbsoluteError / kExpectedFraction; - const double kAcceptableErrorPercent = 3; + const double kAcceptableErrorPercent = 4; LOG(INFO) << "'" << queue << "' transmitted " << (kActualFraction * 100) << "% of forwarded round-robin traffic (expected: " << (kExpectedFraction * 100) @@ -928,6 +1006,84 @@ TEST_P(FrontpanelQosTest, StrictQueuesAreStrictlyPrioritized) { "corrupted, causing subsequent test to fail"; }); + // Save Buffer config and restore at end of the test. + ASSERT_OK_AND_ASSIGN( + const std::string kSutEgressPortBufferProfile, + GetBufferAllocationProfileByEgressPort(kSutEgressPort, *gnmi_stub)); + // Before we update the buffer config, save the current config and + // prepare to restore it at the end of the test. + ASSERT_OK_AND_ASSIGN(const std::string kInitialBufferConfig, + GetBufferAllocationProfileConfig( + kSutEgressPortBufferProfile, *gnmi_stub)); + const auto kRestoreBufferConfig = absl::Cleanup([&] { + EXPECT_OK(UpdateBufferAllocationProfileConfig( + kSutEgressPortBufferProfile, kInitialBufferConfig, *gnmi_stub)) + << "failed to restore initial buffer config -- switch config may be " + "corrupted, causing subsequent tests to fail"; + }); + + // Set equal buffer for all queues. + absl::flat_hash_map bufferConfigByQueueName = { + {"LLQ1", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"LLQ2", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"BE1", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"AF1", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"AF2", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"AF3", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"AF4", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + {"NC1", + {/*.dedicated_buffer =*/0, + /*.use_shared_buffer =*/true, + /*.shared_buffer_type =*/ + "openconfig-qos:DYNAMIC_BASED_ON_SCALING_FACTOR", + /*.dynamic_limit_scaling_factor =*/-3, + /*.shared_static_limit =*/0}}, + }; + ASSERT_OK(SetBufferConfigParameters(kSutEgressPortBufferProfile, + bufferConfigByQueueName, *gnmi_stub)); + // Connect to Ixia and fix constant traffic parameters. LOG(INFO) << "connecting to Ixia"; ASSERT_OK_AND_ASSIGN(const std::string kIxiaHandle, ConnectToIxia(*testbed)); diff --git a/tests/qos/qos_test_util.cc b/tests/qos/qos_test_util.cc index fd868ec3..4e7fbc33 100644 --- a/tests/qos/qos_test_util.cc +++ b/tests/qos/qos_test_util.cc @@ -712,7 +712,6 @@ absl::Status SetBufferConfigParameters( // << config_state_diff; // } return absl::OkStatus(); - } } // namespace pins_test diff --git a/tests/qos/qos_test_util.h b/tests/qos/qos_test_util.h index 30795d5c..2227d680 100644 --- a/tests/qos/qos_test_util.h +++ b/tests/qos/qos_test_util.h @@ -206,8 +206,9 @@ absl::StatusOr> GetStrictlyPrioritizedQueuesInDescendingOrderOfPriority( absl::string_view scheduler_policy_name, gnmi::gNMI::StubInterface &gnmi); // Get queues for an egress port. -absl::StatusOr> GetQueuesByEgressPort( - absl::string_view egress_port, gnmi::gNMI::StubInterface &gnmi); +absl::StatusOr> +GetQueuesByEgressPort(absl::string_view egress_port, + gnmi::gNMI::StubInterface &gnmi); // Reads the name of the buffer allocation profile applied // to the given egress port from the appropriate gNMI state path. diff --git a/tests/sflow/sflow_breakout_test.cc b/tests/sflow/sflow_breakout_test.cc index a5c44173..042c319c 100644 --- a/tests/sflow/sflow_breakout_test.cc +++ b/tests/sflow/sflow_breakout_test.cc @@ -158,16 +158,23 @@ absl::StatusOr TestBreakoutWithSflowConfig( } else { ASSIGN_OR_RETURN(auto port_id_per_port_name, GetAllUpInterfacePortIdsByName(*sut_gnmi_stub)); - std::vector up_ports; + std::vector up_copper_ports; for (const auto& [interface, unused] : port_id_per_port_name) { - up_ports.push_back(interface); + // Run breakout tests only on copper ports to avoid breakout compatibility + // complexity for non-copper ports. + if (auto is_copper = IsCopperPort(sut_gnmi_stub.get(), interface); + is_copper.ok() && *is_copper) { + up_copper_ports.push_back(interface); + } } + RET_CHECK(!up_copper_ports.empty()) << "No UP copper ports found."; // Get a random port from list of front panel UP ports that supports at // least one breakout mode of required type other than its current mode. - ASSIGN_OR_RETURN(selected_port_info, - GetRandomPortWithSupportedBreakoutModes( - *sut_gnmi_stub, platform_json_contents, - BreakoutType::kAny, BreakoutType::kAny, up_ports)); + ASSIGN_OR_RETURN( + selected_port_info, + GetRandomPortWithSupportedBreakoutModes( + *sut_gnmi_stub, platform_json_contents, BreakoutType::kAny, + BreakoutType::kAny, up_copper_ports)); } LOG(INFO) << "Using port " << selected_port_info.port_name << " with current breakout mode " diff --git a/tests/sflow/sflow_test.cc b/tests/sflow/sflow_test.cc index 44d594e8..4b445315 100644 --- a/tests/sflow/sflow_test.cc +++ b/tests/sflow/sflow_test.cc @@ -89,6 +89,7 @@ namespace pins { namespace { using ::gutil::IsOkAndHolds; +using ::testing::HasSubstr; using ::testing::UnorderedElementsAreArray; // Number of packets sent to one port. @@ -828,6 +829,12 @@ absl::StatusOr GetHeaderLenFromSflowOutput( ExtractValueByKey(sflow_datagram, "headerLen")); int header_len; (void)absl::SimpleAtoi(header_len_str, &header_len); + + // `decodedVLAN` field should not exist since vlan tag is stripped. + EXPECT_THAT(ExtractValueByKey(sflow_datagram, "decodedVLAN"), + gutil::StatusIs(absl::StatusCode::kNotFound, + HasSubstr("Cannot find decodedVLAN"))); + return header_len; } @@ -864,18 +871,19 @@ void VerifySflowResult(absl::string_view sflowtool_output, continue; } // Filter dst ip. - auto same_dst_ip = - IsSameIpAddressStr(fields[kDstIpIdx], std::string(dst_ip)); - EXPECT_OK(same_dst_ip.status()) - << same_dst_ip.status().message() << ". Expected dst ip: " << dst_ip - << ". Actual dst address: " << fields[kDstIpIdx]; - if (same_dst_ip.ok() && *same_dst_ip) { + if (auto same_dst_ip = + IsSameIpAddressStr(fields[kDstIpIdx], std::string(dst_ip)); + same_dst_ip.ok() && *same_dst_ip) { interesting_samples++; if (input_port.has_value()) { - EXPECT_EQ(fields[kInputPortIdx], absl::StrCat(*input_port)); + EXPECT_EQ(fields[kInputPortIdx], absl::StrCat(*input_port)) + << "Expected input port: " << absl::StrCat(*input_port) + << ". Actual input port: " << fields[kInputPortIdx]; } if (output_port.has_value()) { - EXPECT_EQ(fields[kOutputPortIdx], absl::StrCat(*output_port)); + EXPECT_EQ(fields[kOutputPortIdx], absl::StrCat(*output_port)) + << "Expected output port: " << absl::StrCat(*output_port) + << ". Actual output port: " << fields[kOutputPortIdx]; } // The sample mac address does not have the "0x" prefix EXPECT_EQ(fields[kSrcMacIdx], src_mac.substr(2)); @@ -889,7 +897,10 @@ void VerifySflowResult(absl::string_view sflowtool_output, // of sample's packet size field would be 1028. if (packet_size.has_value()) { EXPECT_EQ(fields[kPktSizeIdx], - absl::StrCat(std::min(*packet_size, 1028))); + absl::StrCat(std::min(*packet_size, 1028))) + << "Expected packet size: " + << absl::StrCat(std::min(*packet_size, 1028)) + << ". Actual packet size: " << fields[kPktSizeIdx]; } EXPECT_EQ(fields[kSamplingRateIdx], absl::StrCat(sampling_rate)); } @@ -1106,6 +1117,7 @@ absl::StatusOr GetPortIdFromInterfaceName( } // namespace void SflowTestFixture::SetUp() { + GetParam().testbed_interface->SetUp(); ASSERT_NE(ssh_client_, nullptr); // Pick a testbed with an Ixia Traffic Generator. auto requirements = @@ -2174,20 +2186,21 @@ bool HasSampleForProtocol(absl::string_view sflowtool_output, int ip_protocol) { } absl::Status SetupAndVerifySamplingEnabledOnUpPorts( - thinkit::MirrorTestbed& testbed, gnmi::gNMI::StubInterface* sut_gnmi_stub, + thinkit::MirrorTestbed& testbed, thinkit::Switch& sut, + gnmi::gNMI::StubInterface* sut_gnmi_stub, const std::string& sut_gnmi_config, const std::string& agent_address, const std::vector>& collector_address_and_port, - const absl::flat_hash_map& sflow_enabled_interfaces) { + const absl::flat_hash_map& sflow_enabled_interfaces, + const std::string& artifacts_prefix) { ASSIGN_OR_RETURN( auto sut_gnmi_config_with_sflow, UpdateSflowConfig(sut_gnmi_config, agent_address, collector_address_and_port, sflow_enabled_interfaces, kInbandSamplingRate, kSampleHeaderSize)); EXPECT_OK(testbed.Environment().StoreTestArtifact( - "sut_gnmi_config_with_sflow.txt", + absl::StrCat(artifacts_prefix, "_gnmi_config_with_sflow.txt"), json_yang::FormatJsonBestEffort(sut_gnmi_config_with_sflow))); - RETURN_IF_ERROR( - pins_test::PushGnmiConfig(testbed.Sut(), sut_gnmi_config_with_sflow)); + RETURN_IF_ERROR(pins_test::PushGnmiConfig(sut, sut_gnmi_config_with_sflow)); // Wait until all sFLow gNMI states are converged. return pins_test::WaitForCondition( VerifySflowStatesConverged, absl::Seconds(30), sut_gnmi_stub, @@ -2195,6 +2208,54 @@ absl::Status SetupAndVerifySamplingEnabledOnUpPorts( collector_address_and_port, sflow_enabled_interfaces); } +absl::Status SetUpMirrorTestbedWithSflowEnabledOnUpPorts( + thinkit::MirrorTestbed& testbed, gnmi::gNMI::StubInterface* sut_gnmi_stub, + gnmi::gNMI::StubInterface* control_gnmi_stub, + const std::string& sut_gnmi_config, const std::string& sut_agent_address, + const std::string& control_gnmi_config, + const std::string& control_agent_address) { + std::vector> collector_address_and_port{ + {kLocalLoopbackIpv6, 6343}}; + ASSIGN_OR_RETURN(auto port_id_per_port_name, + pins_test::GetAllUpInterfacePortIdsByName(*sut_gnmi_stub)); + if (port_id_per_port_name.empty()) { + return absl::FailedPreconditionError("No up interfaces."); + } + absl::flat_hash_map sflow_enabled_interfaces; + for (const auto& [port_name, port_id] : port_id_per_port_name) { + sflow_enabled_interfaces[port_name] = true; + EXPECT_OK(testbed.Environment().AppendToTestArtifact( + "sut_port_id_per_port_name_before_breakout.txt", + absl::Substitute("port_name=$0, port_id=$1\n", port_name, port_id))); + } + RETURN_IF_ERROR(SetupAndVerifySamplingEnabledOnUpPorts( + testbed, testbed.Sut(), sut_gnmi_stub, sut_gnmi_config, sut_agent_address, + collector_address_and_port, sflow_enabled_interfaces, + /*artifacts_prefix=*/"sut")); + + ASSIGN_OR_RETURN( + port_id_per_port_name, + pins_test::GetAllUpInterfacePortIdsByName(*control_gnmi_stub)); + if (port_id_per_port_name.empty()) { + return absl::FailedPreconditionError("No up interfaces."); + } + absl::flat_hash_map control_sflow_enabled_interfaces; + for (const auto& [port_name, port_id] : port_id_per_port_name) { + control_sflow_enabled_interfaces[port_name] = true; + EXPECT_OK(testbed.Environment().AppendToTestArtifact( + "control_port_id_per_port_name_before_breakout.txt", + absl::Substitute("port_name=$0, port_id=$1\n", port_name, port_id))); + } + + RETURN_IF_ERROR(SetupAndVerifySamplingEnabledOnUpPorts( + testbed, testbed.ControlSwitch(), control_gnmi_stub, control_gnmi_config, + control_agent_address, collector_address_and_port, + control_sflow_enabled_interfaces, + /*artifacts_prefix=*/"control")); + + return absl::OkStatus(); +} + } // namespace void SflowMirrorTestFixture::SetUp() { @@ -2207,19 +2268,29 @@ void SflowMirrorTestFixture::SetUp() { const std::string& sut_gnmi_config = GetParam().sut_gnmi_config; ASSERT_OK(testbed.Environment().StoreTestArtifact("sut_gnmi_config.txt", sut_gnmi_config)); - ASSERT_OK_AND_ASSIGN(sut_p4_session_, - pins_test::ConfigureSwitchAndReturnP4RuntimeSession( - testbed.Sut(), sut_gnmi_config, GetP4Info())); - ASSERT_OK_AND_ASSIGN(ir_p4_info_, pdpi::CreateIrP4Info(GetP4Info())); + // TODO: b/313925661 - push correct p4info during sflow mirror test setup + ASSERT_OK_AND_ASSIGN( + sut_p4_session_, + pins_test::ConfigureSwitchAndReturnP4RuntimeSession( + testbed.Sut(), sut_gnmi_config, /*p4info=*/std::nullopt)); + ASSERT_OK_AND_ASSIGN( + sut_p4_info_, + pdpi::GetOrSetP4Info(*sut_p4_session_, GetParam().sut_p4_info)); + ASSERT_OK_AND_ASSIGN(sut_ir_p4_info_, pdpi::GetIrP4Info(*sut_p4_session_)); // Push gNMI config to control switch. const std::string& control_gnmi_config = GetParam().control_gnmi_config; ASSERT_OK(testbed.Environment().StoreTestArtifact("control_gnmi_config.txt", control_gnmi_config)); + ASSERT_OK_AND_ASSIGN(control_p4_session_, + pins_test::ConfigureSwitchAndReturnP4RuntimeSession( + testbed.ControlSwitch(), control_gnmi_config, + /*p4info=*/std::nullopt)); ASSERT_OK_AND_ASSIGN( - control_p4_session_, - pins_test::ConfigureSwitchAndReturnP4RuntimeSession( - testbed.ControlSwitch(), control_gnmi_config, GetP4Info())); + control_p4_info_, + pdpi::GetOrSetP4Info(*control_p4_session_, GetParam().control_p4_info)); + ASSERT_OK_AND_ASSIGN(control_ir_p4_info_, + pdpi::GetIrP4Info(*control_p4_session_)); ASSERT_OK_AND_ASSIGN(control_gnmi_stub_, testbed.ControlSwitch().CreateGnmiStub()); @@ -2257,7 +2328,7 @@ void SflowMirrorTestFixture::TearDown() { /*artifact_name=*/"posttest_control_table_entries.txt")); } - // Restore sFlow config on SUT. + // Restore config on SUT and clear table entries. if (sut_gnmi_stub_ != nullptr) { ASSERT_OK_AND_ASSIGN(auto sflow_enabled, IsSflowConfigEnabled(GetParam().sut_gnmi_config)); @@ -2266,12 +2337,18 @@ void SflowMirrorTestFixture::TearDown() { ASSERT_OK_AND_ASSIGN( sut_p4_session_, pins_test::ConfigureSwitchAndReturnP4RuntimeSession( - testbed.Sut(), GetParam().sut_gnmi_config, GetP4Info())); - // Restores control switch config. + testbed.Sut(), GetParam().sut_gnmi_config, GetSutP4Info())); + + // Restores control switch config and clear table entries. + if (control_gnmi_stub_ != nullptr) { + ASSERT_OK_AND_ASSIGN(auto sflow_enabled, + IsSflowConfigEnabled(GetParam().control_gnmi_config)); + ASSERT_OK(SetSflowConfigEnabled(control_gnmi_stub_.get(), sflow_enabled)); + } ASSERT_OK_AND_ASSIGN(control_p4_session_, pins_test::ConfigureSwitchAndReturnP4RuntimeSession( testbed.ControlSwitch(), - GetParam().control_gnmi_config, GetP4Info())); + GetParam().control_gnmi_config, GetControlP4Info())); GetParam().testbed_interface->TearDown(); } @@ -2340,18 +2417,20 @@ TEST_P(SflowMirrorTestFixture, TestInbandPathToSflowCollector) { // the control switch loopback0 ipv6. // 3. Define the nexthop and dependent objects for the route entry action. const std::string vrf_id = "vrf-50"; - ASSERT_OK(SetSwitchVrfForAllPackets(*sut_p4_session_, GetIrP4Info(), vrf_id)); + ASSERT_OK( + SetSwitchVrfForAllPackets(*sut_p4_session_, GetSutIrP4Info(), vrf_id)); ASSERT_OK_AND_ASSIGN(std::string next_hop_id, - ProgramNextHops(*sut_p4_session_, GetIrP4Info(), + ProgramNextHops(*sut_p4_session_, GetSutIrP4Info(), absl::StrCat(inband_port.port_id))); - ASSERT_OK(ProgramRoutesForIpv6(*sut_p4_session_, GetIrP4Info(), vrf_id, + ASSERT_OK(ProgramRoutesForIpv6(*sut_p4_session_, GetSutIrP4Info(), vrf_id, collector_ipv6, next_hop_id)); // 1. Start sflowtool on control switch. // 2. Send traffic from control switch. // 3. Get sflowtool result and counters stats. std::string control_sflow_result, sut_sflow_result; + int control_switch_port_id; { ASSERT_OK_AND_ASSIGN( std::thread control_sflowtool_thread, @@ -2364,7 +2443,7 @@ TEST_P(SflowMirrorTestFixture, TestInbandPathToSflowCollector) { std::thread sut_sflowtool_thread, RunSflowCollectorForNSecs( *GetParam().ssh_client, testbed.Sut().ChassisName(), - kSflowtoolFullFormatTemplate, + kSflowtoolLineFormatTemplate, /*sflowtool_runtime=*/packets_num / kInbandTrafficPps + 30, sut_sflow_result)); @@ -2436,14 +2515,14 @@ TEST_P(SflowMirrorTestFixture, TestInbandPathToSflowCollector) { auto control_switch_port_id_per_port_name, pins_test::GetAllUpInterfacePortIdsByName(*control_gnmi_stub_)); ASSERT_OK_AND_ASSIGN( - auto control_switch_port_id, + control_switch_port_id, GetPortIdFromInterfaceName(control_switch_port_id_per_port_name, traffic_port.interface_name)); // Send packets from control switch. ASSERT_OK(SendNPacketsFromSwitch( packets_num, kInbandTrafficPps, control_switch_port_id, - traffic_port.interface_name, sut_gnmi_stub_.get(), GetIrP4Info(), + traffic_port.interface_name, sut_gnmi_stub_.get(), GetControlIrP4Info(), *control_p4_session_, testbed.Environment())); } @@ -2452,10 +2531,13 @@ TEST_P(SflowMirrorTestFixture, TestInbandPathToSflowCollector) { EXPECT_OK(testbed.Environment().StoreTestArtifact("sut_sflow_result.txt", sut_sflow_result)); - // TODO: TestInbandPathToSflowCollector should have a strict - // check on the sflow samples received on control switch. EXPECT_FALSE(control_sflow_result.empty()) << "No samples on " << traffic_port.interface_name; + const int control_sflow_samples = + GetSflowSamplesOnSut(control_sflow_result, control_switch_port_id); + const int sut_sflow_samples = + GetSflowSamplesOnSut(sut_sflow_result, control_switch_port_id); + EXPECT_EQ(control_sflow_samples, sut_sflow_samples); } // 1. Pick an interface and let control switch send some traffic via this @@ -2546,7 +2628,7 @@ TEST_P(SflowMirrorTestFixture, TestSflowDscpValue) { // Send packets from control switch. ASSERT_OK(SendNPacketsFromSwitch( packets_num, kInbandTrafficPps, control_switch_port_id, - traffic_port.interface_name, sut_gnmi_stub_.get(), GetIrP4Info(), + traffic_port.interface_name, sut_gnmi_stub_.get(), GetControlIrP4Info(), *control_p4_session_, testbed.Environment())); } EXPECT_OK(testbed.Environment().StoreTestArtifact("tcpdump_result.txt", @@ -2556,7 +2638,7 @@ TEST_P(SflowMirrorTestFixture, TestSflowDscpValue) { EXPECT_OK(testbed.Environment().StoreTestArtifact("sflow_result.txt", sflow_result)); - VerifySflowResult(sflow_result, control_switch_port_id, kDropPort, + VerifySflowResult(sflow_result, traffic_port.port_id, kDropPort, kSourceMac.ToHexString(), kDstMac.ToHexString(), kEtherTypeIpv4, kIpv4Src.ToString(), GetDstIpv4AddrByPortId(control_switch_port_id), @@ -2651,7 +2733,7 @@ TEST_P(SflowMirrorTestFixture, TestSamplingWorksOnAllInterfaces) { interface_name)); ASSERT_OK(SendNPacketsFromSwitch( packets_num, kInbandTrafficPps, control_switch_port_id, - interface_name, sut_gnmi_stub_.get(), GetIrP4Info(), + interface_name, sut_gnmi_stub_.get(), GetControlIrP4Info(), *control_p4_session_, testbed.Environment())); } } @@ -2775,7 +2857,7 @@ TEST_P(SflowRebootTestFixture, TestSamplingWorksAfterReboot) { interface_name)); ASSERT_OK(SendNPacketsFromSwitch( num_packets, kInbandTrafficPps, control_switch_port_id, - interface_name, sut_gnmi_stub_.get(), GetIrP4Info(), + interface_name, sut_gnmi_stub_.get(), GetControlIrP4Info(), *control_p4_session_, testbed.Environment())); } } @@ -2875,7 +2957,7 @@ TEST_P(SflowRebootTestFixture, TestSamplingWorksAfterReboot) { interface_name)); ASSERT_OK(SendNPacketsFromSwitch( num_packets, kInbandTrafficPps, control_switch_port_id, - interface_name, sut_gnmi_stub_.get(), GetIrP4Info(), + interface_name, sut_gnmi_stub_.get(), GetControlIrP4Info(), *control_p4_session_, testbed.Environment())); } } @@ -3007,7 +3089,7 @@ TEST_P(SflowMirrorTestFixture, TestIp2MePacketsAreSampledAndPunted) { }; ASSERT_OK(SendNSshPacketsFromSwitch( packets_num, traffic_speed, control_switch_port, kSrcIp6Address, - agent_address_, sut_gnmi_stub_.get(), GetIrP4Info(), + agent_address_, sut_gnmi_stub_.get(), GetControlIrP4Info(), *control_p4_session_, testbed.Environment())); } @@ -3023,7 +3105,7 @@ TEST_P(SflowMirrorTestFixture, TestIp2MePacketsAreSampledAndPunted) { << (absl::Now() - start_time); ShowCounters(delta); - VerifySflowResult(sflow_result, control_switch_port_id, kCpuPort, + VerifySflowResult(sflow_result, traffic_port.port_id, kCpuPort, kSourceMac.ToHexString(), kClusterMac.ToHexString(), kEtherTypeIpv6, kSrcIp6Address, agent_address_, /*packet_size=*/std::nullopt, kInbandSamplingRate); @@ -3037,7 +3119,10 @@ TEST_P(SflowMirrorTestFixture, TestIp2MePacketsAreSampledAndPunted) { } // TODO: Check sFlow sampling could still work after restart. -TEST_P(SflowMirrorTestFixture, TestHsflowdRestartSucceed) { +// TODO: b/305006464 - Hsflowd restart is raising minor alarms which is +// affecting the other tests in longevity workflow. Enable the test once the +// issue related to minor alarms is fixed. +TEST_P(SflowRebootTestFixture, TestHsflowdRestartSucceed) { thinkit::MirrorTestbed& testbed = GetParam().testbed_interface->GetMirrorTestbed(); auto& ssh_client = *GetParam().ssh_client; @@ -3086,25 +3171,22 @@ TEST_P(SflowMirrorTestFixture, TestHsflowdRestartSucceed) { // 3. Send packets from control switch via these new ports. // 4. Verify there are samples generated for each interface. TEST_P(SflowPortBreakoutTest, TestPortbreakoutWorks) { + GetParam().testbed_interface->ExpectLinkFlaps(); // Set collector address. thinkit::MirrorTestbed& testbed = GetParam().testbed_interface->GetMirrorTestbed(); - std::vector> collector_address_and_port{ - {kLocalLoopbackIpv6, 6343}}; ASSERT_OK_AND_ASSIGN( - auto port_id_per_port_name, - pins_test::GetAllUpInterfacePortIdsByName(*sut_gnmi_stub_)); - ASSERT_GT(port_id_per_port_name.size(), 0) << "No up interfaces."; - absl::flat_hash_map sflow_enabled_interfaces; - for (const auto& [port_name, port_id] : port_id_per_port_name) { - sflow_enabled_interfaces[port_name] = true; - EXPECT_OK(testbed.Environment().AppendToTestArtifact( - "sut_port_id_per_port_name_before_breakout.txt", - absl::Substitute("port_name=$0, port_id=$1\n", port_name, port_id))); - } - ASSERT_OK(SetupAndVerifySamplingEnabledOnUpPorts( - testbed, sut_gnmi_stub_.get(), GetParam().sut_gnmi_config, agent_address_, - collector_address_and_port, sflow_enabled_interfaces)); + auto control_loopback0_ipv6s, + pins_test::ParseLoopbackIpv6s(GetParam().control_gnmi_config)); + ASSERT_GT(control_loopback0_ipv6s.size(), 0) + << absl::Substitute("No loopback IP found for $0 testbed.", + testbed.ControlSwitch().ChassisName()); + // TODO: b/312207030 - breakout test does not need sFlow to be enabled on + // control switch. + ASSERT_OK(SetUpMirrorTestbedWithSflowEnabledOnUpPorts( + testbed, sut_gnmi_stub_.get(), control_gnmi_stub_.get(), + GetParam().sut_gnmi_config, agent_address_, + GetParam().control_gnmi_config, control_loopback0_ipv6s[0].ToString())); // Perform breakout on SUT. pins_test::SflowBreakoutTestOption sut_option{ @@ -3122,15 +3204,9 @@ TEST_P(SflowPortBreakoutTest, TestPortbreakoutWorks) { ASSERT_OK_AND_ASSIGN( const pins_test::SflowBreakoutResult& sut_breakout_result, pins_test::TestBreakoutWithSflowConfig( - testbed.Sut(), platform_json_contents, GetP4Info(), sut_option)); + testbed.Sut(), platform_json_contents, GetSutP4Info(), sut_option)); // Perform breakout on control switch with the same ports as SUT. - ASSERT_OK_AND_ASSIGN( - auto control_loopback0_ipv6s, - pins_test::ParseLoopbackIpv6s(GetParam().control_gnmi_config)); - ASSERT_GT(control_loopback0_ipv6s.size(), 0) - << absl::Substitute("No loopback IP found for $0 testbed.", - testbed.ControlSwitch().ChassisName()); pins_test::SflowBreakoutTestOption control_option{ .sampling_rate = kInbandSamplingRate, .sampling_header_size = kSampleHeaderSize, @@ -3148,7 +3224,7 @@ TEST_P(SflowPortBreakoutTest, TestPortbreakoutWorks) { ASSERT_OK_AND_ASSIGN( const pins_test::SflowBreakoutResult& control_breakout_result, TestBreakoutWithSflowConfig(testbed.ControlSwitch(), - platform_json_contents, GetP4Info(), + platform_json_contents, GetControlP4Info(), control_option)); ASSERT_THAT( sut_breakout_result.breakout_ports, @@ -3159,7 +3235,7 @@ TEST_P(SflowPortBreakoutTest, TestPortbreakoutWorks) { // Update port name per id mapping for later testing. ASSERT_OK_AND_ASSIGN( - port_id_per_port_name, + auto port_id_per_port_name, pins_test::GetAllUpInterfacePortIdsByName(*sut_gnmi_stub_)); ASSERT_GT(port_id_per_port_name.size(), 0) << "No up interfaces."; ASSERT_OK_AND_ASSIGN( @@ -3212,7 +3288,7 @@ TEST_P(SflowPortBreakoutTest, TestPortbreakoutWorks) { port_name)); ASSERT_OK(SendNPacketsFromSwitch( packets_num, kInbandTrafficPps, control_switch_port_id, port_name, - sut_gnmi_stub_.get(), GetIrP4Info(), *control_p4_session_, + sut_gnmi_stub_.get(), GetControlIrP4Info(), *control_p4_session_, testbed.Environment())); } } diff --git a/tests/sflow/sflow_test.h b/tests/sflow/sflow_test.h index a7182152..a94511aa 100644 --- a/tests/sflow/sflow_test.h +++ b/tests/sflow/sflow_test.h @@ -80,7 +80,8 @@ struct SflowMirrorTestParams { thinkit::SSHClient* ssh_client; std::string sut_gnmi_config; std::string control_gnmi_config; - p4::config::v1::P4Info p4_info; + p4::config::v1::P4Info sut_p4_info; + p4::config::v1::P4Info control_p4_info; // Used for port breakout test. std::string platform_json_path; @@ -97,10 +98,14 @@ class SflowMirrorTestFixture void SetUp() override; void TearDown() override; - const p4::config::v1::P4Info& GetP4Info() { return GetParam().p4_info; } - const pdpi::IrP4Info& GetIrP4Info() { return ir_p4_info_; } - pdpi::IrP4Info ir_p4_info_; + p4::config::v1::P4Info GetSutP4Info() { return sut_p4_info_; } + p4::config::v1::P4Info GetControlP4Info() { return control_p4_info_; } + pdpi::IrP4Info GetSutIrP4Info() { return sut_ir_p4_info_; } + pdpi::IrP4Info GetControlIrP4Info() { return control_ir_p4_info_; } + + p4::config::v1::P4Info sut_p4_info_, control_p4_info_; + pdpi::IrP4Info sut_ir_p4_info_, control_ir_p4_info_; std::unique_ptr sut_p4_session_; std::unique_ptr control_p4_session_; std::unique_ptr sut_gnmi_stub_; diff --git a/tests/thinkit_gnmi_interface_util.cc b/tests/thinkit_gnmi_interface_util.cc index 26b7cfc9..bd1df62c 100644 --- a/tests/thinkit_gnmi_interface_util.cc +++ b/tests/thinkit_gnmi_interface_util.cc @@ -17,6 +17,7 @@ #include "absl/strings/match.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "absl/strings/str_join.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" @@ -354,10 +355,10 @@ absl::StatusOr GetSlotPortLaneForPort( } auto slot_port_lane_str = port.substr(kEthernetLen); std::vector values = absl::StrSplit(slot_port_lane_str, '/'); - if (values.size() != 3) { - return absl::InvalidArgumentError( - absl::StrCat("Requested port (", port, - ") does not have a valid format (EthernetX/Y/Z)")); + if (values.size() < kSlotPortLaneMinValues) { + return absl::InvalidArgumentError(absl::StrCat( + "Requested port (", port, + ") does not have a valid format (EthernetX/Y/Z or EthernetX/Y)")); } SlotPortLane slot_port_lane; if (!absl::SimpleAtoi(values[kSlotIndex], &slot_port_lane.slot)) { @@ -370,10 +371,15 @@ absl::StatusOr GetSlotPortLaneForPort( << "Failed to convert string (" << values[kPortIndex] << ") to integer"; } - if (!absl::SimpleAtoi(values[kLaneIndex], &slot_port_lane.lane)) { - return gutil::InternalErrorBuilder().LogError() - << "Failed to convert string (" << values[kLaneIndex] - << ") to integer"; + // Unchannelizable ports will not contain a lane number. + // Use default lane number of 1. + slot_port_lane.lane = 1; + if (values.size() == kSlotPortLaneMaxValues) { + if (!absl::SimpleAtoi(values[kLaneIndex], &slot_port_lane.lane)) { + return gutil::InternalErrorBuilder().LogError() + << "Failed to convert string (" << values[kLaneIndex] + << ") to integer"; + } } return slot_port_lane; } @@ -507,7 +513,7 @@ absl::StatusOr IsCopperPort(gnmi::gNMI::StubInterface* sut_gnmi_stub, absl::string_view port) { // Get transceiver name for the port. auto state_path = - absl::StrCat("interfaces/interface[name=", port, "]/state/transceiver"); + absl::StrFormat("interfaces/interface[name=%s]/state/transceiver", port); auto resp_parse_str = "openconfig-platform-transceiver:transceiver"; ASSIGN_OR_RETURN( auto xcvrd_name, @@ -517,27 +523,21 @@ absl::StatusOr IsCopperPort(gnmi::gNMI::StubInterface* sut_gnmi_stub, << port); StripSymbolFromString(xcvrd_name, '\"'); - // TODO: Replace with PMD type when supported. - // Get cable length for the port transceiver. - state_path = - absl::StrCat("components/component[name=", xcvrd_name, - "]/transceiver/state/openconfig-platform-ext:cable-length"); - resp_parse_str = "openconfig-platform-ext:cable-length"; + state_path = absl::StrFormat( + "components/component[name=%s]/transceiver/state/ethernet-pmd", + xcvrd_name); + resp_parse_str = "openconfig-platform-transceiver:ethernet-pmd"; ASSIGN_OR_RETURN( - auto cable_length_str, + auto ethernet_pmd, GetGnmiStatePathInfo(sut_gnmi_stub, state_path, resp_parse_str), - _ << "Failed to get GNMI state path value for cable-length for " + _ << "Failed to get GNMI state path value for ethernet-pmd for " "port " << port); - StripSymbolFromString(cable_length_str, '\"'); + StripSymbolFromString(ethernet_pmd, '\"'); - // Only cable lengths of copper ports are a positive value. - float cable_length; - if (!absl::SimpleAtof(cable_length_str, &cable_length)) { - return gutil::InternalErrorBuilder().LogError() - << "Failed to convert string (" << cable_length_str << ") to float"; - } - return (cable_length > 0); + // PMD state path value for copper ports ends in CR2/CR4/CR8. + auto pos = ethernet_pmd.find_last_of('_'); + return (ethernet_pmd.substr(pos + 1, 2) == "CR"); } absl::StatusOr GenerateInterfaceBreakoutConfig( diff --git a/tests/thinkit_gnmi_interface_util.h b/tests/thinkit_gnmi_interface_util.h index 61a592d0..862f1719 100644 --- a/tests/thinkit_gnmi_interface_util.h +++ b/tests/thinkit_gnmi_interface_util.h @@ -38,6 +38,8 @@ inline constexpr char kEthernet[] = "Ethernet"; const int kSlotIndex = 0; const int kPortIndex = 1; const int kLaneIndex = 2; +const int kSlotPortLaneMinValues = 2; +const int kSlotPortLaneMaxValues = 3; // PortBreakoutInfo contains physical channels and operational status for an // interface. diff --git a/tests/thinkit_gnmi_interface_util_tests.cc b/tests/thinkit_gnmi_interface_util_tests.cc index bb9dd6a1..c666cb8a 100644 --- a/tests/thinkit_gnmi_interface_util_tests.cc +++ b/tests/thinkit_gnmi_interface_util_tests.cc @@ -26,11 +26,13 @@ namespace pins_test { using gutil::EqualsProto; +using gutil::IsOkAndHolds; using gutil::StatusIs; using ::nlohmann::json; using ::testing::_; using ::testing::ContainerEq; using ::testing::DoAll; +using ::testing::FieldsAre; using ::testing::HasSubstr; using ::testing::Return; using ::testing::ReturnRefOfCopy; @@ -70,7 +72,7 @@ constexpr char get_xcvrd_resp_str[] = } )pb"; -constexpr char cable_len_req_str[] = +constexpr char ethernet_pmd_req_str[] = R"pb(prefix { origin: "openconfig" } path { elem { name: "components" } @@ -80,11 +82,11 @@ constexpr char cable_len_req_str[] = } elem { name: "transceiver" } elem { name: "state" } - elem { name: "openconfig-platform-ext:cable-length" } + elem { name: "ethernet-pmd" } } type: STATE)pb"; -constexpr char cable_len_resp_copper_str[] = +constexpr char ethernet_pmd_resp_copper_str[] = R"pb(notification { timestamp: 1631864194292383538 prefix { origin: "openconfig" } @@ -97,16 +99,16 @@ constexpr char cable_len_resp_copper_str[] = } elem { name: "transceiver" } elem { name: "state" } - elem { name: "openconfig-platform-ext:cable-length" } + elem { name: "ethernet-pmd" } } val { - json_ietf_val: "{\"openconfig-platform-ext:cable-length\":\"10\"}" + json_ietf_val: "{\"openconfig-platform-transceiver:ethernet-pmd\":\"google-pins-transceivers:ETH_2X400GBASE_CR4\"}" } } } )pb"; -constexpr char cable_len_resp_optic_str[] = +constexpr char ethernet_pmd_resp_optic_str[] = R"pb(notification { timestamp: 1631864194292383538 prefix { origin: "openconfig" } @@ -119,10 +121,10 @@ constexpr char cable_len_resp_optic_str[] = } elem { name: "transceiver" } elem { name: "state" } - elem { name: "cable-length" } + elem { name: "ethernet-pmd" } } val { - json_ietf_val: "{\"openconfig-platform-ext:cable-length\":\"0\"}" + json_ietf_val: "{\"openconfig-platform-transceiver:ethernet-pmd\":\"google-pins-transceivers:ETH_2X400GBASE_CDGR4_PLUS\"}" } } } @@ -1476,15 +1478,15 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(get_xcvrd_req), _)) .WillOnce( DoAll(SetArgPointee<2>(get_xcvrd_resp), Return(grpc::Status::OK))); - gnmi::GetRequest cable_len_req; - ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(cable_len_req_str, - &cable_len_req)); - gnmi::GetResponse cable_len_resp; + gnmi::GetRequest ethernet_pmd_req; ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( - cable_len_resp_optic_str, &cable_len_resp)); - EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(cable_len_req), _)) + ethernet_pmd_req_str, ðernet_pmd_req)); + gnmi::GetResponse ethernet_pmd_resp; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + ethernet_pmd_resp_optic_str, ðernet_pmd_resp)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(ethernet_pmd_req), _)) .WillOnce( - DoAll(SetArgPointee<2>(cable_len_resp), Return(grpc::Status::OK))); + DoAll(SetArgPointee<2>(ethernet_pmd_resp), Return(grpc::Status::OK))); ASSERT_OK(pins_test::GetBreakoutModeConfigFromString( req, mock_gnmi_stub_ptr.get(), port_index, intf_name, breakout_mode)); EXPECT_THAT(req, EqualsProto(expected_breakout_config)); @@ -1517,15 +1519,15 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(get_xcvrd_req), _)) .WillOnce( DoAll(SetArgPointee<2>(get_xcvrd_resp), Return(grpc::Status::OK))); - gnmi::GetRequest cable_len_req; - ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(cable_len_req_str, - &cable_len_req)); - gnmi::GetResponse cable_len_resp; + gnmi::GetRequest ethernet_pmd_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + ethernet_pmd_req_str, ðernet_pmd_req)); + gnmi::GetResponse ethernet_pmd_resp; ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( - cable_len_resp_optic_str, &cable_len_resp)); - EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(cable_len_req), _)) + ethernet_pmd_resp_optic_str, ðernet_pmd_resp)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(ethernet_pmd_req), _)) .WillOnce( - DoAll(SetArgPointee<2>(cable_len_resp), Return(grpc::Status::OK))); + DoAll(SetArgPointee<2>(ethernet_pmd_resp), Return(grpc::Status::OK))); ASSERT_OK(pins_test::GetBreakoutModeConfigFromString( req, mock_gnmi_stub_ptr.get(), port_index, intf_name, breakout_mode)); EXPECT_THAT(req, EqualsProto(expected_breakout_config)); @@ -1558,15 +1560,15 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(get_xcvrd_req), _)) .WillOnce( DoAll(SetArgPointee<2>(get_xcvrd_resp), Return(grpc::Status::OK))); - gnmi::GetRequest cable_len_req; - ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(cable_len_req_str, - &cable_len_req)); - gnmi::GetResponse cable_len_resp; + gnmi::GetRequest ethernet_pmd_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + ethernet_pmd_req_str, ðernet_pmd_req)); + gnmi::GetResponse ethernet_pmd_resp; ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( - cable_len_resp_optic_str, &cable_len_resp)); - EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(cable_len_req), _)) + ethernet_pmd_resp_optic_str, ðernet_pmd_resp)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(ethernet_pmd_req), _)) .WillOnce( - DoAll(SetArgPointee<2>(cable_len_resp), Return(grpc::Status::OK))); + DoAll(SetArgPointee<2>(ethernet_pmd_resp), Return(grpc::Status::OK))); ASSERT_OK(pins_test::GetBreakoutModeConfigFromString( req, mock_gnmi_stub_ptr.get(), port_index, intf_name, breakout_mode)); EXPECT_THAT(req, EqualsProto(expected_breakout_config)); @@ -1599,15 +1601,15 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(get_xcvrd_req), _)) .WillOnce( DoAll(SetArgPointee<2>(get_xcvrd_resp), Return(grpc::Status::OK))); - gnmi::GetRequest cable_len_req; - ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(cable_len_req_str, - &cable_len_req)); - gnmi::GetResponse cable_len_resp; + gnmi::GetRequest ethernet_pmd_req; ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( - cable_len_resp_copper_str, &cable_len_resp)); - EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(cable_len_req), _)) + ethernet_pmd_req_str, ðernet_pmd_req)); + gnmi::GetResponse ethernet_pmd_resp; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + ethernet_pmd_resp_copper_str, ðernet_pmd_resp)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(ethernet_pmd_req), _)) .WillOnce( - DoAll(SetArgPointee<2>(cable_len_resp), Return(grpc::Status::OK))); + DoAll(SetArgPointee<2>(ethernet_pmd_resp), Return(grpc::Status::OK))); ASSERT_OK(pins_test::GetBreakoutModeConfigFromString( req, mock_gnmi_stub_ptr.get(), port_index, intf_name, breakout_mode)); EXPECT_THAT(req, EqualsProto(expected_breakout_config)); @@ -1628,15 +1630,15 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(get_xcvrd_req), _)) .WillOnce( DoAll(SetArgPointee<2>(get_xcvrd_resp), Return(grpc::Status::OK))); - gnmi::GetRequest cable_len_req; - ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(cable_len_req_str, - &cable_len_req)); - gnmi::GetResponse cable_len_resp; + gnmi::GetRequest ethernet_pmd_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + ethernet_pmd_req_str, ðernet_pmd_req)); + gnmi::GetResponse ethernet_pmd_resp; ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( - cable_len_resp_optic_str, &cable_len_resp)); - EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(cable_len_req), _)) + ethernet_pmd_resp_optic_str, ðernet_pmd_resp)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(ethernet_pmd_req), _)) .WillOnce( - DoAll(SetArgPointee<2>(cable_len_resp), Return(grpc::Status::OK))); + DoAll(SetArgPointee<2>(ethernet_pmd_resp), Return(grpc::Status::OK))); gnmi::SetRequest req; EXPECT_THAT( pins_test::GetBreakoutModeConfigFromString( @@ -2199,15 +2201,15 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, TestIsCopperPortSuccessOpticPort) { google::protobuf::TextFormat::ParseFromString(get_xcvrd_resp_str, &resp)); EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(req), _)) .WillOnce(DoAll(SetArgPointee<2>(resp), Return(grpc::Status::OK))); - gnmi::GetRequest cable_len_req; - ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(cable_len_req_str, - &cable_len_req)); - gnmi::GetResponse cable_len_resp; + gnmi::GetRequest ethernet_pmd_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + ethernet_pmd_req_str, ðernet_pmd_req)); + gnmi::GetResponse ethernet_pmd_resp; ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( - cable_len_resp_optic_str, &cable_len_resp)); - EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(cable_len_req), _)) + ethernet_pmd_resp_optic_str, ðernet_pmd_resp)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(ethernet_pmd_req), _)) .WillOnce( - DoAll(SetArgPointee<2>(cable_len_resp), Return(grpc::Status::OK))); + DoAll(SetArgPointee<2>(ethernet_pmd_resp), Return(grpc::Status::OK))); EXPECT_THAT( pins_test::IsCopperPort(mock_gnmi_stub_ptr.get(), "Ethernet1/1/1"), false); @@ -2223,15 +2225,15 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, TestIsCopperPortSuccessCopperPort) { google::protobuf::TextFormat::ParseFromString(get_xcvrd_resp_str, &resp)); EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(req), _)) .WillOnce(DoAll(SetArgPointee<2>(resp), Return(grpc::Status::OK))); - gnmi::GetRequest cable_len_req; - ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(cable_len_req_str, - &cable_len_req)); - gnmi::GetResponse cable_len_resp; + gnmi::GetRequest ethernet_pmd_req; ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( - cable_len_resp_copper_str, &cable_len_resp)); - EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(cable_len_req), _)) + ethernet_pmd_req_str, ðernet_pmd_req)); + gnmi::GetResponse ethernet_pmd_resp; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + ethernet_pmd_resp_copper_str, ðernet_pmd_resp)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(ethernet_pmd_req), _)) .WillOnce( - DoAll(SetArgPointee<2>(cable_len_resp), Return(grpc::Status::OK))); + DoAll(SetArgPointee<2>(ethernet_pmd_resp), Return(grpc::Status::OK))); EXPECT_THAT( pins_test::IsCopperPort(mock_gnmi_stub_ptr.get(), "Ethernet1/1/1"), true); } @@ -2250,7 +2252,7 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, TestIsCopperPortTransceiverGetFailure) { "port transceiver for port Ethernet1/1/1"))); } -TEST_F(GNMIThinkitInterfaceUtilityTest, TestIsCopperPortCableLengthGetFailure) { +TEST_F(GNMIThinkitInterfaceUtilityTest, TestIsCopperPortEthernetPmdGetFailure) { auto mock_gnmi_stub_ptr = absl::make_unique(); gnmi::GetRequest req; ASSERT_TRUE( @@ -2260,62 +2262,22 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, TestIsCopperPortCableLengthGetFailure) { google::protobuf::TextFormat::ParseFromString(get_xcvrd_resp_str, &resp)); EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(req), _)) .WillOnce(DoAll(SetArgPointee<2>(resp), Return(grpc::Status::OK))); - gnmi::GetRequest cable_len_req; - ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(cable_len_req_str, - &cable_len_req)); - EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(cable_len_req), _)) + gnmi::GetRequest ethernet_pmd_req; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + ethernet_pmd_req_str, ðernet_pmd_req)); + EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(ethernet_pmd_req), _)) .WillOnce(Return(grpc::Status(grpc::StatusCode::DEADLINE_EXCEEDED, ""))); EXPECT_THAT( pins_test::IsCopperPort(mock_gnmi_stub_ptr.get(), "Ethernet1/1/1"), StatusIs(absl::StatusCode::kDeadlineExceeded, HasSubstr("Failed to get GNMI state path value for " - "cable-length for port Ethernet1/1/1"))); + "ethernet-pmd for port Ethernet1/1/1"))); } TEST_F(GNMIThinkitInterfaceUtilityTest, - TestIsCopperPortFloatConversionFailure) { - auto mock_gnmi_stub_ptr = absl::make_unique(); - gnmi::GetRequest req; - ASSERT_TRUE( - google::protobuf::TextFormat::ParseFromString(get_xcvrd_req_str, &req)); - gnmi::GetResponse resp; - ASSERT_TRUE( - google::protobuf::TextFormat::ParseFromString(get_xcvrd_resp_str, &resp)); - EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(req), _)) - .WillOnce(DoAll(SetArgPointee<2>(resp), Return(grpc::Status::OK))); - gnmi::GetRequest cable_len_req; - ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(cable_len_req_str, - &cable_len_req)); - gnmi::GetResponse cable_len_resp; - ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( - R"pb(notification { - timestamp: 1631864194292383538 - prefix { origin: "openconfig" } - update { - path { - elem { name: "components" } - elem { - name: "component" - key { key: "name" value: "Ethernet1/1/1" } - } - elem { name: "transceiver" } - elem { name: "state" } - elem { name: "openconfig-platform-ext:cable-length" } - } - val { - json_ietf_val: "{\"openconfig-platform-ext:cable-length\":\"XYZ\"}" - } - } - } - )pb", - &cable_len_resp)); - EXPECT_CALL(*mock_gnmi_stub_ptr, Get(_, EqualsProto(cable_len_req), _)) - .WillOnce( - DoAll(SetArgPointee<2>(cable_len_resp), Return(grpc::Status::OK))); - EXPECT_THAT( - pins_test::IsCopperPort(mock_gnmi_stub_ptr.get(), "Ethernet1/1/1"), - StatusIs(absl::StatusCode::kInternal, - HasSubstr("Failed to convert string (XYZ) to float"))); + TestGetSlotPortLaneForPortFrontPanelPortSuccess) { + EXPECT_THAT(pins_test::GetSlotPortLaneForPort("Ethernet1/1/5"), + IsOkAndHolds(FieldsAre(1, 1, 5))); } TEST_F(GNMIThinkitInterfaceUtilityTest, @@ -2349,10 +2311,17 @@ TEST_F(GNMIThinkitInterfaceUtilityTest, TEST_F(GNMIThinkitInterfaceUtilityTest, TestGetSlotPortLaneForPortInvalidPortFormatFailure) { - EXPECT_THAT(pins_test::GetSlotPortLaneForPort("Ethernet1/1"), - StatusIs(absl::StatusCode::kInvalidArgument, - HasSubstr("Requested port (Ethernet1/1) does not have a " - "valid format (EthernetX/Y/Z)"))); + EXPECT_THAT( + pins_test::GetSlotPortLaneForPort("Ethernet1"), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Requested port (Ethernet1) does not have a " + "valid format (EthernetX/Y/Z or EthernetX/Y)"))); +} + +TEST_F(GNMIThinkitInterfaceUtilityTest, + TestGetSlotPortLaneForPortUnchannelizedPortSuccess) { + EXPECT_THAT(pins_test::GetSlotPortLaneForPort("Ethernet1/33"), + IsOkAndHolds(FieldsAre(1, 33, 1))); } TEST_F(GNMIThinkitInterfaceUtilityTest, diff --git a/thinkit/packet_generation_finalizer.h b/thinkit/packet_generation_finalizer.h index 841efeda..37c5c079 100644 --- a/thinkit/packet_generation_finalizer.h +++ b/thinkit/packet_generation_finalizer.h @@ -18,6 +18,9 @@ #include "absl/status/status.h" #include "absl/time/time.h" +#include "absl/status/status.h" +#include "absl/time/time.h" + namespace thinkit { // Callback when a packet is received, first parameter which is control @@ -29,11 +32,11 @@ using PacketCallback = // PacketGenerationFinalizer will stop listening for packets when it goes out of // scope. class PacketGenerationFinalizer { -public: + public: virtual absl::Status HandlePacketsFor(absl::Duration duration, PacketCallback handler) = 0; virtual ~PacketGenerationFinalizer() = default; }; -} // namespace thinkit -#endif // PINS_THINKIT_PACKET_GENERATION_FINALIZER_H_ +} // namespace thinkit +#endif // PINS_THINKIT_PACKET_GENERATION_FINALIZER_H_