xref: /cloud-hypervisor/api_client/src/lib.rs (revision 190a11f2124b0b60a2d44e85b7c9988373acfb6d)
18de3bd72SRob Bradford // Copyright © 2020 Intel Corporation
28de3bd72SRob Bradford //
38de3bd72SRob Bradford // SPDX-License-Identifier: Apache-2.0
48de3bd72SRob Bradford //
58de3bd72SRob Bradford 
68de3bd72SRob Bradford use std::io::{Read, Write};
79af2968aSSebastien Boeuf use std::os::unix::io::RawFd;
888a9f799SRob Bradford 
95d0d56f5SSamrutGadde use thiserror::Error;
109af2968aSSebastien Boeuf use vmm_sys_util::sock_ctrl_msg::ScmSocket;
118de3bd72SRob Bradford 
125d0d56f5SSamrutGadde #[derive(Debug, Error)]
138de3bd72SRob Bradford pub enum Error {
143f3489e3SPhilipp Schuster     #[error("Error writing to or reading from HTTP socket")]
1538380198SPhilipp Schuster     Socket(#[source] std::io::Error),
163f3489e3SPhilipp Schuster     #[error("Error sending file descriptors")]
1738380198SPhilipp Schuster     SocketSendFds(#[source] vmm_sys_util::errno::Error),
183f3489e3SPhilipp Schuster     #[error("Error parsing HTTP status code")]
1938380198SPhilipp Schuster     StatusCodeParsing(#[source] std::num::ParseIntError),
205d0d56f5SSamrutGadde     #[error("HTTP output is missing protocol statement")]
218de3bd72SRob Bradford     MissingProtocol,
223f3489e3SPhilipp Schuster     #[error("Error parsing HTTP Content-Length field")]
2338380198SPhilipp Schuster     ContentLengthParsing(#[source] std::num::ParseIntError),
24*190a11f2SPhilipp Schuster     #[error("Server responded with error {0:?}: {1:?}")]
25*190a11f2SPhilipp Schuster     ServerResponse(
26*190a11f2SPhilipp Schuster         StatusCode,
27*190a11f2SPhilipp Schuster         // TODO: Move `api` module from `vmm` to dedicated crate and use a common type definition
28*190a11f2SPhilipp Schuster         Option<
29*190a11f2SPhilipp Schuster             String, /* Untyped: Currently Vec<String> of error messages from top to root cause */
30*190a11f2SPhilipp Schuster         >,
31*190a11f2SPhilipp Schuster     ),
328de3bd72SRob Bradford }
338de3bd72SRob Bradford 
348de3bd72SRob Bradford #[derive(Clone, Copy, Debug)]
358de3bd72SRob Bradford pub enum StatusCode {
368de3bd72SRob Bradford     Continue,
37030d6046SRob Bradford     Ok,
388de3bd72SRob Bradford     NoContent,
398de3bd72SRob Bradford     BadRequest,
408de3bd72SRob Bradford     NotFound,
411968805bSFabiano Fidêncio     TooManyRequests,
428de3bd72SRob Bradford     InternalServerError,
438de3bd72SRob Bradford     NotImplemented,
448de3bd72SRob Bradford     Unknown,
458de3bd72SRob Bradford }
468de3bd72SRob Bradford 
478de3bd72SRob Bradford impl StatusCode {
from_raw(code: usize) -> StatusCode488de3bd72SRob Bradford     fn from_raw(code: usize) -> StatusCode {
498de3bd72SRob Bradford         match code {
508de3bd72SRob Bradford             100 => StatusCode::Continue,
51030d6046SRob Bradford             200 => StatusCode::Ok,
528de3bd72SRob Bradford             204 => StatusCode::NoContent,
538de3bd72SRob Bradford             400 => StatusCode::BadRequest,
548de3bd72SRob Bradford             404 => StatusCode::NotFound,
551968805bSFabiano Fidêncio             429 => StatusCode::TooManyRequests,
568de3bd72SRob Bradford             500 => StatusCode::InternalServerError,
578de3bd72SRob Bradford             501 => StatusCode::NotImplemented,
588de3bd72SRob Bradford             _ => StatusCode::Unknown,
598de3bd72SRob Bradford         }
608de3bd72SRob Bradford     }
618de3bd72SRob Bradford 
parse(code: &str) -> Result<StatusCode, Error>628de3bd72SRob Bradford     fn parse(code: &str) -> Result<StatusCode, Error> {
638de3bd72SRob Bradford         Ok(StatusCode::from_raw(
648de3bd72SRob Bradford             code.trim().parse().map_err(Error::StatusCodeParsing)?,
658de3bd72SRob Bradford         ))
668de3bd72SRob Bradford     }
678de3bd72SRob Bradford 
is_server_error(self) -> bool688de3bd72SRob Bradford     fn is_server_error(self) -> bool {
698de3bd72SRob Bradford         !matches!(
708de3bd72SRob Bradford             self,
71030d6046SRob Bradford             StatusCode::Ok | StatusCode::Continue | StatusCode::NoContent
728de3bd72SRob Bradford         )
738de3bd72SRob Bradford     }
748de3bd72SRob Bradford }
758de3bd72SRob Bradford 
get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str>768de3bd72SRob Bradford fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
775e527294SRob Bradford     let header_str = format!("{header}: ");
787a18e247SGaelan Steele     res.find(&header_str)
797a18e247SGaelan Steele         .map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
808de3bd72SRob Bradford }
818de3bd72SRob Bradford 
get_status_code(res: &str) -> Result<StatusCode, Error>828de3bd72SRob Bradford fn get_status_code(res: &str) -> Result<StatusCode, Error> {
838de3bd72SRob Bradford     if let Some(o) = res.find("HTTP/1.1") {
848de3bd72SRob Bradford         Ok(StatusCode::parse(
858de3bd72SRob Bradford             &res[o + "HTTP/1.1 ".len()..res[o..].find('\r').unwrap()],
868de3bd72SRob Bradford         )?)
878de3bd72SRob Bradford     } else {
888de3bd72SRob Bradford         Err(Error::MissingProtocol)
898de3bd72SRob Bradford     }
908de3bd72SRob Bradford }
918de3bd72SRob Bradford 
parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error>928de3bd72SRob Bradford fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
938de3bd72SRob Bradford     let mut res = String::new();
948de3bd72SRob Bradford     let mut body_offset = None;
958de3bd72SRob Bradford     let mut content_length: Option<usize> = None;
968de3bd72SRob Bradford     loop {
978de3bd72SRob Bradford         let mut bytes = vec![0; 256];
988de3bd72SRob Bradford         let count = socket.read(&mut bytes).map_err(Error::Socket)?;
99686e6d50SMaximilian Nitsch         // If the return value is 0, the peer has performed an orderly shutdown.
100686e6d50SMaximilian Nitsch         if count == 0 {
101686e6d50SMaximilian Nitsch             break;
102686e6d50SMaximilian Nitsch         }
1038de3bd72SRob Bradford         res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap());
1048de3bd72SRob Bradford 
1058de3bd72SRob Bradford         // End of headers
1068de3bd72SRob Bradford         if let Some(o) = res.find("\r\n\r\n") {
1078de3bd72SRob Bradford             body_offset = Some(o + "\r\n\r\n".len());
1088de3bd72SRob Bradford 
1098de3bd72SRob Bradford             // With all headers available we can see if there is any body
1108de3bd72SRob Bradford             content_length = if let Some(length) = get_header(&res, "Content-Length") {
1118de3bd72SRob Bradford                 Some(length.trim().parse().map_err(Error::ContentLengthParsing)?)
1128de3bd72SRob Bradford             } else {
1138de3bd72SRob Bradford                 None
1148de3bd72SRob Bradford             };
1158de3bd72SRob Bradford 
1168de3bd72SRob Bradford             if content_length.is_none() {
1178de3bd72SRob Bradford                 break;
1188de3bd72SRob Bradford             }
1198de3bd72SRob Bradford         }
1208de3bd72SRob Bradford 
1218de3bd72SRob Bradford         if let Some(body_offset) = body_offset {
1228de3bd72SRob Bradford             if let Some(content_length) = content_length {
1238de3bd72SRob Bradford                 if res.len() >= content_length + body_offset {
1248de3bd72SRob Bradford                     break;
1258de3bd72SRob Bradford                 }
1268de3bd72SRob Bradford             }
1278de3bd72SRob Bradford         }
1288de3bd72SRob Bradford     }
12987c0791dSMaximilian Nitsch     let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));
1308de3bd72SRob Bradford     let status_code = get_status_code(&res)?;
1318de3bd72SRob Bradford 
1328de3bd72SRob Bradford     if status_code.is_server_error() {
1338de3bd72SRob Bradford         Err(Error::ServerResponse(status_code, body_string))
1348de3bd72SRob Bradford     } else {
1358de3bd72SRob Bradford         Ok(body_string)
1368de3bd72SRob Bradford     }
1378de3bd72SRob Bradford }
1388de3bd72SRob Bradford 
139eea9bceaSJames O. D. Hunt /// Make an API request using the fully qualified command name.
140eea9bceaSJames O. D. Hunt /// For example, full_command could be "vm.create" or "vmm.ping".
simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket>( socket: &mut T, method: &str, full_command: &str, request_body: Option<&str>, request_fds: Vec<RawFd>, ) -> Result<Option<String>, Error>141cd0208feSJames O. D. Hunt pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket>(
1428de3bd72SRob Bradford     socket: &mut T,
1438de3bd72SRob Bradford     method: &str,
144eea9bceaSJames O. D. Hunt     full_command: &str,
1458de3bd72SRob Bradford     request_body: Option<&str>,
1469af2968aSSebastien Boeuf     request_fds: Vec<RawFd>,
147cd0208feSJames O. D. Hunt ) -> Result<Option<String>, Error> {
1488de3bd72SRob Bradford     socket
1499af2968aSSebastien Boeuf         .send_with_fds(
1509af2968aSSebastien Boeuf             &[format!(
1515e527294SRob Bradford                 "{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
1528de3bd72SRob Bradford             )
1539af2968aSSebastien Boeuf             .as_bytes()],
1549af2968aSSebastien Boeuf             &request_fds,
1558de3bd72SRob Bradford         )
1569af2968aSSebastien Boeuf         .map_err(Error::SocketSendFds)?;
1578de3bd72SRob Bradford 
1588de3bd72SRob Bradford     if let Some(request_body) = request_body {
1598de3bd72SRob Bradford         socket
1608de3bd72SRob Bradford             .write_all(format!("Content-Length: {}\r\n", request_body.len()).as_bytes())
1618de3bd72SRob Bradford             .map_err(Error::Socket)?;
1628de3bd72SRob Bradford     }
1638de3bd72SRob Bradford 
1648de3bd72SRob Bradford     socket.write_all(b"\r\n").map_err(Error::Socket)?;
1658de3bd72SRob Bradford 
1668de3bd72SRob Bradford     if let Some(request_body) = request_body {
1678de3bd72SRob Bradford         socket
1688de3bd72SRob Bradford             .write_all(request_body.as_bytes())
1698de3bd72SRob Bradford             .map_err(Error::Socket)?;
1708de3bd72SRob Bradford     }
1718de3bd72SRob Bradford 
1728de3bd72SRob Bradford     socket.flush().map_err(Error::Socket)?;
1738de3bd72SRob Bradford 
174cd0208feSJames O. D. Hunt     parse_http_response(socket)
1758de3bd72SRob Bradford }
176cd0208feSJames O. D. Hunt 
simple_api_full_command_with_fds<T: Read + Write + ScmSocket>( socket: &mut T, method: &str, full_command: &str, request_body: Option<&str>, request_fds: Vec<RawFd>, ) -> Result<(), Error>177cd0208feSJames O. D. Hunt pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
178cd0208feSJames O. D. Hunt     socket: &mut T,
179cd0208feSJames O. D. Hunt     method: &str,
180cd0208feSJames O. D. Hunt     full_command: &str,
181cd0208feSJames O. D. Hunt     request_body: Option<&str>,
182cd0208feSJames O. D. Hunt     request_fds: Vec<RawFd>,
183cd0208feSJames O. D. Hunt ) -> Result<(), Error> {
184cd0208feSJames O. D. Hunt     let response = simple_api_full_command_with_fds_and_response(
185cd0208feSJames O. D. Hunt         socket,
186cd0208feSJames O. D. Hunt         method,
187cd0208feSJames O. D. Hunt         full_command,
188cd0208feSJames O. D. Hunt         request_body,
189cd0208feSJames O. D. Hunt         request_fds,
190cd0208feSJames O. D. Hunt     )?;
191cd0208feSJames O. D. Hunt 
192cd0208feSJames O. D. Hunt     if response.is_some() {
193cd0208feSJames O. D. Hunt         println!("{}", response.unwrap());
194cd0208feSJames O. D. Hunt     }
195cd0208feSJames O. D. Hunt 
1968de3bd72SRob Bradford     Ok(())
1978de3bd72SRob Bradford }
1989af2968aSSebastien Boeuf 
simple_api_full_command<T: Read + Write + ScmSocket>( socket: &mut T, method: &str, full_command: &str, request_body: Option<&str>, ) -> Result<(), Error>199eea9bceaSJames O. D. Hunt pub fn simple_api_full_command<T: Read + Write + ScmSocket>(
200eea9bceaSJames O. D. Hunt     socket: &mut T,
201eea9bceaSJames O. D. Hunt     method: &str,
202eea9bceaSJames O. D. Hunt     full_command: &str,
203eea9bceaSJames O. D. Hunt     request_body: Option<&str>,
204eea9bceaSJames O. D. Hunt ) -> Result<(), Error> {
205eea9bceaSJames O. D. Hunt     simple_api_full_command_with_fds(socket, method, full_command, request_body, Vec::new())
206eea9bceaSJames O. D. Hunt }
207eea9bceaSJames O. D. Hunt 
simple_api_full_command_and_response<T: Read + Write + ScmSocket>( socket: &mut T, method: &str, full_command: &str, request_body: Option<&str>, ) -> Result<Option<String>, Error>208cd0208feSJames O. D. Hunt pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
209cd0208feSJames O. D. Hunt     socket: &mut T,
210cd0208feSJames O. D. Hunt     method: &str,
211cd0208feSJames O. D. Hunt     full_command: &str,
212cd0208feSJames O. D. Hunt     request_body: Option<&str>,
213cd0208feSJames O. D. Hunt ) -> Result<Option<String>, Error> {
214cd0208feSJames O. D. Hunt     simple_api_full_command_with_fds_and_response(
215cd0208feSJames O. D. Hunt         socket,
216cd0208feSJames O. D. Hunt         method,
217cd0208feSJames O. D. Hunt         full_command,
218cd0208feSJames O. D. Hunt         request_body,
219cd0208feSJames O. D. Hunt         Vec::new(),
220cd0208feSJames O. D. Hunt     )
221cd0208feSJames O. D. Hunt }
222cd0208feSJames O. D. Hunt 
simple_api_command_with_fds<T: Read + Write + ScmSocket>( socket: &mut T, method: &str, c: &str, request_body: Option<&str>, request_fds: Vec<RawFd>, ) -> Result<(), Error>223eea9bceaSJames O. D. Hunt pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
224eea9bceaSJames O. D. Hunt     socket: &mut T,
225eea9bceaSJames O. D. Hunt     method: &str,
226eea9bceaSJames O. D. Hunt     c: &str,
227eea9bceaSJames O. D. Hunt     request_body: Option<&str>,
228eea9bceaSJames O. D. Hunt     request_fds: Vec<RawFd>,
229eea9bceaSJames O. D. Hunt ) -> Result<(), Error> {
230eea9bceaSJames O. D. Hunt     // Create the full VM command. For VMM commands, use
231eea9bceaSJames O. D. Hunt     // simple_api_full_command().
2325e527294SRob Bradford     let full_command = format!("vm.{c}");
233eea9bceaSJames O. D. Hunt 
234eea9bceaSJames O. D. Hunt     simple_api_full_command_with_fds(socket, method, &full_command, request_body, request_fds)
235eea9bceaSJames O. D. Hunt }
236eea9bceaSJames O. D. Hunt 
simple_api_command<T: Read + Write + ScmSocket>( socket: &mut T, method: &str, c: &str, request_body: Option<&str>, ) -> Result<(), Error>2379af2968aSSebastien Boeuf pub fn simple_api_command<T: Read + Write + ScmSocket>(
2389af2968aSSebastien Boeuf     socket: &mut T,
2399af2968aSSebastien Boeuf     method: &str,
2409af2968aSSebastien Boeuf     c: &str,
2419af2968aSSebastien Boeuf     request_body: Option<&str>,
2429af2968aSSebastien Boeuf ) -> Result<(), Error> {
2439af2968aSSebastien Boeuf     simple_api_command_with_fds(socket, method, c, request_body, Vec::new())
2449af2968aSSebastien Boeuf }
245