Rustで書かれたVMM firecrackerを読もう!(5)~API Server初期化再訪~
tomo-wait-for-it-yuki.hatenablog.com
前回は、HTTP API Serverの初期化処理を読んでいました。 少しhyperを勉強して、勘違いしていた部分も発覚したので、分かったことを改めて書きます。分からんところは分からんですが。
bind_and_run
src/main.rs
のmain関数から呼び出している関数です。API Serverを起動します。
let f = listener .incoming() .for_each(|(stream, _)| { // For the sake of clarity: when we use self.efd.clone(), the intent is to // clone the wrapping Rc, not the EventFd itself. let service = ApiServerHttpService::new( self.mmds_info.clone(), self.vmm_shared_info.clone(), self.api_request_sender.clone(), self.efd.clone(), ); let connection = http.serve_connection(stream, service); // todo: is spawn() any better/worse than execute()? // We have to adjust the future item and error, to fit spawn()'s definition. handle.spawn(connection.map(|_| ()).map_err(|_| ())); Ok(()) }).map_err(Error::Io);
前回勘違いしていたのですが、どうやらUnix domain socketでHTTPリクエストをlistenできるようです。
DockerでもdockerdのAPIは、Unix domain socketを介したREST APIになっているようです。 firecrackerも本来はjailerで隔離されるので、Unix domain socketで外の世界とやり取りする、のかもしれません。もう少し周りの実装を解析しないとなんとも言えないですね。
ということで、上記のコードは、単純にUnix domain socketで受信したHTTPリクエストを処理している、という解釈で良さそうです。
ApiServerHttpService
は、hyper::server::Servicetraitを実装しています。hyperでは、届いたリクエストごとに
Service` traitを実装したstructが生成されます。
api_server/src/http_service.rs
impl hyper::server::Service for ApiServerHttpService { type Request = hyper::Request; type Response = hyper::Response; type Error = hyper::error::Error; type Future = Box<Future<Item = Self::Response, Error = Self::Error>>; ...
serve_connection
は、Futureを返すようです。
hyper/src/server/mod.rs
を見ると、Connection
を返しています。
/// Bind a connection together with a Service. /// /// This returns a Future that must be polled in order for HTTP to be /// driven on the connection. pub fn serve_connection<S, I, Bd>(&self, io: I, service: S) -> Connection<I, S>
Connection
の定義を見ると、下のようになっていました。うーん、わからん…。
/// A future binding a connection with a Service. /// /// Polling this future will drive HTTP forward. /// /// # Note /// /// This will currently yield an unnameable (`Opaque`) value /// on success. The purpose of this is that nothing can be assumed about /// the type, not even it's name. It's probable that in a later release, /// this future yields the underlying IO object, which could be done without /// a breaking change. /// /// It is likely best to just map the value to `()`, for now. #[must_use = "futures do nothing unless polled"] pub struct Connection<I, S> where S: HyperService, S::ResponseBody: Stream<Error=::Error>, <S::ResponseBody as Stream>::Item: AsRef<[u8]>, { ... }
ただ、コメントを見ると、It is likely best to just map the value to
(), for now.
とあるので、mapで()を返すクロージャを渡していることと辻褄が合います。
let connection = http.serve_connection(stream, service); // todo: is spawn() any better/worse than execute()? // We have to adjust the future item and error, to fit spawn()'s definition. handle.spawn(connection.map(|_| ()).map_err(|_| ()));
おそらく、ここのconnection.map()
で、ApiServerHttpService
のcall
が呼ばれる気がします。ApiServerHttpService
のcall
は次のようになっており、HTTPリクエストを処理しています。
api_server/src/http_service.rs
impl hyper::server::Service for ApiServerHttpService { ... fn call(&self, req: Self::Request) -> Self::Future { ... Box::new(req.body().concat2().and_then(move |b| { // When this will be executed, the body is available. We start by parsing the request. match parse_request(method, path.as_ref(), &b) { Ok(parsed_req) => match parsed_req { GetInstanceInfo => { ...
matchのparse_request()
へ潜っていくと、VMM Actionへパースされる処理が見つかります。下のようなテストが書かれています。
#[test] fn test_parse_actions_req() { // PUT InstanceStart let json = "{ \"action_type\": \"InstanceStart\" }"; let body: Chunk = Chunk::from(json); let path = "/foo"; match parse_actions_req(path, Method::Put, &body) { Ok(pr) => { let (sender, receiver) = oneshot::channel(); assert!(pr.eq(&ParsedRequest::Sync( VmmAction::StartMicroVm(sender), receiver ))); } _ => assert!(false), } ...
"action_type": "InstanceStart"
は、VMを起動するためのbodyですね。
次回はこのあたりを見ていきましょう。