Skip to content

Commit

Permalink
Add generated Dispatcher. (project-oak#628)
Browse files Browse the repository at this point in the history
This is a convenient wrapper around a gRPC service implementation to
implement `OakNode` for it. It is automatically generated for every gRPC
service trait.
  • Loading branch information
wildarch authored Feb 26, 2020
1 parent d1b01c6 commit 6019c50
Show file tree
Hide file tree
Showing 17 changed files with 203 additions and 168 deletions.
74 changes: 31 additions & 43 deletions docs/programming-oak.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,16 @@ it, then define an
so the Oak SDK knows how to instantiate it:

<!-- prettier-ignore-start -->
[embedmd]:# (../examples/rustfmt/module/rust/src/lib.rs Rust /^oak::entrypoint.*/ /}\);$/)
[embedmd]:# (../examples/machine_learning/module/rust/src/lib.rs Rust /^oak::entrypoint.*/ /}\);$/)
```Rust
oak::entrypoint!(oak_main => {
oak_log::init_default();
Node
Node {
training_set_size: 1000,
test_set_size: 1000,
config: None,
model: NaiveBayes::new(),
}
});
```
<!-- prettier-ignore-end -->
Expand Down Expand Up @@ -79,37 +84,26 @@ with the following default behaviour:
[`run_event_loop()`](https://project-oak.github.io/oak/doc/oak/fn.run_event_loop.html)
function.

For gRPC server nodes (the normal "front door" for an Oak Application), the Node
`struct` must implement the
[`oak::grpc::OakNode`](https://project-oak.github.io/oak/doc/oak/grpc/trait.OakNode.html)
trait (which provides an automatic implementation of the
[`Node`](https://project-oak.github.io/oak/doc/oak/trait.Node.html)). This has
two methods:

- A
[`new()`](https://project-oak.github.io/oak/doc/oak/grpc/trait.OakNode.html#tymethod.new)
method to create an instance of the Node, and perform any one-off
initialization. (A common initialization action is to enable logging for the
Node, via the [`oak_log` crate](sdk.md#oak_log-crate).)
- An
[`invoke()`](https://project-oak.github.io/oak/doc/oak/grpc/trait.OakNode.html#tymethod.invoke)
method that is called for each newly-arriving gRPC request from the outside
world.
For gRPC server nodes (the normal "front door" for an Oak Application) the
easiest way to use a gRPC service implementation is to wrap it with the
automatically generated `Dispatcher`, as described in the next section.

<!-- prettier-ignore-start -->
[embedmd]:# (../examples/rustfmt/module/rust/src/lib.rs Rust /impl oak::grpc::OakNode/ /^}/)
[embedmd]:# (../examples/rustfmt/module/rust/src/lib.rs Rust /oak::entrypoint!/ /^}/)
```Rust
impl oak::grpc::OakNode for Node {
fn invoke(&mut self, method: &str, req: &[u8], writer: grpc::ChannelResponseWriter) {
dispatch(self, method, req, writer)
}
oak::entrypoint!(oak_main => {
oak_log::init_default();
Dispatcher::new(Node)
}
```
<!-- prettier-ignore-end -->

The `invoke` method can be written manually, but it is usually easier to rely on
code that is auto-generated from a gRPC service definition, described in the
next section.
Alternatively a Node can implement the
[`oak::grpc::OakNode`](https://project-oak.github.io/oak/doc/oak/grpc/trait.OakNode.html)
trait (which provides an automatic implementation of the
[`Node`](https://project-oak.github.io/oak/doc/oak/trait.Node.html)). The
[machine learning example](https://github.com/project-oak/oak/blob/master/examples/machine_learning/module/rust/src/lib.rs)
demonstrates this.

### Generated gRPC service code

Expand Down Expand Up @@ -153,24 +147,21 @@ pub trait FormatService {
```
<!-- prettier-ignore-end -->

Secondly, the autogenerated code includes a `dispatch()` method which maps a
Secondly, the autogenerated code includes a `Dispatcher` struct which maps a
request (as a method name and encoded request) to an invocation of the relevant
method on the service trait. This `dispatch()` method can then form the entire
method on the service trait. This `Dispatcher` struct can then form the entire
implementation of the `OakNode::invoke()` method described in the previous
section.

Taken altogether, this covers all of the boilerplate needed to have a Node act
as a gRPC server:

- The main `oak_main` entrypoint is auto-generated, and invokes
`oak::grpc::event_loop` with a new Node `struct`.
- This Node `struct` implements `oak::grpc::OakNode` so the `event_loop()`
method can call back into the Node's `invoke()` method.
- The `invoke()` method typically just forwards on to the `dispatch()` method
from the per-gRPC-service auto-generated code.
- The auto-generated `dispatch()` method invokes the relevant per-service method
of the Node, available because the Node `struct` implements the gRPC service
trait.
`oak::grpc::event_loop` with a `Dispatcher`.
- This `Dispatcher` is created by wrapping a Node `struct` that implements the
gRPC generated service trait.
- The `Dispatcher` implements `oak::grpc::OakNode` so the `event_loop()` method
can call into the relevant per-service method of the Node.

## Running an Oak Application

Expand Down Expand Up @@ -281,14 +272,11 @@ sections) would be as follows:
channel for the node. It also creates a new channel for any responses, and
passes a handle for this response channel alongside the request.
- This unblocks the Node code, and `oak::grpc::event_loop` reads and
deserializes the incoming gRPC request. It then calls the Node's `invoke()`
method with the method name and (serialized) gRPC request.
- The Node's `invoke()` method passes this straight on to the auto-generated
gRPC service code in `proto::service_name_grpc::dispatch()`.
- The auto-generated `dispatch()` code invokes the relevant method on the
`Node`.
deserializes the incoming gRPC request. It then calls the `Dispatcher`'s
`invoke()` method with the method name and (serialized) gRPC request.
- The auto-generated `Dispatcher` invokes the relevant method on the `Node`.
- The (user-written) code in this method does its work, and returns a response.
- The auto-generated `dispatch()` code encapsulates the response into a
- The auto-generated `Dispatcher` struct encapsulates the response into a
`GrpcResponse` wrapper message, and serializes into the response channel.
- The Oak Runtime reads this message from the response channel, deserializes it
and sends the inner response back to the client.
Expand Down
11 changes: 3 additions & 8 deletions examples/abitest/module_0/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use expect::{expect, expect_eq};
use log::info;
use oak::{grpc, ChannelReadStatus, OakStatus};
use proto::abitest::{ABITestRequest, ABITestResponse, ABITestResponse_TestResult};
use proto::abitest_grpc::{dispatch, OakABITestService};
use proto::abitest_grpc::{Dispatcher, OakABITestService};
use protobuf::ProtobufEnum;
use rand::Rng;
use std::collections::HashMap;
Expand Down Expand Up @@ -75,16 +75,11 @@ pub extern "C" fn frontend_oak_main(in_handle: u64) {
let _ = std::panic::catch_unwind(|| {
oak::set_panic_hook();
let node = FrontendNode::new();
oak::run_event_loop(node, in_handle);
let dispatcher = Dispatcher::new(node);
oak::run_event_loop(dispatcher, in_handle);
});
}

impl oak::grpc::OakNode for FrontendNode {
fn invoke(&mut self, method: &str, req: &[u8], writer: grpc::ChannelResponseWriter) {
dispatch(self, method, req, writer)
}
}

type TestResult = Result<(), Box<dyn std::error::Error>>;
type TestFn = fn(&FrontendNode) -> TestResult;

Expand Down
24 changes: 17 additions & 7 deletions examples/abitest/module_0/rust/src/proto/abitest_grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,23 @@ pub trait OakABITestService {
}

// Oak Node gRPC method dispatcher
pub fn dispatch<T: OakABITestService>(node: &mut T, method: &str, req: &[u8], writer: grpc::ChannelResponseWriter) {
match method {
"/oak.examples.abitest.OakABITestService/RunTests" => grpc::handle_req_rsp(|r| node.run_tests(r), req, writer),
_ => {
panic!("unknown method name: {}", method);
}
};
pub struct Dispatcher<T: OakABITestService>(T);

impl<T: OakABITestService> Dispatcher<T> {
pub fn new(node: T) -> Dispatcher<T> {
Dispatcher(node)
}
}

impl<T: OakABITestService> grpc::OakNode for Dispatcher<T> {
fn invoke(&mut self, method: &str, req: &[u8], writer: grpc::ChannelResponseWriter) {
match method {
"/oak.examples.abitest.OakABITestService/RunTests" => grpc::handle_req_rsp(|r| self.0.run_tests(r), req, writer),
_ => {
panic!("unknown method name: {}", method);
}
};
}
}

// Client interface
Expand Down
12 changes: 3 additions & 9 deletions examples/chat/module/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@

use command::Command;
use log::info;
use oak::grpc::{self, OakNode};
use oak::grpc;
use proto::chat::{CreateRoomRequest, DestroyRoomRequest, SendMessageRequest, SubscribeRequest};
use proto::chat_grpc::{dispatch, Chat};
use proto::chat_grpc::{Chat, Dispatcher};
use protobuf::well_known_types::Empty;
use protobuf::Message;
use std::collections::hash_map::Entry;
Expand All @@ -38,7 +38,7 @@ struct Node {

oak::entrypoint!(oak_main => {
oak_log::init_default();
Node::default()
Dispatcher::new(Node::default())
});

struct Room {
Expand All @@ -58,12 +58,6 @@ impl Room {
}
}

impl OakNode for Node {
fn invoke(&mut self, method: &str, req: &[u8], writer: grpc::ChannelResponseWriter) {
dispatch(self, method, req, writer)
}
}

fn room_id_not_found_err<T>() -> grpc::Result<T> {
Err(grpc::build_status(grpc::Code::NOT_FOUND, "room not found"))
}
Expand Down
30 changes: 20 additions & 10 deletions examples/chat/module/rust/src/proto/chat_grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,26 @@ pub trait Chat {
}

// Oak Node gRPC method dispatcher
pub fn dispatch<T: Chat>(node: &mut T, method: &str, req: &[u8], writer: grpc::ChannelResponseWriter) {
match method {
"/oak.examples.chat.Chat/CreateRoom" => grpc::handle_req_rsp(|r| node.create_room(r), req, writer),
"/oak.examples.chat.Chat/DestroyRoom" => grpc::handle_req_rsp(|r| node.destroy_room(r), req, writer),
"/oak.examples.chat.Chat/Subscribe" => grpc::handle_req_stream(|r, w| node.subscribe(r, w), req, writer),
"/oak.examples.chat.Chat/SendMessage" => grpc::handle_req_rsp(|r| node.send_message(r), req, writer),
_ => {
panic!("unknown method name: {}", method);
}
};
pub struct Dispatcher<T: Chat>(T);

impl<T: Chat> Dispatcher<T> {
pub fn new(node: T) -> Dispatcher<T> {
Dispatcher(node)
}
}

impl<T: Chat> grpc::OakNode for Dispatcher<T> {
fn invoke(&mut self, method: &str, req: &[u8], writer: grpc::ChannelResponseWriter) {
match method {
"/oak.examples.chat.Chat/CreateRoom" => grpc::handle_req_rsp(|r| self.0.create_room(r), req, writer),
"/oak.examples.chat.Chat/DestroyRoom" => grpc::handle_req_rsp(|r| self.0.destroy_room(r), req, writer),
"/oak.examples.chat.Chat/Subscribe" => grpc::handle_req_stream(|r, w| self.0.subscribe(r, w), req, writer),
"/oak.examples.chat.Chat/SendMessage" => grpc::handle_req_rsp(|r| self.0.send_message(r), req, writer),
_ => {
panic!("unknown method name: {}", method);
}
};
}
}

// Client interface
Expand Down
14 changes: 4 additions & 10 deletions examples/hello_world/module/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ mod tests;

use log::{error, info, warn};
use oak::grpc;
use oak::grpc::OakNode;
use proto::hello_world::{HelloRequest, HelloResponse};
use proto::hello_world_grpc::{dispatch, HelloWorld};
use proto::hello_world_grpc::{Dispatcher, HelloWorld};

oak::entrypoint!(oak_main => {
oak_log::init_default();
Node {
let node = Node {
storage: oak::storage::Storage::default(),
translator: grpc::client::Client::new("translator", "oak_main")
.map(translator_common::TranslatorClient),
}
};
Dispatcher::new(node)
});

struct Node {
Expand All @@ -48,12 +48,6 @@ impl Node {
const STORAGE_NAME: &[u8] = b"HelloWorld";
const FIELD_NAME: &[u8] = b"last-greeting";

impl OakNode for Node {
fn invoke(&mut self, method: &str, req: &[u8], writer: grpc::ChannelResponseWriter) {
dispatch(self, method, req, writer)
}
}

impl HelloWorld for Node {
fn say_hello(&mut self, req: HelloRequest) -> grpc::Result<HelloResponse> {
if req.greeting == "Query-of-Error" {
Expand Down
30 changes: 20 additions & 10 deletions examples/hello_world/module/rust/src/proto/hello_world_grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,26 @@ pub trait HelloWorld {
}

// Oak Node gRPC method dispatcher
pub fn dispatch<T: HelloWorld>(node: &mut T, method: &str, req: &[u8], writer: grpc::ChannelResponseWriter) {
match method {
"/oak.examples.hello_world.HelloWorld/SayHello" => grpc::handle_req_rsp(|r| node.say_hello(r), req, writer),
"/oak.examples.hello_world.HelloWorld/LotsOfReplies" => grpc::handle_req_stream(|r, w| node.lots_of_replies(r, w), req, writer),
"/oak.examples.hello_world.HelloWorld/LotsOfGreetings" => grpc::handle_stream_rsp(|rr| node.lots_of_greetings(rr), req, writer),
"/oak.examples.hello_world.HelloWorld/BidiHello" => grpc::handle_stream_stream(|rr, w| node.bidi_hello(rr, w), req, writer),
_ => {
panic!("unknown method name: {}", method);
}
};
pub struct Dispatcher<T: HelloWorld>(T);

impl<T: HelloWorld> Dispatcher<T> {
pub fn new(node: T) -> Dispatcher<T> {
Dispatcher(node)
}
}

impl<T: HelloWorld> grpc::OakNode for Dispatcher<T> {
fn invoke(&mut self, method: &str, req: &[u8], writer: grpc::ChannelResponseWriter) {
match method {
"/oak.examples.hello_world.HelloWorld/SayHello" => grpc::handle_req_rsp(|r| self.0.say_hello(r), req, writer),
"/oak.examples.hello_world.HelloWorld/LotsOfReplies" => grpc::handle_req_stream(|r, w| self.0.lots_of_replies(r, w), req, writer),
"/oak.examples.hello_world.HelloWorld/LotsOfGreetings" => grpc::handle_stream_rsp(|rr| self.0.lots_of_greetings(rr), req, writer),
"/oak.examples.hello_world.HelloWorld/BidiHello" => grpc::handle_stream_stream(|rr, w| self.0.bidi_hello(rr, w), req, writer),
_ => {
panic!("unknown method name: {}", method);
}
};
}
}

// Client interface
Expand Down
10 changes: 2 additions & 8 deletions examples/private_set_intersection/module/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,17 @@ mod tests;

use oak::grpc;
use proto::private_set_intersection::{GetIntersectionResponse, SubmitSetRequest};
use proto::private_set_intersection_grpc::{dispatch, PrivateSetIntersection};
use proto::private_set_intersection_grpc::{Dispatcher, PrivateSetIntersection};
use protobuf::well_known_types::Empty;
use std::collections::HashSet;

oak::entrypoint!(oak_main => Node::default());
oak::entrypoint!(oak_main => Dispatcher::new(Node::default()));

#[derive(Default)]
struct Node {
values: Option<HashSet<String>>,
}

impl oak::grpc::OakNode for Node {
fn invoke(&mut self, method: &str, req: &[u8], writer: grpc::ChannelResponseWriter) {
dispatch(self, method, req, writer)
}
}

impl PrivateSetIntersection for Node {
fn submit_set(&mut self, req: SubmitSetRequest) -> grpc::Result<Empty> {
let set = req.values.iter().cloned().collect::<HashSet<_>>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,24 @@ pub trait PrivateSetIntersection {
}

// Oak Node gRPC method dispatcher
pub fn dispatch<T: PrivateSetIntersection>(node: &mut T, method: &str, req: &[u8], writer: grpc::ChannelResponseWriter) {
match method {
"/oak.examples.private_set_intersection.PrivateSetIntersection/SubmitSet" => grpc::handle_req_rsp(|r| node.submit_set(r), req, writer),
"/oak.examples.private_set_intersection.PrivateSetIntersection/GetIntersection" => grpc::handle_req_rsp(|r| node.get_intersection(r), req, writer),
_ => {
panic!("unknown method name: {}", method);
}
};
pub struct Dispatcher<T: PrivateSetIntersection>(T);

impl<T: PrivateSetIntersection> Dispatcher<T> {
pub fn new(node: T) -> Dispatcher<T> {
Dispatcher(node)
}
}

impl<T: PrivateSetIntersection> grpc::OakNode for Dispatcher<T> {
fn invoke(&mut self, method: &str, req: &[u8], writer: grpc::ChannelResponseWriter) {
match method {
"/oak.examples.private_set_intersection.PrivateSetIntersection/SubmitSet" => grpc::handle_req_rsp(|r| self.0.submit_set(r), req, writer),
"/oak.examples.private_set_intersection.PrivateSetIntersection/GetIntersection" => grpc::handle_req_rsp(|r| self.0.get_intersection(r), req, writer),
_ => {
panic!("unknown method name: {}", method);
}
};
}
}

// Client interface
Expand Down
Loading

0 comments on commit 6019c50

Please sign in to comment.