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