Skip to content

Commit

Permalink
[P4-PDPI] Update GC to handle multicast entries. Transition mocks to …
Browse files Browse the repository at this point in the history
…support entities.Introduce ClearEntities and deprecate ClearTableEntries. (sonic-net#495)



Co-authored-by: Srikishen Pondicherry Shanmugam <[email protected]>
  • Loading branch information
VSuryaprasad-HCL and kishanps authored Aug 27, 2024
1 parent 2300aaf commit 9592a7c
Show file tree
Hide file tree
Showing 10 changed files with 363 additions and 41 deletions.
3 changes: 3 additions & 0 deletions p4_pdpi/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ cc_library(
# Disable default arguments internally. Using them in PDPI itself is very likely a bug.
local_defines = ["PDPI_DISABLE_TRANSLATION_OPTIONS_DEFAULT"],
deps = [
":built_ins",
":helpers",
":ir_cc_proto",
":references",
Expand All @@ -186,6 +187,7 @@ cc_library(
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
"@com_google_protobuf//:protobuf",
],
)

Expand Down Expand Up @@ -379,6 +381,7 @@ cc_library(
"@com_github_grpc_grpc//:grpc++_public_hdrs",
"@com_github_p4lang_p4runtime//:p4runtime_cc_grpc",
"@com_github_p4lang_p4runtime//:p4runtime_cc_proto",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/functional:any_invocable",
"@com_google_absl//absl/numeric:int128",
Expand Down
68 changes: 66 additions & 2 deletions p4_pdpi/p4_runtime_session.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <utility>
#include <vector>

#include "absl/algorithm/container.h"
#include "absl/functional/any_invocable.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
Expand Down Expand Up @@ -392,11 +393,13 @@ P4RuntimeSession::CreateWithP4InfoAndClearTables(
const P4RuntimeSessionOptionalArgs& metadata) {
ASSIGN_OR_RETURN(std::unique_ptr<P4RuntimeSession> session,
P4RuntimeSession::Create(thinkit_switch, metadata));
RETURN_IF_ERROR(ClearTableEntries(session.get()));
RETURN_IF_ERROR(ClearEntities(*session));
RETURN_IF_ERROR(SetMetadataAndSetForwardingPipelineConfig(
session.get(),
p4::v1::SetForwardingPipelineConfigRequest_Action_RECONCILE_AND_COMMIT,
p4info));
RETURN_IF_ERROR(CheckNoEntities(*session)).SetPrepend()
<< "cleared all entities and set a new forwarding pipeline config: ";
return session;
}

Expand Down Expand Up @@ -518,6 +521,67 @@ absl::StatusOr<p4::v1::CounterData> ReadPiCounterData(
<< target_entry_signature.ShortDebugString() << ">";
}

absl::Status CheckNoEntities(P4RuntimeSession& session) {
ASSIGN_OR_RETURN(
p4::v1::GetForwardingPipelineConfigResponse response,
GetForwardingPipelineConfig(
&session,
p4::v1::GetForwardingPipelineConfigRequest::P4INFO_AND_COOKIE));
// If the switch does not have a p4info, then it cannot have any entities.
if (!response.has_config()) return absl::OkStatus();

// If the switch has a p4info, we read all entities to ensure that there are
// none.
ASSIGN_OR_RETURN(std::vector<Entity> entities, ReadPiEntities(&session));
if (!entities.empty()) {
return gutil::FailedPreconditionErrorBuilder()
<< "expected no entities on switch, but " << entities.size()
<< " entities remain:\n"
<< absl::StrJoin(entities, "", [](std::string* out, auto& entity) {
absl::StrAppend(out, entity.DebugString());
});
}

return absl::OkStatus();
}

absl::Status ClearEntities(P4RuntimeSession& session) {
// Get P4Info from Switch. It is needed to sequence the delete requests.
ASSIGN_OR_RETURN(
p4::v1::GetForwardingPipelineConfigResponse response,
GetForwardingPipelineConfig(
&session, p4::v1::GetForwardingPipelineConfigRequest::ALL));

// If no p4info has been pushed to the switch, then it cannot have any
// entities to clear. Furthermore, reading entities (i.e. part of the
// statement after this one) will fail if no p4info has been pushed.
if (!response.has_config()) return absl::OkStatus();

// Get entities.
ASSIGN_OR_RETURN(std::vector<Entity> entities, ReadPiEntities(&session));

// Early return if there is nothing to clear.
if (entities.empty()) return absl::OkStatus();

// Convert into IrP4Info.
ASSIGN_OR_RETURN(IrP4Info info, CreateIrP4Info(response.config().p4info()));

// Sort by dependency order, then reverse since we will be deleting.
RETURN_IF_ERROR(pdpi::StableSortEntities(info, entities));
absl::c_reverse(entities);

RETURN_IF_ERROR(
SendPiUpdates(&session, CreatePiUpdates(entities, Update::DELETE)))
<< "when attempting to delete the following entities: "
<< absl::StrJoin(entities, "\n");

// Verify that all entities were cleared successfully.
RETURN_IF_ERROR(CheckNoEntities(session)).SetPrepend()
<< "cleared all entities: ";

return absl::OkStatus();
}

absl::Status CheckNoTableEntries(P4RuntimeSession* session) {
ASSIGN_OR_RETURN(
p4::v1::GetForwardingPipelineConfigResponse response,
Expand All @@ -532,7 +596,7 @@ absl::Status CheckNoTableEntries(P4RuntimeSession* session) {
// are none.
ASSIGN_OR_RETURN(auto table_entries, ReadPiTableEntries(session));
if (!table_entries.empty()) {
return gutil::UnknownErrorBuilder()
return gutil::FailedPreconditionErrorBuilder()
<< "expected no table entries on switch, but "
<< table_entries.size() << " entries remain:\n"
<< absl::StrJoin(table_entries, "",
Expand Down
9 changes: 9 additions & 0 deletions p4_pdpi/p4_runtime_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <thread> // NOLINT: third_party code.
#include <vector>

#include "absl/base/attributes.h"
#include "absl/base/thread_annotations.h"
#include "absl/functional/any_invocable.h"
#include "absl/numeric/int128.h"
Expand Down Expand Up @@ -368,10 +369,18 @@ absl::StatusOr<p4::v1::CounterData> ReadPiCounterData(
P4RuntimeSession* session,
const p4::v1::TableEntry& target_entry_signature);

// Checks that a read from `session` returns no entities.
absl::Status CheckNoEntities(P4RuntimeSession& session);

// Deletes all entities read from `session`.
absl::Status ClearEntities(P4RuntimeSession& session);

// Checks that there are no table entries.
ABSL_DEPRECATED("Use CheckNoEntities instead.")
absl::Status CheckNoTableEntries(P4RuntimeSession* session);

// Clears the table entries.
ABSL_DEPRECATED("Use ClearEntities instead.")
absl::Status ClearTableEntries(P4RuntimeSession* session);

// Installs the given PI (program independent) table entry on the switch.
Expand Down
137 changes: 115 additions & 22 deletions p4_pdpi/p4_runtime_session_mocking.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "gtest/gtest.h"
#include "p4/config/v1/p4info.pb.h"
#include "p4/v1/p4runtime.pb.h"
#include "p4/v1/p4runtime_mock.grpc.pb.h"
#include "p4_pdpi/p4_runtime_session.h"

namespace pdpi {
Expand All @@ -51,6 +52,11 @@ using ::testing::Return;
constexpr uint32_t kTableId = 33554433;
constexpr uint32_t kActionId = 16777217;

// Arbitrarily chosen.
constexpr uint32_t kMulticastGroupId = 1;
constexpr uint32_t kMulticastGroupReplicaInstance = 2;
constexpr absl::string_view kMulticastGroupReplicaPort = "3";

} // namespace

p4::v1::Uint128 ConstructElectionId(
Expand All @@ -72,16 +78,16 @@ p4::v1::MasterArbitrationUpdate ConstructMasterArbitrationUpdate(
}

void SetNextReadResponse(p4::v1::MockP4RuntimeStub& mock_p4rt_stub,
std::vector<p4::v1::TableEntry> read_entries) {
std::vector<p4::v1::Entity> read_entities) {
EXPECT_CALL(mock_p4rt_stub, ReadRaw)
.WillOnce([read_entries = std::move(read_entries)](auto*, auto&) {
.WillOnce([read_entities = std::move(read_entities)](auto*, auto&) {
auto* reader =
new grpc::testing::MockClientReader<p4::v1::ReadResponse>();
InSequence s;
EXPECT_CALL(*reader, Read)
.WillOnce([=](p4::v1::ReadResponse* response) -> bool {
for (const auto& entry : read_entries) {
*response->add_entities()->mutable_table_entry() = entry;
for (const auto& entity : read_entities) {
*response->add_entities() = entity;
}
return true;
});
Expand All @@ -92,16 +98,16 @@ void SetNextReadResponse(p4::v1::MockP4RuntimeStub& mock_p4rt_stub,
}

void SetDefaultReadResponse(p4::v1::MockP4RuntimeStub& mock_p4rt_stub,
std::vector<p4::v1::TableEntry> read_entries) {
std::vector<p4::v1::Entity> read_entities) {
ON_CALL(mock_p4rt_stub, ReadRaw)
.WillByDefault([read_entries = std::move(read_entries)](auto*, auto&) {
.WillByDefault([read_entities = std::move(read_entities)](auto*, auto&) {
auto* reader =
new grpc::testing::MockClientReader<p4::v1::ReadResponse>();
InSequence s;
for (const auto& entry : read_entries) {
for (const auto& entity : read_entities) {
EXPECT_CALL(*reader, Read)
.WillOnce([&](p4::v1::ReadResponse* response) -> bool {
*response->add_entities()->mutable_table_entry() = entry;
*response->add_entities() = entity;
return true;
});
}
Expand All @@ -111,6 +117,26 @@ void SetDefaultReadResponse(p4::v1::MockP4RuntimeStub& mock_p4rt_stub,
});
}

void SetNextReadResponse(p4::v1::MockP4RuntimeStub& mock_p4rt_stub,
std::vector<p4::v1::TableEntry> read_entries) {
std::vector<p4::v1::Entity> read_entities;
read_entities.reserve(read_entries.size());
for (const auto& entry : read_entries) {
*read_entities.emplace_back().mutable_table_entry() = entry;
}
SetNextReadResponse(mock_p4rt_stub, read_entities);
}

void SetDefaultReadResponse(p4::v1::MockP4RuntimeStub& mock_p4rt_stub,
std::vector<p4::v1::TableEntry> read_entries) {
std::vector<p4::v1::Entity> read_entities;
read_entities.reserve(read_entries.size());
for (const auto& entry : read_entries) {
*read_entities.emplace_back().mutable_table_entry() = entry;
}
SetDefaultReadResponse(mock_p4rt_stub, read_entities);
}

void MockP4RuntimeSessionCreate(p4::v1::MockP4RuntimeStub& stub,
const P4RuntimeSessionOptionalArgs& metadata) {
// The ReaderWriter stream constructed from the stub. This needs to be
Expand Down Expand Up @@ -166,21 +192,86 @@ p4::v1::TableEntry ConstructTableEntry() {
return table_entry;
}

p4::v1::Entity ConstructMulticastEntity() {
p4::v1::Entity entity;
p4::v1::MulticastGroupEntry* multicast_entry =
entity.mutable_packet_replication_engine_entry()
->mutable_multicast_group_entry();
multicast_entry->set_multicast_group_id(kMulticastGroupId);
multicast_entry->add_replicas()->set_instance(kMulticastGroupReplicaInstance);
multicast_entry->mutable_replicas(0)->set_port(kMulticastGroupReplicaPort);
return entity;
}

p4::v1::WriteRequest ConstructDeleteRequest(
const P4RuntimeSessionOptionalArgs& metadata,
const p4::v1::TableEntry& table_entry) {
p4::v1::Update delete_update;
delete_update.set_type(p4::v1::Update::DELETE);
*delete_update.mutable_entity()->mutable_table_entry() = table_entry;

const std::vector<p4::v1::Entity>& entities) {
p4::v1::WriteRequest delete_request;
*delete_request.add_updates() = delete_update;
delete_request.set_device_id(kDeviceId);
delete_request.set_role(metadata.role);
*delete_request.mutable_election_id() = ConstructElectionId(metadata);

for (const p4::v1::Entity& entity : entities) {
p4::v1::Update* delete_update = delete_request.add_updates();
delete_update->set_type(p4::v1::Update::DELETE);
*delete_update->mutable_entity() = entity;
}

return delete_request;
}

void MockCheckNoEntities(p4::v1::MockP4RuntimeStub& stub,
std::optional<P4Info> p4info) {
// We need to return a valid p4info to get to the stage where we read
// entities.
EXPECT_CALL(stub, GetForwardingPipelineConfig)
.WillOnce([=](auto, auto,
p4::v1::GetForwardingPipelineConfigResponse*
get_pipeline_response) {
*get_pipeline_response->mutable_config()->mutable_p4info() =
p4info.value_or(P4Info());
return grpc::Status::OK;
});

SetNextReadResponse(stub, std::vector<p4::v1::Entity>());
}

void MockClearEntities(p4::v1::MockP4RuntimeStub& stub, const P4Info& p4info,
const P4RuntimeSessionOptionalArgs& metadata) {
// We need to return a valid p4info to get to the stage where we read
// entities.
EXPECT_CALL(stub, GetForwardingPipelineConfig)
.WillOnce([&](auto, auto,
p4::v1::GetForwardingPipelineConfigResponse*
get_pipeline_response) {
*get_pipeline_response->mutable_config()->mutable_p4info() = p4info;
return grpc::Status::OK;
});

{
InSequence s;
p4::v1::Entity table_entity;
*table_entity.mutable_table_entry() = ConstructTableEntry();
p4::v1::Entity multicast_entity = ConstructMulticastEntity();

// We return two entities so that the function exercises the deletion
// portion of clearing entities.
SetNextReadResponse(stub, {table_entity, multicast_entity});

// Mocks the call to delete the entities that we have created, in reverse
// dependency order.
EXPECT_CALL(stub, Write(_,
EqualsProto(ConstructDeleteRequest(
metadata, {multicast_entity, table_entity})),
_))
.Times(1);

// Mocks a `CheckNoEntities` call, ensuring that the entities are really
// cleared.
MockCheckNoEntities(stub, p4info);
}
}

void MockCheckNoEntries(p4::v1::MockP4RuntimeStub& stub,
std::optional<P4Info> p4info) {
// We need to return a valid p4info to get to the stage where we read tables.
Expand All @@ -193,7 +284,7 @@ void MockCheckNoEntries(p4::v1::MockP4RuntimeStub& stub,
return grpc::Status::OK;
});

SetNextReadResponse(stub, {});
SetNextReadResponse(stub, std::vector<p4::v1::TableEntry>());
}

void MockClearTableEntries(p4::v1::MockP4RuntimeStub& stub,
Expand All @@ -210,16 +301,18 @@ void MockClearTableEntries(p4::v1::MockP4RuntimeStub& stub,

{
InSequence s;
p4::v1::TableEntry table_entry = ConstructTableEntry();
p4::v1::Entity table_entity;
*table_entity.mutable_table_entry() = ConstructTableEntry();

// We return a table entry so that the function exercises the deletion
// portion of clearing table entries.
SetNextReadResponse(stub, {table_entry});
// We return the table entity so that the function exercises the deletion
// portion to clear it.
SetNextReadResponse(stub, {table_entity});

// Mocks the call to delete table entry that we have created.
// Mocks the call to delete the table_entity that we have created.
EXPECT_CALL(
stub,
Write(_, EqualsProto(ConstructDeleteRequest(metadata, table_entry)), _))
Write(_, EqualsProto(ConstructDeleteRequest(metadata, {table_entity})),
_))
.Times(1);

// Mocks a `CheckNoEntries` call, ensuring that the tables are really
Expand Down Expand Up @@ -247,7 +340,7 @@ ConstructForwardingPipelineConfigRequest(

absl::StatusOr<P4SessionWithMockStub> MakeP4SessionWithMockStub(
const P4RuntimeSessionOptionalArgs& metadata) {
// No leak: P4RuntimeSession will take ownerhsip.
// No leak: P4RuntimeSession will take ownership.
auto* mock_p4rt_stub = new testing::NiceMock<p4::v1::MockP4RuntimeStub>();
MockP4RuntimeSessionCreate(*mock_p4rt_stub, metadata);
ASSIGN_OR_RETURN(std::unique_ptr<P4RuntimeSession> p4rt_session,
Expand Down
Loading

0 comments on commit 9592a7c

Please sign in to comment.