xref: /cloud-hypervisor/vmm/src/api/http/mod.rs (revision 3f8cd52ffd74627242cb7e8ea1c2bdedadf6741a)
1 // Copyright © 2019 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 
6 use self::http_endpoint::{VmActionHandler, VmCreate, VmInfo, VmmPing, VmmShutdown};
7 #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
8 use crate::api::VmCoredump;
9 use crate::api::{
10     AddDisk, ApiError, ApiRequest, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice,
11     VmAddVdpa, VmAddVsock, VmBoot, VmCounters, VmDelete, VmNmi, VmPause, VmPowerButton, VmReboot,
12     VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, VmResume,
13     VmSendMigration, VmShutdown, VmSnapshot,
14 };
15 use crate::seccomp_filters::{get_seccomp_filter, Thread};
16 use crate::{Error as VmmError, Result};
17 use core::fmt;
18 use hypervisor::HypervisorType;
19 use micro_http::{
20     Body, HttpServer, MediaType, Method, Request, Response, ServerError, StatusCode, Version,
21 };
22 use once_cell::sync::Lazy;
23 use seccompiler::{apply_filter, SeccompAction};
24 use serde_json::Error as SerdeError;
25 use std::collections::BTreeMap;
26 use std::fmt::Display;
27 use std::fs::File;
28 use std::os::unix::io::{IntoRawFd, RawFd};
29 use std::os::unix::net::UnixListener;
30 use std::panic::AssertUnwindSafe;
31 use std::path::PathBuf;
32 use std::sync::mpsc::Sender;
33 use std::thread;
34 use vmm_sys_util::eventfd::EventFd;
35 
36 pub mod http_endpoint;
37 
38 pub type HttpApiHandle = (thread::JoinHandle<Result<()>>, EventFd);
39 
40 /// Errors associated with VMM management
41 #[derive(Debug)]
42 pub enum HttpError {
43     /// API request receive error
44     SerdeJsonDeserialize(SerdeError),
45 
46     /// Attempt to access unsupported HTTP method
47     BadRequest,
48 
49     /// Undefined endpoints
50     NotFound,
51 
52     /// Internal Server Error
53     InternalServerError,
54 
55     /// Error from internal API
56     ApiError(ApiError),
57 }
58 
59 impl Display for HttpError {
60     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61         use self::HttpError::*;
62         match self {
63             BadRequest => write!(f, "Bad Request"),
64             NotFound => write!(f, "Not Found"),
65             InternalServerError => write!(f, "Internal Server Error"),
66             SerdeJsonDeserialize(serde_error) => write!(f, "{}", serde_error),
67             ApiError(api_error) => write!(f, "{}", api_error),
68         }
69     }
70 }
71 
72 impl From<serde_json::Error> for HttpError {
73     fn from(e: serde_json::Error) -> Self {
74         HttpError::SerdeJsonDeserialize(e)
75     }
76 }
77 
78 const HTTP_ROOT: &str = "/api/v1";
79 
80 pub fn error_response(error: HttpError, status: StatusCode) -> Response {
81     let mut response = Response::new(Version::Http11, status);
82     response.set_body(Body::new(format!("{error}")));
83 
84     response
85 }
86 
87 /// An HTTP endpoint handler interface
88 pub trait EndpointHandler {
89     /// Handles an HTTP request.
90     /// After parsing the request, the handler could decide to send an
91     /// associated API request down to the VMM API server to e.g. create
92     /// or start a VM. The request will block waiting for an answer from the
93     /// API server and translate that into an HTTP response.
94     fn handle_request(
95         &self,
96         req: &Request,
97         api_notifier: EventFd,
98         api_sender: Sender<ApiRequest>,
99     ) -> Response {
100         // Cloning the files here is very important as it dup() the file
101         // descriptors, leaving open the one that was received. This way,
102         // rebooting the VM will work since the VM will be created from the
103         // original file descriptors.
104         let files = req.files.iter().map(|f| f.try_clone().unwrap()).collect();
105         let res = match req.method() {
106             Method::Put => self.put_handler(api_notifier, api_sender, &req.body, files),
107             Method::Get => self.get_handler(api_notifier, api_sender, &req.body),
108             _ => return Response::new(Version::Http11, StatusCode::BadRequest),
109         };
110 
111         match res {
112             Ok(response_body) => {
113                 if let Some(body) = response_body {
114                     let mut response = Response::new(Version::Http11, StatusCode::OK);
115                     response.set_body(body);
116                     response
117                 } else {
118                     Response::new(Version::Http11, StatusCode::NoContent)
119                 }
120             }
121             Err(e @ HttpError::BadRequest) => error_response(e, StatusCode::BadRequest),
122             Err(e @ HttpError::SerdeJsonDeserialize(_)) => {
123                 error_response(e, StatusCode::BadRequest)
124             }
125             Err(e) => error_response(e, StatusCode::InternalServerError),
126         }
127     }
128 
129     fn put_handler(
130         &self,
131         _api_notifier: EventFd,
132         _api_sender: Sender<ApiRequest>,
133         _body: &Option<Body>,
134         _files: Vec<File>,
135     ) -> std::result::Result<Option<Body>, HttpError> {
136         Err(HttpError::BadRequest)
137     }
138 
139     fn get_handler(
140         &self,
141         _api_notifier: EventFd,
142         _api_sender: Sender<ApiRequest>,
143         _body: &Option<Body>,
144     ) -> std::result::Result<Option<Body>, HttpError> {
145         Err(HttpError::BadRequest)
146     }
147 }
148 
149 /// An HTTP routes structure.
150 pub struct HttpRoutes {
151     /// routes is a hash table mapping endpoint URIs to their endpoint handlers.
152     pub routes: BTreeMap<String, Box<dyn EndpointHandler + Sync + Send>>,
153 }
154 
155 macro_rules! endpoint {
156     ($path:expr) => {
157         format!("{}{}", HTTP_ROOT, $path)
158     };
159 }
160 
161 /// HTTP_ROUTES contain all the cloud-hypervisor HTTP routes.
162 pub static HTTP_ROUTES: Lazy<HttpRoutes> = Lazy::new(|| {
163     let mut r = HttpRoutes {
164         routes: BTreeMap::new(),
165     };
166 
167     r.routes.insert(
168         endpoint!("/vm.add-device"),
169         Box::new(VmActionHandler::new(&VmAddDevice)),
170     );
171     r.routes.insert(
172         endpoint!("/vm.add-user-device"),
173         Box::new(VmActionHandler::new(&VmAddUserDevice)),
174     );
175     r.routes.insert(
176         endpoint!("/vm.add-disk"),
177         Box::new(VmActionHandler::new(&AddDisk)),
178     );
179     r.routes.insert(
180         endpoint!("/vm.add-fs"),
181         Box::new(VmActionHandler::new(&VmAddFs)),
182     );
183     r.routes.insert(
184         endpoint!("/vm.add-net"),
185         Box::new(VmActionHandler::new(&VmAddNet)),
186     );
187     r.routes.insert(
188         endpoint!("/vm.add-pmem"),
189         Box::new(VmActionHandler::new(&VmAddPmem)),
190     );
191     r.routes.insert(
192         endpoint!("/vm.add-vdpa"),
193         Box::new(VmActionHandler::new(&VmAddVdpa)),
194     );
195     r.routes.insert(
196         endpoint!("/vm.add-vsock"),
197         Box::new(VmActionHandler::new(&VmAddVsock)),
198     );
199     r.routes.insert(
200         endpoint!("/vm.boot"),
201         Box::new(VmActionHandler::new(&VmBoot)),
202     );
203     r.routes.insert(
204         endpoint!("/vm.counters"),
205         Box::new(VmActionHandler::new(&VmCounters)),
206     );
207     r.routes
208         .insert(endpoint!("/vm.create"), Box::new(VmCreate {}));
209     r.routes.insert(
210         endpoint!("/vm.delete"),
211         Box::new(VmActionHandler::new(&VmDelete)),
212     );
213     r.routes.insert(endpoint!("/vm.info"), Box::new(VmInfo {}));
214     r.routes.insert(
215         endpoint!("/vm.pause"),
216         Box::new(VmActionHandler::new(&VmPause)),
217     );
218     r.routes.insert(
219         endpoint!("/vm.power-button"),
220         Box::new(VmActionHandler::new(&VmPowerButton)),
221     );
222     r.routes.insert(
223         endpoint!("/vm.reboot"),
224         Box::new(VmActionHandler::new(&VmReboot)),
225     );
226     r.routes.insert(
227         endpoint!("/vm.receive-migration"),
228         Box::new(VmActionHandler::new(&VmReceiveMigration)),
229     );
230     r.routes.insert(
231         endpoint!("/vm.remove-device"),
232         Box::new(VmActionHandler::new(&VmRemoveDevice)),
233     );
234     r.routes.insert(
235         endpoint!("/vm.resize"),
236         Box::new(VmActionHandler::new(&VmResize)),
237     );
238     r.routes.insert(
239         endpoint!("/vm.resize-zone"),
240         Box::new(VmActionHandler::new(&VmResizeZone)),
241     );
242     r.routes.insert(
243         endpoint!("/vm.restore"),
244         Box::new(VmActionHandler::new(&VmRestore)),
245     );
246     r.routes.insert(
247         endpoint!("/vm.resume"),
248         Box::new(VmActionHandler::new(&VmResume)),
249     );
250     r.routes.insert(
251         endpoint!("/vm.send-migration"),
252         Box::new(VmActionHandler::new(&VmSendMigration)),
253     );
254     r.routes.insert(
255         endpoint!("/vm.shutdown"),
256         Box::new(VmActionHandler::new(&VmShutdown)),
257     );
258     r.routes.insert(
259         endpoint!("/vm.snapshot"),
260         Box::new(VmActionHandler::new(&VmSnapshot)),
261     );
262     #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
263     r.routes.insert(
264         endpoint!("/vm.coredump"),
265         Box::new(VmActionHandler::new(&VmCoredump)),
266     );
267     r.routes
268         .insert(endpoint!("/vmm.ping"), Box::new(VmmPing {}));
269     r.routes
270         .insert(endpoint!("/vmm.shutdown"), Box::new(VmmShutdown {}));
271     r.routes
272         .insert(endpoint!("/vm.nmi"), Box::new(VmActionHandler::new(&VmNmi)));
273 
274     r
275 });
276 
277 fn handle_http_request(
278     request: &Request,
279     api_notifier: &EventFd,
280     api_sender: &Sender<ApiRequest>,
281 ) -> Response {
282     let path = request.uri().get_abs_path().to_string();
283     let mut response = match HTTP_ROUTES.routes.get(&path) {
284         Some(route) => match api_notifier.try_clone() {
285             Ok(notifier) => route.handle_request(request, notifier, api_sender.clone()),
286             Err(_) => error_response(
287                 HttpError::InternalServerError,
288                 StatusCode::InternalServerError,
289             ),
290         },
291         None => error_response(HttpError::NotFound, StatusCode::NotFound),
292     };
293 
294     response.set_server("Cloud Hypervisor API");
295     response.set_content_type(MediaType::ApplicationJson);
296     response
297 }
298 
299 fn start_http_thread(
300     mut server: HttpServer,
301     api_notifier: EventFd,
302     api_sender: Sender<ApiRequest>,
303     seccomp_action: &SeccompAction,
304     exit_evt: EventFd,
305     hypervisor_type: HypervisorType,
306 ) -> Result<HttpApiHandle> {
307     // Retrieve seccomp filter for API thread
308     let api_seccomp_filter = get_seccomp_filter(seccomp_action, Thread::HttpApi, hypervisor_type)
309         .map_err(VmmError::CreateSeccompFilter)?;
310 
311     let api_shutdown_fd = EventFd::new(libc::EFD_NONBLOCK).map_err(VmmError::EventFdCreate)?;
312     let api_shutdown_fd_clone = api_shutdown_fd.try_clone().unwrap();
313 
314     server
315         .add_kill_switch(api_shutdown_fd_clone)
316         .map_err(VmmError::CreateApiServer)?;
317 
318     let thread = thread::Builder::new()
319         .name("http-server".to_string())
320         .spawn(move || {
321             // Apply seccomp filter for API thread.
322             if !api_seccomp_filter.is_empty() {
323                 apply_filter(&api_seccomp_filter)
324                     .map_err(VmmError::ApplySeccompFilter)
325                     .map_err(|e| {
326                         error!("Error applying seccomp filter: {:?}", e);
327                         exit_evt.write(1).ok();
328                         e
329                     })?;
330             }
331 
332             std::panic::catch_unwind(AssertUnwindSafe(move || {
333                 server.start_server().unwrap();
334                 loop {
335                     match server.requests() {
336                         Ok(request_vec) => {
337                             for server_request in request_vec {
338                                 if let Err(e) = server.respond(server_request.process(|request| {
339                                     handle_http_request(request, &api_notifier, &api_sender)
340                                 })) {
341                                     error!("HTTP server error on response: {}", e);
342                                 }
343                             }
344                         }
345                         Err(ServerError::ShutdownEvent) => {
346                             server.flush_outgoing_writes();
347                             return;
348                         }
349                         Err(e) => {
350                             error!(
351                                 "HTTP server error on retrieving incoming request. Error: {}",
352                                 e
353                             );
354                         }
355                     }
356                 }
357             }))
358             .map_err(|_| {
359                 error!("http-server thread panicked");
360                 exit_evt.write(1).ok()
361             })
362             .ok();
363 
364             Ok(())
365         })
366         .map_err(VmmError::HttpThreadSpawn)?;
367 
368     Ok((thread, api_shutdown_fd))
369 }
370 
371 pub fn start_http_path_thread(
372     path: &str,
373     api_notifier: EventFd,
374     api_sender: Sender<ApiRequest>,
375     seccomp_action: &SeccompAction,
376     exit_evt: EventFd,
377     hypervisor_type: HypervisorType,
378 ) -> Result<HttpApiHandle> {
379     let socket_path = PathBuf::from(path);
380     let socket_fd = UnixListener::bind(socket_path).map_err(VmmError::CreateApiServerSocket)?;
381     // SAFETY: Valid FD just opened
382     let server = unsafe { HttpServer::new_from_fd(socket_fd.into_raw_fd()) }
383         .map_err(VmmError::CreateApiServer)?;
384 
385     start_http_thread(
386         server,
387         api_notifier,
388         api_sender,
389         seccomp_action,
390         exit_evt,
391         hypervisor_type,
392     )
393 }
394 
395 pub fn start_http_fd_thread(
396     fd: RawFd,
397     api_notifier: EventFd,
398     api_sender: Sender<ApiRequest>,
399     seccomp_action: &SeccompAction,
400     exit_evt: EventFd,
401     hypervisor_type: HypervisorType,
402 ) -> Result<HttpApiHandle> {
403     // SAFETY: Valid FD
404     let server = unsafe { HttpServer::new_from_fd(fd) }.map_err(VmmError::CreateApiServer)?;
405     start_http_thread(
406         server,
407         api_notifier,
408         api_sender,
409         seccomp_action,
410         exit_evt,
411         hypervisor_type,
412     )
413 }
414 
415 pub fn http_api_graceful_shutdown(http_handle: HttpApiHandle) -> Result<()> {
416     let (api_thread, api_shutdown_fd) = http_handle;
417 
418     api_shutdown_fd.write(1).unwrap();
419     api_thread.join().map_err(VmmError::ThreadCleanup)?
420 }
421