-
Notifications
You must be signed in to change notification settings - Fork 114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rust gRPC server pseudo-node #772
Conversation
7f70a7e
to
fe1ab1b
Compare
@@ -630,6 +631,7 @@ impl RuntimeRef { | |||
entrypoint: &str, | |||
label: &oak_abi::label::Label, | |||
reader: Handle, | |||
writer: Handle, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I add a writer
handle as a parameter to this function, it means that I will need to pass some writer
here:
oak/oak/server/rust/oak_runtime/src/node/wasm.rs
Lines 198 to 211 in 589b2d2
let channel_ref = self.readers.get(&initial_handle).ok_or(()).map_err(|_| { | |
error!("node_create: Invalid handle"); | |
OakStatus::ErrBadHandle | |
})?; | |
self.runtime | |
.node_create( | |
self.node_id, | |
&config_name, | |
&entrypoint, | |
// TODO(#630): Let caller provide this label via the Wasm ABI. | |
&oak_abi::label::Label::public_trusted(), | |
channel_ref.clone(), | |
) |
And IIUC we don't have
None
handles, right?cc @tiziano88 @daviddrysdale @blaxill
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added writer
as a parameter to the node_create
:
oak/oak/server/rust/oak_runtime/src/runtime/mod.rs
Lines 773 to 780 in c2b2ff4
pub fn node_create( | |
self: Arc<Self>, | |
node_id: NodeId, | |
module_name: &str, | |
entrypoint: &str, | |
label: &Label, | |
reader: Handle, | |
) -> Result<(), OakStatus> { |
But this function is called inside a Wasm node, when
oak_abi
funciton node_create
is called (and it doesn't provide a writer
).
Is it ok to search for this writer
in the list of channels and pass to the node_create
in the Wasm node, or it may affect the Control Flow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think perhaps we should have a pre-requisite to this PR to make gRPC node creation more similar to the creation of other pseudo-nodes, i.e. let the wasm node create them. This is what we do for logging, for instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e.g.:
oak/sdk/rust/oak/src/logger/mod.rs
Lines 80 to 99 in 006b921
/// Initialize Node-wide logging via a channel to a logging pseudo-Node. | |
/// | |
/// Initialize logging at the given level, creating a logging pseudo-Node | |
/// whose configuration is identified by the given name. | |
/// | |
/// # Errors | |
/// | |
/// An error is returned if a logger has already been set. | |
pub fn init(level: Level, config: &str) -> Result<(), SetLoggerError> { | |
// Create a channel and pass the read half to a fresh logging Node. | |
let (write_handle, read_handle) = crate::channel_create().expect("could not create channel"); | |
crate::node_create(config, "oak_main", read_handle).expect("could not create node"); | |
crate::channel_close(read_handle.handle).expect("could not close channel"); | |
log::set_boxed_logger(Box::new(OakChannelLogger { | |
channel: crate::io::Sender::new(write_handle), | |
}))?; | |
log::set_max_level(level.to_level_filter()); | |
Ok(()) | |
} |
this is what creates a log pseudo-node, and we also have a default config name for it. We could have the same for gRPC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So pseudo-nodes should be created by the Oak Node, right?
But Runtime also creates a pseudo-node (and passes it a reader):
https://github.com/project-oak/oak/pull/772/files#diff-6c581d36a36131023ea8060843bb2dffR126
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, makes sense, but then I think we should also flip the direction of the channel, so that the gRPC server pseudo-node would have an incoming read channel, like every other node, over which the caller passes a single write end of a channel to receive invocations. Would that work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think it will work.
So before starting the server, gRPC pseudo-node should wait on the channel to receive the writer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the server may as well start as soon as possible, but then at the first incoming gRPC request it would block (wait_on_channels
) to receive the channel to which to write the invocation, store it somewhere in its local state, and then use that channel for all subsequent invocations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Created an init
function:
https://github.com/project-oak/oak/pull/772/files#diff-1211ded35ba967a10825322cdf0cbd20R362-R378
It returns a read handle to read gRPC invocations from. This handle should be used by the Runtime, but I haven't found a loop/server inside the Runtime that reads invocations from a channel and dispatches them (runs corresponding gRPC method).
|
||
impl HttpStatusCode for OakStatus { | ||
fn status_code(&self) -> http::StatusCode { | ||
match self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYH about HTTP status codes that we need to return based on the ChannelReadStatus
and OakStatus
?
cc @tiziano88 @daviddrysdale
It may be worth hooking up some grpc tests (possibly the existing c++ ones) before proceeding too much with this. |
Do you mean tests from |
It's fine to merge something that does not work yet, but yes, we should be in a better position to test everything once the rust |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Ivan, this looks like in the correct direction, I left a few comments around, happy to take a closer look again later on!
writer: Handle, | ||
} | ||
|
||
impl GrpcService { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why could this not be implemented on GrpcServerNode
above? May as well use the same struct, if possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created node thread uses Service
inside it for a Hyper server.
And GrpcServerNode
contains a thread reference.
So it would require creating a custom (non-derive
) clone
function that doesn't copy this thread reference and sets it to None
.
Not sure if it will be readable.
WDYT ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, but perhaps at least create the GrpcService
instance as part of GrpcServerNode
itself, rather than repeat all the fields?
Also I am confused by the naming, why is one called service
and the other server
? I think we should stick to one of them, and I think server
would be appropriate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
); | ||
|
||
// Create a notification message and attach the method-invocation specific channels to it. | ||
let notification = crate::Message { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be called invocation
, also we should have a dedicated type for it somewhere at some point (not in this PR).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
This class exists but it's not a message:
oak/sdk/rust/oak/src/grpc/invocation.rs
Lines 19 to 22 in 158ff84
pub struct Invocation { | |
pub request_receiver: crate::io::Receiver<crate::proto::grpc_encap::GrpcRequest>, | |
pub response_sender: crate::io::Sender<crate::proto::grpc_encap::GrpcResponse>, | |
} |
use protobuf::well_known_types::Any; | ||
|
||
use oak_abi::{ChannelReadStatus, OakStatus}; | ||
use oak::grpc::GrpcRequest; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is adding a dependency on the Rust / Wasm Oak SDK, which I am not sure is appropriate. I guess it's fine because we just need the protobuf generated files, but it feels we should try to split them out if we need to keep using them here perhaps. In fact GrpcRequest
should probably move to oak_abi
anyways?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, but currently it's in the oak/src/proto
- so it's a generated Rust Protobuf file.
Is is ok to move it to oak_abi
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps move that in a separate PR, either before or after this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created an issue #799
e05b00b
to
9b93306
Compare
6fdb112
to
1ea14fd
Compare
Add call function Fix service creation gRPC pseudo-node creation gRPC request processing Clean use Update after review Fix after review Update comments Add config parsing
1ea14fd
to
fd17b2d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it looks good so far, but does not seem quite ready for merging. Specifically the Hyper server coode looks as I would have expected.
@conradgrobler I was planning to add TLS and tests in separate PRs - not to clutter this one. |
|
||
/// Oak Node implementation for the gRPC server. | ||
impl super::Node for GrpcServerNode { | ||
fn start(&mut self) -> Result<(), OakStatus> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The gRPC seems to be combining two different Hyper concepts into one: The server, which is listening for new requests and the service, which handles each request. I think this is why there are so many clones statements needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic was merged into the Node based on the following thread: #772 (comment)
But even if we will have a separate service
object, we'll still need to clone it, since it should live inside a Future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As part of the cleanup in #813, it might be worth looking at using a Tower service (implemented as a gRPC service node), which could encapsulate the needed state so that it does not have to be cloned so many times. It would also reduce the need for so many nested move
s and async move
s.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@conradgrobler 's comment suggests to me that @ipetr0v 's initial approach was perhaps more sensible than what I suggested in that thread? I was not aware of the distinction of those concepts in Hyper. The split should be determined on exactly what is needed inside the future and what not. For instance, I am guessing config_name
and address
are not relevant inside the future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are necessary in the Node thread, but not in the Future, yes.
But Future requires a RuntimeProxy
reference.
}); | ||
|
||
// Start the HTTP server. | ||
let result = block_on(hyper::Server::bind(&server.address).serve(service)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will block as long as the server is listening on the port, which means that start
will never return.
Also, I think this should not be using block_on
, which uses a threadpool executor, but should rather be using Tokio executors as Hyper is based on Tokio. (https://docs.rs/tokio/0.2.16/tokio/runtime/struct.Runtime.html)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is happening inside a newly created thread: https://github.com/project-oak/oak/pull/772/files/327276097e2c3593e513d92efa4f8ce713ab6a0a#diff-475373f331232762d978c36eb8027376R288-R290
Same is happening in Logger node:
oak/oak/server/rust/oak_runtime/src/node/logger.rs
Lines 61 to 65 in 006b921
let thread_handle = thread::Builder::new() | |
.name(self.to_string()) | |
.spawn(move || { | |
let pretty_name = pretty_name_for_thread(&thread::current()); | |
let result = logger(&pretty_name, &runtime, reader); |
Where
logger
is an infinite loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Added a tokio
runtime for executing a Future.
https://github.com/project-oak/oak/pull/772/files#diff-475373f331232762d978c36eb8027376R313-R317
|
||
/// Oak Node implementation for the gRPC server. | ||
impl super::Node for GrpcServerNode { | ||
fn start(&mut self) -> Result<(), OakStatus> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As part of the cleanup in #813, it might be worth looking at using a Tower service (implemented as a gRPC service node), which could encapsulate the needed state so that it does not have to be cloned so many times. It would also reduce the need for so many nested move
s and async move
s.
"gRPC pseudo-node should be specified with a single `writer` \ | ||
handle, found {}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did rustfmt
split the string in this odd way? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I was trying to fit into 100 characters.
And the rustfmt
changed alignment.
Done
|
||
/// Oak Node implementation for the gRPC server. | ||
impl super::Node for GrpcServerNode { | ||
fn start(&mut self) -> Result<(), OakStatus> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@conradgrobler 's comment suggests to me that @ipetr0v 's initial approach was perhaps more sensible than what I suggested in that thread? I was not aware of the distinction of those concepts in Hyper. The split should be determined on exactly what is needed inside the future and what not. For instance, I am guessing config_name
and address
are not relevant inside the future?
sdk/rust/oak/src/grpc/mod.rs
Outdated
init(DEFAULT_CONFIG_NAME).unwrap(); | ||
} | ||
|
||
/// Initialize a gRPC pseudo-node and pass it a handle to write invocations to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought this method returns a handle on which to read invocations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, changed description to Returns a ['Handle'] to read invocations from
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems there is still room for improvement, and I am not sure some things actually work at all (e.g. the Any
conversion), but I guess you can submit as it is, and things should be clearer once we have some tests to exercise the new logic.
Protocol buffer messages that are exchanged in serialized form between Wasm Nodes and Oak-provided pseudo-Nodes are effectively part of the Oak ABI, so move the generated code for them into the oak_abi crate. This allows us to remove the oak_runtime->oak dependency that was introduced by #772. Along the way, put the gRPC encapsulation protobuf messages into an `encap` submodule/namespace. Fixes #764.
This change adds a Rust gRPC server pseudo-node.
Fixes #751
TLS support is tracked by #806
Unit tests are tracked by #807
Checklist
construction.