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