Rustで書かれたVMM firecrackerを読もう!(5)~API Server初期化再訪~

github.com

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できるようです。

qiita.com

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()で、ApiServerHttpServicecallが呼ばれる気がします。ApiServerHttpServicecallは次のようになっており、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ですね。 次回はこのあたりを見ていきましょう。