PI源码分析以及服务注入(2)

P4 Runtime

P4 Runtime Specification has been released. P4 is a language for programming the data plane of network devices. The P4Runtime API is a control plane specification for controlling the data plane elements of a device or program defined by a P4 program.
The architecture can been seen below:
P4 Runtime Archtecture

From the figure, we can see P4runtime use grpc to link server and client, and in SDN archtecture, client reprensents controller and gRPC server represents switch client. P4 Runtime protocol is defined by protobuf.
so if we want to add some new feature to P4 Runtime we can just add some new service to P4runtime.proto or add some new protos to PI, then integrated new service to P4runtime. And I will introduce how to integrated a new simple service to PI at section demo_grpc analysis.

demo_grpc analysis

PI 提供了一个demo, 用来说明使用 P4runtime 进行控制器和交换机的通信。 demo_grpc 位于文件夹 PI/proto/demo_grpc/ 文件夹下。P4runtime proto 的定义位于 PI/proto/p4runtime/proto/p4 文件夹下。通过集成一个服务到PI框架下另外分析demo_grpc。

Step1: 将 Helloworld.proto 进行编译, 类似于编译p4runtime.proto, 这里通过修改Makefile.am来达到在编译p4runtime的同时编译helloworld.
具体方法: 修改 PI/Proto/ 下的 Makefile.am 文件。修改后变动的地方如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protos = \
$(abs_srcdir)/p4/v1/helloworld.proto \
...

EXTRA_DIST = \
$(abs_srcdir)/p4/v1/helloworld.proto \
...

proto_cpp_files = \
cpp_out/p4/v1/helloworld.pb.cc \
cpp_out/p4/v1/helloworld.pb.h \
...

proto_grpc_files = \
grpc_out/p4/v1/helloworld.grpc.pb.cc \
grpc_out/p4/v1/helloworld.grpc.pb.h \
'''
注: 向 $protos 中添加 proto 可以达到编译 proto 的目的; 向 $proto_cpp_files 和 proto_grpc_files 中添加 .cc .h 文件可以达到编译该文件的作用;

Step2: 在 PI/ 目录下执行 make, 这样就可以得到相应的编译之后的文件, 在 PI/proto/cpp_out/p4/v1 下可以看到新增的编译的文件, 例如 helloworld.pb.cc, helloworld.pb.lo 文件等等。

Step3:在demo_grpc目录下将helloworld服务集成到该demo中去。可以发现, 该目录下有一个可执行问价 pi_server_dummy 的可执行文件,由pi_server_main.cpp文件生成,通过修改该文件及其相关文件来达到集成服务的目的。
pi_server_main.cpp, 关键代码:

1
PIGrpcServerRunAddr(server_address);

pi_server.cpp 中有关于该函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void PIGrpcServerRunAddr(const char *server_address) {
server_data = new ::pi::server::ServerData();
server_data->server_address = std::string(server_address);
auto &builder = server_data->builder;
builder.AddListeningPort(
server_data->server_address, grpc::InsecureServerCredentials(),
&server_data->server_port);
builder.RegisterService(&server_data->pi_service);
builder.RegisterService(&server_data->hello_service);// 新增服务 helloworld
#ifdef WITH_SYSREPO
server_data->gnmi_service = ::pi::server::make_gnmi_service_sysrepo();
#else
server_data->gnmi_service = ::pi::server::make_gnmi_service_dummy();
#endif // WITH_SYSREPO
builder.RegisterService(server_data->gnmi_service.get());
builder.SetMaxReceiveMessageSize(256*1024*1024); // 256MB

server_data->server = builder.BuildAndStart();
std::cout << "Server listening on " << server_data->server_address << "\n";
}

分析上述代码可以发现,该函数的主要作用是在server上开启服务,指定服务器的地址,创建 builder, 添加侦听端口,地址,无加密的通信方式,之后注册相关服务。关键数据结构 ServerData。

1
2
3
4
5
6
7
8
9
struct ServerData {
std::string server_address;
int server_port;
P4RuntimeServiceImpl pi_service;
HelloworldServiceImpl hello_service;//New added service
std::unique_ptr<gnmi::gNMI::Service> gnmi_service;
ServerBuilder builder;
std::unique_ptr<Server> server;
};

在这里添加了新增的服务 hello_service。关于服务类型 HelloworldServiceImpl 在 pi_server.cpp 中实现了。

1
2
3
4
5
6
7
8
9
class HelloworldServiceImpl : public helloworld::Greeter::Service {
public:
Status SayHello(ServerContext* context, const HelloRequest* request,
HelloReply* reply) {
std::string prefix("Hello ");
reply->set_message(prefix + request->name());
return Status::OK;
}
};

注意:关于该服务的相头文件需要加到该文件中,到目前为止,helloword服务已经集成到server中去了。

Step4:在client端中添加调用helloworld服务的函数。在该文件目录下,simple_router_mgr.cpp文件实现了client的相关调用函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int SimpleRouterMgr::test_func_txg1(){
std::string user("world");
HelloRequest request;
request.set_name(user);

HelloReply reply;
ClientContext context;
Status status = stub_->SayHello(&context, request, &reply);
// Act upon its status.
if (status.ok()) {
return 0;
} else {
std::cout << status.error_code() << ": " << status.error_message()
<< std::endl;
return 0;
}
}

分析该函数,关键点在于 stub_->SayHello(&context, request, &reply); 其中 stub_是访问helloworld服务的stub, 在该demo中,访问 p4runtime服务的stub是 pi_stub_, 但是使用 pi_stub_ 访问不了helloworld服务。 为了能够在这里使用stub_, 需要提前声明stub_。在文件simple_router_mgr.h, SimpleRouter的成员变量有:

1
2
3
std::unique_ptr<p4::v1::P4Runtime::Stub> pi_stub_;
std::unique_ptr<helloworld::Greeter::Stub> stub_;// New added stub
std::unique_ptr<StreamChannelSyncClient> packet_io_client;

在 simple_router_mgr.cpp中对hello_stub进行初始化:

1
2
3
4
5
6
7
8
SimpleRouterMgr::SimpleRouterMgr(int dev_id,
boost::asio::io_service &io_service,
std::shared_ptr<Channel> channel)
: dev_id(dev_id), io_service(io_service),
pi_stub_(p4v1::P4Runtime::NewStub(channel)),
hello_stub_(helloworld::Greeter::NewStub(channel)),
packet_io_client(new StreamChannelSyncClient(this, channel)) {
}

Step5:
5.1 重新编译;
5.2 启动server

1
./pi_server_dummy

5.3 启动client,在 app.cpp 中的 main 函数中调用 SimpleRouterMgr::test_func_txg1() 函数,相当于启动client 去访问server中的helloworld函数。

1
./controller

至此,整个helloworld服务已经集成到PI框架下了,并且通过验证,结果是正确的。